From 1a08945ee6d522899e65285f2361ac9fbf0d5f4b Mon Sep 17 00:00:00 2001 From: evenp <Philippe.Even@loria.fr> Date: Thu, 10 Dec 2020 20:16:15 +0100 Subject: [PATCH] New comparisons with other detectors added in Expes --- Code/FBSD/BlurredSegment/bsdetector.cpp | 2 +- Code/FBSD/BlurredSegment/nfafilter.cpp | 9 +- .../ImageTools/digitalstraightsegment.cpp | 20 +- Code/FBSD/ImageTools/digitalstraightsegment.h | 24 +- Expes/Testers/TestLines/.qmake.stash | 23 + .../TestLines/BlurredSegment/biptlist.cpp | 256 ++++++ .../TestLines/BlurredSegment/biptlist.h | 146 +++ .../BlurredSegment/blurredsegment.cpp | 328 +++++++ .../TestLines/BlurredSegment/blurredsegment.h | 238 +++++ .../BlurredSegment/blurredsegmentproto.cpp | 294 +++++++ .../BlurredSegment/blurredsegmentproto.h | 139 +++ .../TestLines/BlurredSegment/bsdetector.cpp | 577 ++++++++++++ .../TestLines/BlurredSegment/bsdetector.h | 623 +++++++++++++ .../TestLines/BlurredSegment/bsproto.cpp | 319 +++++++ .../TestLines/BlurredSegment/bsproto.h | 165 ++++ .../TestLines/BlurredSegment/bstracker.cpp | 422 +++++++++ .../TestLines/BlurredSegment/bstracker.h | 200 +++++ .../Testers/TestLines/BlurredSegment/changed | 14 + .../TestLines/BlurredSegment/nfafilter.cpp | 139 +++ .../TestLines/BlurredSegment/nfafilter.h | 94 ++ .../TestLines/BlurredSegment/rechanged | 12 + .../TestLines/ConvexHull/antipodal.cpp | 335 +++++++ .../Testers/TestLines/ConvexHull/antipodal.h | 135 +++ Expes/Testers/TestLines/ConvexHull/changed | 6 + .../Testers/TestLines/ConvexHull/chvertex.cpp | 36 + Expes/Testers/TestLines/ConvexHull/chvertex.h | 92 ++ .../TestLines/ConvexHull/convexhull.cpp | 261 ++++++ .../Testers/TestLines/ConvexHull/convexhull.h | 195 ++++ Expes/Testers/TestLines/ConvexHull/rechanged | 6 + .../DirectionalScanner/adaptivescannero1.cpp | 262 ++++++ .../DirectionalScanner/adaptivescannero1.h | 138 +++ .../DirectionalScanner/adaptivescannero2.cpp | 256 ++++++ .../DirectionalScanner/adaptivescannero2.h | 140 +++ .../DirectionalScanner/adaptivescannero7.cpp | 256 ++++++ .../DirectionalScanner/adaptivescannero7.h | 139 +++ .../DirectionalScanner/adaptivescannero8.cpp | 262 ++++++ .../DirectionalScanner/adaptivescannero8.h | 138 +++ .../TestLines/DirectionalScanner/changed | 28 + .../DirectionalScanner/directionalscanner.cpp | 22 + .../DirectionalScanner/directionalscanner.h | 157 ++++ .../directionalscannero1.cpp | 337 +++++++ .../DirectionalScanner/directionalscannero1.h | 131 +++ .../directionalscannero2.cpp | 335 +++++++ .../DirectionalScanner/directionalscannero2.h | 131 +++ .../directionalscannero7.cpp | 335 +++++++ .../DirectionalScanner/directionalscannero7.h | 131 +++ .../directionalscannero8.cpp | 335 +++++++ .../DirectionalScanner/directionalscannero8.h | 131 +++ .../TestLines/DirectionalScanner/rechanged | 28 + .../DirectionalScanner/scannerprovider.cpp | 194 ++++ .../DirectionalScanner/scannerprovider.h | 93 ++ .../DirectionalScanner/vhscannero1.cpp | 194 ++++ .../DirectionalScanner/vhscannero1.h | 113 +++ .../DirectionalScanner/vhscannero2.cpp | 194 ++++ .../DirectionalScanner/vhscannero2.h | 113 +++ .../DirectionalScanner/vhscannero7.cpp | 194 ++++ .../DirectionalScanner/vhscannero7.h | 113 +++ .../DirectionalScanner/vhscannero8.cpp | 194 ++++ .../DirectionalScanner/vhscannero8.h | 113 +++ Expes/Testers/TestLines/ImageTools/absrat.h | 71 ++ Expes/Testers/TestLines/ImageTools/changed | 13 + .../ImageTools/digitalstraightline.cpp | 460 ++++++++++ .../ImageTools/digitalstraightline.h | 255 ++++++ .../ImageTools/digitalstraightsegment.cpp | 277 ++++++ .../ImageTools/digitalstraightsegment.h | 181 ++++ Expes/Testers/TestLines/ImageTools/edist.cpp | 22 + Expes/Testers/TestLines/ImageTools/edist.h | 154 ++++ Expes/Testers/TestLines/ImageTools/pt2i.cpp | 832 ++++++++++++++++++ Expes/Testers/TestLines/ImageTools/pt2i.h | 195 ++++ Expes/Testers/TestLines/ImageTools/rechanged | 13 + Expes/Testers/TestLines/ImageTools/vmap.cpp | 769 ++++++++++++++++ Expes/Testers/TestLines/ImageTools/vmap.h | 393 +++++++++ Expes/Testers/TestLines/ImageTools/vr2i.cpp | 70 ++ Expes/Testers/TestLines/ImageTools/vr2i.h | 156 ++++ Expes/Testers/TestLines/Images/couloir.gif | Bin 0 -> 113589 bytes Expes/Testers/TestLines/TestLines.pro | 74 ++ Expes/Testers/TestLines/main.cpp | 305 +++++++ Expes/YorkUrbanDB/Scripts/precstat.sh | 17 + Expes/YorkUrbanDB/Scripts/precstats.sh | 112 +++ Expes/YorkUrbanDB/Scripts/ytimes.c | 159 ++++ Expes/YorkUrbanDB/data/moyennes/mprec.c | 136 +++ Ipol/paper/Algos/algoAuto.tex | 2 +- Ipol/paper/Algos/algoInitial.tex | 12 +- Ipol/paper/Algos/algoMulti.tex | 2 +- Ipol/paper/Algos/algoSingle.tex | 7 +- Ipol/paper/algos.tex | 32 +- Methode/evals.tex | 30 +- 87 files changed, 15030 insertions(+), 36 deletions(-) create mode 100644 Expes/Testers/TestLines/.qmake.stash create mode 100644 Expes/Testers/TestLines/BlurredSegment/biptlist.cpp create mode 100644 Expes/Testers/TestLines/BlurredSegment/biptlist.h create mode 100644 Expes/Testers/TestLines/BlurredSegment/blurredsegment.cpp create mode 100644 Expes/Testers/TestLines/BlurredSegment/blurredsegment.h create mode 100644 Expes/Testers/TestLines/BlurredSegment/blurredsegmentproto.cpp create mode 100644 Expes/Testers/TestLines/BlurredSegment/blurredsegmentproto.h create mode 100644 Expes/Testers/TestLines/BlurredSegment/bsdetector.cpp create mode 100644 Expes/Testers/TestLines/BlurredSegment/bsdetector.h create mode 100644 Expes/Testers/TestLines/BlurredSegment/bsproto.cpp create mode 100644 Expes/Testers/TestLines/BlurredSegment/bsproto.h create mode 100644 Expes/Testers/TestLines/BlurredSegment/bstracker.cpp create mode 100644 Expes/Testers/TestLines/BlurredSegment/bstracker.h create mode 100644 Expes/Testers/TestLines/BlurredSegment/changed create mode 100755 Expes/Testers/TestLines/BlurredSegment/nfafilter.cpp create mode 100755 Expes/Testers/TestLines/BlurredSegment/nfafilter.h create mode 100644 Expes/Testers/TestLines/BlurredSegment/rechanged create mode 100644 Expes/Testers/TestLines/ConvexHull/antipodal.cpp create mode 100644 Expes/Testers/TestLines/ConvexHull/antipodal.h create mode 100644 Expes/Testers/TestLines/ConvexHull/changed create mode 100644 Expes/Testers/TestLines/ConvexHull/chvertex.cpp create mode 100644 Expes/Testers/TestLines/ConvexHull/chvertex.h create mode 100644 Expes/Testers/TestLines/ConvexHull/convexhull.cpp create mode 100644 Expes/Testers/TestLines/ConvexHull/convexhull.h create mode 100644 Expes/Testers/TestLines/ConvexHull/rechanged create mode 100644 Expes/Testers/TestLines/DirectionalScanner/adaptivescannero1.cpp create mode 100644 Expes/Testers/TestLines/DirectionalScanner/adaptivescannero1.h create mode 100644 Expes/Testers/TestLines/DirectionalScanner/adaptivescannero2.cpp create mode 100644 Expes/Testers/TestLines/DirectionalScanner/adaptivescannero2.h create mode 100644 Expes/Testers/TestLines/DirectionalScanner/adaptivescannero7.cpp create mode 100644 Expes/Testers/TestLines/DirectionalScanner/adaptivescannero7.h create mode 100644 Expes/Testers/TestLines/DirectionalScanner/adaptivescannero8.cpp create mode 100644 Expes/Testers/TestLines/DirectionalScanner/adaptivescannero8.h create mode 100644 Expes/Testers/TestLines/DirectionalScanner/changed create mode 100644 Expes/Testers/TestLines/DirectionalScanner/directionalscanner.cpp create mode 100644 Expes/Testers/TestLines/DirectionalScanner/directionalscanner.h create mode 100644 Expes/Testers/TestLines/DirectionalScanner/directionalscannero1.cpp create mode 100644 Expes/Testers/TestLines/DirectionalScanner/directionalscannero1.h create mode 100644 Expes/Testers/TestLines/DirectionalScanner/directionalscannero2.cpp create mode 100644 Expes/Testers/TestLines/DirectionalScanner/directionalscannero2.h create mode 100644 Expes/Testers/TestLines/DirectionalScanner/directionalscannero7.cpp create mode 100644 Expes/Testers/TestLines/DirectionalScanner/directionalscannero7.h create mode 100644 Expes/Testers/TestLines/DirectionalScanner/directionalscannero8.cpp create mode 100644 Expes/Testers/TestLines/DirectionalScanner/directionalscannero8.h create mode 100644 Expes/Testers/TestLines/DirectionalScanner/rechanged create mode 100644 Expes/Testers/TestLines/DirectionalScanner/scannerprovider.cpp create mode 100644 Expes/Testers/TestLines/DirectionalScanner/scannerprovider.h create mode 100644 Expes/Testers/TestLines/DirectionalScanner/vhscannero1.cpp create mode 100644 Expes/Testers/TestLines/DirectionalScanner/vhscannero1.h create mode 100644 Expes/Testers/TestLines/DirectionalScanner/vhscannero2.cpp create mode 100644 Expes/Testers/TestLines/DirectionalScanner/vhscannero2.h create mode 100644 Expes/Testers/TestLines/DirectionalScanner/vhscannero7.cpp create mode 100644 Expes/Testers/TestLines/DirectionalScanner/vhscannero7.h create mode 100644 Expes/Testers/TestLines/DirectionalScanner/vhscannero8.cpp create mode 100644 Expes/Testers/TestLines/DirectionalScanner/vhscannero8.h create mode 100644 Expes/Testers/TestLines/ImageTools/absrat.h create mode 100644 Expes/Testers/TestLines/ImageTools/changed create mode 100644 Expes/Testers/TestLines/ImageTools/digitalstraightline.cpp create mode 100644 Expes/Testers/TestLines/ImageTools/digitalstraightline.h create mode 100644 Expes/Testers/TestLines/ImageTools/digitalstraightsegment.cpp create mode 100644 Expes/Testers/TestLines/ImageTools/digitalstraightsegment.h create mode 100644 Expes/Testers/TestLines/ImageTools/edist.cpp create mode 100644 Expes/Testers/TestLines/ImageTools/edist.h create mode 100644 Expes/Testers/TestLines/ImageTools/pt2i.cpp create mode 100644 Expes/Testers/TestLines/ImageTools/pt2i.h create mode 100644 Expes/Testers/TestLines/ImageTools/rechanged create mode 100644 Expes/Testers/TestLines/ImageTools/vmap.cpp create mode 100644 Expes/Testers/TestLines/ImageTools/vmap.h create mode 100644 Expes/Testers/TestLines/ImageTools/vr2i.cpp create mode 100644 Expes/Testers/TestLines/ImageTools/vr2i.h create mode 100644 Expes/Testers/TestLines/Images/couloir.gif create mode 100644 Expes/Testers/TestLines/TestLines.pro create mode 100644 Expes/Testers/TestLines/main.cpp create mode 100644 Expes/YorkUrbanDB/Scripts/precstat.sh create mode 100644 Expes/YorkUrbanDB/Scripts/precstats.sh create mode 100644 Expes/YorkUrbanDB/Scripts/ytimes.c create mode 100644 Expes/YorkUrbanDB/data/moyennes/mprec.c diff --git a/Code/FBSD/BlurredSegment/bsdetector.cpp b/Code/FBSD/BlurredSegment/bsdetector.cpp index 545c181..6b6a4b0 100755 --- a/Code/FBSD/BlurredSegment/bsdetector.cpp +++ b/Code/FBSD/BlurredSegment/bsdetector.cpp @@ -48,7 +48,7 @@ BSDetector::BSDetector () bst1 = new BSTracker (); bst2 = new BSTracker (); - nfaOn = false; + nfaOn = true; nfaf = NULL; nfaf = (nfaOn ? new NFAFilter () : NULL); diff --git a/Code/FBSD/BlurredSegment/nfafilter.cpp b/Code/FBSD/BlurredSegment/nfafilter.cpp index 8a676d4..e525eb5 100755 --- a/Code/FBSD/BlurredSegment/nfafilter.cpp +++ b/Code/FBSD/BlurredSegment/nfafilter.cpp @@ -82,7 +82,7 @@ bool NFAFilter::filter (const BlurredSegment *bs, int start, int end) // Gets point with small gradient int gmin = max_grad2; - int pmin = -1; +// int pmin = -1; std::vector<Pt2i> pts = bs->getAllPoints (); for (int i = start; i < end; i++) { @@ -90,14 +90,15 @@ bool NFAFilter::filter (const BlurredSegment *bs, int start, int end) if (gn < gmin) { gmin = gn; - pmin = i; +// pmin = i; } } // Gets NFA and accept or split the segment double nfa = nfaValue (cum_histo[(int) (sqrt (gmin))], length); - if (nfa < NFA_EPSILON) return true; - return (filter (bs, start, pmin) && filter (bs, pmin + 1, end)); + return (nfa < NFA_EPSILON); +// if (nfa < NFA_EPSILON) return true; +// return (filter (bs, start, pmin) && filter (bs, pmin + 1, end)); } diff --git a/Code/FBSD/ImageTools/digitalstraightsegment.cpp b/Code/FBSD/ImageTools/digitalstraightsegment.cpp index 581250b..c2b509f 100755 --- a/Code/FBSD/ImageTools/digitalstraightsegment.cpp +++ b/Code/FBSD/ImageTools/digitalstraightsegment.cpp @@ -222,17 +222,25 @@ DigitalStraightSegment *DigitalStraightSegment::dilation ( } -DigitalStraightSegment *DigitalStraightSegment::dilation (int radius) const +DigitalStraightSegment *DigitalStraightSegment::dilation (int ar_width) const { - return (new DigitalStraightSegment (a, b, c - radius, - nu + 2 * radius, min, max)); + return (new DigitalStraightSegment (a, b, c - ar_width, + nu + 2 * ar_width, min, max)); } -void DigitalStraightSegment::dilate (int radius) +void DigitalStraightSegment::dilate (int ar_width) { - nu += 2 * radius; - c -= radius; + nu += 2 * ar_width; + c -= ar_width; +} + + +void DigitalStraightSegment::setNaive () +{ + int p = period (); + c += (nu - p) / 2; + nu = p; } diff --git a/Code/FBSD/ImageTools/digitalstraightsegment.h b/Code/FBSD/ImageTools/digitalstraightsegment.h index 5c774a4..891b6a2 100755 --- a/Code/FBSD/ImageTools/digitalstraightsegment.h +++ b/Code/FBSD/ImageTools/digitalstraightsegment.h @@ -24,7 +24,10 @@ public: DigitalStraightSegment (); /** - * \brief Creates a null-thick segment joining two points. + * \brief Creates a null-thick segment passing through two points. + * Caution: the segment ends on given min-max bounds ! + * To create a segment ending on the two points, use constructor with + * two points and width instead. * @param p1 First point on the line. * @param p2 Second point on the line. * @param type Digital line type : DSL_THIN, DSL_NAIVE or DSL_STANDARD. @@ -53,7 +56,7 @@ public: * \brief Creates a digital straight segment from end points and width. * @param p1 First end point of the segment. * @param p2 Second end point of the segment. - * @param width Width value. + * @param width Width value: 1 for a naive line. */ DigitalStraightSegment (Pt2i p1, Pt2i p2, int width); @@ -107,16 +110,21 @@ public: DigitalStraightSegment *dilation (int num, int den) const; /** - * \brief Returns a dilated segment of given radius. - * @param radius Dilation radius. + * \brief Returns a dilated segment of given arithmetical width on each side. + * @param radius Dilation arithmetical width. */ - DigitalStraightSegment *dilation (int radius) const; + DigitalStraightSegment *dilation (int ar_width) const; /** - * \brief Dilates the segment of given radius. - * @param radius Dilation radius. + * \brief Dilates the segment of given arithmetical width on each side. + * @param radius Dilation arithmetical width. */ - void dilate (int radius); + void dilate (int ar_width); + + /** + * \brief Erodes the segment to its naive center line. + */ + void setNaive (); /** * \brief Inquires if given point belongs to a dilation of the segment. diff --git a/Expes/Testers/TestLines/.qmake.stash b/Expes/Testers/TestLines/.qmake.stash new file mode 100644 index 0000000..d6e97ef --- /dev/null +++ b/Expes/Testers/TestLines/.qmake.stash @@ -0,0 +1,23 @@ +QMAKE_CXX.QT_COMPILER_STDCXX = 201402L +QMAKE_CXX.QMAKE_GCC_MAJOR_VERSION = 9 +QMAKE_CXX.QMAKE_GCC_MINOR_VERSION = 3 +QMAKE_CXX.QMAKE_GCC_PATCH_VERSION = 0 +QMAKE_CXX.COMPILER_MACROS = \ + QT_COMPILER_STDCXX \ + QMAKE_GCC_MAJOR_VERSION \ + QMAKE_GCC_MINOR_VERSION \ + QMAKE_GCC_PATCH_VERSION +QMAKE_CXX.INCDIRS = \ + /usr/include/c++/9 \ + /usr/include/x86_64-linux-gnu/c++/9 \ + /usr/include/c++/9/backward \ + /usr/lib/gcc/x86_64-linux-gnu/9/include \ + /usr/local/include \ + /usr/include/x86_64-linux-gnu \ + /usr/include +QMAKE_CXX.LIBDIRS = \ + /usr/lib/gcc/x86_64-linux-gnu/9 \ + /usr/lib/x86_64-linux-gnu \ + /usr/lib \ + /lib/x86_64-linux-gnu \ + /lib diff --git a/Expes/Testers/TestLines/BlurredSegment/biptlist.cpp b/Expes/Testers/TestLines/BlurredSegment/biptlist.cpp new file mode 100644 index 0000000..e416c26 --- /dev/null +++ b/Expes/Testers/TestLines/BlurredSegment/biptlist.cpp @@ -0,0 +1,256 @@ +#include "biptlist.h" + + +BiPtList::BiPtList (Pt2i pt) +{ + pts.push_back (pt); + start = 0; + cpt = 1; +} + + +BiPtList::~BiPtList () +{ +} + + +void BiPtList::addFront (Pt2i pt) +{ + pts.push_front (pt); + start++; + cpt++; +} + + +void BiPtList::addBack (Pt2i pt) +{ + pts.push_back (pt); + cpt++; +} + + +void BiPtList::removeFront (int n) +{ + if (n >= frontSize ()) n = frontSize () - 1; // We keep at least one point + for (int i = 0; i < n; i++) pts.pop_front (); + cpt -= n; + start -= n; + if (start < 0) start = 0; // Theoretically impossible +} + + +void BiPtList::removeBack (int n) +{ + if (n >= backSize ()) n = backSize () - 1; // We keep at least one point + for (int i = 0; i < n; i++) pts.pop_back (); + cpt -= n; + if (start >= cpt) start = cpt - 1; // Theoretically impossible +} + + +void BiPtList::findExtrema (int &xmin, int &ymin, int &xmax, int &ymax) const +{ + std::deque<Pt2i>::const_iterator it = pts.begin (); + xmin = it->x (); + ymin = it->y (); + xmax = it->x (); + ymax = it->y (); + while (it != pts.end ()) + { + if (xmin > it->x ()) xmin = it->x (); + if (xmax < it->x ()) xmax = it->x (); + if (ymin > it->y ()) ymin = it->y (); + if (ymax < it->y ()) ymax = it->y (); + it++; + } +} + + +std::vector<Pt2i> BiPtList::frontToBackPoints () const +{ + std::vector<Pt2i> res; + for (std::deque<Pt2i>::const_iterator it = pts.begin (); + it != pts.end (); it++) + res.push_back (*it); + return (res); +} + + +std::vector<Pt2i> *BiPtList::emptyVector () const +{ + return (new std::vector<Pt2i> ()); +} + + +std::vector<Pt2i> *BiPtList::frontPoints () const +{ + // Entered from extremity to center : relevant ? + std::vector<Pt2i> *res = new std::vector<Pt2i> (); + std::deque<Pt2i>::const_iterator it = pts.begin (); + for (int i = 0; i < start; i++) res->push_back (*it++); + return res; +} + + +std::vector<Pt2i> *BiPtList::backPoints () const +{ + std::vector<Pt2i> *res = new std::vector<Pt2i> (); + std::deque<Pt2i>::const_iterator it = pts.begin (); + it += start + 1; + for (int i = 0; i < cpt - start - 1; i++) res->push_back (*it++); + return res; +} + + +EDist BiPtList::heightToEnds (const Pt2i &pt) const +{ + EDist xh = xHeightToEnds (pt); + EDist yh = yHeightToEnds (pt); + return (xh.lessThan (yh) ? xh : yh); +} + + +EDist BiPtList::xHeightToEnds (const Pt2i &pt) const +{ + int xp = pt.x (), yp = pt.y (); + int p1x = pts.front().x (), p1y = pts.front().y (); + int p2x = pts.back().x (), p2y = pts.back().y (); + int ax, ay, bx, by, cx, cy; + + if (xp < p1x) + { + if (xp < p2x) + { + ax = xp; + ay = yp; + if (p1x < p2x) + { + bx = p1x; + by = p1y; + cx = p2x; + cy = p2y; + } + else + { + bx = p2x; + by = p2y; + cx = p1x; + cy = p1y; + } + } + else + { + ax = p2x; + ay = p2y; + bx = xp; + by = yp; + cx = p1x; + cy = p1y; + } + } + else + { + if (xp < p2x) + { + ax = p1x; + ay = p1y; + bx = xp; + by = yp; + cx = p2x; + cy = p2y; + } + else + { + cx = xp; + cy = yp; + if (p1x < p2x) + { + ax = p1x; + ay = p1y; + bx = p2x; + by = p2y; + } + else + { + ax = p2x; + ay = p2y; + bx = p1x; + by = p1y; + } + } + } + return (EDist ((bx - ax) * (cy - ay) - (by - ay) * (cx - ax), cx - ax)); +} + + +EDist BiPtList::yHeightToEnds (const Pt2i &pt) const +{ + int xp = pt.x (), yp = pt.y (); + int p1x = pts.front().x (), p1y = pts.front().y (); + int p2x = pts.back().x (), p2y = pts.back().y (); + int ax, ay, bx, by, cx, cy; + + if (yp < p1y) + { + if (yp < p2y) + { + ax = xp; + ay = yp; + if (p1y < p2y) + { + bx = p1x; + by = p1y; + cx = p2x; + cy = p2y; + } + else + { + bx = p2x; + by = p2y; + cx = p1x; + cy = p1y; + } + } + else + { + ax = p2x; + ay = p2y; + bx = xp; + by = yp; + cx = p1x; + cy = p1y; + } + } + else + { + if (yp < p2y) + { + ax = p1x; + ay = p1y; + bx = xp; + by = yp; + cx = p2x; + cy = p2y; + } + else + { + cx = xp; + cy = yp; + if (p1y < p2y) + { + ax = p1x; + ay = p1y; + bx = p2x; + by = p2y; + } + else + { + ax = p2x; + ay = p2y; + bx = p1x; + by = p1y; + } + } + } + return (EDist ((bx - ax) * (cy - ay) - (by - ay) * (cx - ax), cy - ay)); +} diff --git a/Expes/Testers/TestLines/BlurredSegment/biptlist.h b/Expes/Testers/TestLines/BlurredSegment/biptlist.h new file mode 100644 index 0000000..417d8b8 --- /dev/null +++ b/Expes/Testers/TestLines/BlurredSegment/biptlist.h @@ -0,0 +1,146 @@ +#ifndef BIPT_LIST_H +#define BIPT_LIST_H + +#include "pt2i.h" +#include "edist.h" +#include <deque> + + +/** + * @class BiPtList biptlist.h + * \brief Bi-directional list of points. + */ +class BiPtList +{ +public: + + /** + * \brief Creates a extendable bi-directional list with one point. + * @param pt Initial point of the list. + */ + BiPtList (Pt2i pt); + + /** + * \brief Deletes the bi-directional list. + */ + ~BiPtList (); + + /** + * \brief Returns the count of point in the bi-directional list. + */ + inline int size () const { return (cpt); } + + /** + * \brief Returns the count of point on back part of bi-directional list. + */ + inline int backSize () const { return (cpt - start - 1); } + + /** + * \brief Returns the count of point on front part of bi-directional list. + */ + inline int frontSize () const { return (start); } + + /** + * \brief Returns the initial point of the bi-directional list. + */ + inline Pt2i initialPoint () const { return (pts[start]); } + + /** + * \brief Returns the back end point of the bi-directional list. + */ + inline Pt2i backPoint () const { return (pts.back ()); } + + /** + * \brief Returns the front end point of the bi-directional list. + */ + inline Pt2i frontPoint () const { return (pts.front ()); } + + /** + * \brief Returns a point Manhattan height to the line between end points. + * Manhattan height is the lowest value of horizontal or vertical height. + * @param pt Tested point. + */ + EDist heightToEnds (const Pt2i &pt) const; + + /** + * \brief Adds a point on front side. + * @param pt Point to add. + */ + void addFront (Pt2i pt); + + /** + * \brief Adds a point on back side. + * @param pt Point to add. + */ + void addBack (Pt2i pt); + + /** + * \brief Removes points on front side. + * @param n The amount of points to remove. + */ + void removeFront (int n); + + /** + * \brief Removes points on back side. + * @param n The amount of points to remove. + */ + void removeBack (int n); + + /** + * \brief Sets given parameters with point min and max coordinates. + * @param xmin Minimum X value to set. + * @param ymin Minimum Y value to set. + * @param xmax Maximum X value to set. + * @param ymax Maximum Y value to set. + */ + void findExtrema (int &xmin, int &ymin, int &xmax, int &ymax) const; + + /** + * \brief Returns front to back points in a vector. + */ + std::vector<Pt2i> frontToBackPoints () const; + + /** + * \brief Returns a pointer to a empty vector of points. + */ + std::vector<Pt2i> *emptyVector () const; + + /** + * \brief Returns a pointer to a vector containing front points. + * Front points are entered from segment edge to the initial point excluded. + */ + std::vector<Pt2i> *frontPoints () const; + + /** + * \brief Returns a pointer to a vector containing back points. + * Back points are entered from initial point excluded to segment edge. + */ + std::vector<Pt2i> *backPoints () const; + + +private: + + /** List of points. */ + std::deque<Pt2i> pts; + /** Index of the initial point. */ + int start; + /** Length of the point list. */ + int cpt; + + + /** + * \brief Returns a point X-height to the line between list end points. + * X-height is the horizontal distance. + * @param pt Tested point. + */ + EDist xHeightToEnds (const Pt2i &pt) const; + + /** + * \brief Returns a point Y-height to the line between list end points. + * Y-height is the vertical distance. + * @param pt Tested point. + */ + EDist yHeightToEnds (const Pt2i &pt) const; + +}; +#endif diff --git a/Expes/Testers/TestLines/BlurredSegment/blurredsegment.cpp b/Expes/Testers/TestLines/BlurredSegment/blurredsegment.cpp new file mode 100644 index 0000000..81e8e20 --- /dev/null +++ b/Expes/Testers/TestLines/BlurredSegment/blurredsegment.cpp @@ -0,0 +1,328 @@ +#include "blurredsegment.h" + + +BlurredSegment::BlurredSegment () +{ + plist = NULL; + dss = NULL; + scan = NULL; +} + + +BlurredSegment::BlurredSegment (BiPtList *ptlist, DigitalStraightSegment *seg, + const Pt2i &aps, const Pt2i &ape, + const Pt2i &apv) +{ + plist = ptlist; + dss = seg; + scan = NULL; + laps.set (aps); + lape.set (ape); + lapv.set (apv); +} + + +BlurredSegment::~BlurredSegment () +{ + if (plist != NULL) delete plist; + if (dss != NULL) delete dss; + if (scan != NULL) delete scan; +} + + +void BlurredSegment::setScan (const Pt2i &pt1, const Pt2i &pt2) +{ + scan = new DigitalStraightLine (pt1, pt2, DigitalStraightLine::DSL_NAIVE); +} + + +void BlurredSegment::setScan (const Pt2i ¢er, const Vr2i &dir) +{ + Pt2i p2 (center.x () + dir.x (), center.y () + dir.y ()); + scan = new DigitalStraightLine (center, p2, DigitalStraightLine::DSL_NAIVE); +} + + +EDist BlurredSegment::minimalWidth () const +{ + return (EDist (dss->width (), dss->period ())); +} + + +std::vector<Pt2i> BlurredSegment::getAllPoints () const +{ + return (plist->frontToBackPoints ()); +} + + +std::vector<Pt2i> *BlurredSegment::getAllRight () const +{ + return (plist->backPoints ()); +} + + +std::vector<Pt2i> *BlurredSegment::getAllLeft () const +{ + return (plist->frontPoints ()); +} + + +DigitalStraightSegment *BlurredSegment::holdSegment () +{ + DigitalStraightSegment *tmp = dss; + dss = NULL; + return tmp; +} + + +int BlurredSegment::size () const +{ + return (plist->size ()); +} + + +int BlurredSegment::extent () const +{ + int l = 0; + if (scan != NULL) + { + l = scan->pavingIndex (plist->backPoint ()) + + scan->pavingIndex (plist->frontPoint ()); + } + else + { + Pt2i pr = plist->backPoint (); + Pt2i pl = plist->frontPoint (); + Pt2i c ((pr.x () + pl.x ()) / 2, (pr.y () + pl.y ()) / 2); + Pt2i d (c.x () + pl.y () - pr.y (), c.y () + pr.x () - pl.x ()); + DigitalStraightLine line (c, d, DigitalStraightLine::DSL_NAIVE); + l = line.pavingIndex (plist->backPoint ()) + + line.pavingIndex (plist->frontPoint ()); + } + return (l < 0 ? 1 - l : 1 + l); +} + + +std::vector<Pt2i> BlurredSegment::getStartPt () const +{ + std::vector<Pt2i> res; + res.push_back (plist->initialPoint ()); + return res; +} + + +const Pt2i BlurredSegment::getLastRight () const +{ + return (plist->backPoint ()); +} + + +const Pt2i BlurredSegment::getLastLeft () const +{ + return (plist->frontPoint ()); +} + + +int BlurredSegment::getSquarredLength () const +{ + Pt2i rp = plist->backPoint (); + Pt2i lp = plist->frontPoint (); + return ((rp.x () - lp.x ()) * (rp.x () - lp.x ()) + + (rp.y () - lp.y ()) * (rp.y () - lp.y ())); +} + + +const Pt2i BlurredSegment::getMiddle () const +{ + Pt2i rp = plist->backPoint (); + Pt2i lp = plist->frontPoint (); + return (Pt2i ((rp.x () + lp.x ()) / 2, (rp.y () + lp.y ()) / 2)); +} + + +Vr2i BlurredSegment::getSupportVector () +{ + return (dss->supportVector ()); +} + + +Vr2i BlurredSegment::boundingBoxSize () const +{ + int xmin, ymin, xmax, ymax; + plist->findExtrema (xmin, ymin, xmax, ymax); + return (Vr2i (xmax - xmin, ymax - ymin)); +} + + +std::vector <std::vector <Pt2i> > BlurredSegment::connectedComponents () const +{ + std::vector <std::vector <Pt2i> > ccs; + std::vector <Pt2i> pts = getAllPoints (); + if (pts.size () > 1) + { + std::vector <Pt2i> cc; + bool started = false; + std::vector <Pt2i>::const_iterator it = pts.begin (); + Pt2i pix (*it++); + while (it != pts.end ()) + { + if (it->isConnectedTo (pix)) + { + if (! started) + { + cc.push_back (pix); + started = true; + } + cc.push_back (*it); + } + else + { + if (started) + { + ccs.push_back (cc); + cc.clear (); + started = false;; + } + } + pix.set (*it++); + } + } + return ccs; +} + + +int BlurredSegment::countOfConnectedPoints () const +{ + int count = 0; + std::vector <Pt2i> pts = getAllPoints (); + if (pts.size () > 1) + { + bool started = false; + std::vector <Pt2i>::const_iterator it = pts.begin (); + Pt2i pix (*it++); + while (it != pts.end ()) + { + if (it->isConnectedTo (pix)) + { + if (started) count ++; + else + { + count += 2; + started = true; + } + } + else + { + if (started) started = false;; + } + pix.set (*it++); + } + } + return count; +} + + +int BlurredSegment::countOfConnectedComponents () const +{ + int count = 0; + std::vector <Pt2i> pts = getAllPoints (); + if (pts.size () > 1) + { + bool started = false; + std::vector <Pt2i>::const_iterator it = pts.begin (); + Pt2i pix (*it++); + while (it != pts.end ()) + { + if (it->isConnectedTo (pix)) + { + if (! started) + { + count ++; + started = true; + } + } + else + { + if (started) started = false;; + } + pix.set (*it++); + } + } + return count; +} + + +int BlurredSegment::countOfConnectedPoints (int min) const +{ + int count = 0; + std::vector <Pt2i> pts = getAllPoints (); + if (pts.size () > 1) + { + int cpt = 1; + std::vector <Pt2i>::const_iterator it = pts.begin (); + Pt2i pix (*it++); + while (it != pts.end ()) + { + if (it->isConnectedTo (pix)) + { + if (++cpt == min) count += min; + else if (cpt > min) count ++; + } + else cpt = 1; + pix.set (*it++); + } + } + return count; +} + + +int BlurredSegment::countOfConnectedComponents (int min) const +{ + int count = 0; + std::vector <Pt2i> pts = getAllPoints (); + if (pts.size () > 1) + { + int cpt = 1; + std::vector <Pt2i>::const_iterator it = pts.begin (); + Pt2i pix (*it++); + while (it != pts.end ()) + { + if (it->isConnectedTo (pix)) + { + if (++cpt == min) count ++; + } + else cpt = 1;; + pix.set (*it++); + } + } + return count; +} + + +std::vector <std::vector <Pt2i> > +BlurredSegment::getConnectedComponents () const +{ + std::vector <std::vector <Pt2i> > res; + std::vector <Pt2i> pts = getAllPoints (); + if (pts.size () > 1) + { + std::vector <Pt2i>::const_iterator bit = pts.begin (); + std::vector <Pt2i>::const_iterator eit = pts.end (); + while (bit != eit) + { + std::vector <Pt2i> lres; + Pt2i pix = *bit++; + bool compose = true; + do + { + lres.push_back (pix); + compose = bit->isConnectedTo (pix); + if (compose) pix.set (*bit++); + } + while (compose && bit != eit); + if (compose) lres.push_back (pix); + res.push_back (lres); + } + } + return res; +} diff --git a/Expes/Testers/TestLines/BlurredSegment/blurredsegment.h b/Expes/Testers/TestLines/BlurredSegment/blurredsegment.h new file mode 100644 index 0000000..d7a7e07 --- /dev/null +++ b/Expes/Testers/TestLines/BlurredSegment/blurredsegment.h @@ -0,0 +1,238 @@ +#ifndef BLURRED_SEGMENT_H +#define BLURRED_SEGMENT_H + +#include "convexhull.h" +#include "digitalstraightsegment.h" +#include "biptlist.h" + + +/** + * @class BlurredSegment blurredsegment.h + * \brief A set of 2D points lying inside a digital straight line. + */ +class BlurredSegment +{ +public: + + /** + * \brief Creates an empty blurred segment. + */ + BlurredSegment (); + + /** + * \brief Creates a blurred segment from a list of points. + * @param ptlist List of points of the blurred segment to build. + * @param seg Bounding digital straight segment. + * @param aps Start point of the antipodal edge. + * @param ape End point of the antipodal edge. + * @param apv Antipodal vertex. + */ + BlurredSegment (BiPtList *ptlist, DigitalStraightSegment *seg, + const Pt2i &aps, const Pt2i &ape, const Pt2i &apv); + + /** + * \brief Deletes the blurred segment. + */ + virtual ~BlurredSegment (); + + /** + * \brief Sets the scan line used for detection. + * @param pt1 Scan start point. + * @param pt2 Scan end point. + */ + virtual void setScan (const Pt2i &pt1, const Pt2i &pt2); + + /** + * \brief Sets the scan line used for detection. + * @param center Scan center point. + * @param dir Scan line direction. + */ + virtual void setScan (const Pt2i ¢er, const Vr2i &dir); + + /** + * \brief Returns the minimal vertical or horizontal width. + */ + virtual EDist minimalWidth () const; + + /** + * \brief Returns if the segment has non null thickness (not aligned points). + */ + inline bool isThick () const { return (dss->width () > 1); } + + /** + * \brief Returns the optimal digital straight segment. + */ + inline DigitalStraightSegment *getSegment () { return dss; } + + /** + * \brief Returns a pointer to a copy of the optimal DSS. + */ + DigitalStraightSegment *holdSegment (); + + /** + * \brief Returns the count of points of the blurred segment. + */ + int size () const; + + /** + * \brief Returns the scan distance between end points. + */ + int extent () const; + + /** + * \brief Returns the start point of the blurred segment. + */ + inline const Pt2i getCenter () const { return plist->initialPoint (); } + + /** + * \brief Returns the colinear points at the left of the start point. + */ + inline const std::vector<Pt2i> *getLeftLine () const { + return plist->emptyVector (); } + + /** + * \brief Returns the colinear points at the right of the start point. + */ + inline const std::vector<Pt2i> *getRightLine () const { + return plist->emptyVector (); } + + /** + * \brief Returns the left points added to the blurred segment start point. + */ + inline const std::vector<Pt2i> *getLeftPoints () const { + return plist->frontPoints (); } + + /** + * \brief Returns the right points added to the blurred segment start point. + */ + inline const std::vector<Pt2i> *getRightPoints () const { + return plist->backPoints (); } + + /** + * \brief Returns the set of all the points on the blurred segment. + * Points are ordered from the left end point up to the right end point. + */ + std::vector<Pt2i> getAllPoints () const; + + /** + * \brief Returns the set of points on the left part of the blurred segment. + * Points are ordered from the furthest to the nearest to the start point. + */ + std::vector<Pt2i> *getAllLeft () const; + + /** + * \brief Returns the set of points on the left part of the blurred segment. + * Points are ordered from the nearest to the furthest to the start point. + */ + std::vector<Pt2i> *getAllRight () const; + + /** + * \brief Returns a vector containing the start point of the blurred segment. + */ + std::vector<Pt2i> getStartPt () const; + + /** + * \brief Returns the last accepted point on the right side. + */ + const Pt2i getLastRight () const; + + /** + * \brief Returns the last accepted point on the left side. + */ + const Pt2i getLastLeft () const; + + /** + * \brief Returns the squarred length of the blurred segment. + * Returns the squarred distance between end points. + */ + int getSquarredLength () const; + + /** + * \brief Returns the middle point of the blurred segment. + * Returns the middle point between end points. + */ + const Pt2i getMiddle () const; + + /** + * \brief Returns the start point of the last antipodal edge. + */ + inline const Pt2i antipodalEdgeStart () const { return laps; } + + /** + * \brief Returns the end point of the last antipodal edge. + */ + inline const Pt2i antipodalEdgeEnd () const { return lape; } + + /** + * \brief Returns the last antipodal vertex. + */ + inline const Pt2i antipodalVertex () const { return lapv; } + + /** + * \brief Returns the support vector of the blurred segment. + */ + virtual Vr2i getSupportVector (); + + /** + * \brief Returns the size of the segment bounding box in a vector 2D. + */ + Vr2i boundingBoxSize () const; + + /** + * \brief Returns the connected components of the blurred segment. + */ + std::vector <std::vector <Pt2i> > connectedComponents () const; + + /** + * \brief Returns the count of connected points in the blurred segment. + */ + int countOfConnectedPoints () const; + + /** + * \brief Returns the count of connected components in the blurred segment. + */ + int countOfConnectedComponents () const; + + /** + * \brief Returns the count of connected points of given minimal size. + * @param min Minimal size of the connected components. + */ + int countOfConnectedPoints (int min) const; + + /** + * \brief Returns the count of connected components of given minimal size. + * @param min Minimal size of the connected components. + */ + int countOfConnectedComponents (int min) const; + + /** + * \brief Returns the connected components of the blurred segment. + */ + std::vector <std::vector <Pt2i> > getConnectedComponents () const; + + /** + * \brief Checks if given point is one of the three antipodal points. + * @param pt Given point. + */ + inline bool isAntipodal (Pt2i pt) const { + return (pt.equals (laps) || pt.equals (lape) || pt.equals (lapv)); } + + +protected: + + /** Bi-directional list of points: the effective blurred segment. */ + BiPtList *plist; + /** Bounding straight segment. */ + DigitalStraightSegment *dss; + /** Central scan line used for detection (if defined). */ + DigitalStraightLine *scan; + + /** Start point of the last known antipodal edge. */ + Pt2i laps; + /** End point of the last known antipodal edge. */ + Pt2i lape; + /** Last known antipodal vertex. */ + Pt2i lapv; + +}; +#endif diff --git a/Expes/Testers/TestLines/BlurredSegment/blurredsegmentproto.cpp b/Expes/Testers/TestLines/BlurredSegment/blurredsegmentproto.cpp new file mode 100644 index 0000000..c11e553 --- /dev/null +++ b/Expes/Testers/TestLines/BlurredSegment/blurredsegmentproto.cpp @@ -0,0 +1,294 @@ +#include "blurredsegmentproto.h" + + +BlurredSegmentProto::BlurredSegmentProto (int maxWidth, Pt2i pix) +{ + this->maxWidth.set (maxWidth); + plist = new BiPtList (pix); + leftOK = false; + rightOK = false; + bsFlat = false; + bsOK = false; + convexhull = NULL; + chChanged = false; + dss = NULL; +} + + +BlurredSegmentProto::BlurredSegmentProto (int maxWidth, Pt2i center, + const vector<Pt2i> &leftPts, const vector<Pt2i> &rightPts) +{ + this->maxWidth.set (maxWidth); + plist = new BiPtList (center); + leftOK = false; + rightOK = false; + bsFlat = false; + bsOK = false; + convexhull = NULL; + chChanged = false; + dss = NULL; + + vector<Pt2i>::const_iterator itr = rightPts.begin (); + vector<Pt2i>::const_iterator itl = leftPts.begin (); + bool scanningRight = true; + bool scanningLeft = true; + while (scanningRight || scanningLeft) + { + if (scanningRight) + { + if (itr == rightPts.end ()) scanningRight = false; + else + if (! addRight (*itr++)) scanningRight = false; + } + if (scanningLeft) + { + if (itl == leftPts.end ()) scanningLeft = false; + else + if (! addLeft (*itl++)) scanningLeft = false; + } + } +} + + +BlurredSegmentProto::~BlurredSegmentProto () +{ + if (convexhull != NULL) delete convexhull; +} + + +EDist BlurredSegmentProto::strictThickness () const +{ + return (convexhull != NULL ? convexhull->thickness () : EDist (0, 1)); +} + + +EDist BlurredSegmentProto::digitalThickness () const +{ + if (bsOK) + { + Pt2i s, e, v; + convexhull->antipodalEdgeAndVertex (s, e, v); + DigitalStraightLine l (s, e, v); + return (EDist (l.width (), l.period ())); + } + return (EDist (1, 1)); +} + + +DigitalStraightLine *BlurredSegmentProto::getLine () const +{ + if (bsOK) + { + Pt2i s, e, v; + convexhull->antipodalEdgeAndVertex (s, e, v); + return (new DigitalStraightLine (s, e, v)); + } + if (bsFlat || leftOK || rightOK) + return (new DigitalStraightLine (getLastLeft (), getLastRight (), + DigitalStraightLine::DSL_THIN)); + return (NULL); // No line if only one point +} + + +bool BlurredSegmentProto::addLeft (Pt2i pix) +{ + if (bsOK) // convexhull defined + { + bool res = addPoint (pix, true); + return (res); + } + + else if (bsFlat) // leftOK && rightOK + { + EDist height = plist->heightToEnds (pix); + if (height.greaterThan (maxWidth)) return false; + if (height.numerator () != 0) + { + convexhull = new ConvexHull (pix, plist->frontPoint (), + plist->backPoint ()); + bsOK = true; + } + plist->addFront (pix); + } + + else if (leftOK) // thus ! rightOK + { + EDist height = plist->heightToEnds (pix); + if (height.greaterThan (maxWidth)) return false; + if (height.numerator () == 0) bsFlat = true; + else + { + convexhull = new ConvexHull (pix, plist->frontPoint (), + plist->backPoint ()); + bsOK = true; + } + plist->addFront (pix); + } + + else if (rightOK) + { + EDist height = plist->heightToEnds (pix); + if (height.greaterThan (maxWidth)) return false; + if (height.numerator () == 0) bsFlat = true; + else + { + convexhull = new ConvexHull (pix, plist->frontPoint (), + plist->backPoint ()); + bsOK = true; + } + plist->addFront (pix); + } + + else // only the central point is ok + { + plist->addFront (pix); + leftOK = true; + } + + chChanged = true; + return true; +} + + +bool BlurredSegmentProto::addRight (Pt2i pix) +{ + if (bsOK) // bs != NULL + { + bool res = addPoint (pix, false); + return (res); + } + + else if (bsFlat) // leftOK && rightOK + { + EDist height = plist->heightToEnds (pix); + if (height.greaterThan (maxWidth)) return false; + if (height.numerator () != 0) + { + convexhull = new ConvexHull (plist->frontPoint (), + plist->backPoint (), pix); + bsOK = true; + } + plist->addBack (pix); + } + + else if (rightOK) // thus ! leftOK + { + EDist height = plist->heightToEnds (pix); + if (height.greaterThan (maxWidth)) return false; + if (height.numerator () == 0) bsFlat = true; + else + { + convexhull = new ConvexHull (plist->frontPoint (), + plist->backPoint (), pix); + bsOK = true; + } + plist->addBack (pix); + } + + else if (leftOK) + { + EDist height = plist->heightToEnds (pix); + if (height.greaterThan (maxWidth)) return false; + if (height.numerator () == 0) bsFlat = true; + else + { + convexhull = new ConvexHull (plist->frontPoint (), + plist->backPoint (), pix); + bsOK = true; + } + plist->addBack (pix); + } + + else // only the central point is ok + { + plist->addBack (pix); + rightOK = true; + } + + chChanged = true; + return true; +} + + +bool BlurredSegmentProto::addPoint (Pt2i p, bool onleft) +{ + bool inserted = convexhull->addPointDS (p, onleft); + if ((strictThickness ()).greaterThan (maxWidth)) + { + if (inserted) convexhull->restore (); + return false; + } + + if (onleft) plist->addFront (p); + else plist->addBack (p); + chChanged = true; + return true; +} + + +void BlurredSegmentProto::removeLeft (int n) +{ + if (bsOK) plist->removeFront (n); +} + + +void BlurredSegmentProto::removeRight (int n) +{ + if (bsOK) plist->removeBack (n); +} + + +Vr2i BlurredSegmentProto::getSupportVector () +{ + if (bsOK) + { + Pt2i s, e, v; + convexhull->antipodalEdgeAndVertex (s, e, v); + return (s.vectorTo (e)); + } + if (bsFlat || leftOK || rightOK) + return (getLastLeft().vectorTo (getLastRight ())); + return (Vr2i (1, 0)); // hardly better with only one point ! +} + + +BlurredSegment *BlurredSegmentProto::endOfBirth () +{ + DigitalStraightSegment *seg = NULL; + if (bsOK) + { + int xmin, ymin, xmax, ymax; + plist->findExtrema (xmin, ymin, xmax, ymax); + Pt2i s, e, v; + convexhull->antipodalEdgeAndVertex (s, e, v); + seg = new DigitalStraightSegment (s, e, v, xmin, ymin, xmax, ymax); + } + else if (bsFlat || rightOK || leftOK) + { + Pt2i llast = plist->frontPoint (); + Pt2i rlast = plist->backPoint (); + if (llast.equals (rlast)) // Strange, should not be flat, rightok or leftok + { + plist = NULL; + return (NULL); + } + int xmin = llast.x (); + if (rlast.x () < llast.x ()) xmin = rlast.x (); + int ymin = llast.y (); + if (rlast.y () < llast.y ()) ymin = rlast.y (); + int xmax = llast.x (); + if (rlast.x () > llast.x ()) xmax = rlast.x (); + int ymax = llast.y (); + if (rlast.y () > llast.y ()) ymax = rlast.y (); + seg = new DigitalStraightSegment (llast, rlast, + DigitalStraightLine::DSL_THIN, + xmin, ymin, xmax, ymax); + } + else return (NULL); + Pt2i aps (-1, -1), ape (-1, -1), apv (-1, -1); + if (convexhull != NULL) + convexhull->antipodalEdgeAndVertex (aps, ape, apv); + BlurredSegment *bbs = new BlurredSegment (plist, seg, aps, ape, apv); + plist = NULL; // NECESSARY TO AVOID CONTENTS CLEARANCE !!! + return (bbs); +} diff --git a/Expes/Testers/TestLines/BlurredSegment/blurredsegmentproto.h b/Expes/Testers/TestLines/BlurredSegment/blurredsegmentproto.h new file mode 100644 index 0000000..1489b07 --- /dev/null +++ b/Expes/Testers/TestLines/BlurredSegment/blurredsegmentproto.h @@ -0,0 +1,139 @@ +#ifndef OLD_BLURRED_SEGMENT_PROTO_H +#define OLD_BLURRED_SEGMENT_PROTO_H + +#include "blurredsegment.h" + + +/** + * @class BlurredSegmentProto blurredsegmentproto.h + * \brief A prototype of blurred segment, untill complete specification. + * It is mostly based on a evolving list of points, its convex hull and + * the successive states of the blurred segment construction. + */ +class [[deprecated("Use BSProto instead")]] +BlurredSegmentProto : public BlurredSegment +{ +public: + + /** + * \brief Creates a blurred segment prototype. + * @param maxWidth Maximal width of the blurred segment to build + * @param pix Central point of the blurred segment to build + */ + BlurredSegmentProto (int maxWidth, Pt2i pix); + + /** + * \brief Creates a blurred segment prototype with lists of points. + * @param maxWidth Maximal width of the blurred segment to build. + * @param center Central point of the blurred segment to build. + * @param leftPts Points to add on left side + * @param rightPts Points to add on right side. + */ + BlurredSegmentProto (int maxWidth, Pt2i center, + const vector<Pt2i> &leftPts, const vector<Pt2i> &rightPts); + + /** + * \brief Deletes the blurred segment prototype. + */ + ~BlurredSegmentProto (); + + /** + * \brief Checks if the blurred segment has at least two points. + */ + inline bool isLineable () const { + return (bsOK || bsFlat || leftOK || rightOK); } + + /** + * \brief Returns the built-in blurred segment strict thickness. + * The strict thickness is the distance between bounding lines, ie (nu-1)/p. + */ + EDist strictThickness () const; + + /** + * \brief Returns the built-in blurred segment digital thickness. + * The digital thickness is the width of the digital straight line, ie nu/p. + */ + EDist digitalThickness () const; + + /** + * \brief Returns the requested max width of the segment. + */ + inline const EDist getMaxWidth () const { return maxWidth; } + + /** + * \brief Sets the requested max width of the segment. + * @param maxwidth The new vaue for the requested max width. + */ + inline void setMaxWidth (const EDist &val) { maxWidth.set (val); } + + /** + * \brief Returns the underlying digital straight line. + */ + DigitalStraightLine *getLine () const; + + /** + * \brief Adds a new point on the left. + * @param pix point to be added. + * Returns true if the point is accepted. + */ + bool addLeft (Pt2i pix); + + /** + * \brief Adds a new point on the right. + * @param pix point to be added. + * Returns true if the point is accepted. + */ + bool addRight (Pt2i pix); + + /** + * \brief Remove last points on the left side. + * @param n Number of points to remove. + */ + void removeLeft (int n); + + /** + * \brief Remove last points on the right side. + * @param n Number of points to remove. + */ + void removeRight (int n); + + /** + * \brief Returns the support vector of the blurred segment. + */ + Vr2i getSupportVector (); + + /** + * \brief Returns a stable blurred segment from available information. + */ + BlurredSegment *endOfBirth (); + + +protected: + + /** Maximal width of the blurred segment. */ + EDist maxWidth; + + /** Maintained convex hull of the blurred segment. */ + ConvexHull *convexhull; + + /** Indicates if the blurred segment is constructed. */ + bool bsOK; + /** Indicates if the points are aligned. */ + bool bsFlat; + /** Indicates if the left point is defined. */ + bool leftOK; + /** Indicates if the right point is defined. */ + bool rightOK; + + /** Flag indicating if the convex hull changed since last DSS extraction. */ + bool chChanged; + + + /** + * \brief Submits a new point to extend the blurred segment. + * @param p Submitted point. + * @param onleft Adding direction (true for LEFT, false for RIGHT). + */ + bool addPoint (Pt2i p, bool onleft); +}; +#endif diff --git a/Expes/Testers/TestLines/BlurredSegment/bsdetector.cpp b/Expes/Testers/TestLines/BlurredSegment/bsdetector.cpp new file mode 100644 index 0000000..6b6a4b0 --- /dev/null +++ b/Expes/Testers/TestLines/BlurredSegment/bsdetector.cpp @@ -0,0 +1,577 @@ +#include "bsdetector.h" + + +const std::string BSDetector::VERSION = "1.3.0"; + +const int BSDetector::STEP_FINAL = 0; +const int BSDetector::STEP_INITIAL = 1; +const int BSDetector::STEP_PRELIM = 2; + +const int BSDetector::RESULT_VOID = -2; +const int BSDetector::RESULT_UNDETERMINED = -1; +const int BSDetector::RESULT_OK = 0; +const int BSDetector::RESULT_PRELIM_NO_DETECTION = 1; +const int BSDetector::RESULT_PRELIM_TOO_FEW = 2; +const int BSDetector::RESULT_INITIAL_NO_DETECTION = 11; +const int BSDetector::RESULT_INITIAL_TOO_FEW = 12; +const int BSDetector::RESULT_INITIAL_TOO_SPARSE = 13; +const int BSDetector::RESULT_INITIAL_TOO_MANY_OUTLIERS = 14; +const int BSDetector::RESULT_INITIAL_CLOSE_ORIENTATION = 15; +const int BSDetector::RESULT_FINAL_NO_DETECTION = 21; +const int BSDetector::RESULT_FINAL_TOO_FEW = 22; +const int BSDetector::RESULT_FINAL_TOO_SPARSE = 23; +const int BSDetector::RESULT_FINAL_TOO_SMALL = 24; +const int BSDetector::RESULT_FINAL_TOO_MANY_OUTLIERS = 25; + +const int BSDetector::DEFAULT_FAST_TRACK_SCAN_WIDTH = 16; +const int BSDetector::DEFAULT_ASSIGNED_THICKNESS = 3; +const int BSDetector::DEFAULT_ACCEPTED_LACKS = 5; +const int BSDetector::FAST_TRACK_MARGIN = 2; + +const int BSDetector::BS_MIN_SIZE = 3; +const int BSDetector::DEFAULT_INITIAL_MIN_SIZE = 5; +const int BSDetector::DEFAULT_FINAL_MIN_SIZE = 10; +const int BSDetector::DEFAULT_FRAGMENT_MIN_SIZE = 5; +const int BSDetector::DEFAULT_AUTO_SWEEPING_STEP = 15; +const int BSDetector::PRELIM_MIN_HALF_WIDTH = 10; + + + +BSDetector::BSDetector () +{ + gMap = NULL; + inThick = DEFAULT_ASSIGNED_THICKNESS; + prelimDetectionOn = false; + singleMultiOn = true; + + bst0 = (prelimDetectionOn ? new BSTracker () : NULL); + bst1 = new BSTracker (); + bst2 = new BSTracker (); + + nfaOn = true; + nfaf = NULL; + nfaf = (nfaOn ? new NFAFilter () : NULL); + + acceptedLacks = DEFAULT_ACCEPTED_LACKS; + oppositeGradientDir = false; // main edge detection + initialMinSize = DEFAULT_INITIAL_MIN_SIZE; + fragmentMinSize = DEFAULT_FRAGMENT_MIN_SIZE; + initialSparsityTestOn = true; + finalSizeTestOn = true; + finalMinSize = DEFAULT_FINAL_MIN_SIZE; + finalSparsityTestOn = false; + multiSelection = false; + autodet = false; + autoSweepingStep = DEFAULT_AUTO_SWEEPING_STEP; + maxtrials = 0; + nbtrials = 0; + + bspre = NULL; + bsini = NULL; + bsf = NULL; + resultValue = RESULT_UNDETERMINED; +} + + +BSDetector::~BSDetector () +{ + if (prelimDetectionOn) delete bst0; + delete bst1; + delete bst2; + + if (bsini != NULL) delete bsini; + if (bsf != NULL) delete bsf; + std::vector <BlurredSegment *>::iterator it = mbsf.begin (); + while (it != mbsf.end ()) delete (*it++); +} + + +void BSDetector::clearAll () +{ + if (bsini != NULL) + { + delete bsini; + bsini = NULL; + } + if (bsf != NULL) + { + delete bsf; + bsf = NULL; + } + std::vector <BlurredSegment *>::iterator it = mbsf.begin (); + while (it != mbsf.end ()) delete (*it++); + mbsf.clear (); + vbsf.clear (); + rbsf.clear (); +} + + +void BSDetector::setGradientMap (VMap *data) +{ + gMap = data; + if (prelimDetectionOn) bst0->setGradientMap (data); + if (bst1) bst1->setGradientMap (data); + if (bst2) bst2->setGradientMap (data); + if (nfaf) nfaf->init (data); +} + + +void BSDetector::detectAll () +{ + // Initializes the multi-detection structures + autodet = true; + freeMultiSelection (); + gMap->setMasking (true); + gMap->clearMask (); + + // Runs the automatic detection sweep algorithm + bool isnext = true; + nbtrials = 0; + int width = gMap->getWidth (); + int height = gMap->getHeight (); + for (int x = width / 2; isnext && x > 0; x -= autoSweepingStep) + isnext = detectMulti (Pt2i (x, 0), Pt2i (x, height - 1)); + for (int x = width / 2 + autoSweepingStep; + isnext && x < width - 1; x += autoSweepingStep) + isnext = detectMulti (Pt2i (x, 0), Pt2i (x, height - 1)); + for (int y = height / 2; isnext && y > 0; y -= autoSweepingStep) + isnext = detectMulti (Pt2i (0, y), Pt2i (width - 1, y)); + for (int y = height / 2 + autoSweepingStep; + isnext && y < height - 1; y += autoSweepingStep) + isnext = detectMulti (Pt2i (0, y), Pt2i (width - 1, y)); + + // Updates the selected segment for survey + if (maxtrials > (int) (mbsf.size ())) maxtrials = 0; + + // Filters the detection output using NFA measure + if (nfaf) nfaf->filter (mbsf, vbsf, rbsf); + gMap->setMasking (false); +} + + +void BSDetector::detectAllWithBalancedXY () +{ + // Initializes the multi-detection structures + autodet = true; + freeMultiSelection (); + gMap->setMasking (true); + gMap->clearMask (); + + // Runs the automatic detection balanced sweep algorithm + bool isnext = true; + nbtrials = 0; + int width = gMap->getWidth (); + int height = gMap->getHeight (); + int xg = width / 2, yb = height / 2; + int xd = xg + autoSweepingStep, yh = yb + autoSweepingStep; + bool agauche = true, enbas = true, adroite = true, enhaut = true; + while (isnext && (agauche || enbas || adroite || enhaut)) + { + if (agauche) + { + isnext = detectMulti (Pt2i (xg, 0), Pt2i (xg, height - 1)); + xg -= autoSweepingStep; + if (xg <= 0) agauche = false; + } + if (isnext && enbas) + { + isnext = detectMulti (Pt2i (0, yb), Pt2i (width - 1, yb)); + yb -= autoSweepingStep; + if (yb <= 0) enbas = false; + } + if (isnext && adroite) + { + isnext = detectMulti (Pt2i (xd, 0), Pt2i (xd, height - 1)); + xd += autoSweepingStep; + if (xd >= width - 1) adroite = false; + } + if (isnext && enhaut) + { + isnext = detectMulti (Pt2i (0, yh), Pt2i (width - 1, yh)); + yh += autoSweepingStep; + if (yh >= height - 1) enhaut = false; + } + } + // Updates the selected segment for survey + if (maxtrials > (int) (mbsf.size ())) maxtrials = 0; + + // Filters the detection output using NFA measure + if (nfaf) nfaf->filter (mbsf, vbsf, rbsf); + gMap->setMasking (false); +} + + +void BSDetector::detectSelection (const Pt2i &p1, const Pt2i &p2) +{ + autodet = false; + freeMultiSelection (); + + // Runs multi-detection if set + if (multiSelection) + { + gMap->setMasking (true); + gMap->clearMask (); + nbtrials = 0; + detectMulti (p1, p2); + + // Updates the selected segment for survey + if (maxtrials > (int) (mbsf.size ())) maxtrials = 0; + gMap->setMasking (false); + } + + // Runs single detection otherwise + else resultValue = detectSingle (p1, p2); +} + + +void BSDetector::redetect () +{ + if (autodet) detectAll (); + else detectSelection (inip1, inip2); +} + + +void BSDetector::freeMultiSelection () +{ + std::vector<BlurredSegment *>::iterator it = mbsf.begin (); + while (it != mbsf.end ()) delete (*it++); + mbsf.clear (); +} + + +bool BSDetector::detectMulti (const Pt2i &p1, const Pt2i &p2) +{ + // Finds and sorts local max of gradient magnitude along the input stroke + std::vector<Pt2i> pts; + p1.draw (pts, p2); + int *locmax = new int[pts.size ()]; + int nlm = gMap->localMax (locmax, pts); + + // Detects a blurred segment for each local max + bool isnext = true; + for (int i = 0; isnext && i < nlm; i++) + { + Pt2i ptstart = pts.at (locmax[i]); + if (gMap->isFree (ptstart)) + { + // Handles opposite edge orientations + bool savedOppDir = oppositeGradientDir; + oppositeGradientDir = false; + int nbDets = (gMap->isOrientationConstraintOn () ? 2 : 1); + if (singleMultiOn) nbDets = 1; + while (isnext && nbDets != 0) + { + // Detects a blurred segment + if (detectSingle (p1, p2, true, ptstart) == RESULT_OK) + { + gMap->setMask (bsf->getAllPoints ()); + mbsf.push_back (bsf); + bsf = NULL; // to avoid BS deletion + + // Interrupts the detection when the selected segment is reached + if ((int) (mbsf.size ()) == maxtrials) isnext = false; + } + oppositeGradientDir = ! oppositeGradientDir; + nbDets --; + nbtrials ++; + } + oppositeGradientDir = savedOppDir; + } + } + return (isnext); +} + + +int BSDetector::detectSingle (const Pt2i &p1, const Pt2i &p2, + bool centralp, const Pt2i &pc) +{ + // Entry check + //------------ + if (p1.equals (p2) + || ((! centralp) && p1.chessboard (p2) < BSTracker::MIN_SCAN)) + return RESULT_VOID; + + // Clearance + //---------- + if (prelimDetectionOn) bst0->clear (); + bst1->clear (); + bst2->clear (); + if (bspre != NULL) delete bspre; + bspre = NULL; + if (bsini != NULL) delete bsini; + bsini = NULL; + if (bsf != NULL) delete bsf; + bsf = NULL; + + prep1.set (p1); + prep2.set (p2); + prewidth = (centralp ? DEFAULT_FAST_TRACK_SCAN_WIDTH : 0); + prepc.set (pc); + + // Preliminary based on highest gradient without orientation constraint + //--------------------------------------------------------------------- + if (prelimDetectionOn) + { + bspre = bst0->fastTrack (prep1, prep2, inThick + FAST_TRACK_MARGIN, + acceptedLacks, prewidth, prepc); + if (bspre == NULL || bspre->size () < initialMinSize) + return (bspre == NULL ? RESULT_PRELIM_NO_DETECTION + : RESULT_PRELIM_TOO_FEW); + + Vr2i v0 = bspre->getSupportVector (); + int l = v0.chessboard (); + if (l != 0) + { + Pt2i pcentral = bspre->getSegment()->centerOfIntersection (prep1, prep2); + int detw = 2 * (1 + bspre->minimalWidth().floor ()); + if (detw < PRELIM_MIN_HALF_WIDTH) detw = PRELIM_MIN_HALF_WIDTH; + int dx = (int) ((v0.y () * detw) / l); + int dy = (int) (- (v0.x () * detw) / l); + inip1.set (pcentral.x () + dx, pcentral.y () + dy); + inip2.set (pcentral.x () - dx, pcentral.y () - dy); + iniwidth = 0; + } + } + else + { + inip1.set (p1); + inip2.set (p2); + iniwidth = (centralp ? DEFAULT_FAST_TRACK_SCAN_WIDTH : 0); + inipc.set (pc); + } + + // Initial detection based on highest gradient without orientation constraint + //--------------------------------------------------------------------------- + bsini = bst1->fastTrack (inip1, inip2, inThick + FAST_TRACK_MARGIN, + acceptedLacks, iniwidth, inipc); + if (bsini == NULL || bsini->size () < initialMinSize) + return (bsini == NULL ? RESULT_INITIAL_NO_DETECTION + : RESULT_INITIAL_TOO_FEW); + + // Sparsity test + //------------- + if (initialSparsityTestOn) + { + if (bsini->size () < bsini->extent () / 2) + return RESULT_INITIAL_TOO_SPARSE; + } + + // Orientation test for automatic extractions + //------------------------------------------- + Vr2i bsinidir = bsini->getSupportVector(); + if (bsinidir.orientedAs (inip1.vectorTo (inip2))) + return RESULT_INITIAL_CLOSE_ORIENTATION; + + // Gradient reference selection + //----------------------------- + Pt2i pCenter = bsini->getCenter (); + Vr2i gRef = gMap->getValue (pCenter); + if (oppositeGradientDir && gMap->isOrientationConstraintOn ()) + gRef.invert (); + + // Scan recentering and fitting + //----------------------------- + pCenter.set (bsini->getSegment()->centerOfIntersection (inip1, inip2)); + + // Finer detection based on gradient maxima with orientation constraint + //--------------------------------------------------------------------- + bsf = bst2->fineTrack (pCenter, bsinidir, inThick, acceptedLacks, gRef); + if (bsf == NULL || bsf->size () < initialMinSize) + return (bsf == NULL ? RESULT_FINAL_NO_DETECTION : RESULT_FINAL_TOO_FEW); + + // Size test + //------------ + if (finalSizeTestOn) + { + // DigitalStraightSegment *dss = bsf->getSegment (); + if ((int) (bsf->getAllPoints().size ()) < finalMinSize) + return RESULT_FINAL_TOO_SMALL; + } + + // Sparsity test + //-------------- + if (finalSparsityTestOn) + { + if (bsf->size () < (bsf->extent () * 4) / 5) + return RESULT_FINAL_TOO_SPARSE; + } + + return RESULT_OK; +} + + +BlurredSegment *BSDetector::getBlurredSegment (int step) const +{ + if (step == STEP_PRELIM) return (bspre); + else if (step == STEP_INITIAL) return (bsini); + else if (mbsf.empty ()) return (bsf); + else return (mbsf.back ()); + return NULL; +} + + +int BSDetector::getDigitalStraightSegments ( + std::vector<DigitalStraightSegment *> &dss) const +{ + if (mbsf.empty ()) + { + if (bsf == NULL) return 0; + DigitalStraightSegment *ds = bsf->getSegment (); + if (ds == NULL) return (0); + dss.push_back (ds); + return (1); + } + int nb = 0; + std::vector<BlurredSegment *>::const_iterator it = mbsf.begin (); + while (it != mbsf.end ()) + { + BlurredSegment *bs = (*it++); + if (bs != NULL) + { + DigitalStraightSegment *ds = bs->getSegment (); + if (ds != NULL) + { + dss.push_back (ds); + nb ++; + } + } + } + return nb; +} + + +int BSDetector::copyDigitalStraightSegments ( + std::vector<DigitalStraightSegment> &dss) const +{ + if (mbsf.empty ()) + { + if (bsf == NULL) return 0; + DigitalStraightSegment *ds = bsf->getSegment (); + if (ds == NULL) return 0; + dss.push_back (DigitalStraightSegment (ds)); + return 1; + } + int nb = 0; + std::vector<BlurredSegment *>::const_iterator it = mbsf.begin (); + while (it != mbsf.end ()) + { + BlurredSegment *bs = (*it++); + if (bs != NULL) + { + DigitalStraightSegment *ds = bs->getSegment (); + if (ds != NULL) + { + dss.push_back (DigitalStraightSegment (ds)); + nb ++; + } + } + } + return nb; +} + + +void BSDetector::setPixelLackTolerence (int number) +{ + if (number >= 0) acceptedLacks = number; +} + + +void BSDetector::incMaxDetections (bool dir) +{ + maxtrials = maxtrials + (dir ? -1 : 1); + if (maxtrials < 0) maxtrials = (int) (mbsf.size ()); +} + + +void BSDetector::getScanInput (int step, + Pt2i &p1, Pt2i &p2, int &swidth, Pt2i &pc) const +{ + if (step == STEP_PRELIM) + { + if (prelimDetectionOn) + { + p1.set (prep1); + p2.set (prep2); + swidth = prewidth; + pc.set (prepc); + } + } + else if (step == STEP_INITIAL) + { + p1.set (inip1); + p2.set (inip2); + swidth = iniwidth; + pc.set (inipc); + } +} + + +const std::vector <std::vector <Pt2i> > BSDetector::getFinalScans () const +{ + return (bst2->getScans ()); +} + + +bool BSDetector::finalScansRecordOn () const +{ + return (bst2->scanRecordOn ()); +} + + +void BSDetector::setFinalScansRecord (bool status) +{ + bst2->setScanRecord (status); + if (status) redetect (); +} + + +void BSDetector::switchNFA () +{ + nfaOn = ! nfaOn; + if (nfaOn && nfaf == NULL) + { + nfaf = new NFAFilter (); + nfaf->init (gMap); + } +} + + +bool BSDetector::incFragmentSizeMinValue (bool inc) +{ + if ((! inc) && fragmentMinSize <= 1) return false; + fragmentMinSize += (inc ? 1 : -1); + return true; +} + + +void BSDetector::switchPreliminary () +{ + if (prelimDetectionOn) + { + delete bst0; + prelimDetectionOn = false; + } + else + { + prelimDetectionOn = true; + bst0 = new BSTracker (); + bst0->setGradientMap (gMap); + } +} + + +bool BSDetector::switchOppositeGradient () +{ + if (gMap != NULL && gMap->isOrientationConstraintOn ()) + { + oppositeGradientDir = ! oppositeGradientDir; + return true; + } + return false; +} + + +std::string BSDetector::version () +{ + return BSDetector::VERSION; +} diff --git a/Expes/Testers/TestLines/BlurredSegment/bsdetector.h b/Expes/Testers/TestLines/BlurredSegment/bsdetector.h new file mode 100644 index 0000000..bc4010a --- /dev/null +++ b/Expes/Testers/TestLines/BlurredSegment/bsdetector.h @@ -0,0 +1,623 @@ +#ifndef BLURRED_SEGMENT_DETECTOR_H +#define BLURRED_SEGMENT_DETECTOR_H + +#include "bstracker.h" +#include "nfafilter.h" +#include <string> + + +/** + * @class BSDetector bsdetector.h + * \brief Blurred segment detector in grey level images. + */ +class BSDetector +{ +public: + + /** Version number */ + static const std::string VERSION; + + /** Identifier for the final detection step. */ + static const int STEP_FINAL; + /** Identifier for the initial detection step. */ + static const int STEP_INITIAL; + /** Identifier for the preliminary detection step. */ + static const int STEP_PRELIM; + + /** Extraction result : void input. */ + static const int RESULT_VOID; + /** Extraction result : successful extraction. */ + static const int RESULT_UNDETERMINED; + /** Extraction result : successful extraction. */ + static const int RESULT_OK; + /** Extraction result : no initial detection (bsini == NULL). */ + static const int RESULT_PRELIM_NO_DETECTION; + /** Extraction result : too few points at initial detection. */ + static const int RESULT_PRELIM_TOO_FEW; + /** Extraction result : no initial detection (bsini == NULL). */ + static const int RESULT_INITIAL_NO_DETECTION; + /** Extraction result : too few points at initial detection. */ + static const int RESULT_INITIAL_TOO_FEW; + /** Extraction result : unsuccessful density test at initial detection. */ + static const int RESULT_INITIAL_TOO_SPARSE; + /** Extraction result : unsuccessful filter test at initial detection. */ + static const int RESULT_INITIAL_TOO_MANY_OUTLIERS; + /** Extraction result : initial detection of a closely oriented segment. */ + static const int RESULT_INITIAL_CLOSE_ORIENTATION; + /** Extraction result : no final detection (bsf == NULL). */ + static const int RESULT_FINAL_NO_DETECTION; + /** Extraction result : too few points at final detection. */ + static const int RESULT_FINAL_TOO_FEW; + /** Extraction result : unsuccessful connectivity test at final detection. */ + static const int RESULT_FINAL_TOO_SPARSE; + /** Extraction result : unsuccessful spread test at final detection. */ + static const int RESULT_FINAL_TOO_SMALL; + /** Extraction result : unsuccessful filter test at final detection. */ + static const int RESULT_FINAL_TOO_MANY_OUTLIERS; + + + /** + * \brief Creates a segment detector. + */ + BSDetector (); + + /** + * \brief Deletes the segment detector. + */ + ~BSDetector (); + + /** + * \brief Delete all detected structures. + */ + void clearAll (); + + /** + * \brief Returns the minimal vertical or horizontal width. + */ + inline int result () const { return resultValue; } + + /** + * \brief Sets the gradient map. + */ + void setGradientMap (VMap *data); + + /** + * \brief Detects all blurred segments in the picture. + * Parses X direction first, then Y direction. + */ + void detectAll (); + + /** + * \brief Detects all blurred segments in the picture. + * Parses simultaneously X and Y directions. + */ + void detectAllWithBalancedXY (); + + /** + * \brief Detects blurred segments between two input points. + * @param p1 First input point. + * @param p2 Second input point. + */ + void detectSelection (const Pt2i &p1, const Pt2i &p2); + + /** + * \brief Runs the last detection again. + */ + void redetect (); + + /** + * \brief Detects a blurred segment between two input points. + * Step 1: For each scan line, one candidate is selected + * based on the gradient norm only (no direction test). + * Step 2: Outliers pruning based on parameter space pre-filtering. + * Step 3: For each scan line, local candidates are detected + * on top of gradient ridges with closest direction. + * The first candidates that prolongates the segment are retained. + * Step 4: Outliers pruning based on parameter space filtering. + * Note : Multi-detection along a stroke requires an initial start point. + * Returns the detection status (RESULT_OK if successfull). + * @param p1 First input point. + * @param p2 Second input point. + * @param centralp Set to true if the central point is provided. + * @param pc Initial central point. + */ + int detectSingle (const Pt2i &p1, const Pt2i &p2, + bool centralp = false, const Pt2i &pc = Pt2i ()); + + /** + * \brief Returns the detected blurred segment at given step. + * @param step Detection step. + */ + BlurredSegment *getBlurredSegment (int step = STEP_FINAL) const; + + /** + * \brief Returns the list of detected blurred segments at final step. + */ + inline const std::vector<BlurredSegment *> getBlurredSegments () const { + return (mbsf); } + + /** + * \brief Returns the list of NFA-validated blurred segments at final step. + */ + inline const std::vector<BlurredSegment *> getValidSegments () const { + return (vbsf); } + + /** + * \brief Returns the list of NFA-rejected blurred segments at final step. + */ + inline const std::vector<BlurredSegment *> getRejectedSegments () const { + return (rbsf); } + + /** + * \brief Avoids the deletion of the last extracted blurred segment. + */ + inline void preserveFormerBlurredSegment () { bsf = NULL; } + + /** + * \brief Avoids the deletion of the last extracted blurred segments. + */ + inline void preserveFormerBlurredSegments () { mbsf.clear (); } + + /** + * \brief Adds digital straight segments to provided list. + * @param dss List of digital straight segments to be completed. + */ + int getDigitalStraightSegments ( + std::vector<DigitalStraightSegment *> &dss) const; + + /** + * \brief Adds a copy of the digital straight segments to provided list. + * @param dss List of digital straight segments to be completed. + */ + int copyDigitalStraightSegments ( + std::vector<DigitalStraightSegment> &dss) const; + + /** + * \brief Returns the assigned maximal thickness to detector. + */ + inline int assignedThickness () const { return inThick; } + + /** + * \brief Sets the assigned maximal thickness to detector. + * @param val New assigned thickness value. + */ + inline void setAssignedThickness (int val) { if (val > 0) inThick = val; } + + /** + * \brief Returns the output blurred segment minimal size. + */ + inline int initialSizeMinValue () const { return initialMinSize; } + + /** + * \brief Sets the output blurred segment minimal size. + * @param val New minimal size value for the output blurred segment. + */ + inline void setInitialMinSize (int val) { + initialMinSize = (val >= BS_MIN_SIZE ? val : BS_MIN_SIZE); } + + /** + * \brief Returns the threshold used for maximal gradient detection. + */ + inline int getSensitivity () const { + return (gMap->getGradientThreshold ()); } + + /** + * \brief Increments the threshold used for maximal gradient detection. + * @param inc Increment value. + */ + inline void incSensitivity (int inc) { + return (gMap->incGradientThreshold (inc)); } + + /** + * \brief Returns the gradient threshold used for local max filtering. + */ + inline int getLocalMaxGradientResolution () const { + return (gMap->getLocalMaxGradientResolution ()); } + + /** + * \brief Increments the gradient threshold used for local maximal filtering. + * @param inc Increment value. + */ + inline void incLocalMaxGradientResolution (int inc) { + return (gMap->incLocalMaxGradientResolution (inc)); } + + /** + * \brief Returns the stroke sweeping step value for automatic detections. + */ + inline int getAutoSweepingStep () const { return autoSweepingStep; } + + /** + * \brief Sets the stroke sweeping step value for automatic detections. + * @param number New value for the stroke sweeping. + */ + inline void setAutoSweepingStep (int number) { + if (number > 0 && number < gMap->getWidth () / 8) autoSweepingStep = number; } + + /** + * \brief Returns the pixel lack tolerence. + */ + inline int getPixelLackTolerence () const { return acceptedLacks; } + + /** + * \brief Sets the pixel lack tolerence. + * @param number New value for the pixel lack tolerance. + */ + void setPixelLackTolerence (int number); + + /** + * \brief Returns the preliminary detection modality status. + */ + inline bool isPreliminary () { return (prelimDetectionOn); } + + /** + * \brief Switches preliminary detection modality. + */ + void switchPreliminary (); + + /** + * \brief Returns whether opposite edge direction is set for single edge mode. + */ + inline bool isOppositeGradientOn () const { return (oppositeGradientDir); } + + /** + * \brief Inverts the edge direction for detection stage. + * Effective only in single edge detection mode. + * Returns whether the modification was actually set. + */ + bool switchOppositeGradient (); + + /** + * \brief Returns whether opposite edge direction is set in double edge mode. + */ + inline bool isSingleEdgeModeOn () const { + return (gMap != NULL ? gMap->isOrientationConstraintOn () : true); } + + /** + * \brief Switches between single and double edge detection. + */ + inline void switchSingleOrDoubleEdge () { + if (gMap != NULL) gMap->switchOrientationConstraint (); } + + /** + * \brief Returns whether a single edge mode is set for multidetections. + */ + inline bool isSingleMultiOn () const { return (singleMultiOn); } + + /** + * \brief Switches between single and double edge mode for multidetections. + */ + inline void switchSingleOrDoubleMultiDetection () { + singleMultiOn = ! singleMultiOn; } + + /** + * \brief Returns true if the multi-selection modality is set. + */ + inline bool isMultiSelection () const { return multiSelection; } + + /** + * \brief Switches on or off the multi-selection modality. + */ + inline void switchMultiSelection () { multiSelection = ! multiSelection; } + + /** + * \brief Returns the scan lines at final step. + */ + const std::vector <std::vector <Pt2i> > getFinalScans () const; + + /** + * \brief Returns whether the final scan record modality is set. + */ + bool finalScansRecordOn () const; + + /** + * \brief Sets the scan record modality at final step. + * @param status Sets on if true, off otherwise. + */ + void setFinalScansRecord (bool status); + + /** + * \brief Toggles the initial step bounding. + */ + inline void switchInitialBounding () { bst1->switchScanExtent (); } + + /** + * \brief Returns the maximal scan extent of the initial detection. + */ + inline int initialDetectionMaxExtent () { return (bst1->maxScanExtent ()); } + + /** + * \brief Returns the proximity threshold used for fast tracking. + */ + inline int getFastTrackProximityThreshold () const { + return (bst1->getProximityThreshold ()); } + + /** + * \brief Increments the proximity threshold used for fast tracking. + * @param inc Increment sign. + */ + inline void incFastTrackProximityThreshold (bool inc) { + bst1->incProximityThreshold (inc); } + + /** + * \brief Returns the proximity test status. + */ + inline bool fastTrackProximityConstraintOn () const { + return (bst1->proximityConstraintOn ()); } + + /** + * \brief Switches the proximity test used for fast tracking. + */ + inline void switchFastTrackProximityConstraint () { + bst1->switchProximityConstraint (); } + + /** + * \brief Returns the assigned thickness control delay. + */ + inline int getAssignedThicknessControlDelay () const { + return bst2->getAssignedThicknessControlDelay (); } + + /** + * \brief Increments the assigned thickness control delay. + * @param val Increment value. + */ + inline void incAssignedThicknessControlDelay (int val) { + bst2->incAssignedThicknessControlDelay (val); } + + /** + * \brief Returns if NFA-based filter is activated. + */ + inline bool isNFA () const { return (nfaOn); } + + /** + * \brief Toggles the use of the NFA-based filter. + */ + void switchNFA (); + + /** + * \brief Returns the NFA length ratio parameter value. + */ + inline double nfaLengthRatio () const { + return (nfaOn ? nfaf->lengthRatio () : 0.0); } + + /** + * \brief Increments the NFA length ratio parameter value. + * @param inc Increment direction. + */ + inline void incNfaLengthRatio (int inc) { + if (nfaOn) nfaf->incLengthRatio (inc); } + + /** + * \brief Returns whether the density test at initial step is set. + */ + inline bool isInitialSparsityTestOn () const { + return (initialSparsityTestOn); } + + /** + * \brief Switches on or off the initial density test modality. + */ + inline void switchInitialSparsityTest () { + initialSparsityTestOn = ! initialSparsityTestOn; } + + /** + * \brief Returns whether the density test at final step is set. + */ + inline bool isFinalSparsityTestOn () const { return (finalSparsityTestOn); } + + /** + * \brief Switches on or off the final density test modality. + */ + inline void switchFinalSparsityTest () { + finalSparsityTestOn = ! finalSparsityTestOn; } + + /** + * \brief Returns whether the size test at final step is set. + */ + inline bool isFinalSizeTestOn () const { return (finalSizeTestOn); } + + /** + * \brief Switches on or off the final size test modality. + */ + inline void switchFinalSizeTest () { + finalSizeTestOn = ! finalSizeTestOn; } + + /** + * \brief Returns the minimal size of final blurred segments. + */ + inline int finalSizeMinValue () const { + return (finalSizeTestOn ? finalMinSize : BS_MIN_SIZE); } + + /** + * \brief Sets the minimal spread of final blurred segments. + * @param val New size value. + */ + inline void setFinalSizeMinValue (int val) { + finalMinSize = (val < BS_MIN_SIZE ? BS_MIN_SIZE : val); } + + /** + * \brief Returns the minimal size of the segment connected components. + */ + inline int fragmentSizeMinValue () const { return fragmentMinSize; } + + /** + * \brief Increments the minimal size of the connected components. + * @param inc Positive increment if true, negative otherwise. + */ + bool incFragmentSizeMinValue (bool inc); + + /* + * \brief Returns the count of trials in a multi-detection. + */ + inline int countOfTrials () const { return (nbtrials); } + + /** + * \brief Returns the maximum number of detections set for a multi-detection. + */ + inline int getMaxDetections () const { return maxtrials; } + + /** + * \brief Sets the maximum number of trials in a multi-detection. + * Just kept for IPOL demo compatibility. + * @param nb Number of trials (0 if illimited). + */ + inline void setMaxTrials (int nb) { maxtrials = nb; resetMaxDetections (); } + + /** + * \brief Increments the maximum number of detections in a multi-detection. + * @param dir Increments if true, otherwise decrements. + */ + void incMaxDetections (bool dir); + + /** + * \brief Sets the maximum number of detections in a multi-detection. + * @param nb Number of detections (0 if illimited). + */ + inline void setMaxDetections (int nb) { maxtrials = nb; } + + /** + * \brief Resets the maximum number of detections set for a multi-detection. + */ + inline void resetMaxDetections () { maxtrials = 0; } + + /** + * \brief Gets the last detection inputs. + * @param step Detection step. + * @param p1 Input stroke first point to fill in. + * @param p2 Input stroke end point to fill in. + * @param swidth Scan width if not set by P1 and p2, otherwise 0. + * @param pc Input central point to fill in. + */ + void getScanInput (int step, + Pt2i &p1, Pt2i &p2, int &swidth, Pt2i &pc) const; + + /** + * \brief Returns the version number. + */ + std::string version (); + + +private : + + /** Default value for the scan width for fast tracks. */ + static const int DEFAULT_FAST_TRACK_SCAN_WIDTH; + /** Default value for the assigned thickess to detection method. */ + static const int DEFAULT_ASSIGNED_THICKNESS; + /** Default value for the accepted number of successive lacks. */ + static const int DEFAULT_ACCEPTED_LACKS; + /** Additional assigned thickness margin for fast tracks. */ + static const int FAST_TRACK_MARGIN; + /** Default minimal size of the initial blurred segment. */ + static const int DEFAULT_INITIAL_MIN_SIZE; + /** Minimal size of a blurred segment. */ + static const int BS_MIN_SIZE; + /** Default minimal size of the final blurred segment. */ + static const int DEFAULT_FINAL_MIN_SIZE; + /** Default value for the minimal size of segment fragments. */ + static const int DEFAULT_FRAGMENT_MIN_SIZE; + /** Default value of the stroke sweeping step for automatic detections. */ + static const int DEFAULT_AUTO_SWEEPING_STEP; + /** Default value for the preliminary stroke half length. */ + static const int PRELIM_MIN_HALF_WIDTH; + + + /** Processed gradient map. */ + VMap *gMap; + /** Assigned maximal thickness for current detection. */ + int inThick; + /** Accepted number of successive lacks (wrt restart points). */ + int acceptedLacks; + + /** Preliminary rough tracker. */ + BSTracker *bst0; + /** Preliminary stage modality. */ + bool prelimDetectionOn; + /** Last input start point. */ + Pt2i prep1; + /** Last input end point. */ + Pt2i prep2; + /** Last input central point. */ + Pt2i prepc; + /** Preliminary fast scan width if not set by an input selection. */ + int prewidth; + /** Preliminary detected blurred segment. */ + BlurredSegment *bspre; + + /** Initial rough tracker. */ + BSTracker *bst1; + /** Last input start point for initial step. */ + Pt2i inip1; + /** Last input end point for initial step. */ + Pt2i inip2; + /** Last input central point for initial step. */ + Pt2i inipc; + /** Initial fast scan width if not set by an input selection. */ + int iniwidth; + /** Initially detected blurred segment (initial step result). */ + BlurredSegment *bsini; + + /** Fine tracker. */ + BSTracker *bst2; + /** Detected blurred segment (final result). */ + BlurredSegment *bsf; + + /** Detected blurred segments in case of multi-detection (final results). */ + std::vector<BlurredSegment *> mbsf; + /** NFA-validated blurred segments. */ + std::vector<BlurredSegment *> vbsf; + /** NFA-rejected blurred segments. */ + std::vector<BlurredSegment *> rbsf; + /** NFA-based filtering modality. */ + bool nfaOn; + /** NFA-based filter. */ + NFAFilter *nfaf; + + /** Status of the detection of points with opposite gradient direction. + * Opposite to gradient direction at start point. + * Used to discriminate two close edges with opposite gradients. + */ + bool oppositeGradientDir; + /** Minimal size of the initial segment. */ + int initialMinSize; + /** Minimal size of the segment fragments. */ + int fragmentMinSize; + /** Sparsity test modality after initial detection. */ + bool initialSparsityTestOn; + /** Sparsity test modality after final detection. */ + bool finalSparsityTestOn; + /** Size test modality after final detection. */ + bool finalSizeTestOn; + /** Minimal size of the final segment. */ + int finalMinSize; + /** Count of small BS eliminated by the spread test. */ + int nbSmallBS; + /** Segment multi-selection modality status. */ + bool multiSelection; + /** Single or double mode for multi-selections. */ + bool singleMultiOn; + /** Count of trials in a multi-detection. */ + int nbtrials; + /** Automatic detection modality. */ + bool autodet; + /** Stroke sweeping step for the automatic extraction. */ + int autoSweepingStep; + /** Result of the blurred segment extraction. */ + int resultValue; + + /** Maximum number of trials in a multi-detection (for survey). */ + int maxtrials; // DVPT + + + /** + * \brief Resets the multi-selection list. + */ + void freeMultiSelection (); + + /** + * \brief Detects all blurred segments between two input points. + * Returns the continuation modality. + * @param p1 First input point. + * @param p2 Second input point. + */ + bool detectMulti (const Pt2i &p1, const Pt2i &p2); + +}; +#endif diff --git a/Expes/Testers/TestLines/BlurredSegment/bsproto.cpp b/Expes/Testers/TestLines/BlurredSegment/bsproto.cpp new file mode 100644 index 0000000..04bc51c --- /dev/null +++ b/Expes/Testers/TestLines/BlurredSegment/bsproto.cpp @@ -0,0 +1,319 @@ +#include "bsproto.h" + + +BSProto::BSProto (int maxWidth, Pt2i pix) +{ + this->maxWidth.set (maxWidth); + plist = new BiPtList (pix); + leftOK = false; + rightOK = false; + bsFlat = false; + bsOK = false; + convexhull = NULL; + chChanged = false; + dss = NULL; +} + + +BSProto::BSProto (int maxWidth, Pt2i center, + const std::vector<Pt2i> &leftPts, + const std::vector<Pt2i> &rightPts) +{ + this->maxWidth.set (maxWidth); + plist = new BiPtList (center); + leftOK = false; + rightOK = false; + bsFlat = false; + bsOK = false; + convexhull = NULL; + chChanged = false; + dss = NULL; + + std::vector<Pt2i>::const_iterator itr = rightPts.begin (); + std::vector<Pt2i>::const_iterator itl = leftPts.begin (); + bool scanningRight = true; + bool scanningLeft = true; + while (scanningRight || scanningLeft) + { + if (scanningRight) + { + if (itr == rightPts.end ()) scanningRight = false; + else + if (! addRight (*itr++)) scanningRight = false; + } + if (scanningLeft) + { + if (itl == leftPts.end ()) scanningLeft = false; + else + if (! addLeft (*itl++)) scanningLeft = false; + } + } +} + + +BSProto::~BSProto () +{ + if (convexhull != NULL) delete convexhull; +} + + +EDist BSProto::strictThickness () const +{ + return (convexhull != NULL ? convexhull->thickness () : EDist (0, 1)); +} + + +EDist BSProto::digitalThickness () const +{ + if (bsOK) + { + Pt2i s, e, v; + convexhull->antipodalEdgeAndVertex (s, e, v); + DigitalStraightLine l (s, e, v); + return (EDist (l.width (), l.period ())); + } + return (EDist (1, 1)); +} + + +DigitalStraightLine *BSProto::getLine () const +{ + if (bsOK) + { + Pt2i s, e, v; + convexhull->antipodalEdgeAndVertex (s, e, v); + return (new DigitalStraightLine (s, e, v)); + } + if (bsFlat || leftOK || rightOK) + return (new DigitalStraightLine (getLastLeft (), getLastRight (), + DigitalStraightLine::DSL_THIN)); + return (NULL); // No line if only one point +} + + +bool BSProto::addLeftSorted (Pt2i pix) +{ + if (pix.equals (plist->frontPoint ())) + { + plist->addFront (pix); + chChanged = false; + return true; + } + return addLeft (pix); +} + + +bool BSProto::addRightSorted (Pt2i pix) +{ + if (pix.equals (plist->backPoint ())) + { + plist->addBack (pix); + chChanged = false; + return true; + } + return addRight (pix); +} + + +bool BSProto::addLeft (Pt2i pix) +{ + if (bsOK) // convexhull defined + { + bool res = addPoint (pix, true); + return (res); + } + + else if (bsFlat) // leftOK && rightOK + { + EDist height = plist->heightToEnds (pix); + if (height.greaterThan (maxWidth)) return false; + if (height.num () != 0) + { + convexhull = new ConvexHull (pix, plist->frontPoint (), + plist->backPoint ()); + bsOK = true; + } + plist->addFront (pix); + } + + else if (leftOK) // thus ! rightOK + { + EDist height = plist->heightToEnds (pix); + if (height.greaterThan (maxWidth)) return false; + if (height.num () == 0) bsFlat = true; + else + { + convexhull = new ConvexHull (pix, plist->frontPoint (), + plist->backPoint ()); + bsOK = true; + } + plist->addFront (pix); + } + + else if (rightOK) + { + EDist height = plist->heightToEnds (pix); + if (height.greaterThan (maxWidth)) return false; + if (height.num () == 0) bsFlat = true; + else + { + convexhull = new ConvexHull (pix, plist->frontPoint (), + plist->backPoint ()); + bsOK = true; + } + plist->addFront (pix); + } + + else // only the central point is ok + { + plist->addFront (pix); + leftOK = true; + } + + chChanged = true; + return true; +} + + +bool BSProto::addRight (Pt2i pix) +{ + if (bsOK) // bs != NULL + { + bool res = addPoint (pix, false); + return (res); + } + + else if (bsFlat) // leftOK && rightOK + { + EDist height = plist->heightToEnds (pix); + if (height.greaterThan (maxWidth)) return false; + if (height.num () != 0) + { + convexhull = new ConvexHull (plist->frontPoint (), + plist->backPoint (), pix); + bsOK = true; + } + plist->addBack (pix); + } + + else if (rightOK) // thus ! leftOK + { + EDist height = plist->heightToEnds (pix); + if (height.greaterThan (maxWidth)) return false; + if (height.num () == 0) bsFlat = true; + else + { + convexhull = new ConvexHull (plist->frontPoint (), + plist->backPoint (), pix); + bsOK = true; + } + plist->addBack (pix); + } + + else if (leftOK) + { + EDist height = plist->heightToEnds (pix); + if (height.greaterThan (maxWidth)) return false; + if (height.num () == 0) bsFlat = true; + else + { + convexhull = new ConvexHull (plist->frontPoint (), + plist->backPoint (), pix); + bsOK = true; + } + plist->addBack (pix); + } + + else // only the central point is ok + { + plist->addBack (pix); + rightOK = true; + } + + chChanged = true; + return true; +} + + +bool BSProto::addPoint (Pt2i p, bool onleft) +{ + bool inserted = convexhull->addPointDS (p, onleft); + if ((strictThickness ()).greaterThan (maxWidth)) + { + if (inserted) convexhull->restore (); + return false; + } + + if (onleft) plist->addFront (p); + else plist->addBack (p); + chChanged = true; + return true; +} + + +void BSProto::removeLeft (int n) +{ + if (bsOK) plist->removeFront (n); +} + + +void BSProto::removeRight (int n) +{ + if (bsOK) plist->removeBack (n); +} + + +Vr2i BSProto::getSupportVector () +{ + if (bsOK) + { + Pt2i s, e, v; + convexhull->antipodalEdgeAndVertex (s, e, v); + return (s.vectorTo (e)); + } + if (bsFlat || leftOK || rightOK) + return (getLastLeft().vectorTo (getLastRight ())); + return (Vr2i (1, 0)); // hardly better with only one point ! +} + + +BlurredSegment *BSProto::endOfBirth () +{ + DigitalStraightSegment *seg = NULL; + if (bsOK) + { + int xmin, ymin, xmax, ymax; + plist->findExtrema (xmin, ymin, xmax, ymax); + Pt2i s, e, v; + convexhull->antipodalEdgeAndVertex (s, e, v); + seg = new DigitalStraightSegment (s, e, v, xmin, ymin, xmax, ymax); + } + else if (bsFlat || rightOK || leftOK) + { + Pt2i llast = plist->frontPoint (); + Pt2i rlast = plist->backPoint (); + if (llast.equals (rlast)) // Strange, should not be flat, rightok or leftok + { + plist = NULL; + return (NULL); + } + int xmin = llast.x (); + if (rlast.x () < llast.x ()) xmin = rlast.x (); + int ymin = llast.y (); + if (rlast.y () < llast.y ()) ymin = rlast.y (); + int xmax = llast.x (); + if (rlast.x () > llast.x ()) xmax = rlast.x (); + int ymax = llast.y (); + if (rlast.y () > llast.y ()) ymax = rlast.y (); + seg = new DigitalStraightSegment (llast, rlast, + DigitalStraightLine::DSL_THIN, + xmin, ymin, xmax, ymax); + } + else return (NULL); + Pt2i aps (-1, -1), ape (-1, -1), apv (-1, -1); + if (convexhull != NULL) + convexhull->antipodalEdgeAndVertex (aps, ape, apv); + BlurredSegment *bbs = new BlurredSegment (plist, seg, aps, ape, apv); + plist = NULL; // NECESSARY TO AVOID CONTENTS CLEARANCE !!! + return (bbs); +} diff --git a/Expes/Testers/TestLines/BlurredSegment/bsproto.h b/Expes/Testers/TestLines/BlurredSegment/bsproto.h new file mode 100644 index 0000000..b315d75 --- /dev/null +++ b/Expes/Testers/TestLines/BlurredSegment/bsproto.h @@ -0,0 +1,165 @@ +#ifndef BLURRED_SEGMENT_PROTO_H +#define BLURRED_SEGMENT_PROTO_H + +#include "blurredsegment.h" + + +/** + * @class BSProto bsproto.h + * \brief A prototype of blurred segment, untill complete specification. + * It is mostly based on a evolving list of points, its convex hull and + * the successive states of the blurred segment construction. + * Replaces former deprecated BlurredSegmentProto class. + */ +class BSProto : public BlurredSegment +{ +public: + + /** + * \brief Creates a blurred segment prototype. + * @param maxWidth Maximal width of the blurred segment to build + * @param pix Central point of the blurred segment to build + */ + BSProto (int maxWidth, Pt2i pix); + + /** + * \brief Creates a blurred segment prototype with lists of points. + * @param maxWidth Maximal width of the blurred segment to build. + * @param center Central point of the blurred segment to build. + * @param leftPts Points to add on left side + * @param rightPts Points to add on right side. + */ + BSProto (int maxWidth, Pt2i center, + const std::vector<Pt2i> &leftPts, const std::vector<Pt2i> &rightPts); + + /** + * \brief Deletes the blurred segment prototype. + */ + ~BSProto (); + + /** + * \brief Checks if the blurred segment has at least two points. + */ + inline bool isExtending () const { + return (bsOK || bsFlat || leftOK || rightOK); } + + /** + * \brief Checks if the blurred segment is not flat (true BS). + */ + inline bool isNotFlat () const { return (bsOK); } + + /** + * \brief Returns the built-in blurred segment strict thickness. + * The strict thickness is the distance between bounding lines, ie (nu-1)/p. + */ + EDist strictThickness () const; + + /** + * \brief Returns the built-in blurred segment digital thickness. + * The digital thickness is the width of the digital straight line, ie nu/p. + */ + EDist digitalThickness () const; + + /** + * \brief Returns the assigned maximal width of the segment. + */ + inline const EDist getMaxWidth () const { return maxWidth; } + + /** + * \brief Sets the assigned maximal width of the segment. + * @param maxwidth New value for the assigned maximal width. + */ + inline void setMaxWidth (const EDist &val) { maxWidth.set (val); } + + /** + * \brief Returns the optimal digital straight line. + */ + DigitalStraightLine *getLine () const; + + /** + * \brief Adds a new sorted point to the left. + * Added point may coincide to previous one. + * Returns True if the point is inserted. + * @param pix Point to be added. + */ + bool addLeftSorted (Pt2i pix); + + /** + * Adds a new sorted point to the right. + * Added point may coincide to previous one. + * Returns True if the point is inserted. + * @param pix Point to be added. + */ + bool addRightSorted (Pt2i pix); + + /** + * Adds a new point on the left. + * Added point may not coincide to previous one. + * Returns true if the point is inserted. + * @param pix Point to be added. + */ + bool addLeft (Pt2i pix); + + /** + * Adds a new point on the right. + * Added point may not coincide to previous one. + * Returns true if the point is inserted. + * @param pix Point to be added. + */ + bool addRight (Pt2i pix); + + /** + * \brief Remove last points on the left side. + * @param n Amount of points to remove. + */ + void removeLeft (int n); + + /** + * \brief Remove last points on the right side. + * @param n Amount of points to remove. + */ + void removeRight (int n); + + /** + * \brief Returns the support vector of the blurred segment. + */ + Vr2i getSupportVector (); + + /** + * \brief Returns a static blurred segment. + * The returned blurred segment can not be extended anymore. + * Returns NULL if the segment is inconsistent (less than 2 points). + */ + BlurredSegment *endOfBirth (); + + +protected: + + /** Maximal width of the blurred segment. */ + EDist maxWidth; + + /** Maintained convex hull of the blurred segment. */ + ConvexHull *convexhull; + + /** Indicates if the blurred segment is constructed. */ + bool bsOK; + /** Indicates if the points are aligned. */ + bool bsFlat; + /** Indicates if the left point is defined. */ + bool leftOK; + /** Indicates if the right point is defined. */ + bool rightOK; + + /** Flag indicating if the convex hull changed since last DSS extraction. */ + bool chChanged; + + + /** + * \brief Submits a new point to extend the blurred segment. + * @param p Submitted point. + * @param onleft Adding direction (true for LEFT, false for RIGHT). + */ + bool addPoint (Pt2i p, bool onleft); + +}; +#endif diff --git a/Expes/Testers/TestLines/BlurredSegment/bstracker.cpp b/Expes/Testers/TestLines/BlurredSegment/bstracker.cpp new file mode 100644 index 0000000..6e148b9 --- /dev/null +++ b/Expes/Testers/TestLines/BlurredSegment/bstracker.cpp @@ -0,0 +1,422 @@ +#include "bstracker.h" +#include "bsproto.h" + + +const int BSTracker::DEFAULT_PROX_THRESHOLD = 10; +const int BSTracker::MIN_SCAN = 8; +const int BSTracker::DEFAULT_MAX_SCAN = 32; +const int BSTracker::DEFAULT_FITTING_DELAY = 20; + +const int BSTracker::DEFAULT_ASSIGNED_THICKNESS_CONTROL_DELAY = 20; + +const int BSTracker::FAILURE_NO_START = 1; +const int BSTracker::FAILURE_IMAGE_BOUND_ON_RIGHT = 2; +const int BSTracker::FAILURE_IMAGE_BOUND_ON_LEFT = 4; +const int BSTracker::FAILURE_LOST_ORIENTATION = 32; + + + +BSTracker::BSTracker () +{ + proxTestOff = true; + proxThreshold = DEFAULT_PROX_THRESHOLD; + maxScan = DEFAULT_MAX_SCAN; + fail_status = 0; + + fittingDelay = DEFAULT_FITTING_DELAY; + assignedThicknessControlDelay = DEFAULT_ASSIGNED_THICKNESS_CONTROL_DELAY; + recordScans = false; + + gMap = NULL; + cand = new int[1]; // to avoid systematic tests +} + + +BSTracker::~BSTracker () +{ + delete cand; +} + + +void BSTracker::clear () +{ + scanBound1.clear (); + scanBound2.clear (); + scanLine.clear (); +} + + +void BSTracker::setGradientMap (VMap *data) +{ + gMap = data; + scanp.setSize (gMap->getWidth (), gMap->getHeight ()); + delete cand; + cand = new int[data->getHeightWidthMax ()]; +} + + +BlurredSegment *BSTracker::fastTrack (const Pt2i &p1, const Pt2i &p2, + int bsMaxWidth, int acceptedLacks, + int swidth, const Pt2i &pc) +{ + // Creates a static directional scanner + DirectionalScanner *ds = NULL; + if (swidth != 0) + { + if (swidth < MIN_SCAN) swidth = MIN_SCAN; + ds = scanp.getScanner (pc, p1.vectorTo (p2), swidth, false); + } + else ds = scanp.getScanner (p1, p2); + if (ds == NULL) return NULL; + + // Gets a first scan + std::vector<Pt2i> pix; + if (ds->first (pix) < MIN_SCAN) + { + delete ds; + return NULL; + } + if (recordScans) + { + scanBound1.push_back (pix.front ()); + scanBound2.push_back (pix.back ()); + scanLine.push_back (pix); + } + + // Initializes a blurred segment with a first candidate + int candide; + Pt2i pfirst; + if (swidth != 0) pfirst.set (pc.x (), pc.y ()); + else + { + candide = gMap->largestIn (pix); + if (candide == -1) + { + delete ds; + return NULL; + } + pfirst.set (pix.at (candide)); + } + BSProto bsp (bsMaxWidth, pfirst); + Pt2i lastLeft (pfirst); + Pt2i lastRight (pfirst); + + // Extends the segment + int lstop = 0; + int rstop = 0; + bool added = false; + bool scanningRight = true; + bool scanningLeft = true; + int fsCount = maxScan; + + while ((scanningRight || scanningLeft) && (fsCount--)) + { + // Extends on right + if (scanningRight) + { + // Gets next scan + if (ds->nextOnRight (pix) < MIN_SCAN) scanningRight = false; + else + { + if (recordScans) + { + scanBound1.push_back (pix.front ()); + scanBound2.push_back (pix.back ()); + scanLine.push_back (pix); + } + + // Gets and tests a new candidate + added = false; + candide = gMap->largestIn (pix); + if (candide != -1) + { + if (proxTestOff + || lastRight.manhattan (pix.at (candide)) <= proxThreshold) + added = bsp.addRight (pix.at (candide)); + } + if (added) + { + lastRight.set (pix.at (candide)); + if (rstop != 0) rstop = 0; + } + else if (++rstop > acceptedLacks) scanningRight = false; + } + } + + // Extends on left + if (scanningLeft) + { + // Gets next scan + if (ds->nextOnLeft (pix) < MIN_SCAN) scanningLeft = false; + else + { + if (recordScans) + { + scanBound1.push_back (pix.front ()); + scanBound2.push_back (pix.back ()); + scanLine.push_back (pix); + } + + // Gets and tests a new candidate + added = false; + candide = gMap->largestIn (pix); + if (candide != -1) + { + if (proxTestOff + || lastLeft.manhattan (pix.at (candide)) <= proxThreshold) + added = bsp.addLeft (pix.at (candide)); + } + if (added) + { + lastLeft.set (pix.at (candide)); + if (lstop != 0) lstop = 0; + } + else if (++lstop > acceptedLacks) scanningLeft = false; + } + } + } + delete ds; + + // Validates (regenerates) and returns the blurred segment + BlurredSegment *bs = bsp.endOfBirth (); + if (bs != NULL) + { + if (swidth != 0) bs->setScan (pc, p1.vectorTo (p2)); + else bs->setScan (p1, p2); + } + return (bs); +} + + + +BlurredSegment *BSTracker::fineTrack (const Pt2i ¢er, const Vr2i &scandir, + int bsMaxWidth, int acceptedLacks, + const Vr2i &gref) +{ + // Checks scan width minimal size + int scanwidth = 2 * bsMaxWidth; + if (scanwidth < MIN_SCAN) scanwidth = MIN_SCAN; + + // Gets detected segment normal vector + Vr2i normal = scandir.orthog (); + if (! normal.directedAs (gref)) normal.invert (); + + fail_status = 0; + + // Creates an adaptive directional scanner + DirectionalScanner *ds = scanp.getScanner (center, normal, scanwidth, true); + if (ds == NULL) + { + fail_status = FAILURE_NO_START; + return NULL; + } + + // Looks for a central point + std::vector<Pt2i> pix; + if (ds->first (pix) < MIN_SCAN) + { + delete ds; + fail_status = FAILURE_NO_START; + return NULL; + } + if (recordScans) + { + scanBound1.push_back (pix.front ()); + scanBound2.push_back (pix.back ()); + scanLine.push_back (pix); + } + + // Gets candidates: sorted local max of gradient magnitude + int nbc = gMap->localMax (cand, pix, normal); + if (nbc == 0) + { + delete ds; + fail_status = FAILURE_NO_START; + return NULL; + } + + // Initializes a blurred segment with the first candidate + BSProto bsp (bsMaxWidth, pix[cand[0]]); + + // Handles assigned thickness control + bool atcOn = true; + int stab_count = 0; + int count = 0; + + // Extends the segment + int lstop = 0; + int rstop = 0; + int lstart = 0; + int rstart = 0; + bool added = false; + bool scanningRight = true; + bool scanningLeft = true; + + while (scanningRight || scanningLeft) + { + count ++; + EDist sw = bsp.strictThickness (); + + // Handles assigned thickness control + if (atcOn && stab_count >= assignedThicknessControlDelay) + { + EDist finalWidth (bsp.digitalThickness().sumWithOneHalf ()); + if (finalWidth.lessThan (bsp.getMaxWidth ())) + bsp.setMaxWidth (finalWidth); + atcOn = false; + } + + // Resets the scan strip + if (count > fittingDelay && bsp.isExtending ()) + { + // Stops the detection if the segment gets crosswise + if (count == fittingDelay + 1) + { + Vr2i dirn = bsp.getSupportVector (); + if (4 * dirn.squaredScalarProduct (scandir) + < 3 * dirn.norm2 () * scandir.norm2 ()) // div. angle > 30 degrees + { + scanningLeft = false; + scanningRight = false; + fail_status += FAILURE_LOST_ORIENTATION; + } + } + int ppa, ppb, ppc; + bsp.getLine()->getCentralLine (ppa, ppb, ppc); + ds->bindTo (ppa, ppb, ppc); + } + + // Extends on right + if (scanningRight) + { + // Gets next scan + if (ds->nextOnRight (pix) < MIN_SCAN) + { + fail_status += FAILURE_IMAGE_BOUND_ON_RIGHT; + scanningRight = false; + } + else + { + if (recordScans) + { + scanBound1.push_back (pix.front ()); + scanBound2.push_back (pix.back ()); + scanLine.push_back (pix); + } + + // Gets and tries candidates: sorted local max of gradient magnitude + added = false; + nbc = gMap->localMax (cand, pix, normal); + for (int i = 0; ! added && i < nbc; i++) + added = bsp.addRight (pix[cand[i]]); + stab_count ++; + if (added) + { + // Checks if the segment width is stable + if (atcOn && sw.lessThan (bsp.strictThickness ())) stab_count = 0; + + // Handles detection interruptions + if (rstop == 0) rstart = 0; + else + { + rstart ++; + if (rstart >= rstop) + { + rstop = 0; + rstart = 0; + } + } + } + else + { + if (++rstop - rstart > acceptedLacks) + { + if (bsp.size () <= 3) fail_status = FAILURE_NO_START; + scanningRight = false; + } + } + } + } + + // Extends on left + if (scanningLeft) + { + // Gets next scan + if (ds->nextOnLeft (pix) < MIN_SCAN) + { + fail_status += FAILURE_IMAGE_BOUND_ON_LEFT; + scanningLeft = false; + } + else + { + if (recordScans) + { + scanBound1.push_back (pix.front ()); + scanBound2.push_back (pix.back ()); + scanLine.push_back (pix); + } + + // Gets and tries candidates: sorted local max of gradient magnitude + added = false; + nbc = gMap->localMax (cand, pix, normal); + for (int i = 0; ! added && i < nbc; i++) + added = bsp.addLeft (pix[cand[i]]); + stab_count ++; + if (added) + { + // Checks if the segment width is stable + if (atcOn && sw.lessThan (bsp.strictThickness ())) stab_count = 0; + + // Handles detection interruptions + if (lstop == 0) lstart = 0; + else + { + lstart ++; + if (lstart >= lstop) + { + lstop = 0; + lstart = 0; + } + } + } + else + { + if (++lstop - lstart > acceptedLacks) + { + if (bsp.size () <= 3) fail_status = FAILURE_NO_START; + scanningLeft = false; + } + } + } + } + } + if (rstart) bsp.removeRight (rstart); + if (lstart) bsp.removeLeft (lstart); + delete ds; + + // Validates (regenerates) and returns the blurred segment + BlurredSegment *bs = bsp.endOfBirth (); + if (bs != NULL) bs->setScan (center, normal); + return (bs); +} + + +void BSTracker::incProximityThreshold (bool inc) +{ + proxThreshold += (inc ? 1 : -1); + if (proxThreshold < 1) proxThreshold = 1; +} + + +void BSTracker::incAssignedThicknessControlDelay (int val) +{ + assignedThicknessControlDelay += val; + if (assignedThicknessControlDelay < 1) assignedThicknessControlDelay = 1; +} + + +void BSTracker::switchScanExtent () +{ + maxScan = (maxScan == gMap->getHeightWidthMax () ? + DEFAULT_MAX_SCAN : gMap->getHeightWidthMax ()); +} diff --git a/Expes/Testers/TestLines/BlurredSegment/bstracker.h b/Expes/Testers/TestLines/BlurredSegment/bstracker.h new file mode 100644 index 0000000..681ccf4 --- /dev/null +++ b/Expes/Testers/TestLines/BlurredSegment/bstracker.h @@ -0,0 +1,200 @@ +#ifndef BLURRED_SEGMENT_TRACKER_H +#define BLURRED_SEGMENT_TRACKER_H + +#include "scannerprovider.h" +#include "blurredsegment.h" +#include "vmap.h" + + +/** + * @class BSTracker bstracker.h + * \brief Blurred segment tracker in grey level images. + */ +class BSTracker +{ +public: + + /** Minimal length of scan length to be processed. */ + static const int MIN_SCAN; + + + /** + * \brief Creates a blurred segment tracker. + */ + BSTracker (); + + /** + * \brief Deletes the blurred segment tracker. + */ + ~BSTracker (); + + /** + * \brief Clears off stored information. + */ + void clear (); + + /** + * \brief Sets the image data. + * @param data Reference to gradient map to be processed. + */ + void setGradientMap (VMap *data); + + /** + * \brief Builds and returns a blurred segment from only gradient maximum. + * @param p1 Initial stroke start point. + * @param p2 Initial stroke end point. + * @param bsMaxWidth Blurred segment assigned maximal width. + * @param acceptedLacks Count of maximal successive detection fails. + * @param swidth Set to 0 if no start point is provided. + * @param pc Initial segment start point (if swidth is set). + */ + BlurredSegment *fastTrack (const Pt2i &p1, const Pt2i &p2, + int bsMaxWidth, int acceptedLacks, + int swidth = 0, const Pt2i &pc = Pt2i ()); + + /** + * \brief Builds and returns a blurred segment from local gradient maxima. + * Finer detection using gradient ridges and direction input. + * @param center Central point of the scan. + * @param scandir Scan strip direction. + * @param bsMaxWidth Initial assigned maximal width of the blurred segment. + * @param acceptedLacks Count of maximal successive detection fails. + * @param gref Gradient vector reference to select candidates. + */ + BlurredSegment *fineTrack (const Pt2i ¢er, const Vr2i &scandir, + int bsMaxWidth, int acceptedLacks, + const Vr2i &gref); + + /** + * \brief Returns the proximity test status. + */ + inline bool proximityConstraintOn () const { return (! proxTestOff); } + + /** + * \brief Switches on or off the proximity test used for fast tracking. + */ + inline void switchProximityConstraint () { proxTestOff = ! proxTestOff; } + + /** + * \brief Returns the proximity threshold used for fast tracking. + */ + inline int getProximityThreshold () const { return proxThreshold; } + + /** + * \brief Increments the proximity threshold used for fast tracking. + * @param bool Increment value. + */ + void incProximityThreshold (bool inc); + + /** + * \brief Returns the registered upper bounds of the final scan lines. + * @param side Upper bound if set to 1, lower bound otherwise. + */ + inline std::vector<Pt2i> getScanBound (int side) const { + return (side == 1 ? scanBound1 : scanBound2); } + + /** + * \brief Returns the registered scan lines. + */ + inline const std::vector <std::vector <Pt2i> > &getScans () const { + return scanLine; } + + /** + * \brief Returns whether the scan record modality is set. + */ + inline bool scanRecordOn () { return recordScans; } + + /** + * \brief Sets the scan record modality. + * @param status Sets on if true, off otherwise. + */ + inline void setScanRecord (bool status) { recordScans = status; } + + /** + * \brief Switches the scan extent limitation. + */ + void switchScanExtent (); + + /** + * \brief Returns the scan extent limit. + */ + inline int maxScanExtent () { return (maxScan); } + + /** + * \brief Returns the assigned thickness control delay. + */ + inline int getAssignedThicknessControlDelay () const { + return assignedThicknessControlDelay; } + + /** + * \brief Increments the assigned thickness control delay. + * @param val New value for the delay of assigned thickness control start. + */ + void incAssignedThicknessControlDelay (int val); + + +private : + + // Segment detection default parameters. + /** Default value for the proximity test used for fast tracking. */ + static const int DEFAULT_PROX_THRESHOLD; + /** Default value for the maximal number of scans processed on each side. */ + static const int DEFAULT_MAX_SCAN; + + // Adaptive scan default parameters. + /* Count of points before activating the fitting on the detected segment. */ + static const int DEFAULT_FITTING_DELAY; + /* Maximal count of points before activating assigned thickness control. */ + static const int DEFAULT_ASSIGNED_THICKNESS_CONTROL_DELAY; + + /** Segment stop information : no start point found. */ + static const int FAILURE_NO_START; + /** Segment stop information : image bound reached on the right. */ + static const int FAILURE_IMAGE_BOUND_ON_RIGHT; + /** Segment stop information : image bound reached on the left. */ + static const int FAILURE_IMAGE_BOUND_ON_LEFT; + /** Segment stop information : lost orientation at dynamical reset start. */ + static const int FAILURE_LOST_ORIENTATION; + + + /** Scanned map left bound. */ + int xmin; + /** Scanned map lower bound. */ + int ymin; + /** Scanned map width. */ + int width; + /** Scanned map height. */ + int height; + + /** Reference to processed gradient map. */ + VMap *gMap; + /** Candidates array for internal use. */ + int *cand; + /** Failure cause registration. */ + int fail_status; + + /** Directional scanner provider (selects relevant octant). */ + ScannerProvider scanp; + /** Dynamical scanner record modality. */ + bool recordScans; + /** Upper bound of the scan. */ + std::vector<Pt2i> scanBound1; + /** Lower bound of the scan. */ + std::vector<Pt2i> scanBound2; + /** Scan lines. */ + std::vector <std::vector <Pt2i> > scanLine; + + /** Maximum number of scans to process at fast tracking stage. */ + int maxScan; + /** Minimal detection width before activating the dynamical scans. */ + int fittingDelay; + /** Count of stable point insertion before activation of ATC. */ + int assignedThicknessControlDelay; + + /** Status of the proximity constraint used for fast tracking. */ + bool proxTestOff; // DVPT + /** Proximity threshold used for fast tracking. */ + int proxThreshold; // DVPT + +}; +#endif diff --git a/Expes/Testers/TestLines/BlurredSegment/changed b/Expes/Testers/TestLines/BlurredSegment/changed new file mode 100644 index 0000000..c398e67 --- /dev/null +++ b/Expes/Testers/TestLines/BlurredSegment/changed @@ -0,0 +1,14 @@ +cmp biptlist.cpp ~/git/2019-FBSD/Code/FBSD/BlurredSegment/biptlist.cpp +cmp biptlist.h ~/git/2019-FBSD/Code/FBSD/BlurredSegment/biptlist.h +cmp blurredsegment.cpp ~/git/2019-FBSD/Code/FBSD/BlurredSegment/blurredsegment.cpp +cmp blurredsegment.h ~/git/2019-FBSD/Code/FBSD/BlurredSegment/blurredsegment.h +cmp blurredsegmentproto.cpp ~/git/2019-FBSD/Code/FBSD/BlurredSegment/blurredsegmentproto.cpp +cmp blurredsegmentproto.h ~/git/2019-FBSD/Code/FBSD/BlurredSegment/blurredsegmentproto.h +cmp bsdetector.cpp ~/git/2019-FBSD/Code/FBSD/BlurredSegment/bsdetector.cpp +cmp bsdetector.h ~/git/2019-FBSD/Code/FBSD/BlurredSegment/bsdetector.h +cmp bsproto.cpp ~/git/2019-FBSD/Code/FBSD/BlurredSegment/bsproto.cpp +cmp bsproto.h ~/git/2019-FBSD/Code/FBSD/BlurredSegment/bsproto.h +cmp bstracker.cpp ~/git/2019-FBSD/Code/FBSD/BlurredSegment/bstracker.cpp +cmp bstracker.h ~/git/2019-FBSD/Code/FBSD/BlurredSegment/bstracker.h +cmp nfafilter.cpp ~/git/2019-FBSD/Code/FBSD/BlurredSegment/nfafilter.cpp +cmp nfafilter.h ~/git/2019-FBSD/Code/FBSD/BlurredSegment/nfafilter.h diff --git a/Expes/Testers/TestLines/BlurredSegment/nfafilter.cpp b/Expes/Testers/TestLines/BlurredSegment/nfafilter.cpp new file mode 100755 index 0000000..958d8d6 --- /dev/null +++ b/Expes/Testers/TestLines/BlurredSegment/nfafilter.cpp @@ -0,0 +1,139 @@ +#include <cmath> +#include "nfafilter.h" + +const double NFAFilter::NFA_EPSILON = 1.0; +const double NFAFilter::DEFAULT_LRATIO = 1.0; +//const double NFAFilter::DEFAULT_LRATIO = 2.25; +const int NFAFilter::DEFAULT_MIN_SECTION_LENGTH = 3; + + +NFAFilter::NFAFilter () +{ + min_section_length = DEFAULT_MIN_SECTION_LENGTH; + max_grad2 = 0; + gradient_map = NULL; + cum_histo = NULL; + bs_section_count = 0; + lratio = DEFAULT_LRATIO; +} + + +NFAFilter::~NFAFilter () +{ + delete [] cum_histo; +} + + +void NFAFilter::init (VMap *gmap) +{ + // Stores the reference to the gradient map + gradient_map = gmap; + int width = gradient_map->getWidth (); + int height = gradient_map->getHeight (); + + // Counts the number of non-gradient pixels + int m = 0; + max_grad2 = 0; + for (int j = 0; j < height; j++) + { + for (int i = 0; i < width; i++) + { + int gradval = gradient_map->sqNorm (i, j); + if (gradval != 0) + { + m++; + if (gradval > max_grad2) max_grad2 = gradval; + } + } + } + + // Rectification + m = (width -2) * (height - 2); + + // Gets gradient histogram + int gmax = (int) (sqrt (max_grad2)); + cum_histo = new double[gmax + 1]; + for (int j = 0; j < height; j++) + for (int i = 0; i < width; i++) + cum_histo[(int) (sqrt (gradient_map->sqNorm (i, j)))] ++; + + // Gets cumulated histogram + for (int i = gmax; i > 0; i--) + cum_histo[i-1] += cum_histo[i]; + + // Normalization + for (int i = 0; i <= gmax; i++) + cum_histo[i] /= m; +} + + +double NFAFilter::nfaValue (double proba, int length) +{ + length = (int) (length / lratio); // Magic number : divForTestSeg + double nfa = bs_section_count; + for (int i = 0; i < length && nfa > NFA_EPSILON; i++) nfa *= proba; + return nfa; +} + + +bool NFAFilter::filter (const BlurredSegment *bs, int start, int end) +{ + int length = end - start; + if (length < min_section_length) return false; + + // Gets point with small gradient + int gmin = max_grad2; +// int pmin = -1; + std::vector<Pt2i> pts = bs->getAllPoints (); + for (int i = start; i < end; i++) + { + int gn = (gradient_map->getValue (pts[i])).norm2 (); + if (gn < gmin) + { + gmin = gn; +// pmin = i; + } + } + + // Gets NFA and accept or split the segment + double nfa = nfaValue (cum_histo[(int) (sqrt (gmin))], length); + return (nfa < NFA_EPSILON); +// if (nfa < NFA_EPSILON) return true; +// return (filter (bs, start, pmin) && filter (bs, pmin + 1, end)); +} + + +void NFAFilter::filter (const std::vector<BlurredSegment *> &bss, + std::vector<BlurredSegment *> &vsegs, + std::vector<BlurredSegment *> &rsegs) +{ + vsegs.clear (); + rsegs.clear (); + + // Computes Np + bs_section_count = 0; + std::vector<BlurredSegment *>::const_iterator it = bss.begin (); + while (it != bss.end ()) + { + int length = (*it)->size (); + bs_section_count += length * (length - 1) / 2; + it ++; + } + + // Computes and test each segment NFA + it = bss.begin (); + while (it != bss.end ()) + { + if (filter (*it, 0, (*it)->size ())) vsegs.push_back (*it); + else rsegs.push_back (*it); + it ++; + } +} + + +void NFAFilter::incLengthRatio (int inc) +{ + lratio += inc * 0.05; + if (lratio < 1.0) lratio = 1.0; + else if (lratio > 3.0) lratio = 3.0; +} diff --git a/Expes/Testers/TestLines/BlurredSegment/nfafilter.h b/Expes/Testers/TestLines/BlurredSegment/nfafilter.h new file mode 100755 index 0000000..79c0055 --- /dev/null +++ b/Expes/Testers/TestLines/BlurredSegment/nfafilter.h @@ -0,0 +1,94 @@ +#ifndef NFA_FILTER_H +#define NFA_FILTER_H + +#include "blurredsegment.h" +#include "vmap.h" + + +/** + * @class NFAFilter nfafilter.h + * \brief Number of false alarm based filter for selecting blurred segments. + */ +class NFAFilter +{ +public: + + /** + * \brief Creates an empty NFA-based filter. + */ + NFAFilter (); + + /** + * \brief Deletes the filter. + */ + ~NFAFilter (); + + /** + * \brief Initializes the filter before any detection. + * @param gmap Reference to used gradient map. + */ + void init (VMap *gmap); + + /** + * \brief Filters a set of blurred segments. + * @param bss Input set of blurred segments. + * @param vbss Output set of valid blurred segments. + * @param rbss Output set of rejected blurred segments. + */ + void filter (const std::vector<BlurredSegment *> &bss, + std::vector<BlurredSegment *> &vbss, + std::vector<BlurredSegment *> &rbss); + + /** + * \brief Returns the division ratio applied to chain length for NFA test. + */ + inline double lengthRatio () const { return lratio; } + + /** + * \brief Increments the division ratio applied to chain length for NFA test. + * @param inc Increment sign (-1 or +1). + */ + void incLengthRatio (int inc); + + +private : + + /** Tolered number of false alarm expectation. */ + static const double NFA_EPSILON; + /** Default length ratio for NFA measure. */ + static const double DEFAULT_LRATIO; + /** Default value for the smallest blurred segment section considered. */ + static const int DEFAULT_MIN_SECTION_LENGTH; + + /** Smallest blurred segment section considered. */ + int min_section_length; + /** Maximal squarred gradient value. */ + int max_grad2; + /** Reference to used gradient map. */ + VMap *gradient_map; + /** Cumulated gradient histogramm (H). */ + double *cum_histo; + /** Count of any blurred segment sections (Np). */ + int bs_section_count; + + /** Division ratio applied to chain length for NFA test. */ + double lratio; + + + /** + * \brief Computes number of false alarms of a segment section. + * @param proba Validation probability associated to min gradient point. + * @param length Section length. + */ + double nfaValue (double proba, int length); + + /** + * \brief Filters a blurred segment section. + * @param bss Reference to input blurred segment. + * @param start Index of start point in the section. + * @param end Index of first point out of the section. + */ + bool filter (const BlurredSegment *bs, int start, int end); + +}; +#endif diff --git a/Expes/Testers/TestLines/BlurredSegment/rechanged b/Expes/Testers/TestLines/BlurredSegment/rechanged new file mode 100644 index 0000000..27a1635 --- /dev/null +++ b/Expes/Testers/TestLines/BlurredSegment/rechanged @@ -0,0 +1,12 @@ +cmp biptlist.cpp ~/tmp/ASD/BlurredSegment/biptlist.cpp +cmp biptlist.h ~/tmp/ASD/BlurredSegment/biptlist.h +cmp blurredsegment.cpp ~/tmp/ASD/BlurredSegment/blurredsegment.cpp +cmp blurredsegment.h ~/tmp/ASD/BlurredSegment/blurredsegment.h +cmp bsdetector.cpp ~/tmp/ASD/BlurredSegment/bsdetector.cpp +cmp bsdetector.h ~/tmp/ASD/BlurredSegment/bsdetector.h +cmp bsproto.cpp ~/tmp/ASD/BlurredSegment/bsproto.cpp +cmp bsproto.h ~/tmp/ASD/BlurredSegment/bsproto.h +cmp bstracker.cpp ~/tmp/ASD/BlurredSegment/bstracker.cpp +cmp bstracker.h ~/tmp/ASD/BlurredSegment/bstracker.h +cmp bsfilter.cpp ~/tmp/ASD/BlurredSegment/bsfilter.cpp +cmp bsfilter.h ~/tmp/ASD/BlurredSegment/bsfilter.h diff --git a/Expes/Testers/TestLines/ConvexHull/antipodal.cpp b/Expes/Testers/TestLines/ConvexHull/antipodal.cpp new file mode 100644 index 0000000..992f524 --- /dev/null +++ b/Expes/Testers/TestLines/ConvexHull/antipodal.cpp @@ -0,0 +1,335 @@ +#include "antipodal.h" + + +Antipodal::Antipodal () +{ + ix = 0; + iy = 1; + vpt = NULL; + ept1 = NULL; + ept2 = NULL; +} + + +void Antipodal::init (CHVertex *v1, CHVertex *v2, CHVertex *v3) +{ + if (v1->get (iy) < v2->get (iy)) + { + if (v2->get (iy) < v3->get (iy)) + { + vpt = v2; + ept1 = v1; + ept2 = v3; + } + else + { + if (v1->get (iy) < v3->get (iy)) + { + vpt = v3; + ept1 = v1; + ept2 = v2; + } + else + { + vpt = v1; + ept1 = v2; + ept2 = v3; + } + } + } + else + { + if (v1->get (iy) < v3->get (iy)) + { + vpt = v1; + ept1 = v2; + ept2 = v3; + } + else + { + if (v2->get (iy) <= v3->get (iy)) // EQUIV : rather than "<" !!! + { + vpt = v3; + ept1 = v1; + ept2 = v2; + } + else + { + vpt = v2; + ept1 = v1; + ept2 = v3; + } + } + } +} + + +EDist Antipodal::thickness () const +{ + int den = ept2->get (iy) - ept1->get (iy); + return (EDist (((vpt->get (ix) - ept1->get (ix)) * den + - (vpt->get (iy) - ept1->get (iy)) + * (ept2->get (ix) - ept1->get (ix))), den)); +} + + +int Antipodal::remainder (CHVertex *v) const +{ + int a = ept2->y () - ept1->y (); + int b = ept2->x () - ept1->x (); + if (a == 0) return ((b > 0 ? -b : b) * v->y ()); + if (a < 0) + { + a = -a; + b = -b; + } + return (a * v->x () - b * v->y ()); +} + + +bool Antipodal::edgeInFirstQuadrant () const +{ + if (iy) return true; + int a = ept2->y () - ept1->y (); + if (a == 0) return true; + return (a > 0 ? (ept1->x () < ept2->x ()) : (ept2->x () < ept1->x ())); +} + + +int Antipodal::getA () const +{ + int a = ept2->y () - ept1->y (); + return (a < 0 ? -a : a); +} + + +int Antipodal::getB () const +{ + int a = ept2->y () - ept1->y (); + int b = ept2->x () - ept1->x (); + if (a < 0) b = -b; + else if (a == 0 && b < 0) b = -b; + return (b); +} + + +/* +ostream& operator<< (ostream &os, const Antipodal &ap) +{ + os << (ap.ix ? "AV [" : "AH [") << *(ap.vpt) << " + (" + << *(ap.ept1) << " - " << *(ap.ept2) << ")]"; + if (ap.remainder (ap.vpt) == ap.remainder (ap.ept1)) os << "--HS--"; + return os; +} +*/ + + +void Antipodal::update (CHVertex *pt) +{ + CHVertex *rpt = pt->right (); + CHVertex *lpt = pt->left (); + + int rmp = remainder (pt); + int rmv = remainder (vpt); + int rme = remainder (ept1); + int zpt = pt->get (iy); // vertical AP : Z -> X -- horizontal AP : Z -> Y + int zav = vpt->get (iy); // coord of antipodal vertex + int zas = ept1->get (iy); // coord of antipodal edge start + int zae = ept2->get (iy); // coord of antipodal edge end + + CHVertex *pvertex; + if (remainder (rpt) == rmv) pvertex = rpt; + else if (remainder (lpt) == rmv) pvertex = lpt; + else pvertex = vpt; + + CHVertex *pedge; + if (remainder (rpt) == rme) pedge = rpt; + else if (remainder (lpt) == rme) pedge = lpt; + else pedge = ept1; + + + // P on the line supported by the Edge + if (rmp == rme) + { + // P between start end end of antipodal Edge : no change (IMPOSSIBLE) + if ((zpt == zas) || (zpt == zae) || ((zpt < zas) != (zpt < zae))) return; + // -> prolongation of antipodal Edge up to P + setEdge (pt, pedge); + return; + } + + // P on the line (parallel to Edge) supported by the Vertex + if (rmp == rmv) + { + // P at the height of Edge -> P is the new Vertex + if ((zpt == zas) || (zpt == zae) || ((zpt < zas) != (zpt < zae))) + setVertex (pt); + else + { + // P beyond Edge Start : -> the Edge Start is the new Vertex + if ((zas == zae) || ((zas < zpt) != (zas < zae))) setVertex (ept1); + // P beyond Edge End : -> the Edge End is the new Vertex + if ((zae < zpt) != (zae < zas)) setVertex (ept2); + // -> the new Edge joins P to the former Vertex + setEdge (pt, pvertex); + } + return; + } + + // P strictly between antipodal Edge and Vertex -> no change + if ((rmp < rmv) != (rmp < rme)) return; + + + // P at the height of the antipodal Vertex + if (zpt == zav) + { + // P beyond the antipodal Vertex + if ((rmv < rmp) != (rmv < rme)) + { + // -> P is the new Vertex + setVertex (pt); + return; + } + + CHVertex *oldvpt = vpt; + if (zav != lpt->get (iy)) + { + if (oldvpt->vprod (oldvpt->left (), lpt, pt) > 0) + { + setVertex (oldvpt); + setEdge (lpt, pt); + } + else + { + setVertex (pt); + setEdge (oldvpt, oldvpt->left ()); + } + } + else + { + if (oldvpt->vprod (oldvpt->right (), rpt, pt) < 0) + { + setVertex (oldvpt); + setEdge (rpt, pt); + } + else + { + setVertex (pt); + setEdge (oldvpt, oldvpt->right ()); + } + } + return; + } + + + // Main case + //============================================================== + CHVertex *cvx = NULL; // candidate rotation vertex + CHVertex *lvx, *rvx; // left and right vertices of candidate + int zvx; // coord of candidate + + bool firstQuad = true; + if (edgeInFirstQuadrant ()) + { + if (((rmp > rme) && (rmp > rmv) && (zpt > zav)) + || ((rmp < rme) && (rmp < rmv) && (zpt < zav))) firstQuad = false; + } + else + if (((rmp > rme) && (rmp > rmv) && (zpt < zav)) + || ((rmp < rme) && (rmp < rmv) && (zpt > zav))) firstQuad = false; + + if (firstQuad) + { + if ((rme < rmp) != (rme < rmv)) cvx = pvertex; + if ((rmv < rme) != (rmv < rmp)) + cvx = (ept1->right () == ept2 ? ept1 : ept2); + zvx = cvx->get (iy); + lvx = cvx->left (); + rvx = cvx->right (); + + while (cvx->vprod (rvx, rpt, pt) > 0) + { + cvx = rvx; + lvx = cvx->left (); + rvx = cvx->right (); + zvx = cvx->get (iy); + int zpn = lvx->get (iy); + if ((zpt == zvx) || (zpt == zpn) || ((zpt < zvx) != (zpt < zpn))) break; + } + + if (zvx == zpt) + { + if (cvx->vprod (rvx, rpt, pt) <= 0) // Au lieu de < chez Phuong + { + setVertex (cvx); + setEdge (rpt, pt); + } + else + { + setVertex (pt); + setEdge (cvx, rvx); + } + } + else + { + int zpn = rpt->get (iy); + if ((zvx == zpn) || ((zvx < zpt) != (zvx < zpn))) + { + setVertex (cvx); + setEdge (rpt, pt); + } + else + { + setVertex (pt); + setEdge (lvx, cvx); + } + } + } + + else // second quadrant + { + if ((rme < rmp) != (rme < rmv)) cvx = pvertex; + if ((rmv < rme) != (rmv < rmp)) + cvx = (ept1->left () == ept2 ? ept1 : ept2); + zvx = cvx->get (iy); + rvx = cvx->right (); + lvx = cvx->left (); + + while (cvx->vprod (lvx, lpt, pt) < 0) + { + cvx = lvx; + rvx = cvx->right (); + lvx = cvx->left (); + zvx = cvx->get (iy); + int zvn = rvx->get (iy); + if ((zpt == zvx) || (zpt == zvn) || ((zpt < zvx) != (zpt < zvn))) break; + } + if (zvx == zpt) + { + if (cvx->vprod (lvx, lpt, pt) >= 0) + { + setVertex (cvx); + setEdge (lpt, pt); + } + else + { + setVertex (pt); + setEdge (cvx, lvx); + } + } + else + { + int zvn = lpt->get (iy); + if ((zvx == zvn) || ((zvx < zvn) != (zvx < zpt))) + { + setVertex (cvx); + setEdge (lpt, pt); + } + else + { + setVertex (pt); + setEdge (rvx, cvx); + } + } + } +} diff --git a/Expes/Testers/TestLines/ConvexHull/antipodal.h b/Expes/Testers/TestLines/ConvexHull/antipodal.h new file mode 100644 index 0000000..52d8dba --- /dev/null +++ b/Expes/Testers/TestLines/ConvexHull/antipodal.h @@ -0,0 +1,135 @@ +#ifndef ANTIPODAL_H +#define ANTIPODAL_H + +#include "chvertex.h" +#include "edist.h" + + +/** + * @class Antipodal antipodal.h + * \brief Horizontal or vertical antipodal pair of a polyline convex hull. + */ +class Antipodal +{ +public: + + /** + * \brief Builds an empty (undetermined) horizontal antipodal pair. + */ + Antipodal (); + + /** + * \brief Aligns the antipodal pair on vertical direction. + */ + inline void setVertical () { ix = 1; iy = 0; } + + /** + * \brief Initializes the vertex/edge pair from three unordered vertices. + * @param v1 First vertex. + * @param v2 Second vertex. + * @param v3 Third vertex. + */ + void init (CHVertex *v1, CHVertex *v2, CHVertex *v3); + + /** + * \brief Sets both vertex and edge of the antipodal pair. + * @param pt New vertex. + * @param es Start vertex of new edge. + * @param ee End vertex of new edge. + */ + inline void setVertexAndEdge (CHVertex *pt, CHVertex *es, CHVertex *ee) { + vpt = pt; ept1 = es; ept2 = ee; } + + /** + * \brief Sets the vertex of the antipodal pair. + * @param pt New vertex. + */ + inline void setVertex (CHVertex *pt) { vpt = pt; } + + /** + * \brief Sets the edge of the antipodal pair. + * @param es Start vertex of new edge. + * @param ee End vertex of new edge. + */ + inline void setEdge (CHVertex *es, CHVertex *ee) { + ept1 = es; ept2 = ee; } + + /** + * \brief Returns the vertex of the antipodal pair. + */ + inline CHVertex *vertex () const { return vpt; } + + /** + * \brief Returns the leaning edge start vertex of the antipodal pair. + */ + inline CHVertex *edgeStart () const { return ept1; } + + /** + * \brief Returns the leaning edge end vertex of the antipodal pair. + */ + inline CHVertex *edgeEnd () const { return ept2; } + + /** + * \brief Returns the antipodal pair horizontal thickness. + * It is computed as the vertex horizontal distance to the edge. + */ + EDist thickness () const; + + /** + * \brief Computes the antipodal pair horizontal thickness. + * It is the vertex horizontal distance to the edge. + * @param num Numerator of the thickness rational value. + * @param den Denominator of the thickness rational value. + */ + //void thickness (int &num, int &den) const; + + /** + * \brief Returns the remainder of the edge line equation for given vertex. + * @param v Given vertex. + */ + int remainder (CHVertex *v) const; + + /** + * \brief Checks if the antipodal edge lies in first quadrant. + * More formally, checks if sign(Ex) = sign(Ey). + */ + bool edgeInFirstQuadrant () const; + + /** + * \brief Returns the edge vector Y coordinate. + */ + int getA () const; + + /** + * \brief Returns the edge vector X coordinate. + */ + int getB () const; + + /** + * \brief Updates the antipodal pair after the insertion of a new vertex. + * @param pt Pointer to inserted vertex. + */ + void update (CHVertex *pt); + + /** + * \brief Returns a string that represents the antipodal pair. + */ + // friend ostream& operator<< (ostream &os, const Antipodal &ap); + + +protected: + + /** First coordinate (X for horizonal pair, Y for vertical pair). */ + int ix; + /** Second coordinate (Y for horizonal pair, X for vertical pair). */ + int iy; + + /** Leaning vertex. */ + CHVertex *vpt; + /** Start vertex of leaning edge. */ + CHVertex *ept1; + /** End vertex of leaning edge. */ + CHVertex *ept2; + +}; +#endif diff --git a/Expes/Testers/TestLines/ConvexHull/changed b/Expes/Testers/TestLines/ConvexHull/changed new file mode 100644 index 0000000..bb276c6 --- /dev/null +++ b/Expes/Testers/TestLines/ConvexHull/changed @@ -0,0 +1,6 @@ +cmp antipodal.cpp ~/git/2019-FBSD/Code/FBSD/ConvexHull/antipodal.cpp +cmp antipodal.h ~/git/2019-FBSD/Code/FBSD/ConvexHull/antipodal.h +cmp chvertex.cpp ~/git/2019-FBSD/Code/FBSD/ConvexHull/chvertex.cpp +cmp chvertex.h ~/git/2019-FBSD/Code/FBSD/ConvexHull/chvertex.h +cmp convexhull.cpp ~/git/2019-FBSD/Code/FBSD/ConvexHull/convexhull.cpp +cmp convexhull.h ~/git/2019-FBSD/Code/FBSD/ConvexHull/convexhull.h diff --git a/Expes/Testers/TestLines/ConvexHull/chvertex.cpp b/Expes/Testers/TestLines/ConvexHull/chvertex.cpp new file mode 100644 index 0000000..d52655b --- /dev/null +++ b/Expes/Testers/TestLines/ConvexHull/chvertex.cpp @@ -0,0 +1,36 @@ +#include "chvertex.h" + + +CHVertex::CHVertex () : Pt2i () +{ + lv = NULL; + rv = NULL; +} + + +CHVertex::CHVertex (int x, int y) : Pt2i (x, y) +{ + lv = NULL; + rv = NULL; +} + + +CHVertex::CHVertex (const Pt2i &p) : Pt2i (p) +{ + lv = NULL; + rv = NULL; +} + + +CHVertex::~CHVertex () +{ +} + + +/* +ostream& operator<< (ostream &os, const CHVertex &v) +{ + os << "(" << v.xp << ", " << v.yp << ")"; + return os; +} +*/ diff --git a/Expes/Testers/TestLines/ConvexHull/chvertex.h b/Expes/Testers/TestLines/ConvexHull/chvertex.h new file mode 100644 index 0000000..82b7226 --- /dev/null +++ b/Expes/Testers/TestLines/ConvexHull/chvertex.h @@ -0,0 +1,92 @@ +#ifndef CHVERTEX_H +#define CHVERTEX_H + +#include "pt2i.h" + + +/** + * @class CHVertex chvertex.h + * \brief Chained vertex with two adjacent points, on left and right. + */ +class CHVertex : public Pt2i +{ +public: + + /** + * \brief Builds a default vertex. + */ + CHVertex (); + + /** + * \brief Builds a vertex on given coordinates. + * @param x First ccordinate value. + * @param y Second ccordinate value. + */ + CHVertex (int x, int y); + + /** + * \brief Builds a vertex at position of given point. + * @param p Reference to given point. + */ + CHVertex (const Pt2i &p); + + /** + * \brief Deletes the vertex. + */ + ~CHVertex (); + + /** + * \brief Returns a pointer to adjacent vertex on left side. + */ + inline CHVertex *left () const { return lv; } + + /** + * \brief Returns a pointer to adjacent vertex on right side. + */ + inline CHVertex *right () const { return rv; } + + /** + * \brief Sets adjacent vertex on left side. + * @param v Pointer to new adjacent vertex. + */ + inline void setLeft (CHVertex *v) { lv = v; } + + /** + * \brief Sets adjacent vertex on right side. + * @param v Pointer to new adjacent vertex. + */ + inline void setRight (CHVertex *v) { rv = v; } + + /** + * \brief Returns the cross product of vector (pt - this) and vector (vx, vy). + * The cross product of U and V is defined by: CP = Ux * Vy - Vx * Uy. + * @param pt Given end point. + * @param vx First coordinate of given vector. + * @param vy Second coordinate of given vector. + */ + inline int vprod (CHVertex *pt, int vx, int vy) const { + return ((pt->xp - xp) * vy - vx * (pt->yp - yp)); } + + /** + * \brief Returns the cross product of vector (p2 - this) and vector (p4 - p3) + * The cross product of U and V is defined by: CP = Ux * Vy - Vx * Uy. + * @param p2 First end point. + * @param p3 Second start point. + * @param p4 Second end point. + */ + inline int vprod (CHVertex *p2, CHVertex *p3, CHVertex *p4) const { + return ((p2->xp - xp) * (p4->yp - p3->yp) + - (p4->xp - p3->xp) * (p2->yp - yp)); } + + // friend ostream& operator<< (ostream &os, const CHVertex &v); + + +protected: + + /** Adjacent vertex on left side. */ + CHVertex *lv; + /** Adjacent vertex on right side. */ + CHVertex *rv; + +}; +#endif diff --git a/Expes/Testers/TestLines/ConvexHull/convexhull.cpp b/Expes/Testers/TestLines/ConvexHull/convexhull.cpp new file mode 100644 index 0000000..fa7d8c3 --- /dev/null +++ b/Expes/Testers/TestLines/ConvexHull/convexhull.cpp @@ -0,0 +1,261 @@ +#include "convexhull.h" + + +ConvexHull::ConvexHull (const Pt2i &lpt, const Pt2i &cpt, const Pt2i &rpt) +{ + CHVertex *cvert = new CHVertex (cpt); + leftVertex = new CHVertex (lpt); + rightVertex = new CHVertex (rpt); + lastToLeft = false; + + if (lpt.toLeft (cpt, rpt)) + { + leftVertex->setRight (cvert); + cvert->setLeft (leftVertex); + cvert->setRight (rightVertex); + rightVertex->setLeft (cvert); + rightVertex->setRight (leftVertex); + leftVertex->setLeft (rightVertex); + } + else + { + leftVertex->setRight (rightVertex); + rightVertex->setLeft (leftVertex); + rightVertex->setRight (cvert); + cvert->setLeft (rightVertex); + cvert->setRight (leftVertex); + leftVertex->setLeft (cvert); + } + + aph.init (leftVertex, cvert, rightVertex); + apv.setVertical (); + apv.init (leftVertex, cvert, rightVertex); + + gbg.push_back (leftVertex); + gbg.push_back (cvert); + gbg.push_back (rightVertex); + + old_left = leftVertex; + old_right = rightVertex; + old_aph_vertex = aph.vertex (); + old_aph_edge_start = aph.edgeStart (); + old_aph_edge_end = aph.edgeEnd (); + old_apv_vertex = apv.vertex (); + old_apv_edge_start = apv.edgeStart (); + old_apv_edge_end = apv.edgeEnd (); +} + + +ConvexHull::~ConvexHull () +{ + for (int i = 0; i < (int) (gbg.size ()); ++i) delete gbg [i]; +} + + +void ConvexHull::preserve () +{ + old_aph_vertex = aph.vertex (); + old_aph_edge_start = aph.edgeStart (); + old_aph_edge_end = aph.edgeEnd (); + old_apv_vertex = apv.vertex (); + old_apv_edge_start = apv.edgeStart (); + old_apv_edge_end = apv.edgeEnd (); + old_left = leftVertex; + old_right = rightVertex; +} + + +void ConvexHull::restore () +{ + rconnect->setLeft (rdisconnect); + lconnect->setRight (ldisconnect); + leftVertex = old_left; + rightVertex = old_right; + aph.setVertexAndEdge (old_aph_vertex, old_aph_edge_start, old_aph_edge_end); + apv.setVertexAndEdge (old_apv_vertex, old_apv_edge_start, old_apv_edge_end); +} + + +bool ConvexHull::addPoint (const Pt2i &pt, bool toleft) +{ + if (inHull (pt, toleft)) return false; + CHVertex *vx = new CHVertex (pt); + lastToLeft = toleft; + gbg.push_back (vx); + preserve (); + insert (vx, toleft); + aph.update (vx); + apv.update (vx); + return true; +} + + +bool ConvexHull::addPointDS (const Pt2i &pt, bool toleft) +{ + CHVertex *vx = new CHVertex (pt); + lastToLeft = toleft; + gbg.push_back (vx); + preserve (); + insertDS (vx, toleft); + aph.update (vx); + apv.update (vx); + return true; +} + + +bool ConvexHull::moveLastPoint (const Pt2i &pos) +{ + restore (); + if (inHull (pos, lastToLeft)) return false; + gbg.pop_back (); + preserve (); + addPoint (pos, lastToLeft); + return true; +} + + +EDist ConvexHull::thickness () const +{ + EDist aphw = aph.thickness (); + EDist apvw = apv.thickness (); + return (apvw.lessThan (aphw) ? apvw : aphw); +} + + +void ConvexHull::antipodalEdgeAndVertex (Pt2i &s, Pt2i &e, Pt2i &v) const +{ + EDist aphw = aph.thickness (); + EDist apvw = apv.thickness (); + const Antipodal *ap = (apvw.lessThan (aphw) ? &apv : &aph); + s.set (*(ap->edgeStart ())); + e.set (*(ap->edgeEnd ())); + v.set (*(ap->vertex ())); +} + + +bool ConvexHull::inHull (const Pt2i &pt, bool toleft) const +{ + CHVertex *ext = (toleft ? leftVertex : rightVertex); + return (pt.toLeftOrOn (*ext, *(ext->right ())) + && pt.toLeftOrOn (*(ext->left ()), *ext)); +} + + +void ConvexHull::insert (CHVertex *pt, bool toleft) +{ + bool opIn = false; // Opposite polyline top in the new convex hull + CHVertex *opVertex = NULL; // Opposite vertex + + if (toleft) + { + lconnect = leftVertex; + rconnect = leftVertex; + leftVertex = pt; + opVertex = rightVertex; + } + else + { + lconnect = rightVertex; + rconnect = rightVertex; + rightVertex = pt; + opVertex = leftVertex; + } + + ldisconnect = lconnect->right (); + while (pt->toLeftOrOn (*lconnect, *(lconnect->left ()))) + { + if (lconnect == opVertex) opIn = true; + ldisconnect = lconnect; + lconnect = lconnect->left (); + } + if (opIn) + { + if (toleft) rightVertex = lconnect; + else leftVertex = lconnect; + } + + opIn = false; + rdisconnect = rconnect->left (); + while (! pt->toLeft (*rconnect, *(rconnect->right ()))) + { + if (rconnect == opVertex) opIn = true; + rdisconnect = rconnect; + rconnect = rconnect->right (); + } + if (opIn) + { + if (toleft) rightVertex = rconnect; + else leftVertex = rconnect; + } + + lconnect->setRight (pt); + pt->setLeft (lconnect); + rconnect->setLeft (pt); + pt->setRight (rconnect); +} + + +void ConvexHull::insertDS (CHVertex *pt, bool toleft) +{ + if (toleft) + { + lconnect = leftVertex; + rconnect = leftVertex; + leftVertex = pt; + } + else + { + lconnect = rightVertex; + rconnect = rightVertex; + rightVertex = pt; + } + + ldisconnect = lconnect->right (); + while (pt->toLeftOrOn (*lconnect, *(lconnect->left ()))) + { + ldisconnect = lconnect; + lconnect = lconnect->left (); + } + + rdisconnect = rconnect->left (); + while (! pt->toLeft (*rconnect, *(rconnect->right ()))) + { + rdisconnect = rconnect; + rconnect = rconnect->right (); + } + + lconnect->setRight (pt); + pt->setLeft (lconnect); + rconnect->setLeft (pt); + pt->setRight (rconnect); +} + + +/* +ostream& operator<< (ostream &os, const ConvexHull &ch) +{ + os << "APH = " << ch.aph << endl; + os << "APV = " << ch.apv << endl; + os << "FIRST " << *(ch.leftVertex); + CHVertex *next = ch.leftVertex->right (); + int i = 0; + while (i++ < 20 && next != ch.leftVertex) + { + os << " - " << *next; + next = next->right (); + } + if (i >= 20) os << " ---"; + os << endl; + os << "LAST " << *(ch.rightVertex); + next = ch.rightVertex->left (); + i = 0; + while (i++ < 20 && next != ch.rightVertex) + { + os << " - " << *next; + next = next->left (); + } + if (i >= 20) os << " ---"; + + return os; +} +*/ diff --git a/Expes/Testers/TestLines/ConvexHull/convexhull.h b/Expes/Testers/TestLines/ConvexHull/convexhull.h new file mode 100644 index 0000000..d1483b5 --- /dev/null +++ b/Expes/Testers/TestLines/ConvexHull/convexhull.h @@ -0,0 +1,195 @@ +#ifndef CONVEXHULL +#define CONVEXHULL + +#include "antipodal.h" + + +/** + * @class ConvexHull convexhull.h + * \brief Convex hull of a polyline. + */ +class ConvexHull +{ +public: + + /** + * \brief Creates a convex hull from a triangle. + * Be very careful with the points ordering : lpt, cpt, then rpt. + * Ensure that the points are NOT COLINEAR (not tested here). + * @param lpt : left end vertex of the polyline. + * @param cpt : center vertex of the polyline. + * @param rpt : right end vertex of the polyline. + */ + ConvexHull (const Pt2i &lpt, const Pt2i &cpt, const Pt2i &rpt); + + /** + * \brief Deletes the convex hull. + * Removes all registered vertices. + */ + ~ConvexHull (); + + /** + * \brief Restores the convexhull features after a modification. + */ + void restore (); + + + /** + * \brief Appends a new point at one side of the convex hull. + * @param pt Reference to the point to add. + * @param Side at which the point is added. + */ + bool addPoint (const Pt2i &pt, bool toleft); + + /** + * \brief Appends a new point at one side of the convex hull. + * To be used with directional scans: + * in that case, added point can not be inside the hull. + * @param pt Reference to the point to add. + * @param toleft Add the point at left side if true, right side otherwise. + */ + bool addPointDS (const Pt2i &pt, bool toleft); + + /** + * \brief Moves the last inserted point and returns the success. + * @param pos New position for the last point. + */ + bool moveLastPoint (const Pt2i &pos); + + /** + * \brief Checks whether the line to given point crosses the hull. + * @param pt Given point. + * @param toleft Set to true if the point was added on left side. + */ + bool inHull (const Pt2i &pt, bool toleft) const; + + + /** + * \brief Returns the antipodal edge and vertex. + * @param s Edge start vertex. + * @param e Edge end vertex. + * @param v Vertex. + */ + void antipodalEdgeAndVertex (Pt2i &s, Pt2i &e, Pt2i &v) const; + + /** + * \brief Returns the convex hull thickness. + * The thickness is the minimal vertical or horizontal thickness. + * It is computed as the minimal value of both antipodal pairs. + */ + EDist thickness () const; + + /** + * \brief Returns a string that represents the convex hull. + */ + // friend ostream& operator<< (ostream &os, const ConvexHull &ch); + + /** + * \brief Returns the first (left) vertex of the convex hull. + */ + inline CHVertex *getFirstVertex () const { return (leftVertex); } + + /** + * \brief Returns the last (right) vertex of the convex hull. + */ + inline CHVertex *getLastVertex () const { return (rightVertex); } + + /** + * \brief Returns the horizontal antipodal vertex. + */ + inline CHVertex *getAphVertex () const { return (aph.vertex ()); } + + /** + * \brief Returns the horizontal antipodal edge start vertex. + */ + inline CHVertex *getAphEdgeStart () const { return (aph.edgeStart ()); } + + /** + * \brief Returns the horizontal antipodal edge end vertex. + */ + inline CHVertex *getAphEdgeEnd () const { return (aph.edgeEnd ()); } + + /** + * \brief Returns the vertical antipodal vertex. + */ + inline CHVertex *getApvVertex () const { return (apv.vertex ()); } + + /** + * \brief Returns the vertical antipodal edge start vertex. + */ + inline CHVertex *getApvEdgeStart () const { return (apv.edgeStart ()); } + + /** + * \brief Returns the vertical antipodal edge end vertex. + */ + inline CHVertex *getApvEdgeEnd () const { return (apv.edgeEnd ()); } + + +protected: + + /** Polyline left end point. */ + CHVertex *leftVertex; + /** Polyline right end point. */ + CHVertex *rightVertex; + /** Indicates if the last vertex was entered to the left. */ + bool lastToLeft; + + /** Antipodal pair in horizontal direction. */ + Antipodal aph; + /** Antipodal pair in vertical direction. */ + Antipodal apv; + + /** Registered vertex of previous horizontal antipodal pair. */ + CHVertex *old_aph_vertex; + /** Registered edge start of previous horizontal antipodal pair. */ + CHVertex *old_aph_edge_start; + /** Registered edge end of previous horizontal antipodal pair. */ + CHVertex *old_aph_edge_end; + /** Registered vertex of previous vertical antipodal pair. */ + CHVertex *old_apv_vertex; + /** Registered edge start of previous vertical antipodal pair. */ + CHVertex *old_apv_edge_start; + /** Registered edge end of previous vertical antipodal pair. */ + CHVertex *old_apv_edge_end; + /** Registered left end point of previous polyline. */ + CHVertex *old_left; + /** Registered right end point of previous polyline. */ + CHVertex *old_right; + /** Registered connected point to the left of previous polyline. */ + CHVertex *lconnect; + /** Registered disconnected point to the left of previous polyline. */ + CHVertex *ldisconnect; + /** Registered connected point to the right of previous polyline. */ + CHVertex *rconnect; + /** Registered disconnected point to the right of previous polyline. */ + CHVertex *rdisconnect; + + /** Collection of released vertices for clearance. */ + std::vector<CHVertex*> gbg; + + +private: + + /** + * \brief Stores convex hull features before a modification. + */ + void preserve (); + + /** + * \brief Inserts a new point into the convex hull. + * @param pt Pointer to the point to add. + * @param toleft Adds to left if true, to right otherwise. + */ + void insert (CHVertex *pt, bool toleft); + + /** + * \brief Inserts a new point into the convex hull. + * To be used with directional scans : + * In that case, opposite ends of the polyline can never pass each other. + * @param pt Pointer to the point to add. + * @param toleft Adds to left if true, to right otherwise. + */ + void insertDS (CHVertex *pt, bool toleft); + +}; +#endif diff --git a/Expes/Testers/TestLines/ConvexHull/rechanged b/Expes/Testers/TestLines/ConvexHull/rechanged new file mode 100644 index 0000000..6503942 --- /dev/null +++ b/Expes/Testers/TestLines/ConvexHull/rechanged @@ -0,0 +1,6 @@ +cmp antipodal.cpp ~/tmp/ASD/ConvexHull/antipodal.cpp +cmp antipodal.h ~/tmp/ASD/ConvexHull/antipodal.h +cmp chvertex.cpp ~/tmp/ASD/ConvexHull/chvertex.cpp +cmp chvertex.h ~/tmp/ASD/ConvexHull/chvertex.h +cmp convexhull.cpp ~/tmp/ASD/ConvexHull/convexhull.cpp +cmp convexhull.h ~/tmp/ASD/ConvexHull/convexhull.h diff --git a/Expes/Testers/TestLines/DirectionalScanner/adaptivescannero1.cpp b/Expes/Testers/TestLines/DirectionalScanner/adaptivescannero1.cpp new file mode 100644 index 0000000..9e2bdf1 --- /dev/null +++ b/Expes/Testers/TestLines/DirectionalScanner/adaptivescannero1.cpp @@ -0,0 +1,262 @@ +#include "adaptivescannero1.h" + + +AdaptiveScannerO1::AdaptiveScannerO1 ( + int xmin, int ymin, int xmax, int ymax, + int a, int b, int c, + int nbs, bool *steps, int sx, int sy) + : DirectionalScanner (xmin, ymin, xmax, ymax, + nbs, steps, sx, sy) +{ + this->dla = a; + this->dlb = b; + this->dlc2 = c; + this->dlc1 = a * sx + b * sy; + + this->templ_a = a; + this->templ_b = b; + this->templ_nu = this->dlc1 - this->dlc2; + + lst2 = steps; + rst2 = steps; + fs = steps + nbs; +} + + +AdaptiveScannerO1::AdaptiveScannerO1 ( + int xmin, int ymin, int xmax, int ymax, + int a, int b, int c1, int c2, + int nbs, bool *steps, int cx, int cy) + : DirectionalScanner (xmin, ymin, xmax, ymax, + nbs, steps, cx, cy) +{ + this->dla = a; + this->dlb = b; + if (c2 > c1) + { + this->dlc1 = c2; + this->dlc2 = c1; + c1 = c2; + } + else + { + this->dlc1 = c1; + this->dlc2 = c2; + } + + this->templ_a = a; + this->templ_b = b; + this->templ_nu = this->dlc1 - this->dlc2; + + // Looking for the central scan start position + bool *st = steps + nbs; + do + { + if (--st < steps) st = steps + nbs - 1; + if (*st) lcx ++; + lcy --; + } + while (dla * lcx + dlb * lcy < c1); + lst2 = st; + rst2 = st; + + rcx = lcx; + rcy = lcy; + fs = steps + nbs; +} + + +AdaptiveScannerO1::AdaptiveScannerO1 ( + int xmin, int ymin, int xmax, int ymax, + int a, int b, + int nbs, bool *steps, int cx, int cy, int length) + : DirectionalScanner (xmin, ymin, xmax, ymax, + nbs, steps, cx, cy) +{ + this->dla = a; + this->dlb = b; + fs = steps + nbs; + int w_2 = (length + 1) / 2; + + // Looking for the central scan start position + bool *st = steps + nbs; + for (int i = 0; i < w_2; i++) + { + if (--st < steps) st = steps + nbs - 1; + if (*st) lcx ++; + lcy --; + } + dlc1 = dla * lcx + dlb * lcy; + lst2 = st; + rst2 = st; + + // Looking for the upper leaning line + st = steps; + while (w_2-- > 0) + { + if (*st) cx--; + cy++; + if (++st >= fs) st = steps; + } + dlc2 = dla * cx + dlb * cy; + + this->templ_a = a; + this->templ_b = b; + this->templ_nu = this->dlc1 - this->dlc2; + + rcx = lcx; + rcy = lcy; +} + + +AdaptiveScannerO1::AdaptiveScannerO1 (AdaptiveScannerO1 *ds) + : DirectionalScanner (ds) +{ + templ_a = ds->templ_a; + templ_b = ds->templ_b; + templ_nu = ds->templ_nu; + dlc1 = ds->dlc1; +} + + +DirectionalScanner *AdaptiveScannerO1::getCopy () +{ + return (new AdaptiveScannerO1 (this)); +} + + +int AdaptiveScannerO1::first (std::vector<Pt2i> &scan) const +{ + int x = lcx, y = lcy; // Current position coordinates + bool *nst = lst2; // Current step in scan direction (jpts) + + while ((x >= xmax || y < ymin) && dla * x + dlb * y >= dlc2) + { + if (*nst) x--; + y++; + if (++nst >= fs) nst = steps; + } + while (dla * x + dlb * y >= dlc2 && x >= xmin && y < ymax) + { + scan.push_back (Pt2i (x, y)); + if (*nst) x--; + y++; + if (++nst >= fs) nst = steps; + } + return ((int) (scan.size ())); +} + + +int AdaptiveScannerO1::nextOnLeft (std::vector<Pt2i> &scan) +{ + // Prepares the next scan + if (clearance) scan.clear (); + lcx --; + // Whenever the control line changed + while (lcy < ymax - 1 && lcx >= xmin && dla * lcx + dlb * lcy > dlc1) + { + if (*lst2) lcx --; + lcy ++; + if (++lst2 >= fs) lst2 = steps; + } + while (lcy > ymin && lcx < xmax && dla * lcx + dlb * lcy < dlc1) + { + if (--lst2 < steps) lst2 = steps + nbs - 1; + if (*lst2) lcx ++; + lcy --; + } + + // Computes the next scan + int x = lcx; + int y = lcy; + bool *nst = lst2; + while ((x >= xmax || y < ymin) && dla * x + dlb * y >= dlc2) + { + if (*nst) x --; + y ++; + if (++nst >= fs) nst = steps; + } + while (dla * x + dlb * y >= dlc2 && x >= xmin && y < ymax) + { + scan.push_back (Pt2i (x, y)); + if (*nst) x --; + y ++; + if (++nst >= fs) nst = steps; + } + return ((int) (scan.size ())); +} + + +int AdaptiveScannerO1::nextOnRight (std::vector<Pt2i> &scan) +{ + // Prepares the next scan + if (clearance) scan.clear (); + rcx ++; + while (rcy < ymax - 1 && rcx >= xmin && dla * rcx + dlb * rcy > dlc1) + { + if (*rst2) rcx --; + rcy ++; + if (++rst2 >= fs) rst2 = steps; + } + while (rcy > ymin && rcx < xmax && dla * rcx + dlb * rcy < dlc1) + { + if (--rst2 < steps) rst2 = steps + nbs - 1; + if (*rst2) rcx ++; + rcy --; + } + + // Computes the next scan + int x = rcx; + int y = rcy; + bool *nst = rst2; + while ((x >= xmax || y < ymin) && dla * x + dlb * y >= dlc2) + { + if (*nst) x--; + y++; + if (++nst >= fs) nst = steps; + } + while (dla * x + dlb * y >= dlc2 && x >= xmin && y < ymax) + { + scan.push_back (Pt2i (x, y)); + if (*nst) x--; + y++; + if (++nst >= fs) nst = steps; + } + return ((int) (scan.size ())); +} + + +void AdaptiveScannerO1::bindTo (int a, int b, int c) +{ + if (a < 0) + { + dla = -a; + dlb = -b; + c = -c; + } + else + { + dla = a; + dlb = b; + } + int old_b = (templ_b < 0 ? -templ_b : templ_b); + int old_n1 = templ_a + old_b; + int old_ninf = (old_b > templ_a ? old_b : templ_a); + int new_a = (a < 0 ? -a : a); + int new_b = (b < 0 ? -b : b); + int new_n1 = new_a + new_b; + int new_ninf = (new_b > new_a ? new_b : new_a); + int nu; + if (new_n1 * old_ninf > old_n1 * new_ninf) + nu = (templ_nu * new_n1) / old_n1; + else + nu = (templ_nu * new_ninf) / old_ninf; + if (dlb > 0) // dlb should stay negative to avoid the direction change + { // of the support line inequations. + dla = -dla; + dlb = -dlb; + c = -c; + } + dlc1 = c + nu / 2; + dlc2 = c - nu / 2; +} diff --git a/Expes/Testers/TestLines/DirectionalScanner/adaptivescannero1.h b/Expes/Testers/TestLines/DirectionalScanner/adaptivescannero1.h new file mode 100644 index 0000000..6121356 --- /dev/null +++ b/Expes/Testers/TestLines/DirectionalScanner/adaptivescannero1.h @@ -0,0 +1,138 @@ +#ifndef ADAPTIVE_SCANNER_O1_H +#define ADAPTIVE_SCANNER_O1_H + +#include "directionalscanner.h" + + +/** + * @class Adaptive adaptivescannero1.h + * \brief Adaptive directional scanner for the 1st octant. + */ +class AdaptiveScannerO1 : public DirectionalScanner +{ +public: + + /** + * \brief Creates an adaptive DS from pattern, start and upper bound. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a start point, a line pattern, and an upper bound. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the discrete support line. + * @param b Value of parameter 'b' of the discrete support line. + * @param c Value of parameter 'c' of the upper bounding line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param sx X-coordinate of the central scan start point. + * @param sy Y-coordinate of the central scan start point. + */ + AdaptiveScannerO1 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int c, + int nbs, bool *steps, int sx, int sy); + + /** + * \brief Creates an adaptive DS from pattern, center and bounds. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a center, a line pattern, upper and lower bounds. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the support discrete line. + * @param b Value of parameter 'b' of the support discrete line. + * @param c1 Value of parameter 'c' of one of the support lines. + * @param c2 Value of parameter 'c' of the other support line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param cx X-coordinate of the central scan center. + * @param cy Y-coordinate of the central scan center. + */ + AdaptiveScannerO1 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int c1, int c2, + int nbs, bool *steps, int cx, int cy); + + /** + * \brief Creates an adaptive DS from pattern, center and length. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a center, a line pattern, and a length value. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the discrete support line. + * @param b Value of parameter 'b' of the discrete support line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param cx X-coordinate of the central scan center. + * @param cy Y-coordinate of the central scan center. + * @param length Length of a scan strip. + */ + AdaptiveScannerO1 (int xmin, int ymin, int xmax, int ymax, + int a, int b, + int nbs, bool *steps, + int cx, int cy, int length); + + /** + * \brief Returns a copy of the directional scanner. + */ + DirectionalScanner *getCopy (); + + /** + * \brief Gets the central scan in a vector. + * Adds central scan points to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int first (std::vector<Pt2i> &scan) const; + + /** + * \brief Gets the next scan on the left in a vector. + * Adds points of next left scan to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int nextOnLeft (std::vector<Pt2i> &scan); + + /** + * \brief Gets the next scan on the right in a vector. + * Adds points of next right scan to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int nextOnRight (std::vector<Pt2i> &scan); + + /** + * \brief Binds the scan strip to wrap the given digital line. + * Resets bounding lines parameters to center the scan strip on given line. + * @param a Parameter 'a' of given digital line. + * @param b Parameter 'b' of given digital line. + * @param c Parameter 'c' of given digital line. + */ + void bindTo (int a, int b, int c); + + +protected : + + /** Parameter 'a' of template support discrete line template. */ + int templ_a; + /** Parameter 'b' of template support discrete line template. */ + int templ_b; + /** Parameter 'nu' of template support discrete line template. */ + int templ_nu; + + /** Shift parameter of the lower support discrete line. */ + int dlc1; + + + /** + * \brief Creates an empty adaptive directional scanner. + */ + AdaptiveScannerO1 () { } + + /** + * \brief Creates a copy of given directional scanner. + * @param ds Source directional scanner. + */ + AdaptiveScannerO1 (AdaptiveScannerO1 *ds); + +}; +#endif diff --git a/Expes/Testers/TestLines/DirectionalScanner/adaptivescannero2.cpp b/Expes/Testers/TestLines/DirectionalScanner/adaptivescannero2.cpp new file mode 100644 index 0000000..6417c10 --- /dev/null +++ b/Expes/Testers/TestLines/DirectionalScanner/adaptivescannero2.cpp @@ -0,0 +1,256 @@ +#include "adaptivescannero2.h" + + +AdaptiveScannerO2::AdaptiveScannerO2 ( + int xmin, int ymin, int xmax, int ymax, + int a, int b, int c, + int nbs, bool *steps, int sx, int sy) + : DirectionalScanner (xmin, ymin, xmax, ymax, + nbs, steps, sx, sy) +{ + this->dla = a; + this->dlb = b; + this->dlc2 = c; + this->dlc1 = a * sx + b * sy; + + this->templ_a = a; + this->templ_b = b; + this->templ_nu = this->dlc1 - this->dlc2; + + lst2 = steps; + rst2 = steps; + fs = steps + nbs; +} + + +AdaptiveScannerO2::AdaptiveScannerO2 ( + int xmin, int ymin, int xmax, int ymax, + int a, int b, int c1, int c2, + int nbs, bool *steps, int cx, int cy) + : DirectionalScanner (xmin, ymin, xmax, ymax, + nbs, steps, cx, cy) +{ + this->dla = a; + this->dlb = b; + if (c2 > c1) + { + this->dlc1 = c2; + this->dlc2 = c1; + c1 = c2; + } + else + { + this->dlc1 = c1; + this->dlc2 = c2; + } + + this->templ_a = a; + this->templ_b = b; + this->templ_nu = this->dlc1 - this->dlc2; + + // Looking for the central scan start position + bool *st = steps + nbs; + do + { + if (--st < steps) st = steps + nbs - 1; + if (*st) lcy --; + lcx ++; + } + while (dla * lcx + dlb * lcy < c1); + lst2 = st; + rst2 = st; + + rcx = lcx; + rcy = lcy; + fs = steps + nbs; +} + + +AdaptiveScannerO2::AdaptiveScannerO2 ( + int xmin, int ymin, int xmax, int ymax, + int a, int b, + int nbs, bool *steps, int cx, int cy, int length) + : DirectionalScanner (xmin, ymin, xmax, ymax, + nbs, steps, cx, cy) +{ + this->dla = a; + this->dlb = b; + fs = steps + nbs; + int w_2 = (length + 1) / 2; + + // Looking for the central scan start position + bool *st = steps + nbs; + for (int i = 0; i < w_2; i++) + { + if (--st < steps) st = steps + nbs - 1; + if (*st) lcy --; + lcx ++; + } + dlc1 = dla * lcx + dlb * lcy; + lst2 = st; + rst2 = st; + + // Looking for the upper leaning line + st = steps; + while (w_2-- > 0) + { + if (*st) cy++; + cx--; + if (++st >= fs) st = steps; + } + dlc2 = dla * cx + dlb * cy; + + this->templ_a = a; + this->templ_b = b; + this->templ_nu = this->dlc1 - this->dlc2; + + rcx = lcx; + rcy = lcy; +} + + +AdaptiveScannerO2::AdaptiveScannerO2 (AdaptiveScannerO2 *ds) + : DirectionalScanner (ds) +{ + templ_a = ds->templ_a; + templ_b = ds->templ_b; + templ_nu = ds->templ_nu; + dlc1 = ds->dlc1; +} + + +DirectionalScanner *AdaptiveScannerO2::getCopy () +{ + return (new AdaptiveScannerO2 (this)); +} + + +int AdaptiveScannerO2::first (std::vector<Pt2i> &scan) const +{ + int x = lcx, y = lcy; // Current position coordinates + bool *nst = lst2; // Current step in scan direction (jpts) + + while ((y < ymin || x >= xmax) && dla * x + dlb * y >= dlc2) + { + if (*nst) y++; + x--; + if (++nst >= fs) nst = steps; + } + while (dla * x + dlb * y >= dlc2 && y < ymax && x >= xmin) + { + scan.push_back (Pt2i (x, y)); + if (*nst) y++; + x--; + if (++nst >= fs) nst = steps; + } + return ((int) (scan.size ())); +} + + +int AdaptiveScannerO2::nextOnLeft (std::vector<Pt2i> &scan) +{ + // Prepares the next scan + if (clearance) scan.clear (); + lcy --; + // Whenever the control line changed + while (lcx > xmin && lcy < ymax && dla * lcx + dlb * lcy > dlc1) + { + if (*lst2) lcy ++; + lcx --; + if (++lst2 >= fs) lst2 = steps; + } + while (lcx < xmax - 1 && lcy >= ymin && dla * lcx + dlb * lcy < dlc1) + { + if (--lst2 < steps) lst2 = steps + nbs - 1; + if (*lst2) lcy --; + lcx ++; + } + + // Computes the next scan + int x = lcx; + int y = lcy; + bool *nst = lst2; + while ((y < ymin || x >= xmax) && dla * x + dlb * y >= dlc2) + { + if (*nst) y++; + x--; + if (++nst >= fs) nst = steps; + } + while (dla * x + dlb * y >= dlc2 && y < ymax && x >= xmin) + { + scan.push_back (Pt2i (x, y)); + if (*nst) y++; + x--; + if (++nst >= fs) nst = steps; + } + return ((int) (scan.size ())); +} + + +int AdaptiveScannerO2::nextOnRight (std::vector<Pt2i> &scan) +{ + // Prepares the next scan + if (clearance) scan.clear (); + rcy ++; + while (rcx > xmin && rcy < ymax && dla * rcx + dlb * rcy > dlc1) + { + if (*rst2) rcy ++; + rcx --; + if (++rst2 >= fs) rst2 = steps; + } + while (rcx < xmax - 1 && rcy >= ymin && dla * rcx + dlb * rcy < dlc1) + { + if (--rst2 < steps) rst2 = steps + nbs - 1; + if (*rst2) rcy --; + rcx ++; + } + + // Computes the next scan + int x = rcx; + int y = rcy; + bool *nst = rst2; + while ((y < ymin || x >= xmax) && dla * x + dlb * y >= dlc2) + { + if (*nst) y++; + x--; + if (++nst >= fs) nst = steps; + } + while (dla * x + dlb * y >= dlc2 && y < ymax && x >= xmin) + { + scan.push_back (Pt2i (x, y)); + if (*nst) y++; + x--; + if (++nst >= fs) nst = steps; + } + return ((int) (scan.size ())); +} + + +void AdaptiveScannerO2::bindTo (int a, int b, int c) +{ + if (a < 0) + { + dla = -a; + dlb = -b; + c = -c; + } + else + { + dla = a; + dlb = b; + } + int old_b = (templ_b < 0 ? -templ_b : templ_b); + int old_n1 = templ_a + old_b; + int old_ninf = (old_b > templ_a ? old_b : templ_a); + int new_a = (a < 0 ? -a : a); + int new_b = (b < 0 ? -b : b); + int new_n1 = new_a + new_b; + int new_ninf = (new_b > new_a ? new_b : new_a); + int nu; + if (new_n1 * old_ninf > old_n1 * new_ninf) + nu = (templ_nu * new_n1) / old_n1; + else + nu = (templ_nu * new_ninf) / old_ninf; + dlc1 = c + nu / 2; + dlc2 = c - nu / 2; +} diff --git a/Expes/Testers/TestLines/DirectionalScanner/adaptivescannero2.h b/Expes/Testers/TestLines/DirectionalScanner/adaptivescannero2.h new file mode 100644 index 0000000..cdfab8f --- /dev/null +++ b/Expes/Testers/TestLines/DirectionalScanner/adaptivescannero2.h @@ -0,0 +1,140 @@ +#ifndef ADAPTIVE_SCANNER_O2_H +#define ADAPTIVE_SCANNER_O2_H + +#include "directionalscanner.h" + + + +/** + * @class AdaptiveScannerO2 adaptivescannero2.h + * \brief Adaptive directional scanner for the 2nd octant. + */ +class AdaptiveScannerO2 : public DirectionalScanner +{ + +public: + + /** + * \brief Creates an adaptive DS from pattern, start and upper bound. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a start point, a line pattern, and an upper bound. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the discrete support line. + * @param b Value of parameter 'b' of the discrete support line. + * @param c Value of parameter 'c' of the upper bounding line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param sx X-coordinate of the central scan start point. + * @param sy Y-coordinate of the central scan start point. + */ + AdaptiveScannerO2 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int c, + int nbs, bool *steps, int sx, int sy); + + /** + * \brief Creates an adaptive DS from pattern, center and bounds. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a center, a line pattern, upper and lower bounds. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the discrete support line. + * @param b Value of parameter 'b' of the discrete support line. + * @param c1 Value of parameter 'c' of one of the support lines. + * @param c2 Value of parameter 'c' of the other support line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param cx X-coordinate of the central scan center. + * @param cy Y-coordinate of the central scan center. + */ + AdaptiveScannerO2 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int c1, int c2, + int nbs, bool *steps, int cx, int cy); + + /** + * \brief Creates an adaptive DS from pattern, center and length. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a center, a line pattern, and a length value. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the discrete support line. + * @param b Value of parameter 'b' of the discrete support line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param cx X-coordinate of the central scan center. + * @param cy Y-coordinate of the central scan center. + * @param length Length of a scan strip. + */ + AdaptiveScannerO2 (int xmin, int ymin, int xmax, int ymax, + int a, int b, + int nbs, bool *steps, + int cx, int cy, int length); + + /** + * \brief Returns a copy of the directional scanner. + */ + DirectionalScanner *getCopy (); + + /** + * \brief Gets the central scan in a vector. + * Adds central scan points to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int first (std::vector<Pt2i> &scan) const; + + /** + * \brief Gets the next scan on the left in a vector. + * Adds points of next left scan to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int nextOnLeft (std::vector<Pt2i> &scan); + + /** + * \brief Gets the next scan on the right in a vector. + * Adds points of next right scan to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int nextOnRight (std::vector<Pt2i> &scan); + + /** + * \brief Binds the scan strip to wrap the given digital line. + * Resets bounding lines parameters to center the scan strip on given line. + * @param a Parameter 'a' of given digital line. + * @param b Parameter 'b' of given digital line. + * @param c Parameter 'c' of given digital line. + */ + void bindTo (int a, int b, int c); + + +protected : + + /** Parameter 'a' of template support discrete line template. */ + int templ_a; + /** Parameter 'b' of template support discrete line template. */ + int templ_b; + /** Parameter 'nu' of template support discrete line template. */ + int templ_nu; + + /** Shift parameter of the lower support discrete line */ + int dlc1; + + + /** + * \brief Creates an empty adaptive directional scanner. + */ + AdaptiveScannerO2 () { } + + /** + * \brief Creates a copy of given directional scanner. + * @param ds Source directional scanner. + */ + AdaptiveScannerO2 (AdaptiveScannerO2 *ds); + +}; +#endif diff --git a/Expes/Testers/TestLines/DirectionalScanner/adaptivescannero7.cpp b/Expes/Testers/TestLines/DirectionalScanner/adaptivescannero7.cpp new file mode 100644 index 0000000..7c476ed --- /dev/null +++ b/Expes/Testers/TestLines/DirectionalScanner/adaptivescannero7.cpp @@ -0,0 +1,256 @@ +#include "adaptivescannero7.h" + + +AdaptiveScannerO7::AdaptiveScannerO7 ( + int xmin, int ymin, int xmax, int ymax, + int a, int b, int c, + int nbs, bool *steps, int sx, int sy) + : DirectionalScanner (xmin, ymin, xmax, ymax, + nbs, steps, sx, sy) +{ + this->dla = a; + this->dlb = b; + this->dlc2 = c; + this->dlc1 = a * sx + b * sy; + + this->templ_a = a; + this->templ_b = b; + this->templ_nu = this->dlc2 - this->dlc1; + + lst2 = steps; + rst2 = steps; + fs = steps + nbs; +} + + +AdaptiveScannerO7::AdaptiveScannerO7 ( + int xmin, int ymin, int xmax, int ymax, + int a, int b, int c1, int c2, + int nbs, bool *steps, int cx, int cy) + : DirectionalScanner (xmin, ymin, xmax, ymax, + nbs, steps, cx, cy) +{ + this->dla = a; + this->dlb = b; + if (c2 < c1) + { + this->dlc1 = c2; + this->dlc2 = c1; + c1 = c2; + } + else + { + this->dlc1 = c1; + this->dlc2 = c2; + } + + this->templ_a = a; + this->templ_b = b; + this->templ_nu = this->dlc2 - this->dlc1; + + // Looking for the central scan start position + bool *st = steps + nbs; + do + { + if (--st < steps) st = steps + nbs - 1; + if (*st) lcy --; + lcx --; + } + while (dla * lcx + dlb * lcy > c1); + lst2 = st; + rst2 = st; + + rcx = lcx; + rcy = lcy; + fs = steps + nbs; +} + + +AdaptiveScannerO7::AdaptiveScannerO7 ( + int xmin, int ymin, int xmax, int ymax, + int a, int b, + int nbs, bool *steps, int cx, int cy, int length) + : DirectionalScanner (xmin, ymin, xmax, ymax, + nbs, steps, cx, cy) +{ + this->dla = a; + this->dlb = b; + fs = steps + nbs; + int w_2 = (length + 1) / 2; + + // Looking for the central scan start position + bool *st = steps + nbs; + for (int i = 0; i < w_2; i++) + { + if (--st < steps) st = steps + nbs - 1; + if (*st) lcy --; + lcx --; + } + dlc1 = dla * lcx + dlb * lcy; + lst2 = st; + rst2 = st; + + // Looking for the upper leaning line + st = steps; + while (w_2-- > 0) + { + if (*st) cy++; + cx++; + if (++st >= fs) st = steps; + } + dlc2 = dla * cx + dlb * cy; + + this->templ_a = a; + this->templ_b = b; + this->templ_nu = this->dlc2 - this->dlc1; + + rcx = lcx; + rcy = lcy; +} + + +AdaptiveScannerO7::AdaptiveScannerO7 (AdaptiveScannerO7 *ds) + : DirectionalScanner (ds) +{ + templ_a = ds->templ_a; + templ_b = ds->templ_b; + templ_nu = ds->templ_nu; + dlc1 = ds->dlc1; +} + + +DirectionalScanner *AdaptiveScannerO7::getCopy () +{ + return (new AdaptiveScannerO7 (this)); +} + + +int AdaptiveScannerO7::first (std::vector<Pt2i> &scan) const +{ + int x = lcx, y = lcy; // Current position coordinates + bool *nst = lst2; // Current step in scan direction (jpts) + + while ((y < ymin || x < xmin) && dla * x + dlb * y <= dlc2) + { + if (*nst) y++; + x++; + if (++nst >= fs) nst = steps; + } + while (dla * x + dlb * y <= dlc2 && y < ymax && x < xmax) + { + scan.push_back (Pt2i (x, y)); + if (*nst) y++; + x++; + if (++nst >= fs) nst = steps; + } + return ((int) (scan.size ())); +} + + +int AdaptiveScannerO7::nextOnLeft (std::vector<Pt2i> &scan) +{ + // Prepares the next scan + if (clearance) scan.clear (); + lcy ++; + while (lcx < xmax - 1 && lcy < ymax && dla * lcx + dlb * lcy < dlc1) + { + if (*lst2) lcy ++; + lcx ++; + if (++lst2 >= fs) lst2 = steps; + } + while (lcx > xmin && lcy >= ymin && dla * lcx + dlb * lcy > dlc1) + { + if (--lst2 < steps) lst2 = steps + nbs - 1; + if (*lst2) lcy --; + lcx --; + } + + // Computes the next scan + int x = lcx; + int y = lcy; + bool *nst = lst2; + while ((y < ymin || x < xmin) && dla * x + dlb * y <= dlc2) + { + if (*nst) y++; + x++; + if (++nst >= fs) nst = steps; + } + while (dla * x + dlb * y <= dlc2 && y < ymax && x < xmax) + { + scan.push_back (Pt2i (x, y)); + if (*nst) y++; + x++; + if (++nst >= fs) nst = steps; + } + return ((int) (scan.size ())); +} + + +int AdaptiveScannerO7::nextOnRight (std::vector<Pt2i> &scan) +{ + // Prepares the next scan + if (clearance) scan.clear (); + rcy --; + // Whenever the control corridor changed + while (rcx < xmax - 1 && rcy < ymax && dla * rcx + dlb * rcy < dlc1) + { + if (*rst2) rcy ++; + rcx ++; + if (++rst2 >= fs) rst2 = steps; + } + while (rcx > xmin && rcy >= ymin && dla * rcx + dlb * rcy > dlc1) + { + if (--rst2 < steps) rst2 = steps + nbs - 1; + if (*rst2) rcy --; + rcx --; + } + + // Computes the next scan + int x = rcx; + int y = rcy; + bool *nst = rst2; + while ((y < ymin || x < xmin) && dla * x + dlb * y <= dlc2) + { + if (*nst) y++; + x++; + if (++nst == fs) nst = steps; + } + while (dla * x + dlb * y <= dlc2 && y < ymax && x < xmax) + { + scan.push_back (Pt2i (x, y)); + if (*nst) y++; + x++; + if (++nst == fs) nst = steps; + } + return ((int) (scan.size ())); +} + + +void AdaptiveScannerO7::bindTo (int a, int b, int c) +{ + if (a < 0) + { + dla = -a; + dlb = -b; + c = -c; + } + else + { + dla = a; + dlb = b; + } + int old_b = (templ_b < 0 ? -templ_b : templ_b); + int old_n1 = templ_a + old_b; + int old_ninf = (old_b > templ_a ? old_b : templ_a); + int new_a = (a < 0 ? -a : a); + int new_b = (b < 0 ? -b : b); + int new_n1 = new_a + new_b; + int new_ninf = (new_b > new_a ? new_b : new_a); + int nu; + if (new_n1 * old_ninf > old_n1 * new_ninf) + nu = (templ_nu * new_n1) / old_n1; + else + nu = (templ_nu * new_ninf) / old_ninf; + dlc1 = c - nu / 2; + dlc2 = c + nu / 2; +} diff --git a/Expes/Testers/TestLines/DirectionalScanner/adaptivescannero7.h b/Expes/Testers/TestLines/DirectionalScanner/adaptivescannero7.h new file mode 100644 index 0000000..50a6223 --- /dev/null +++ b/Expes/Testers/TestLines/DirectionalScanner/adaptivescannero7.h @@ -0,0 +1,139 @@ +#ifndef ADAPTIVE_SCANNER_O7_H +#define ADAPTIVE_SCANNER_O7_H + +#include "directionalscanner.h" + + +/** + * @class AdaptiveScannerO7 adaptivescannero7.h + * \brief Adaptive directional scanner for the 7th octant. + */ +class AdaptiveScannerO7 : public DirectionalScanner +{ + +public: + + /** + * \brief Creates an adaptive DS from pattern, start and upper bound. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a start point, a line pattern, and an upper bound. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the discrete support line. + * @param b Value of parameter 'b' of the discrete support line. + * @param c Value of parameter 'c' of the upper bounding line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param sx X-coordinate of the central scan start point. + * @param sy Y-coordinate of the central scan start point. + */ + AdaptiveScannerO7 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int c, + int nbs, bool *steps, int sx, int sy); + + /** + * \brief Creates an adaptive DS from pattern, center and bounds. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a center, a line pattern, upper and lower bounds. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the discrete support line. + * @param b Value of parameter 'b' of the discrete support line. + * @param c1 Value of parameter 'c' of one of the support lines. + * @param c2 Value of parameter 'c' of the other support line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param cx X-coordinate of the central scan center. + * @param cy Y-coordinate of the central scan center. + */ + AdaptiveScannerO7 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int c1, int c2, + int nbs, bool *steps, int cx, int cy); + + /** + * \brief Creates an adaptive DS from pattern, center and length. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a center, a line pattern, and a length value. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the discrete support line. + * @param b Value of parameter 'b' of the discrete support line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param cx X-coordinate of the central scan center. + * @param cy Y-coordinate of the central scan center. + * @param length Length of a scan strip. + */ + AdaptiveScannerO7 (int xmin, int ymin, int xmax, int ymax, + int a, int b, + int nbs, bool *steps, + int cx, int cy, int length); + + /** + * \brief Returns a copy of the directional scanner. + */ + DirectionalScanner *getCopy (); + + /** + * \brief Gets the central scan in a vector. + * Adds central scan points to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int first (std::vector<Pt2i> &scan) const; + + /** + * \brief Gets the next scan on the left in a vector. + * Adds points of next left scan to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int nextOnLeft (std::vector<Pt2i> &scan); + + /** + * \brief Gets the next scan on the right in a vector. + * Adds points of next right scan to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int nextOnRight (std::vector<Pt2i> &scan); + + /** + * \brief Binds the scan strip to wrap the given digital line. + * Resets bounding lines parameters to center the scan strip on given line. + * @param a Parameter 'a' of given digital line. + * @param b Parameter 'b' of given digital line. + * @param c Parameter 'c' of given digital line. + */ + void bindTo (int a, int b, int c); + + +protected : + + /** Parameter 'a' of template support discrete line template. */ + int templ_a; + /** Parameter 'b' of template support discrete line template. */ + int templ_b; + /** Parameter 'nu' of template support discrete line template. */ + int templ_nu; + + /** Shift parameter of the lower support discrete line */ + int dlc1; + + + /** + * \brief Creates an empty adaptive directional scanner. + */ + AdaptiveScannerO7 () { } + + /** + * \brief Creates a copy of given directional scanner. + * @param ds Source directional scanner. + */ + AdaptiveScannerO7 (AdaptiveScannerO7 *ds); + +}; +#endif diff --git a/Expes/Testers/TestLines/DirectionalScanner/adaptivescannero8.cpp b/Expes/Testers/TestLines/DirectionalScanner/adaptivescannero8.cpp new file mode 100644 index 0000000..1c838fe --- /dev/null +++ b/Expes/Testers/TestLines/DirectionalScanner/adaptivescannero8.cpp @@ -0,0 +1,262 @@ +#include "adaptivescannero8.h" + + +AdaptiveScannerO8::AdaptiveScannerO8 ( + int xmin, int ymin, int xmax, int ymax, + int a, int b, int c, + int nbs, bool *steps, int sx, int sy) + : DirectionalScanner (xmin, ymin, xmax, ymax, + nbs, steps, sx, sy) +{ + this->dla = a; + this->dlb = b; + this->dlc2 = c; + this->dlc1 = a * sx + b * sy; + + this->templ_a = a; + this->templ_b = b; + this->templ_nu = this->dlc2 - this->dlc1; + + lst2 = steps; + rst2 = steps; + fs = steps + nbs; +} + + +AdaptiveScannerO8::AdaptiveScannerO8 ( + int xmin, int ymin, int xmax, int ymax, + int a, int b, int c1, int c2, + int nbs, bool *steps, int cx, int cy) + : DirectionalScanner (xmin, ymin, xmax, ymax, + nbs, steps, cx, cy) +{ + this->dla = a; + this->dlb = b; + if (c2 < c1) + { + this->dlc1 = c2; + this->dlc2 = c1; + c1 = c2; + } + else + { + this->dlc1 = c1; + this->dlc2 = c2; + } + + this->templ_a = a; + this->templ_b = b; + this->templ_nu = this->dlc2 - this->dlc1; + + // Looking for the central scan start position + bool *st = steps + nbs; + do + { + if (--st < steps) st = steps + nbs - 1; + if (*st) lcx --; + lcy --; + } + while (dla * lcx + dlb * lcy > c1); + lst2 = st; + rst2 = st; + + rcx = lcx; + rcy = lcy; + fs = steps + nbs; +} + + +AdaptiveScannerO8::AdaptiveScannerO8 ( + int xmin, int ymin, int xmax, int ymax, + int a, int b, + int nbs, bool *steps, int cx, int cy, int length) + : DirectionalScanner (xmin, ymin, xmax, ymax, + nbs, steps, cx, cy) +{ + this->dla = a; + this->dlb = b; + fs = steps + nbs; + int w_2 = (length + 1) / 2; + + // Looking for the central scan start position + bool *st = steps + nbs; + for (int i = 0; i < w_2; i++) + { + if (--st < steps) st = steps + nbs - 1; + if (*st) lcx --; + lcy --; + } + dlc1 = dla * lcx + dlb * lcy; + lst2 = st; + rst2 = st; + + // Looking for the upper leaning line + st = steps; + while (w_2-- > 0) + { + if (*st) cx++; + cy++; + if (++st >= fs) st = steps; + } + dlc2 = dla * cx + dlb * cy; + + this->templ_a = a; + this->templ_b = b; + this->templ_nu = this->dlc2 - this->dlc1; + + rcx = lcx; + rcy = lcy; +} + + +AdaptiveScannerO8::AdaptiveScannerO8 (AdaptiveScannerO8 *ds) + : DirectionalScanner (ds) +{ + templ_a = ds->templ_a; + templ_b = ds->templ_b; + templ_nu = ds->templ_nu; + dlc1 = ds->dlc1; +} + + +DirectionalScanner *AdaptiveScannerO8::getCopy () +{ + return (new AdaptiveScannerO8 (this)); +} + + +int AdaptiveScannerO8::first (std::vector<Pt2i> &scan) const +{ + int x = lcx, y = lcy; // Current position coordinates + bool *nst = lst2; // Current step in scan direction (jpts) + + while ((x < xmin || y < ymin) && dla * x + dlb * y <= dlc2) + { + if (*nst) x++; + y++; + if (++nst >= fs) nst = steps; + } + while (dla * x + dlb * y <= dlc2 && x < xmax && y < ymax) + { + scan.push_back (Pt2i (x, y)); + if (*nst) x++; + y++; + if (++nst >= fs) nst = steps; + } + return ((int) (scan.size ())); +} + + +int AdaptiveScannerO8::nextOnLeft (std::vector<Pt2i> &scan) +{ + // Prepares the next scan + if (clearance) scan.clear (); + lcx --; + while (lcy < ymax - 1 && lcx < xmax && dla * lcx + dlb * lcy < dlc1) + { + if (*lst2) lcx ++; + lcy ++; + if (++lst2 >= fs) lst2 = steps; + } + while (lcy > ymin && lcx >= xmin && dla * lcx + dlb * lcy > dlc1) + { + if (--lst2 < steps) lst2 = steps + nbs - 1; + if (*lst2) lcx --; + lcy --; + } + + // Computes the next scan + int x = lcx; + int y = lcy; + bool *nst = lst2; + while ((x < xmin || y < ymin) && dla * x + dlb * y <= dlc2) + { + if (*nst) x++; + y++; + if (++nst >= fs) nst = steps; + } + while (dla * x + dlb * y <= dlc2 && x < xmax && y < ymax) + { + scan.push_back (Pt2i (x, y)); + if (*nst) x++; + y++; + if (++nst >= fs) nst = steps; + } + return ((int) (scan.size ())); +} + + +int AdaptiveScannerO8::nextOnRight (std::vector<Pt2i> &scan) +{ + // Prepares the next scan + if (clearance) scan.clear (); + rcx ++; + // Whenever the control corridor changed + while (rcy < ymax - 1 && rcx < xmax && dla * rcx + dlb * rcy < dlc1) + { + if (*rst2) rcx ++; + rcy ++; + if (++rst2 >= fs) rst2 = steps; + } + while (rcy > ymin && rcx >= xmin && dla * rcx + dlb * rcy > dlc1) + { + if (--rst2 < steps) rst2 = steps + nbs - 1; + if (*rst2) rcx --; + rcy --; + } + + // Computes the next scan + int x = rcx; + int y = rcy; + bool *nst = rst2; + while ((x < xmin || y < ymin) && dla * x + dlb * y <= dlc2) + { + if (*nst) x++; + y++; + if (++nst >= fs) nst = steps; + } + while (dla * x + dlb * y <= dlc2 && x < xmax && y < ymax) + { + scan.push_back (Pt2i (x, y)); + if (*nst) x++; + y++; + if (++nst >= fs) nst = steps; + } + return ((int) (scan.size ())); +} + + +void AdaptiveScannerO8::bindTo (int a, int b, int c) +{ + if (a < 0) + { + dla = -a; + dlb = -b; + c = -c; + } + else + { + dla = a; + dlb = b; + } + int old_b = (templ_b < 0 ? -templ_b : templ_b); + int old_n1 = templ_a + old_b; + int old_ninf = (old_b > templ_a ? old_b : templ_a); + int new_a = (a < 0 ? -a : a); + int new_b = (b < 0 ? -b : b); + int new_n1 = new_a + new_b; + int new_ninf = (new_b > new_a ? new_b : new_a); + int nu; + if (new_n1 * old_ninf > old_n1 * new_ninf) + nu = (templ_nu * new_n1) / old_n1; + else + nu = (templ_nu * new_ninf) / old_ninf; + if (dlb < 0) // dlb should stay positive to avoid the direction change + { // of the support line inequations. + dla = -dla; + dlb = -dlb; + c = -c; + } + dlc1 = c - nu / 2; + dlc2 = c + nu / 2; +} diff --git a/Expes/Testers/TestLines/DirectionalScanner/adaptivescannero8.h b/Expes/Testers/TestLines/DirectionalScanner/adaptivescannero8.h new file mode 100644 index 0000000..fcb196c --- /dev/null +++ b/Expes/Testers/TestLines/DirectionalScanner/adaptivescannero8.h @@ -0,0 +1,138 @@ +#ifndef ADAPTIVE_SCANNER_O8_H +#define ADAPTIVE_SCANNER_O8_H + +#include "directionalscanner.h" + + +/** + * @class AdaptiveScannerO8 adaptivescannero8.h + * \brief Adaptive directional scanner for the 8th octant. + */ +class AdaptiveScannerO8 : public DirectionalScanner +{ +public: + + /** + * \brief Creates an adaptive DS from pattern, start and upper bound. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a start point, a line pattern, and an upper bound. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the discrete support line. + * @param b Value of parameter 'b' of the discrete support line. + * @param c Value of parameter 'c' of the upper bounding line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param sx X-coordinate of the central scan start point. + * @param sy Y-coordinate of the central scan start point. + */ + AdaptiveScannerO8 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int c, + int nbs, bool *steps, int sx, int sy); + + /** + * \brief Creates an adaptive DS from pattern, center and bounds. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a center, a line pattern, upper and lower bounds. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the discrete support line. + * @param b Value of parameter 'b' of the discrete support line. + * @param c1 Value of parameter 'c' of one of the support lines. + * @param c2 Value of parameter 'c' of the other support line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param cx X-coordinate of the central scan center. + * @param cy Y-coordinate of the central scan center. + */ + AdaptiveScannerO8 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int c1, int c2, + int nbs, bool *steps, int cx, int cy); + + /** + * \brief Creates an adaptive DS from pattern, center and length. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a center, a line pattern, and a length value. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the discrete support line. + * @param b Value of parameter 'b' of the discrete support line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param cx X-coordinate of the central scan center. + * @param cy Y-coordinate of the central scan center. + * @param length Length of a scan strip. + */ + AdaptiveScannerO8 (int xmin, int ymin, int xmax, int ymax, + int a, int b, + int nbs, bool *steps, + int cx, int cy, int length); + + /** + * \brief Returns a copy of the directional scanner. + */ + DirectionalScanner *getCopy (); + + /** + * \brief Gets the central scan in a vector. + * Adds central scan points to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int first (std::vector<Pt2i> &scan) const; + + /** + * \brief Gets the next scan on the left in a vector. + * Adds points of next left scan to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int nextOnLeft (std::vector<Pt2i> &scan); + + /** + * \brief Gets the next scan on the right in a vector. + * Adds points of next right scan to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int nextOnRight (std::vector<Pt2i> &scan); + + /** + * \brief Binds the scan strip to wrap the given digital line. + * Resets bounding lines parameters to center the scan strip on given line. + * @param a Parameter 'a' of given digital line. + * @param b Parameter 'b' of given digital line. + * @param c Parameter 'c' of given digital line. + */ + void bindTo (int a, int b, int c); + + +protected : + + /** Parameter 'a' of template support discrete line template. */ + int templ_a; + /** Parameter 'b' of template support discrete line template. */ + int templ_b; + /** Parameter 'nu' of template support discrete line template. */ + int templ_nu; + + /** Shift parameter of the discrete lower support line */ + int dlc1; + + + /** + * \brief Creates an empty adaptive directional scanner. + */ + AdaptiveScannerO8 () { } + + /** + * \brief Creates a copy of given directional scanner. + * @param ds Source directional scanner. + */ + AdaptiveScannerO8 (AdaptiveScannerO8 *ds); + +}; +#endif diff --git a/Expes/Testers/TestLines/DirectionalScanner/changed b/Expes/Testers/TestLines/DirectionalScanner/changed new file mode 100644 index 0000000..41fc5a2 --- /dev/null +++ b/Expes/Testers/TestLines/DirectionalScanner/changed @@ -0,0 +1,28 @@ +cmp directionalscanner.cpp ~/git/2019-FBSD/Code/FBSD/DirectionalScanner/directionalscanner.cpp +cmp directionalscanner.h ~/git/2019-FBSD/Code/FBSD/DirectionalScanner/directionalscanner.h +cmp directionalscannero1.cpp ~/git/2019-FBSD/Code/FBSD/DirectionalScanner/directionalscannero1.cpp +cmp directionalscannero1.h ~/git/2019-FBSD/Code/FBSD/DirectionalScanner/directionalscannero1.h +cmp directionalscannero2.cpp ~/git/2019-FBSD/Code/FBSD/DirectionalScanner/directionalscannero2.cpp +cmp directionalscannero2.h ~/git/2019-FBSD/Code/FBSD/DirectionalScanner/directionalscannero2.h +cmp directionalscannero7.cpp ~/git/2019-FBSD/Code/FBSD/DirectionalScanner/directionalscannero7.cpp +cmp directionalscannero7.h ~/git/2019-FBSD/Code/FBSD/DirectionalScanner/directionalscannero7.h +cmp directionalscannero8.cpp ~/git/2019-FBSD/Code/FBSD/DirectionalScanner/directionalscannero8.cpp +cmp directionalscannero8.h ~/git/2019-FBSD/Code/FBSD/DirectionalScanner/directionalscannero8.h +cmp adaptivescannero1.cpp ~/git/2019-FBSD/Code/FBSD/DirectionalScanner/adaptivescannero1.cpp +cmp adaptivescannero1.h ~/git/2019-FBSD/Code/FBSD/DirectionalScanner/adaptivescannero1.h +cmp adaptivescannero2.cpp ~/git/2019-FBSD/Code/FBSD/DirectionalScanner/adaptivescannero2.cpp +cmp adaptivescannero2.h ~/git/2019-FBSD/Code/FBSD/DirectionalScanner/adaptivescannero2.h +cmp adaptivescannero7.cpp ~/git/2019-FBSD/Code/FBSD/DirectionalScanner/adaptivescannero7.cpp +cmp adaptivescannero7.h ~/git/2019-FBSD/Code/FBSD/DirectionalScanner/adaptivescannero7.h +cmp adaptivescannero8.cpp ~/git/2019-FBSD/Code/FBSD/DirectionalScanner/adaptivescannero8.cpp +cmp adaptivescannero8.h ~/git/2019-FBSD/Code/FBSD/DirectionalScanner/adaptivescannero8.h +cmp scannerprovider.cpp ~/git/2019-FBSD/Code/FBSD/DirectionalScanner/scannerprovider.cpp +cmp scannerprovider.h ~/git/2019-FBSD/Code/FBSD/DirectionalScanner/scannerprovider.h +cmp vhscannero1.cpp ~/git/2019-FBSD/Code/FBSD/DirectionalScanner/vhscannero1.cpp +cmp vhscannero1.h ~/git/2019-FBSD/Code/FBSD/DirectionalScanner/vhscannero1.h +cmp vhscannero2.cpp ~/git/2019-FBSD/Code/FBSD/DirectionalScanner/vhscannero2.cpp +cmp vhscannero2.h ~/git/2019-FBSD/Code/FBSD/DirectionalScanner/vhscannero2.h +cmp vhscannero7.cpp ~/git/2019-FBSD/Code/FBSD/DirectionalScanner/vhscannero7.cpp +cmp vhscannero7.h ~/git/2019-FBSD/Code/FBSD/DirectionalScanner/vhscannero7.h +cmp vhscannero8.cpp ~/git/2019-FBSD/Code/FBSD/DirectionalScanner/vhscannero8.cpp +cmp vhscannero8.h ~/git/2019-FBSD/Code/FBSD/DirectionalScanner/vhscannero8.h diff --git a/Expes/Testers/TestLines/DirectionalScanner/directionalscanner.cpp b/Expes/Testers/TestLines/DirectionalScanner/directionalscanner.cpp new file mode 100644 index 0000000..a056e18 --- /dev/null +++ b/Expes/Testers/TestLines/DirectionalScanner/directionalscanner.cpp @@ -0,0 +1,22 @@ +#include "directionalscanner.h" + + +DirectionalScanner::~DirectionalScanner () +{ + if (steps != NULL) delete steps; + steps = NULL; +} + + +void DirectionalScanner::bindTo (int a, int b, int c) +{ + (void) a; + (void) b; + (void) c; +} + + +Pt2i DirectionalScanner::locate (const Pt2i & pt) const +{ + return (Pt2i (pt)); +} diff --git a/Expes/Testers/TestLines/DirectionalScanner/directionalscanner.h b/Expes/Testers/TestLines/DirectionalScanner/directionalscanner.h new file mode 100644 index 0000000..6ee1b13 --- /dev/null +++ b/Expes/Testers/TestLines/DirectionalScanner/directionalscanner.h @@ -0,0 +1,157 @@ +#ifndef DIRECTIONAL_SCANNER_H +#define DIRECTIONAL_SCANNER_H + +#include "pt2i.h" + + +/** + * @class DirectionalScanner directionalscanner.h + * \brief Incremental directional scanner. + * This scanner iterately provides parallel scan lines. + */ +class DirectionalScanner +{ +public: + + /** + * \brief Deletes the directional scanner. + */ + virtual ~DirectionalScanner (); + + /** + * \brief Returns a copy of the directional scanner. + */ + virtual DirectionalScanner *getCopy () = 0; + + /** + * \brief Gets the central scan in a vector. + * Adds central scan points to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + virtual int first (std::vector<Pt2i> &scan) const = 0; + + /** + * \brief Gets next scan on the left in a vector. + * Adds points of next left scan to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + virtual int nextOnLeft (std::vector<Pt2i> &scan) = 0; + + /** + * \brief Gets next scan on the right in a vector. + * Adds points of next right scan to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + virtual int nextOnRight (std::vector<Pt2i> &scan) = 0; + + /** + * \brief Binds the scan strip to wrap the given digital line. + * Resets bounding lines parameters to center the scan strip on given line. + * @param a New value for the 'a' parameter of current scan strip. + * @param b New value for the 'b' parameter of current scan strip. + * @param c New value for the 'c' parameter of current scan strip. + */ + virtual void bindTo (int a, int b, int c); + + /** + * \brief Returns the scanner coordinates of given point. + * Scanner coordinates are the scan index and the position in the scan. + * @param pt Image coordinates of the point. + */ + virtual Pt2i locate (const Pt2i &pt) const; + + /** + * \brief Releases clearance status of output vector before filling. + */ + inline void releaseClearance () { clearance = false; } + + +protected: + + /** Scanable area left border. */ + int xmin; + /** Scanable area bottom border. */ + int ymin; + /** Scanable area right border. */ + int xmax; + /** Scanable area top border. */ + int ymax; + + /** Parameter 'a' of the upper support discrete line. */ + int dla; + /** Parameter 'b' of the upper support discrete line. */ + int dlb; + /** Parameter 'c' of the upper support discrete line. */ + int dlc2; + + /** Size of the discrete line pattern. */ + int nbs; + + /** Discrete line pattern. */ + bool *steps; + /** Pointer to the end of discrete line pattern. */ + bool *fs; + + /** X-start position of central scan (still used in locate (Pt2i)). */ + int ccx; + /** Y-start position of central scan (still used in locate (Pt2i)). */ + int ccy; + /** X-start position of last scan to the left. */ + int lcx; + /** Y-start position of last scan to the left. */ + int lcy; + /** X-start position of last scan to the right. */ + int rcx; + /** Y-start position of last scan to the right. */ + int rcy; + + /** Current pattern step in scan line direction for left scans. */ + bool *lst2; + /** Current pattern step in scan line direction for right scans. */ + bool *rst2; + + /** Flag indicating if the output vector should be cleared before filling. + Set to true by default. */ + bool clearance; + + + /** + * \brief Creates an empty directional scanner. + */ + DirectionalScanner () { } + + /** + * \brief Creates an incremental directional scanner. + * Creates a directional scanner from pattern and start. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a start point and a line pattern. + * @param xmini Left border of the scan area. + * @param ymini Bottom border of the scan area. + * @param xmaxi Right border of the scan area. + * @param ymaxi Top border of the scan area. + * @param nb Size of the support line pattern. + * @param st Support line pattern. + * @param sx X-coordinate of the central scan start point. + * @param sy Y-coordinate of the central scan start point. + */ + DirectionalScanner (int xmini, int ymini, int xmaxi, int ymaxi, + int nb, bool* st, int sx, int sy) + : xmin (xmini), ymin (ymini), xmax (xmaxi), ymax (ymaxi), + nbs (nb), steps (st), + ccx (sx), ccy (sy), lcx (sx), lcy (sy), rcx (sx), rcy (sy), + clearance (true) { } + + /** + * \brief Creates a copy of given directional scanner. + * @param ds Source directional scanner. + */ + DirectionalScanner (DirectionalScanner *ds) + : xmin (ds->xmin), ymin (ds->ymin), xmax (ds->xmax), ymax (ds->ymax), + dla (ds->dla), dlb (ds->dlb), dlc2 (ds->dlc2), + nbs (ds->nbs), steps (ds->steps), fs (ds->fs), + ccx (ds->ccx), ccy (ds->ccy), + lcx (ds->lcx), lcy (ds->lcy), rcx (ds->rcx), rcy (ds->rcy), + lst2 (ds->lst2), rst2 (ds->rst2), clearance (ds->clearance) { } + +}; +#endif diff --git a/Expes/Testers/TestLines/DirectionalScanner/directionalscannero1.cpp b/Expes/Testers/TestLines/DirectionalScanner/directionalscannero1.cpp new file mode 100644 index 0000000..c4a634f --- /dev/null +++ b/Expes/Testers/TestLines/DirectionalScanner/directionalscannero1.cpp @@ -0,0 +1,337 @@ +#include "directionalscannero1.h" + + +DirectionalScannerO1::DirectionalScannerO1 ( + int xmin, int ymin, int xmax, int ymax, + int a, int b, int c, + int nbs, bool *steps, int sx, int sy) + : DirectionalScanner (xmin, ymin, xmax, ymax, + nbs, steps, sx, sy) +{ + this->dla = a; + this->dlb = b; + this->dlc2 = c; + + lst1 = steps; + rst1 = steps; + lst2 = steps; + rst2 = steps; + fs = steps + nbs; + lstop = false; + rstop = false; +} + + +DirectionalScannerO1::DirectionalScannerO1 ( + int xmin, int ymin, int xmax, int ymax, + int a, int b, int c1, int c2, + int nbs, bool *steps, int cx, int cy) + : DirectionalScanner (xmin, ymin, xmax, ymax, + nbs, steps, cx, cy) +{ + this->dla = a; + this->dlb = b; + if (c2 > c1) + { + this->dlc2 = c1; + c1 = c2; + } + else this->dlc2 = c2; + + // Looking for the central scan start position + bool *st = steps + nbs; + do + { + if (--st < steps) st = steps + nbs - 1; + if (*st) lcx ++; + lcy --; + } + while (dla * lcx + dlb * lcy < c1); + lst2 = st; + rst2 = st; + + rcx = lcx; + rcy = lcy; +/** ZZZ */ +ccx = lcx; +ccy = lcy; +/** ZZZ */ + lst1 = steps; + rst1 = steps; + fs = steps + nbs; + lstop = false; + rstop = false; +} + + +DirectionalScannerO1::DirectionalScannerO1 ( + int xmin, int ymin, int xmax, int ymax, + int a, int b, + int nbs, bool *steps, int cx, int cy, int length) + : DirectionalScanner (xmin, ymin, xmax, ymax, + nbs, steps, cx, cy) +{ + this->dla = a; + this->dlb = b; + fs = steps + nbs; + int w_2 = (length + 1) / 2; + + // Looking for the central scan start position + bool *st = steps + nbs; + for (int i = 0; i < w_2; i++) + { + if (--st < steps) st = steps + nbs - 1; + if (*st) lcx ++; + lcy --; + } + lst2 = st; + rst2 = st; + + // Looking for the upper leaning line + st = steps; + while (w_2-- > 0) + { + if (*st) cx--; + cy++; + if (++st >= fs) st = steps; + } + dlc2 = dla * cx + dlb * cy; + + rcx = lcx; + rcy = lcy; +/** ZZZ */ +ccx = lcx; +ccy = lcy; +/** ZZZ */ + lst1 = steps; + rst1 = steps; + lstop = false; + rstop = false; +} + + +DirectionalScannerO1::DirectionalScannerO1 (DirectionalScannerO1 *ds) + : DirectionalScanner (ds) +{ + lst1 = ds->lst1; + rst1 = ds->rst1; + lstop = ds->lstop; + rstop = ds->rstop; +} + + +DirectionalScanner *DirectionalScannerO1::getCopy () +{ + return (new DirectionalScannerO1 (this)); +} + + +int DirectionalScannerO1::first (std::vector<Pt2i> &scan) const +{ + int x = lcx, y = lcy; // Current position coordinates + bool *nst = lst2; // Current step in scan direction (jpts) + + while ((x >= xmax || y < ymin) && dla * x + dlb * y >= dlc2) + { + if (*nst) x--; + y++; + if (++nst >= fs) nst = steps; + } + while (dla * x + dlb * y >= dlc2 && x >= xmin && y < ymax) + { + scan.push_back (Pt2i (x, y)); + if (*nst) x--; + y++; + if (++nst >= fs) nst = steps; + } + return ((int) (scan.size ())); +} + + +int DirectionalScannerO1::nextOnLeft (std::vector<Pt2i> &scan) +{ + // Prepares the next scan + if (clearance) scan.clear (); + if (lstop) + { + lcy --; + if (--lst2 < steps) lst2 = fs - 1; + lstop = false; + } + else + { + if (--lst1 < steps) lst1 = fs - 1; + lcx --; + if (*lst1) + { + lcy --; + if (--lst2 < steps) lst2 = fs - 1; + if (*lst2) + { + if (++lst2 >= fs) lst2 = steps; + lcy ++; + lstop = true; + } + } + } + + // Computes the next scan + int x = lcx; + int y = lcy; + bool *nst = lst2; + while ((x >= xmax || y < ymin) && dla * x + dlb * y >= dlc2) + { + if (*nst) x--; + y++; + if (++nst >= fs) nst = steps; + } + while (dla * x + dlb * y >= dlc2 && x >= xmin && y < ymax) + { + scan.push_back (Pt2i (x, y)); + if (*nst) x--; + y++; + if (++nst >= fs) nst = steps; + } + return ((int) (scan.size ())); +} + + +int DirectionalScannerO1::nextOnRight (std::vector<Pt2i> &scan) +{ + // Prepares the next scan + if (clearance) scan.clear (); + if (rstop) + { + rcx ++; + rstop = false; + } + else + { + rcx ++; + if (*rst1) + { + if (*rst2) + { + rcx --; + rstop = true; + } + rcy ++; + if (++rst2 >= fs) rst2 = steps; + } + if (++rst1 >= fs) rst1 = steps; + } + + // Computes the next scan + int x = rcx; + int y = rcy; + bool *nst = rst2; + while ((x >= xmax || y < ymin) && dla * x + dlb * y >= dlc2) + { + if (*nst) x--; + y++; + if (++nst >= fs) nst = steps; + } + while (dla * x + dlb * y >= dlc2 && x >= xmin && y < ymax) + { + scan.push_back (Pt2i (x, y)); + if (*nst) x--; + y++; + if (++nst >= fs) nst = steps; + } + return ((int) (scan.size ())); +} + + +Pt2i DirectionalScannerO1::locate (const Pt2i &pt) const +{ + int x = ccx, y = ccy; // Current position coordinates + bool *nst = steps; // Current step in scan direction (jpts) + int cx = 0, cy = pt.y () - y; + bool *st1 = steps; + bool *st2 = steps; + + if (cy >= 0) + { + // Climbs the first scan up + while (y < pt.y ()) + { + if (*nst) x--; + y++; + if (++nst >= fs) nst = steps; + } + } + else + { + // Climbs the first scan down + while (y > pt.y ()) + { + y--; + if (--nst < steps) nst = fs -1; + if (*nst) x++; + } + } + cx = pt.x () - x; + + // Comes back to scan origin + x = ccx; + y = ccy; + int nx = cx; + bool trans = false; + while (nx != 0) + { + // Jumps leftwards along scan bound + if (cx < 0) + { + if (trans) + { + y --; + if (--st2 < steps) st2 = fs - 1; + trans = false; + } + else + { + if (--st1 < steps) st1 = fs - 1; + x --; + if (*st1) + { + y --; + if (--st2 < steps) st2 = fs - 1; + if (*st2) + { + if (++st2 >= fs) st2 = steps; + y ++; + trans = true; + } + } + } + nx ++; + } + else + // Jumps rightwards along scan bound + { + if (trans) + { + x ++; + trans = false; + } + else + { + x ++; + if (*st1) + { + if (*st2) + { + x --; + trans = true; + } + y ++; + if (++st2 >= fs) st2 = steps; + } + if (++st1 >= fs) st1 = steps; + } + nx --; + } + } + + return (Pt2i (cx, pt.y () - y)); +} diff --git a/Expes/Testers/TestLines/DirectionalScanner/directionalscannero1.h b/Expes/Testers/TestLines/DirectionalScanner/directionalscannero1.h new file mode 100644 index 0000000..f815866 --- /dev/null +++ b/Expes/Testers/TestLines/DirectionalScanner/directionalscannero1.h @@ -0,0 +1,131 @@ +#ifndef DIRECTIONAL_SCANNER_O1_H +#define DIRECTIONAL_SCANNER_O1_H + +#include "directionalscanner.h" + + +/** + * @class DirectionalScannerO1 directionalscannero1.h + * \brief Incremental directional scanner for the 1st octant. + */ +class DirectionalScannerO1 : public DirectionalScanner +{ +public: + + /** + * \brief Creates a directional scanner from pattern, start and upper bound. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a start point, a line pattern, and an upper bound. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the discrete support line. + * @param b Value of parameter 'b' of the discrete support line. + * @param c Value of parameter 'c' of the upper bounding line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param sx X-coordinate of the central scan start point. + * @param sy Y-coordinate of the central scan start point. + */ + DirectionalScannerO1 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int c, + int nbs, bool *steps, int sx, int sy); + + /** + * \brief Creates a directional scanner from pattern, center and bounds. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a center, a line pattern, upper and lower bounds. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the discrete support line. + * @param b Value of parameter 'b' of the discrete support line. + * @param c1 Value of parameter 'c' of one of the support lines. + * @param c2 Value of parameter 'c' of the other support line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param cx X-coordinate of the central scan center. + * @param cy Y-coordinate of the central scan center. + */ + DirectionalScannerO1 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int c1, int c2, + int nbs, bool *steps, int cx, int cy); + + /** + * \brief Creates a directional scanner from pattern, center and length. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a center, a line pattern, and a length value. + * @param xmin Left border of the scan area. + * @param xmax Right border of the scan area. + * @param ymin Bottom border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the discrete support line. + * @param b Value of parameter 'b' of the discrete support line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param cx X-coordinate of the central scan center. + * @param cy Y-coordinate of the central scan center. + * @param length Length of a scan strip. + */ + DirectionalScannerO1 (int xmin, int ymin, int xmax, int ymax, + int a, int b, + int nbs, bool *steps, + int cx, int cy, int length); + + /** + * \brief Returns a copy of the directional scanner. + */ + DirectionalScanner *getCopy (); + + /** + * \brief Gets the central scan in a vector. + * Adds central scan points to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int first (std::vector<Pt2i> &scan) const; + + /** + * \brief Gets the next scan on the left in a vector. + * Adds points of next left scan to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int nextOnLeft (std::vector<Pt2i> &scan); + + /** + * \brief Gets the next scan on the right in a vector. + * Adds points of next right scan to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int nextOnRight (std::vector<Pt2i> &scan); + + /** + * \brief Returns the scanner coordinates of given point. + * Scanner coordinates are the scan index and the position in the scan. + * @param pt Image coordinates of the point. + */ + virtual Pt2i locate (const Pt2i &pt) const; + + +private: + + /** Current pattern step in strip direction on left side. */ + bool *lst1; + /** Current pattern step in strip direction on right side. */ + bool *rst1; + + /** Status indicating no move in strip direction on next left scan. */ + bool lstop; + /** Status indicating no move in strip direction on next right scan. */ + bool rstop; + + + /** + * \brief Creates a copy of given directional scanner. + * @param ds Source directional scanner. + */ + DirectionalScannerO1 (DirectionalScannerO1 *ds); + +}; +#endif diff --git a/Expes/Testers/TestLines/DirectionalScanner/directionalscannero2.cpp b/Expes/Testers/TestLines/DirectionalScanner/directionalscannero2.cpp new file mode 100644 index 0000000..e4164b7 --- /dev/null +++ b/Expes/Testers/TestLines/DirectionalScanner/directionalscannero2.cpp @@ -0,0 +1,335 @@ +#include "directionalscannero2.h" + + +DirectionalScannerO2::DirectionalScannerO2 ( + int xmin, int ymin, int xmax, int ymax, + int a, int b, int c, + int nbs, bool *steps, int sx, int sy) + : DirectionalScanner (xmin, ymin, xmax, ymax, + nbs, steps, sx, sy) +{ + this->dla = a; + this->dlb = b; + this->dlc2 = c; + + lst1 = steps; + rst1 = steps; + lst2 = steps; + rst2 = steps; + fs = steps + nbs; + lstop = false; + rstop = false; +} + + +DirectionalScannerO2::DirectionalScannerO2 ( + int xmin, int ymin, int xmax, int ymax, + int a, int b, int c1, int c2, + int nbs, bool *steps, int cx, int cy) + : DirectionalScanner (xmin, ymin, xmax, ymax, + nbs, steps, cx, cy) +{ + this->dla = a; + this->dlb = b; + if (c2 > c1) + { + this->dlc2 = c1; + c1 = c2; + } + else this->dlc2 = c2; + + // Looking for the central scan start position + bool *st = steps + nbs; + do + { + if (--st < steps) st = steps + nbs - 1; + if (*st) lcy --; + lcx ++; + } + while (dla * lcx + dlb * lcy < c1); + lst2 = st; + rst2 = st; + + rcx = lcx; + rcy = lcy; +/** ZZZ */ +ccx = lcx; +ccy = lcy; +/** ZZZ */ + lst1 = steps; + rst1 = steps; + fs = steps + nbs; + lstop = false; + rstop = false; +} + + +DirectionalScannerO2::DirectionalScannerO2 ( + int xmin, int ymin, int xmax, int ymax, + int a, int b, + int nbs, bool *steps, int cx, int cy, int length) + : DirectionalScanner (xmin, ymin, xmax, ymax, + nbs, steps, cx, cy) +{ + this->dla = a; + this->dlb = b; + fs = steps + nbs; + int w_2 = (length + 1) / 2; + + // Looking for the central scan start position + bool *st = steps + nbs; + for (int i = 0; i < w_2; i++) + { + if (--st < steps) st = steps + nbs - 1; + if (*st) lcy --; + lcx ++; + } + lst2 = st; + rst2 = st; + + // Looking for the upper leaning line + st = steps; + while (w_2-- > 0) + { + if (*st) cy++; + cx--; + if (++st >= fs) st = steps; + } + dlc2 = dla * cx + dlb * cy; + + rcx = lcx; + rcy = lcy; +/** ZZZ */ +ccx = lcx; +ccy = lcy; +/** ZZZ */ + lst1 = steps; + rst1 = steps; + lstop = false; + rstop = false; +} + + +DirectionalScannerO2::DirectionalScannerO2 (DirectionalScannerO2 *ds) + : DirectionalScanner (ds) +{ + lst1 = ds->lst1; + rst1 = ds->rst1; + lstop = ds->lstop; + rstop = ds->rstop; +} + + +DirectionalScanner *DirectionalScannerO2::getCopy () +{ + return (new DirectionalScannerO2 (this)); +} + + +int DirectionalScannerO2::first (std::vector<Pt2i> &scan) const +{ + int x = lcx, y = lcy; // Current position coordinates + bool *nst = lst2; // Current step in scan direction (jpts) + + while ((y < ymin || x >= xmax) && dla * x + dlb * y >= dlc2) + { + if (*nst) y++; + x--; + if (++nst >= fs) nst = steps; + } + while (dla * x + dlb * y >= dlc2 && y < ymax && x >= xmin) + { + scan.push_back (Pt2i (x, y)); + if (*nst) y++; + x--; + if (++nst >= fs) nst = steps; + } + return ((int) (scan.size ())); +} + + +int DirectionalScannerO2::nextOnLeft (std::vector<Pt2i> &scan) +{ + // Prepares the next scan + if (clearance) scan.clear (); + if (lstop) + { + lcy --; + lstop = false; + } + else + { + if (--lst1 < steps) lst1 = fs - 1; + lcy --; + if (*lst1) + { + lcx --; + if (*lst2) + { + lcy ++; + lstop = true; + } + if (++lst2 >= fs) lst2 = steps; + } + } + + // Computes the next scan + int x = lcx; + int y = lcy; + bool *nst = lst2; + while ((y < ymin || x >= xmax) && dla * x + dlb * y >= dlc2) + { + if (*nst) y++; + x--; + if (++nst >= fs) nst = steps; + } + while (dla * x + dlb * y >= dlc2 && y < ymax && x >= xmin) + { + scan.push_back (Pt2i (x, y)); + if (*nst) y++; + x--; + if (++nst >= fs) nst = steps; + } + return ((int) (scan.size ())); +} + + +int DirectionalScannerO2::nextOnRight (std::vector<Pt2i> &scan) +{ + // Prepares the next scan + if (clearance) scan.clear (); + if (rstop) + { + rcx ++; + if (--rst2 < steps) rst2 = fs - 1; + rstop = false; + } + else + { + rcy ++; + if (*rst1) + { + if (--rst2 < steps) rst2 = fs - 1; + if (*rst2) + { + if (++rst2 >= fs) rst2 = steps; + rstop = true; + } + else rcx ++; + } + if (++rst1 >= fs) rst1 = steps; + } + + // Computes the next scan + int x = rcx; + int y = rcy; + bool *nst = rst2; + while ((y < ymin || x >= xmax) && dla * x + dlb * y >= dlc2) + { + if (*nst) y++; + x--; + if (++nst >= fs) nst = steps; + } + while (dla * x + dlb * y >= dlc2 && y < ymax && x >= xmin) + { + scan.push_back (Pt2i (x, y)); + if (*nst) y++; + x--; + if (++nst >= fs) nst = steps; + } + return ((int) (scan.size ())); +} + + +Pt2i DirectionalScannerO2::locate (const Pt2i &pt) const +{ + int x = ccx, y = ccy; // Current position coordinates + bool *nst = steps; // Current step in scan direction (jpts) + int cx = pt.x () - x, cy = 0; + bool *st1 = steps; + bool *st2 = steps; + + if (cx <= 0) + { + // Climbs the first scan up + while (x > pt.x ()) + { + if (*nst) y++; + x--; + if (++nst >= fs) nst = steps; + } + } + else + { + // Climbs the first scan down + while (x < pt.x ()) + { + x++; + if (--nst < steps) nst = fs - 1; + if (*nst) y--; + } + } + cy = pt.y () - y; + + // Comes back to scan origin + x = ccx; + y = ccy; + int ny = cy; + bool trans = false; + while (ny != 0) + { + // Jumps leftwards along scan bound + if (cy < 0) + { + if (trans) + { + y --; + trans = false; + } + else + { + if (--st1 < steps) st1 = fs - 1; + y --; + if (*st1) + { + x --; + if (*st2) + { + y ++; + trans = true; + } + if (++st2 >= fs) st2 = steps; + } + } + ny ++; + } + else + // Jumps rightwards along scan bound + { + if (trans) + { + x ++; + if (--st2 < steps) st2 = fs - 1; + trans = false; + } + else + { + y ++; + if (*st1) + { + if (--st2 < steps) st2 = fs - 1; + if (*st2) + { + if (++st2 >= fs) st2 = steps; + trans = true; + } + else x ++; + } + if (++st1 >= fs) st1 = steps; + } + ny --; + } + } + + return (Pt2i (cy, x - pt.x ())); +} diff --git a/Expes/Testers/TestLines/DirectionalScanner/directionalscannero2.h b/Expes/Testers/TestLines/DirectionalScanner/directionalscannero2.h new file mode 100644 index 0000000..ab6d543 --- /dev/null +++ b/Expes/Testers/TestLines/DirectionalScanner/directionalscannero2.h @@ -0,0 +1,131 @@ +#ifndef DIRECTIONAL_SCANNER_O2_H +#define DIRECTIONAL_SCANNER_O2_H + +#include "directionalscanner.h" + + +/** + * @class DirectionalScannerO2 directionalscannero2.h + * \brief Incremental directional scanner for the 2nd octant. + */ +class DirectionalScannerO2 : public DirectionalScanner +{ +public: + + /** + * \brief Creates a directional scanner from pattern, start and upper bound. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a start point, a line pattern, and an upper bound. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'b' 'a' of the discrete support line. + * @param b Value of parameter 'b' 'b' of the discrete support line. + * @param c Value of parameter 'b' 'c' of the upper bounding line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param sx X-coordinate of the central scan start point. + * @param sy Y-coordinate of the central scan start point. + */ + DirectionalScannerO2 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int c, + int nbs, bool *steps, int sx, int sy); + + /** + * \brief Creates a directional scanner from pattern, center and bounds. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a center, a line pattern, upper and lower bounds. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the discrete support line. + * @param b Value of parameter 'b' of the discrete support line. + * @param c1 Value of parameter 'c' of one of the support lines. + * @param c2 Valur of parameter 'c' of the other support line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param cx X-coordinate of the central scan center. + * @param cy Y-coordinate of the central scan center. + */ + DirectionalScannerO2 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int c1, int c2, + int nbs, bool *steps, int cx, int cy); + + /** + * \brief Creates a directional scanner from pattern, center and length. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a center, a line pattern, and a length value. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the discrete support line. + * @param b Value of parameter 'a' of the discrete support line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param cx X-coordinate of the central scan center. + * @param cy Y-coordinate of the central scan center. + * @param length Length of a scan strip. + */ + DirectionalScannerO2 (int xmin, int ymin, int xmax, int ymax, + int a, int b, + int nbs, bool *steps, + int cx, int cy, int length); + + /** + * \brief Returns a copy of the directional scanner. + */ + DirectionalScanner *getCopy (); + + /** + * \brief Gets the central scan in a vector. + * Adds central scan points to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int first (std::vector<Pt2i> &scan) const; + + /** + * \brief Gets the next scan on the left in a vector. + * Adds points of next left scan to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int nextOnLeft (std::vector<Pt2i> &scan); + + /** + * \brief Gets the next scan on the right in a vector. + * Adds points of next right scan to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int nextOnRight (std::vector<Pt2i> &scan); + + /** + * \brief Returns the scanner coordinates of given point. + * Scanner coordinates are the scan index and the position in the scan. + * @param pt Image coordinates of the point. + */ + virtual Pt2i locate (const Pt2i &pt) const; + + +private: + + /** Current pattern step in strip direction on left side. */ + bool *lst1; + /** Current pattern step in strip direction on right side. */ + bool *rst1; + + /** Status indicating no move in strip direction on next left scan. */ + bool lstop; + /** Status indicating no move in strip direction on next right scan. */ + bool rstop; + + + /** + * \brief Creates a copy of given directional scanner. + * @param ds Source directional scanner. + */ + DirectionalScannerO2 (DirectionalScannerO2 *ds); + +}; +#endif diff --git a/Expes/Testers/TestLines/DirectionalScanner/directionalscannero7.cpp b/Expes/Testers/TestLines/DirectionalScanner/directionalscannero7.cpp new file mode 100644 index 0000000..efa3d7d --- /dev/null +++ b/Expes/Testers/TestLines/DirectionalScanner/directionalscannero7.cpp @@ -0,0 +1,335 @@ +#include "directionalscannero7.h" + + +DirectionalScannerO7::DirectionalScannerO7 ( + int xmin, int ymin, int xmax, int ymax, + int a, int b, int c, + int nbs, bool *steps, int sx, int sy) + : DirectionalScanner (xmin, ymin, xmax, ymax, + nbs, steps, sx, sy) +{ + this->dla = a; + this->dlb = b; + this->dlc2 = c; + + lst1 = steps; + rst1 = steps; + lst2 = steps; + rst2 = steps; + fs = steps + nbs; + lstop = false; + rstop = false; +} + + +DirectionalScannerO7::DirectionalScannerO7 ( + int xmin, int ymin, int xmax, int ymax, + int a, int b, int c1, int c2, + int nbs, bool *steps, int cx, int cy) + : DirectionalScanner (xmin, ymin, xmax, ymax, + nbs, steps, cx, cy) +{ + this->dla = a; + this->dlb = b; + if (c2 < c1) + { + this->dlc2 = c1; + c1 = c2; + } + else this->dlc2 = c2; + + // Looking for the central scan start position + bool *st = steps + nbs; + do + { + if (--st < steps) st = steps + nbs - 1; + if (*st) lcy --; + lcx --; + } + while (dla * lcx + dlb * lcy > c1); + lst2 = st; + rst2 = st; + + rcx = lcx; + rcy = lcy; +/** ZZZ */ +ccx = lcx; +ccy = lcy; +/** ZZZ */ + lst1 = steps; + rst1 = steps; + fs = steps + nbs; + lstop = false; + rstop = false; +} + + +DirectionalScannerO7::DirectionalScannerO7 ( + int xmin, int ymin, int xmax, int ymax, + int a, int b, + int nbs, bool *steps, int cx, int cy, int length) + : DirectionalScanner (xmin, ymin, xmax, ymax, + nbs, steps, cx, cy) +{ + this->dla = a; + this->dlb = b; + fs = steps + nbs; + int w_2 = (length + 1) / 2; + + // Looking for the central scan start position + bool *st = steps + nbs; + for (int i = 0; i < w_2; i++) + { + if (--st < steps) st = steps + nbs - 1; + if (*st) lcy --; + lcx --; + } + lst2 = st; + rst2 = st; + + // Looking for the upper leaning line + st = steps; + while (w_2-- > 0) + { + if (*st) cy++; + cx++; + if (++st >= fs) st = steps; + } + dlc2 = dla * cx + dlb * cy; + + rcx = lcx; + rcy = lcy; +/** ZZZ */ +ccx = lcx; +ccy = lcy; +/** ZZZ */ + lst1 = steps; + rst1 = steps; + lstop = false; + rstop = false; +} + + +DirectionalScannerO7::DirectionalScannerO7 (DirectionalScannerO7 *ds) + : DirectionalScanner (ds) +{ + lst1 = ds->lst1; + rst1 = ds->rst1; + lstop = ds->lstop; + rstop = ds->rstop; +} + + +DirectionalScanner *DirectionalScannerO7::getCopy () +{ + return (new DirectionalScannerO7 (this)); +} + + +int DirectionalScannerO7::first (std::vector<Pt2i> &scan) const +{ + int x = lcx, y = lcy; // Current position coordinates + bool *nst = lst2; // Current step in scan direction (jpts) + + while ((y < ymin || x < xmin) && dla * x + dlb * y <= dlc2) + { + if (*nst) y++; + x++; + if (++nst >= fs) nst = steps; + } + while (dla * x + dlb * y <= dlc2 && y < ymax && x < xmax) + { + scan.push_back (Pt2i (x, y)); + if (*nst) y++; + x++; + if (++nst >= fs) nst = steps; + } + return ((int) (scan.size ())); +} + + +int DirectionalScannerO7::nextOnLeft (std::vector<Pt2i> &scan) +{ + // Prepares the next scan + if (clearance) scan.clear (); + if (lstop) + { + lcx --; + if (--lst2 < steps) lst2 = fs - 1; + lstop = false; + } + else + { + if (--lst1 < steps) lst1 = fs - 1; + lcy ++; + if (*lst1) + { + if (--lst2 < steps) lst2 = fs - 1; + if (*lst2) + { + if (++lst2 >= fs) lst2 = steps; + lstop = true; + } + else lcx --; + } + } + + // Computes the next scan + int x = lcx; + int y = lcy; + bool *nst = lst2; + while ((y < ymin || x < xmin) && dla * x + dlb * y <= dlc2) + { + if (*nst) y++; + x++; + if (++nst >= fs) nst = steps; + } + while (dla * x + dlb * y <= dlc2 && y < ymax && x < xmax) + { + scan.push_back (Pt2i (x, y)); + if (*nst) y++; + x++; + if (++nst >= fs) nst = steps; + } + return ((int) (scan.size ())); +} + + +int DirectionalScannerO7::nextOnRight (std::vector<Pt2i> &scan) +{ + // Prepares the next scan + if (clearance) scan.clear (); + if (rstop) + { + rcy --; + rstop = false; + } + else + { + rcy --; + if (*rst1) + { + rcx ++; + if (*rst2) + { + rcy ++; + rstop = true; + } + if (++rst2 >= fs) rst2 = steps; + } + if (++rst1 >= fs) rst1 = steps; + } + + // Computes the next scan + int x = rcx; + int y = rcy; + bool *nst = rst2; + while ((y < ymin || x < xmin) && dla * x + dlb * y <= dlc2) + { + if (*nst) y++; + x++; + if (++nst == fs) nst = steps; + } + while (dla * x + dlb * y <= dlc2 && y < ymax && x < xmax) + { + scan.push_back (Pt2i (x, y)); + if (*nst) y++; + x++; + if (++nst == fs) nst = steps; + } + return ((int) (scan.size ())); +} + + +Pt2i DirectionalScannerO7::locate (const Pt2i &pt) const +{ + int x = ccx, y = ccy; // Current position coordinates + bool *nst = steps; // Current step in scan direction (jpts) + int cx = pt.x () - x, cy = 0; + bool *st1 = steps; + bool *st2 = steps; + + if (cx >= 0) + { + // Climbs the first scan up + while (x < pt.x ()) + { + if (*nst) y++; + x++; + if (++nst >= fs) nst = steps; + } + } + else + { + // Climbs the first scan down + while (x < pt.x ()) + { + x--; + if (--nst < steps) nst = fs - 1; + if (*nst) y--; + } + } + cy = y - pt.y (); + + // Comes back to scan origin + x = ccx; + y = ccy; + int ny = cy; + bool trans = false; + while (ny != 0) + { + // Jumps leftwards along scan bound + if (cy < 0) + { + if (trans) + { + y --; + trans = false; + } + else + { + if (--st1 < steps) st1 = fs - 1; + y --; + if (*st1) + { + x --; + if (*st2) + { + y ++; + trans = true; + } + if (++st2 >= fs) st2 = steps; + } + } + ny ++; + } + else + // Jumps rightwards along scan bound + { + if (trans) + { + x ++; + if (--st2 < steps) st2 = fs - 1; + trans = false; + } + else + { + y ++; + if (*st1) + { + if (--st2 < steps) st2 = fs - 1; + if (*st2) + { + if (++st2 >= fs) st2 = steps; + trans = true; + } + else x ++; + } + if (++st1 >= fs) st1 = steps; + } + ny --; + } + } + + return (Pt2i (cy, pt.x () - x)); +} diff --git a/Expes/Testers/TestLines/DirectionalScanner/directionalscannero7.h b/Expes/Testers/TestLines/DirectionalScanner/directionalscannero7.h new file mode 100644 index 0000000..9873163 --- /dev/null +++ b/Expes/Testers/TestLines/DirectionalScanner/directionalscannero7.h @@ -0,0 +1,131 @@ +#ifndef DIRECTIONAL_SCANNER_O7_H +#define DIRECTIONAL_SCANNER_O7_H + +#include "directionalscanner.h" + + +/** + * @class DirectionalScannerO7 directionalscannero7.h + * \brief Incremental directional scanner for the 7th octant. + */ +class DirectionalScannerO7 : public DirectionalScanner +{ +public: + + /** + * \brief Creates a directional scanner from pattern, start and upper bound. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a start point, a line pattern, and an upper bound. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the discrete support line. + * @param b Value of parameter 'b' of the discrete support line. + * @param c Value of parameter 'c' of the upper bounding line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param sx X-coordinate of the central scan start point. + * @param sy Y-coordinate of the central scan start point. + */ + DirectionalScannerO7 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int c, + int nbs, bool *steps, int sx, int sy); + + /** + * \brief Creates a directional scanner from pattern, center and bounds. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a center, a line pattern, upper and lower bounds. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the discrete support line. + * @param b Value of parameter 'b' of the discrete support line. + * @param c1 Value of parameter 'c' of one of the support lines. + * @param c2 Value of parameter 'c' of the other support line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param cx X-coordinate of the central scan center. + * @param cy Y-coordinate of the central scan center. + */ + DirectionalScannerO7 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int c1, int c2, + int nbs, bool *steps, int cx, int cy); + + /** + * \brief Creates a directional scanner from pattern, center and length. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a center, a line pattern, and a length. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the discrete support line. + * @param b Value of parameter 'b' of the discrete support line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param cx X-coordinate of the central scan center. + * @param cy Y-coordinate of the central scan center. + * @param length Length of a scan strip. + */ + DirectionalScannerO7 (int xmin, int ymin, int xmax, int ymax, + int a, int b, + int nbs, bool *steps, + int cx, int cy, int length); + + /** + * \brief Returns a copy of the directional scanner. + */ + DirectionalScanner *getCopy (); + + /** + * \brief Gets the central scan in a vector. + * Adds central scan points to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int first (std::vector<Pt2i> &scan) const; + + /** + * \brief Gets the next scan on the left in a vector. + * Adds points of next left scan to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int nextOnLeft (std::vector<Pt2i> &scan); + + /** + * \brief Gets the next scan on the right in a vector. + * Adds points of next right scan to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int nextOnRight (std::vector<Pt2i> &scan); + + /** + * \brief Returns the scanner coordinates of given point. + * Scanner coordinates are the scan index and the position in the scan. + * @param pt Image coordinates of the point. + */ + virtual Pt2i locate (const Pt2i &pt) const; + + +private: + + /** Current pattern step in strip direction on left side. */ + bool *lst1; + /** Current pattern step in strip direction on right side. */ + bool *rst1; + + /** Status indicating no move in strip direction on next left scan. */ + bool lstop; + /** Status indicating no move in strip direction on next right scan. */ + bool rstop; + + + /** + * \brief Creates a copy of given directional scanner. + * @param ds Source directional scanner. + */ + DirectionalScannerO7 (DirectionalScannerO7 *ds); + +}; +#endif diff --git a/Expes/Testers/TestLines/DirectionalScanner/directionalscannero8.cpp b/Expes/Testers/TestLines/DirectionalScanner/directionalscannero8.cpp new file mode 100644 index 0000000..1134235 --- /dev/null +++ b/Expes/Testers/TestLines/DirectionalScanner/directionalscannero8.cpp @@ -0,0 +1,335 @@ +#include "directionalscannero8.h" + + +DirectionalScannerO8::DirectionalScannerO8 ( + int xmin, int ymin, int xmax, int ymax, + int a, int b, int c, + int nbs, bool *steps, int sx, int sy) + : DirectionalScanner (xmin, ymin, xmax, ymax, + nbs, steps, sx, sy) +{ + this->dla = a; + this->dlb = b; + this->dlc2 = c; + + lst1 = steps; + rst1 = steps; + lst2 = steps; + rst2 = steps; + fs = steps + nbs; + lstop = false; + rstop = false; +} + + +DirectionalScannerO8::DirectionalScannerO8 ( + int xmin, int ymin, int xmax, int ymax, + int a, int b, int c1, int c2, + int nbs, bool *steps, int cx, int cy) + : DirectionalScanner (xmin, ymin, xmax, ymax, + nbs, steps, cx, cy) +{ + this->dla = a; + this->dlb = b; + if (c2 < c1) + { + this->dlc2 = c1; + c1 = c2; + } + else this->dlc2 = c2; + + // Looking for the central scan start position + bool *st = steps + nbs; + do + { + if (--st < steps) st = steps + nbs - 1; + if (*st) lcx --; + lcy --; + } + while (dla * lcx + dlb * lcy > c1); + lst2 = st; + rst2 = st; + + rcx = lcx; + rcy = lcy; +/** ZZZ */ +ccx = lcx; +ccy = lcy; +/** ZZZ */ + lst1 = steps; + rst1 = steps; + fs = steps + nbs; + lstop = false; + rstop = false; +} + + +DirectionalScannerO8::DirectionalScannerO8 ( + int xmin, int ymin, int xmax, int ymax, + int a, int b, + int nbs, bool *steps, int cx, int cy, int length) + : DirectionalScanner (xmin, ymin, xmax, ymax, + nbs, steps, cx, cy) +{ + this->dla = a; + this->dlb = b; + fs = steps + nbs; + int w_2 = (length + 1) / 2; + + // Looking for the central scan start position + bool *st = steps + nbs; + for (int i = 0; i < w_2; i++) + { + if (--st < steps) st = steps + nbs - 1; + if (*st) lcx --; + lcy --; + } + lst2 = st; + rst2 = st; + + // Looking for the upper leaning line + st = steps; + while (w_2-- > 0) + { + if (*st) cx++; + cy++; + if (++st >= fs) st = steps; + } + dlc2 = dla * cx + dlb * cy; + + rcx = lcx; + rcy = lcy; +/** ZZZ */ +ccx = lcx; +ccy = lcy; +/** ZZZ */ + lst1 = steps; + rst1 = steps; + lstop = false; + rstop = false; +} + + +DirectionalScannerO8::DirectionalScannerO8 (DirectionalScannerO8 *ds) + : DirectionalScanner (ds) +{ + lst1 = ds->lst1; + rst1 = ds->rst1; + lstop = ds->lstop; + rstop = ds->rstop; +} + + +DirectionalScanner *DirectionalScannerO8::getCopy () +{ + return (new DirectionalScannerO8 (this)); +} + + +int DirectionalScannerO8::first (std::vector<Pt2i> &scan) const +{ + int x = lcx, y = lcy; // Current position coordinates + bool *nst = lst2; // Current step in scan direction (jpts) + + while ((x < xmin || y < ymin) && dla * x + dlb * y <= dlc2) + { + if (*nst) x++; + y++; + if (++nst >= fs) nst = steps; + } + while (dla * x + dlb * y <= dlc2 && x < xmax && y < ymax) + { + scan.push_back (Pt2i (x, y)); + if (*nst) x++; + y++; + if (++nst >= fs) nst = steps; + } + return ((int) (scan.size ())); +} + + +int DirectionalScannerO8::nextOnLeft (std::vector<Pt2i> &scan) +{ + // Prepares the next scan + if (clearance) scan.clear (); + if (lstop) + { + lcx --; + lstop = false; + } + else + { + if (--lst1 < steps) lst1 = fs - 1; + lcx --; + if (*lst1) + { + lcy ++; + if (*lst2) + { + lcx ++; + lstop = true; + } + if (++lst2 >= fs) lst2 = steps; + } + } + + // Computes the next scan + int x = lcx; + int y = lcy; + bool *nst = lst2; + while ((x < xmin || y < ymin) && dla * x + dlb * y <= dlc2) + { + if (*nst) x++; + y++; + if (++nst >= fs) nst = steps; + } + while (dla * x + dlb * y <= dlc2 && x < xmax && y < ymax) + { + scan.push_back (Pt2i (x, y)); + if (*nst) x++; + y++; + if (++nst >= fs) nst = steps; + } + return ((int) (scan.size ())); +} + + +int DirectionalScannerO8::nextOnRight (std::vector<Pt2i> &scan) +{ + // Prepares the next scan + if (clearance) scan.clear (); + if (rstop) + { + rcy --; + if (--rst2 < steps) rst2 = fs - 1; + rstop = false; + } + else + { + rcx ++; + if (*rst1) + { + if (--rst2 < steps) rst2 = fs - 1; + if (*rst2) + { + if (++rst2 >= fs) rst2 = steps; + rstop = true; + } + else rcy --; + } + if (++rst1 >= fs) rst1 = steps; + } + + // Computes the next scan + int x = rcx; + int y = rcy; + bool *nst = rst2; + while ((x < xmin || y < ymin) && dla * x + dlb * y <= dlc2) + { + if (*nst) x++; + y++; + if (++nst >= fs) nst = steps; + } + while (dla * x + dlb * y <= dlc2 && x < xmax && y < ymax) + { + scan.push_back (Pt2i (x, y)); + if (*nst) x++; + y++; + if (++nst >= fs) nst = steps; + } + return ((int) (scan.size ())); +} + + +Pt2i DirectionalScannerO8::locate (const Pt2i &pt) const +{ + int x = ccx, y = ccy; // Current position coordinates + bool *nst = steps; // Current step in scan direction (jpts) + int cx = 0, cy = pt.y () - y; + bool *st1 = steps; + bool *st2 = steps; + + if (cy >= 0) + { + // Climbs the first scan up + while (y < pt.y ()) + { + if (*nst) x++; + y++; + if (++nst >= fs) nst = steps; + } + } + else + { + // Climbs the first scan down + while (y > pt.y ()) + { + y--; + if (--nst < steps) nst = fs -1; + if (*nst) x--; + } + } + cx = pt.x () - x; + + // Comes back to scan origin + x = ccx; + y = ccy; + int nx = cx; + bool trans = false; + while (nx != 0) + { + // Jumps leftwards along scan bound + if (cx < 0) + { + if (trans) + { + x --; + trans = false; + } + else + { + if (--st1 < steps) st1 = fs - 1; + x --; + if (*st1) + { + y ++; + if (*st2) + { + x ++; + trans = true; + } + if (++st2 >= fs) st2 = steps; + } + } + nx ++; + } + else + // Jumps rightwards along scan bound + { + if (trans) + { + y --; + if (--st2 < steps) st2 = fs - 1; + trans = false; + } + else + { + x ++; + if (*st1) + { + if (--st2 < steps) st2 = fs - 1; + if (*st2) + { + if (++st2 >= fs) st2 = steps; + trans = true; + } + else y --; + } + if (++st1 >= fs) st1 = steps; + } + nx --; + } + } + + return (Pt2i (cx, pt.y () - y)); +} diff --git a/Expes/Testers/TestLines/DirectionalScanner/directionalscannero8.h b/Expes/Testers/TestLines/DirectionalScanner/directionalscannero8.h new file mode 100644 index 0000000..ff56274 --- /dev/null +++ b/Expes/Testers/TestLines/DirectionalScanner/directionalscannero8.h @@ -0,0 +1,131 @@ +#ifndef DIRECTIONAL_SCANNER_O8_H +#define DIRECTIONAL_SCANNER_O8_H + +#include "directionalscanner.h" + + +/** + * @class DirectionalScannerO8 directionalscannero8.h + * \brief Incremental directional scanner for the 8th octant. + */ +class DirectionalScannerO8 : public DirectionalScanner +{ +public: + + /** + * \brief Creates a directional scanner from pattern, start and upper bound. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a start point, a line pattern, and an upper bound. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the discrete support line. + * @param b Value of parameter 'b' of the discrete support line. + * @param c Value of parameter 'c' of the upper bounding line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param sx X-coordinate of the central scan start point. + * @param sy Y-coordinate of the central scan start point. + */ + DirectionalScannerO8 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int c, + int nbs, bool *steps, int sx, int sy); + + /** + * \brief Creates a directional scanner from pattern, center and bounds. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a center, a line pattern, upper and lower bounds. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the discrete support line. + * @param b Value of parameter 'b' of the discrete support line. + * @param c1 Value of parameter 'c' of one of the support lines. + * @param c2 Value of parameter 'c' of the other support line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param cx X-coordinate of the central scan center. + * @param cy Y-coordinate of the central scan center. + */ + DirectionalScannerO8 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int c1, int c2, + int nbs, bool *steps, int cx, int cy); + + /** + * \brief Creates a directional scanner from pattern, center and length. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a center, a line pattern, and a length value. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the discrete support line. + * @param b Value of parameter 'b' of the discrete support line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param cx X-coordinate of the central scan center. + * @param cy Y-coordinate of the central scan center. + * @param length Length of a scan strip. + */ + DirectionalScannerO8 (int xmin, int ymin, int xmax, int ymax, + int a, int b, + int nbs, bool *steps, + int cx, int cy, int length); + + /** + * \brief Returns a copy of the directional scanner. + */ + DirectionalScanner *getCopy (); + + /** + * \brief Gets the central scan in a vector. + * Adds central scan points to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int first (std::vector<Pt2i> &scan) const; + + /** + * \brief Gets the next scan on the left in a vector. + * Adds points of next left scan to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int nextOnLeft (std::vector<Pt2i> &scan); + + /** + * \brief Gets the next scan on the right in a vector. + * Adds points of next right scan to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int nextOnRight (std::vector<Pt2i> &scan); + + /** + * \brief Returns the scanner coordinates of given point. + * Scanner coordinates are the scan index and the position in the scan. + * @param pt Image coordinates of the point. + */ + virtual Pt2i locate (const Pt2i &pt) const; + + +private: + + /** Current pattern step in strip direction on left side. */ + bool *lst1; + /** Current pattern step in strip direction on right side. */ + bool *rst1; + + /** Status indicating no move in strip direction on next left scan. */ + bool lstop; + /** Status indicating no move in strip direction on next right scan. */ + bool rstop; + + + /** + * \brief Creates a copy of given directional scanner. + * @param ds Source directional scanner. + */ + DirectionalScannerO8 (DirectionalScannerO8 *ds); + +}; +#endif diff --git a/Expes/Testers/TestLines/DirectionalScanner/rechanged b/Expes/Testers/TestLines/DirectionalScanner/rechanged new file mode 100644 index 0000000..6eab3c6 --- /dev/null +++ b/Expes/Testers/TestLines/DirectionalScanner/rechanged @@ -0,0 +1,28 @@ +cmp directionalscanner.cpp ~/tmp/ASD/DirectionalScanner/directionalscanner.cpp +cmp directionalscanner.h ~/tmp/ASD/DirectionalScanner/directionalscanner.h +cmp directionalscannero1.cpp ~/tmp/ASD/DirectionalScanner/directionalscannero1.cpp +cmp directionalscannero1.h ~/tmp/ASD/DirectionalScanner/directionalscannero1.h +cmp directionalscannero2.cpp ~/tmp/ASD/DirectionalScanner/directionalscannero2.cpp +cmp directionalscannero2.h ~/tmp/ASD/DirectionalScanner/directionalscannero2.h +cmp directionalscannero7.cpp ~/tmp/ASD/DirectionalScanner/directionalscannero7.cpp +cmp directionalscannero7.h ~/tmp/ASD/DirectionalScanner/directionalscannero7.h +cmp directionalscannero8.cpp ~/tmp/ASD/DirectionalScanner/directionalscannero8.cpp +cmp directionalscannero8.h ~/tmp/ASD/DirectionalScanner/directionalscannero8.h +cmp adaptivescannero1.cpp ~/tmp/ASD/DirectionalScanner/adaptivescannero1.cpp +cmp adaptivescannero1.h ~/tmp/ASD/DirectionalScanner/adaptivescannero1.h +cmp adaptivescannero2.cpp ~/tmp/ASD/DirectionalScanner/adaptivescannero2.cpp +cmp adaptivescannero2.h ~/tmp/ASD/DirectionalScanner/adaptivescannero2.h +cmp adaptivescannero7.cpp ~/tmp/ASD/DirectionalScanner/adaptivescannero7.cpp +cmp adaptivescannero7.h ~/tmp/ASD/DirectionalScanner/adaptivescannero7.h +cmp adaptivescannero8.cpp ~/tmp/ASD/DirectionalScanner/adaptivescannero8.cpp +cmp adaptivescannero8.h ~/tmp/ASD/DirectionalScanner/adaptivescannero8.h +cmp scannerprovider.cpp ~/tmp/ASD/DirectionalScanner/scannerprovider.cpp +cmp scannerprovider.h ~/tmp/ASD/DirectionalScanner/scannerprovider.h +cmp vhscannero1.cpp ~/tmp/ASD/DirectionalScanner/vhscannero1.cpp +cmp vhscannero1.h ~/tmp/ASD/DirectionalScanner/vhscannero1.h +cmp vhscannero2.cpp ~/tmp/ASD/DirectionalScanner/vhscannero2.cpp +cmp vhscannero2.h ~/tmp/ASD/DirectionalScanner/vhscannero2.h +cmp vhscannero7.cpp ~/tmp/ASD/DirectionalScanner/vhscannero7.cpp +cmp vhscannero7.h ~/tmp/ASD/DirectionalScanner/vhscannero7.h +cmp vhscannero8.cpp ~/tmp/ASD/DirectionalScanner/vhscannero8.cpp +cmp vhscannero8.h ~/tmp/ASD/DirectionalScanner/vhscannero8.h diff --git a/Expes/Testers/TestLines/DirectionalScanner/scannerprovider.cpp b/Expes/Testers/TestLines/DirectionalScanner/scannerprovider.cpp new file mode 100644 index 0000000..8d4d492 --- /dev/null +++ b/Expes/Testers/TestLines/DirectionalScanner/scannerprovider.cpp @@ -0,0 +1,194 @@ +#include "scannerprovider.h" +#include "directionalscannero2.h" +#include "directionalscannero7.h" +#include "directionalscannero1.h" +#include "directionalscannero8.h" +#include "vhscannero2.h" +#include "vhscannero7.h" +#include "vhscannero1.h" +#include "vhscannero8.h" + + +DirectionalScanner *ScannerProvider::getScanner (Pt2i p1, Pt2i p2, + bool controlable) +{ + // Enforces P1 to be lower than P2 + // or to left of P2 in case of equality + lastScanReversed = (p1.y () > p2.y ()) + || ((p1.y () == p2.y ()) && (p1.x () > p2.x ())); + if (lastScanReversed) + { + Pt2i tmp (p1); + p1.set (p2); + p2.set (tmp); + } + + // Computes the steps position array + int nbs = 0; + bool *steps = p1.stepsTo (p2, &nbs); + + // Equation of the strip support lines : ax + by = c + int a = p2.x () - p1.x (); + int b = p2.y () - p1.y (); + if (a < 0 || (a == 0 && b < 0)) // Enforces a >= 0, then b > 0 + { + a = -a; + b = -b; + } + int c2 = a * p2.x () + b * p2.y (); + + // Builds and returns the appropriate scanner + if (b < 0) + if (-b > a) + { + if (isOrtho) + { + int repx = (p1.x () + p2.x ()) / 2; // central scan start + int repy = p1.y () - (int) ((p1.x () - repx) * (p1.x () - p2.x ()) + / (p2.y () - p1.y ())); + return (new VHScannerO1 (xmin, ymin, xmax, ymax, + a, b, c2, nbs, steps, repx, repy)); + } + else return (controlable ? + (DirectionalScanner *) + new AdaptiveScannerO1 (xmin, ymin, xmax, ymax, + a, b, c2, nbs, steps, p1.x (), p1.y ()) : + new DirectionalScannerO1 (xmin, ymin, xmax, ymax, + a, b, c2, nbs, steps, p1.x (), p1.y ())); + } + else + { + if (isOrtho) + { + int repy = (p1.y () + p2.y ()) / 2; // central scan start + int repx = p1.x () + (int) ((repy - p1.y ()) * (p2.y () - p1.y ()) + / (p1.x () - p2.x ())); + return (new VHScannerO2 (xmin, ymin, xmax, ymax, + a, b, c2, nbs, steps, repx, repy)); + } + else return (controlable ? + (DirectionalScanner *) + new AdaptiveScannerO2 (xmin, ymin, xmax, ymax, + a, b, c2, nbs, steps, p1.x (), p1.y ()) : + new DirectionalScannerO2 (xmin, ymin, xmax, ymax, + a, b, c2, nbs, steps, p1.x (), p1.y ())); + } + else + if (b > a) + { + if (isOrtho) + { + int repx = (p1.x () + p2.x ()) / 2; // central scan start + int repy = p1.y () - (int) ((repx - p1.x ()) * (p2.x () - p1.x ()) + / (p2.y () - p1.y ())); + return (new VHScannerO8 (xmin, ymin, xmax, ymax, + a, b, c2, nbs, steps, repx, repy)); + } + else return (controlable ? + (DirectionalScanner *) + new AdaptiveScannerO8 (xmin, ymin, xmax, ymax, + a, b, c2, nbs, steps, p1.x (), p1.y ()) : + new DirectionalScannerO8 (xmin, ymin, xmax, ymax, + a, b, c2, nbs, steps, p1.x (), p1.y ())); + } + else + { + if (isOrtho) + { + int repy = (p1.y () + p2.y ()) / 2; // central scan start + int repx = p1.x () - (int) ((repy - p1.y ()) * (p2.y () - p1.y ()) + / (p2.x () - p1.x ())); + return (new VHScannerO7 (xmin, ymin, xmax, ymax, + a, b, c2, nbs, steps, repx, repy)); + } + else return (controlable ? + (DirectionalScanner *) + new AdaptiveScannerO7 (xmin, ymin, xmax, ymax, + a, b, c2, nbs, steps, p1.x (), p1.y ()) : + new DirectionalScannerO7 (xmin, ymin, xmax, ymax, + a, b, c2, nbs, steps, p1.x (), p1.y ())); + } +} + + +DirectionalScanner *ScannerProvider::getScanner (Pt2i centre, Vr2i normal, + int length, bool controlable) +{ + // Gets the steps position array + int nbs = 0; + bool *steps = centre.stepsTo (Pt2i (centre.x () + normal.x (), + centre.y () + normal.y ()), &nbs); + + // Orients rightwards + int a = normal.x (); + int b = normal.y (); // as equation is (ax + by = c) + if (a < 0 || (a == 0 && b < 0)) + { + a = -a; + b = -b; + } + + // Builds and returns the appropriate scanner + if (b < 0) + if (-b > a) + return (controlable ? + (isOrtho ? + (DirectionalScanner *) + new VHScannerO1 (xmin, ymin, xmax, ymax, + a, b, nbs, steps, + centre.x (), centre.y (), length) : + (DirectionalScanner *) + new AdaptiveScannerO1 (xmin, ymin, xmax, ymax, + a, b, nbs, steps, + centre.x (), centre.y (), length)) : + (DirectionalScanner *) + new DirectionalScannerO1 (xmin, ymin, xmax, ymax, + a, b, nbs, steps, + centre.x (), centre.y (), length)); + else + return (controlable ? + (isOrtho ? + (DirectionalScanner *) + new VHScannerO2 (xmin, ymin, xmax, ymax, + a, b, nbs, steps, + centre.x (), centre.y (), length) : + (DirectionalScanner *) + new AdaptiveScannerO2 (xmin, ymin, xmax, ymax, + a, b, nbs, steps, + centre.x (), centre.y (), length)) : + (DirectionalScanner *) + new DirectionalScannerO2 (xmin, ymin, xmax, ymax, + a, b, nbs, steps, + centre.x (), centre.y (), length)); + else + if (b > a) + return (controlable ? + (isOrtho ? + (DirectionalScanner *) + new VHScannerO8 (xmin, ymin, xmax, ymax, + a, b, nbs, steps, + centre.x (), centre.y (), length) : + (DirectionalScanner *) + new AdaptiveScannerO8 (xmin, ymin, xmax, ymax, + a, b, nbs, steps, + centre.x (), centre.y (), length)) : + (DirectionalScanner *) + new DirectionalScannerO8 (xmin, ymin, xmax, ymax, + a, b, nbs, steps, + centre.x (), centre.y (), length)); + else + return (controlable ? + (isOrtho ? + (DirectionalScanner *) + new VHScannerO7 (xmin, ymin, xmax, ymax, + a, b, nbs, steps, + centre.x (), centre.y (), length) : + (DirectionalScanner *) + new AdaptiveScannerO7 (xmin, ymin, xmax, ymax, + a, b, nbs, steps, + centre.x (), centre.y (), length)) : + (DirectionalScanner *) + new DirectionalScannerO7 (xmin, ymin, xmax, ymax, + a, b, nbs, steps, + centre.x (), centre.y (), length)); +} diff --git a/Expes/Testers/TestLines/DirectionalScanner/scannerprovider.h b/Expes/Testers/TestLines/DirectionalScanner/scannerprovider.h new file mode 100644 index 0000000..f8af1f7 --- /dev/null +++ b/Expes/Testers/TestLines/DirectionalScanner/scannerprovider.h @@ -0,0 +1,93 @@ +#ifndef SCANNER_PROVIDER_H +#define SCANNER_PROVIDER_H + +#include "directionalscanner.h" + + +/** + * @class ScannerProvider scannerprovider.h + * \brief Directional scanner provider. + * Provides ad-hoc directional scanners in the relevant octant + * and according to static or dynamical control. + */ +class ScannerProvider +{ +public: + + /** + * \brief Builds a directional scanner provider. + */ + ScannerProvider () : isOrtho (false), lastScanReversed (false), + xmin (0), ymin (0), xmax (100), ymax (100) { } + + /** + * \brief Sets the scanned area size. + * @param sizex Scan area width. + * @param sizey Scan area height. + */ + void setSize (int sizex, int sizey) { + xmax = xmin + sizex; ymax = ymin + sizey; } + + /** + * \brief Sets the scanned area size. + * @param x0 Left column coordinate of the scan area. + * @param y0 Lower line coordinate of the scan area. + * @param sizex Scan area width. + * @param sizey Scan area height. + */ + void setArea (int x0, int y0, int sizex, int sizey) { + xmin = x0, ymin = y0, xmax = x0 + sizex; ymax = y0 + sizey; } + + /** + * \brief Returns a directional scanner from initial scan end points. + * Returns a directional scanner from two control points. + * The scan strip is composed of parallel scans (line segments), + * the initial one being defined by control points p1 and p2. + * @param p1 Initial scan start point. + * @param p2 Initial scan end point. + * @param controlable Control modality (true for an adaptive scanner). + */ + DirectionalScanner *getScanner (Pt2i p1, Pt2i p2, bool controlable = false); + + /** + * \brief Returns a directional scanner from scan center, vector and length. + * The scan strip is composed of parallel scans (line segments), + * the first one defined by its center, its direct vector, and its length. + * @param centre Initial scan center. + * @param normal Initial scan direct vector. + * @param length Initial scan length. + * @param controlable Control modality (true for an adaptive scanner). + */ + DirectionalScanner *getScanner (Pt2i centre, Vr2i normal, + int length, bool controlable = false); + + /** + * \brief Returns whether the currently used scan end points were permutated. + */ + inline bool isLastScanReversed () const { return lastScanReversed; } + + /** + * \brief Sets the orthogonal scanner modality. + * @param status New status for the orthogonal scanner modality. + */ + inline void setOrtho (bool status) { isOrtho = status; } + + +private: + + /** Orthogonal scanner modality. */ + bool isOrtho; + /** Last scan end points permutation modality. */ + bool lastScanReversed; + + /** Scan area lowest x coordinate. */ + int xmin; + /** Scan area lowest y coordinate. */ + int ymin; + /** Scan area highest x coordinate. */ + int xmax; + /** Scan area highest y coordinate. */ + int ymax; + +}; +#endif diff --git a/Expes/Testers/TestLines/DirectionalScanner/vhscannero1.cpp b/Expes/Testers/TestLines/DirectionalScanner/vhscannero1.cpp new file mode 100644 index 0000000..8a7c512 --- /dev/null +++ b/Expes/Testers/TestLines/DirectionalScanner/vhscannero1.cpp @@ -0,0 +1,194 @@ +#include "vhscannero1.h" + + +VHScannerO1::VHScannerO1 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int c, + int nbs, bool *steps, int sx, int sy) + : AdaptiveScannerO1 (xmin, ymin, xmax, ymax, + a, b, c, nbs, steps, sx, sy) +{ +} + + +VHScannerO1::VHScannerO1 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int c1, int c2, + int nbs, bool *steps, int cx, int cy) +{ + this->xmin = xmin; + this->xmax = xmax; + this->ymin = ymin; + this->ymax = ymax; + this->nbs = nbs; + this->steps = steps; + lcx = cx; + lcy = cy; + rcx = cx; + rcy = cy; + this->dla = a; + this->dlb = b; + if (c2 > c1) + { + this->dlc1 = c2; + this->dlc2 = c1; + c1 = c2; + } + else + { + this->dlc1 = c1; + this->dlc2 = c2; + } + + this->templ_a = a; + this->templ_b = b; + this->templ_nu = this->dlc1 - this->dlc2; + + // Looking for the central scan start position + do + { + lcy --; + } + while (dla * lcx + dlb * lcy < c1); + + rcx = lcx; + rcy = lcy; + lst2 = steps; + rst2 = steps; + fs = steps + nbs; +} + + +VHScannerO1::VHScannerO1 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int nbs, bool *steps, + int cx, int cy, int length) +{ + this->xmin = xmin; + this->xmax = xmax; + this->ymin = ymin; + this->ymax = ymax; + this->nbs = nbs; + this->steps = steps; + lcx = cx; + lcy = cy; + rcx = cx; + rcy = cy; + this->dla = a; + this->dlb = b; + fs = steps + nbs; + int w_2 = (length + 1) / 2; + + // Looking for the central scan start position + for (int i = 0; i < w_2; i++) + { + lcy --; + } + dlc1 = dla * lcx + dlb * lcy; + + // Looking for the upper leaning line + while (w_2-- > 0) + { + cy++; + } + dlc2 = dla * cx + dlb * cy; + + this->templ_a = a; + this->templ_b = b; + this->templ_nu = this->dlc1 - this->dlc2; + + rcx = lcx; + rcy = lcy; + lst2 = steps; + rst2 = steps; +} + + +VHScannerO1::VHScannerO1 (VHScannerO1 *ds) : AdaptiveScannerO1 (ds) +{ +} + + +DirectionalScanner *VHScannerO1::getCopy () +{ + return (new VHScannerO1 (this)); +} + + +int VHScannerO1::first (std::vector<Pt2i> &scan) const +{ + int x = lcx, y = lcy; // Current position coordinates + + while (y < ymin && dla * x + dlb * y >= dlc2) + { + y++; + } + while (dla * x + dlb * y >= dlc2 && y < ymax) + { + scan.push_back (Pt2i (x, y)); + y++; + } + return ((int) (scan.size ())); +} + + +int VHScannerO1::nextOnLeft (std::vector<Pt2i> &scan) +{ + // Prepares the next scan + if (clearance) scan.clear (); + lcx --; + if (lcx < xmin) return 0; + + // Whenever the control line changed + while (lcy < ymax - 1 && dla * lcx + dlb * lcy > dlc1) + { + lcy ++; + } + while (lcy > ymin && dla * lcx + dlb * lcy < dlc1) + { + lcy --; + } + + // Computes the next scan + int x = lcx; + int y = lcy; + while (y < ymin && dla * x + dlb * y >= dlc2) + { + y ++; + } + while (dla * x + dlb * y >= dlc2 && y < ymax) + { + scan.push_back (Pt2i (x, y)); + y ++; + } + return ((int) (scan.size ())); +} + + +int VHScannerO1::nextOnRight (std::vector<Pt2i> &scan) +{ + // Prepares the next scan + if (clearance) scan.clear (); + rcx ++; + if (rcx >= xmax) return 0; + + while (rcy < ymax - 1 && dla * rcx + dlb * rcy > dlc1) + { + rcy ++; + } + while (rcy > ymin && dla * rcx + dlb * rcy < dlc1) + { + rcy --; + } + + // Computes the next scan + int x = rcx; + int y = rcy; + while (y < ymin && dla * x + dlb * y >= dlc2) + { + y++; + } + while (dla * x + dlb * y >= dlc2 && y < ymax) + { + scan.push_back (Pt2i (x, y)); + y++; + } + return ((int) (scan.size ())); +} diff --git a/Expes/Testers/TestLines/DirectionalScanner/vhscannero1.h b/Expes/Testers/TestLines/DirectionalScanner/vhscannero1.h new file mode 100644 index 0000000..868dfbe --- /dev/null +++ b/Expes/Testers/TestLines/DirectionalScanner/vhscannero1.h @@ -0,0 +1,113 @@ +#ifndef VH_SCANNER_O1_H +#define VH_SCANNER_O1_H + +#include "adaptivescannero1.h" + + +/** + * @class VHScannerO1 vhscannero1.h + * \brief Vertical/horizontal adaptive DS for the 1st octant. + */ +class VHScannerO1 : public AdaptiveScannerO1 +{ +public: + + /** + * \brief Creates a vh scanner from pattern, start and upper bound. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a start point, a line pattern, and an upper bound. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the discrete support line. + * @param b Value of parameter 'b' of the discrete support line. + * @param c Value of parameter 'c' of the upper bounding line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param sx X-coordinate of the central scan start point. + * @param sy Y-coordinate of the central scan start point. + */ + VHScannerO1 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int c, + int nbs, bool *steps, int sx, int sy); + + /** + * \brief Creates a vh scanner from pattern, center and bounds. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a center, a line pattern, upper and lower bounds. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the discrete support line. + * @param b Value of parameter 'b' of the discrete support line. + * @param c1 Value of parameter 'c' of one of the support lines. + * @param c2 Value of parameter 'c' of the other support line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param cx X-coordinate of the central scan center. + * @param cy Y-coordinate of the central scan center. + */ + VHScannerO1 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int c1, int c2, + int nbs, bool *steps, int cx, int cy); + + /** + * \brief Creates a vh scanner from pattern, center and length . + * The scan strip is composed of parallel scan lines, the first one being + * defined by a center, a line pattern, and a length value. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the discrete support line. + * @param b Value of parameter 'b' of the discrete support line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param cx X-coordinate of the central scan center. + * @param cy Y-coordinate of the central scan center. + * @param length Length of a scan strip. + */ + VHScannerO1 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int nbs, bool *steps, + int cx, int cy, int length); + + /** + * \brief Returns a copy of the directional scanner. + */ + DirectionalScanner *getCopy (); + + /** + * \brief Gets the central scan in a vector. + * Adds central scan points to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int first (std::vector<Pt2i> &scan) const; + + /** + * \brief Gets the next scan on the left in a vector. + * Adds points of next left scan to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int nextOnLeft (std::vector<Pt2i> &scan); + + /** + * \brief Gets the next scan on the right in a vector. + * Adds points of next right scan to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int nextOnRight (std::vector<Pt2i> &scan); + + +private: + + /** + * \brief Creates a copy of given directional scanner. + * @param ds Source directional scanner. + */ + VHScannerO1 (VHScannerO1 *ds); + +}; + +#endif diff --git a/Expes/Testers/TestLines/DirectionalScanner/vhscannero2.cpp b/Expes/Testers/TestLines/DirectionalScanner/vhscannero2.cpp new file mode 100644 index 0000000..005b7db --- /dev/null +++ b/Expes/Testers/TestLines/DirectionalScanner/vhscannero2.cpp @@ -0,0 +1,194 @@ +#include "vhscannero2.h" + + +VHScannerO2::VHScannerO2 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int c, + int nbs, bool *steps, int sx, int sy) + : AdaptiveScannerO2 (xmin, ymin, xmax, ymax, + a, b, c, nbs, steps, sx, sy) +{ +} + + +VHScannerO2::VHScannerO2 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int c1, int c2, + int nbs, bool *steps, int cx, int cy) +{ + this->xmin = xmin; + this->xmax = xmax; + this->ymin = ymin; + this->ymax = ymax; + this->nbs = nbs; + this->steps = steps; + lcx = cx; + lcy = cy; + rcx = cx; + rcy = cy; + this->dla = a; + this->dlb = b; + if (c2 > c1) + { + this->dlc1 = c2; + this->dlc2 = c1; + c1 = c2; + } + else + { + this->dlc1 = c1; + this->dlc2 = c2; + } + + this->templ_a = a; + this->templ_b = b; + this->templ_nu = this->dlc1 - this->dlc2; + + // Looking for the central scan start position + do + { + lcx ++; + } + while (dla * lcx + dlb * lcy < c1); + + rcx = lcx; + rcy = lcy; + lst2 = steps; + rst2 = steps; + fs = steps + nbs; +} + + +VHScannerO2::VHScannerO2 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int nbs, bool *steps, + int cx, int cy, int length) +{ + this->xmin = xmin; + this->xmax = xmax; + this->ymin = ymin; + this->ymax = ymax; + this->nbs = nbs; + this->steps = steps; + lcx = cx; + lcy = cy; + rcx = cx; + rcy = cy; + this->dla = a; + this->dlb = b; + fs = steps + nbs; + int w_2 = (length + 1) / 2; + + // Looking for the central scan start position + for (int i = 0; i < w_2; i++) + { + lcx ++; + } + dlc1 = dla * lcx + dlb * lcy; + + // Looking for the upper leaning line + while (w_2-- > 0) + { + cx--; + } + dlc2 = dla * cx + dlb * cy; + + this->templ_a = a; + this->templ_b = b; + this->templ_nu = this->dlc1 - this->dlc2; + + rcx = lcx; + rcy = lcy; + lst2 = steps; + rst2 = steps; +} + + +VHScannerO2::VHScannerO2 (VHScannerO2 *ds) : AdaptiveScannerO2 (ds) +{ +} + + +DirectionalScanner *VHScannerO2::getCopy () +{ + return (new VHScannerO2 (this)); +} + + +int VHScannerO2::first (std::vector<Pt2i> &scan) const +{ + int x = lcx, y = lcy; // Current position coordinates + + while (x >= xmax && dla * x + dlb * y >= dlc2) + { + x--; + } + while (dla * x + dlb * y >= dlc2 && x >= xmin) + { + scan.push_back (Pt2i (x, y)); + x--; + } + return ((int) (scan.size ())); +} + + +int VHScannerO2::nextOnLeft (std::vector<Pt2i> &scan) +{ + // Prepares the next scan + if (clearance) scan.clear (); + lcy --; + if (lcy < ymin) return 0; + + // Whenever the control line changed + while (lcx > xmin && dla * lcx + dlb * lcy > dlc1) + { + lcx --; + } + while (lcx < xmax - 1 && dla * lcx + dlb * lcy < dlc1) + { + lcx ++; + } + + // Computes the next scan + int x = lcx; + int y = lcy; + while (x >= xmax && dla * x + dlb * y >= dlc2) + { + x--; + } + while (dla * x + dlb * y >= dlc2 && x >= xmin) + { + scan.push_back (Pt2i (x, y)); + x--; + } + return ((int) (scan.size ())); +} + + +int VHScannerO2::nextOnRight (std::vector<Pt2i> &scan) +{ + // Prepares the next scan + if (clearance) scan.clear (); + rcy ++; + if (rcy >= ymax) return 0; + + while (rcx > xmin && dla * rcx + dlb * rcy > dlc1) + { + rcx --; + } + while (rcx < xmax - 1 && dla * rcx + dlb * rcy < dlc1) + { + rcx ++; + } + + // Computes the next scan + int x = rcx; + int y = rcy; + while ((y < ymin || x >= xmax) && dla * x + dlb * y >= dlc2) + { + x--; + } + while (dla * x + dlb * y >= dlc2 && y < ymax && x >= xmin) + { + scan.push_back (Pt2i (x, y)); + x--; + } + return ((int) (scan.size ())); +} diff --git a/Expes/Testers/TestLines/DirectionalScanner/vhscannero2.h b/Expes/Testers/TestLines/DirectionalScanner/vhscannero2.h new file mode 100644 index 0000000..6012abb --- /dev/null +++ b/Expes/Testers/TestLines/DirectionalScanner/vhscannero2.h @@ -0,0 +1,113 @@ +#ifndef VH_SCANNER_O2_H +#define VH_SCANNER_O2_H + +#include "adaptivescannero2.h" + + +/** + * @class VHScannerO2 vhscannero2.h + * \brief Vertical / horizontal adpative DS for the 2nd octant. + */ +class VHScannerO2 : public AdaptiveScannerO2 +{ +public: + + /** + * \brief Creates a vh scanner from pattern, start and upper bound. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a start point, a line pattern, and an upper bound. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the discrete support line. + * @param b Value of parameter 'b' of the discrete support line. + * @param c Value of parameter 'c' of the upper bounding line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param sx X-coordinate of the central scan start point. + * @param sy Y-coordinate of the central scan start point. + */ + VHScannerO2 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int c, + int nbs, bool *steps, int sx, int sy); + + /** + * \brief Creates a vh scanner from pattern, center and bounds. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a center, a line pattern, upper and lower bounds. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the discrete support line. + * @param b Value of parameter 'b' of the discrete support line. + * @param c1 Value of parameter 'c' of one of the support lines. + * @param c2 Value of parameter 'c' of the other support line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param cx X-coordinate of the central scan center. + * @param cy Y-coordinate of the central scan center. + */ + VHScannerO2 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int c1, int c2, + int nbs, bool *steps, int cx, int cy); + + /** + * \brief Creates a vh scanner from pattern, center and length. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a center, a line pattern, and a length value. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the discrete support line. + * @param b Value of parameter 'b' of the discrete support line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param cx X-coordinate of the central scan center. + * @param cy Y-coordinate of the central scan center. + * @param length Length of a scan strip. + */ + VHScannerO2 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int nbs, bool *steps, + int cx, int cy, int length); + + /** + * \brief Returns a copy of the directional scanner. + */ + DirectionalScanner *getCopy (); + + /** + * \brief Gets the central scan in a vector. + * Adds central scan points to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int first (std::vector<Pt2i> &scan) const; + + /** + * \brief Gets the next scan on the left in a vector. + * Adds points of next left scan to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int nextOnLeft (std::vector<Pt2i> &scan); + + /** + * \brief Gets the next scan on the right in a vector. + * Adds points of next right scan to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int nextOnRight (std::vector<Pt2i> &scan); + + +private : + + /** + * \brief Creates a copy of given directional scanner. + * @param ds Source directional scanner. + */ + VHScannerO2 (VHScannerO2 *ds); + +}; + +#endif diff --git a/Expes/Testers/TestLines/DirectionalScanner/vhscannero7.cpp b/Expes/Testers/TestLines/DirectionalScanner/vhscannero7.cpp new file mode 100644 index 0000000..924bff3 --- /dev/null +++ b/Expes/Testers/TestLines/DirectionalScanner/vhscannero7.cpp @@ -0,0 +1,194 @@ +#include "vhscannero7.h" + + +VHScannerO7::VHScannerO7 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int c, + int nbs, bool *steps, int sx, int sy) + : AdaptiveScannerO7 (xmin, ymin, xmax, ymax, + a, b, c, nbs, steps, sx, sy) +{ +} + + +VHScannerO7::VHScannerO7 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int c1, int c2, + int nbs, bool *steps, int cx, int cy) +{ + this->xmin = xmin; + this->xmax = xmax; + this->ymin = ymin; + this->ymax = ymax; + this->nbs = nbs; + this->steps = steps; + lcx = cx; + lcy = cy; + rcx = cx; + rcy = cy; + this->dla = a; + this->dlb = b; + if (c2 < c1) + { + this->dlc1 = c2; + this->dlc2 = c1; + c1 = c2; + } + else + { + this->dlc1 = c1; + this->dlc2 = c2; + } + + this->templ_a = a; + this->templ_b = b; + this->templ_nu = this->dlc2 - this->dlc1; + + // Looking for the central scan start position + do + { + lcx --; + } + while (dla * lcx + dlb * lcy > c1); + + rcx = lcx; + rcy = lcy; + lst2 = steps; + rst2 = steps; + fs = steps + nbs; +} + + +VHScannerO7::VHScannerO7 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int nbs, bool *steps, + int cx, int cy, int length) +{ + this->xmin = xmin; + this->xmax = xmax; + this->ymin = ymin; + this->ymax = ymax; + this->nbs = nbs; + this->steps = steps; + lcx = cx; + lcy = cy; + rcx = cx; + rcy = cy; + this->dla = a; + this->dlb = b; + fs = steps + nbs; + int w_2 = (length + 1) / 2; + + // Looking for the central scan start position + for (int i = 0; i < w_2; i++) + { + lcx --; + } + dlc1 = dla * lcx + dlb * lcy; + + // Looking for the upper leaning line + while (w_2-- > 0) + { + cx++; + } + dlc2 = dla * cx + dlb * cy; + + this->templ_a = a; + this->templ_b = b; + this->templ_nu = this->dlc2 - this->dlc1; + + rcx = lcx; + rcy = lcy; + lst2 = steps; + rst2 = steps; +} + + +VHScannerO7::VHScannerO7 (VHScannerO7 *ds) : AdaptiveScannerO7 (ds) +{ +} + + +DirectionalScanner *VHScannerO7::getCopy () +{ + return (new VHScannerO7 (this)); +} + + +int VHScannerO7::first (std::vector<Pt2i> &scan) const +{ + int x = lcx, y = lcy; // Current position coordinates + + while (x < xmin && dla * x + dlb * y <= dlc2) + { + x++; + } + while (dla * x + dlb * y <= dlc2 && x < xmax) + { + scan.push_back (Pt2i (x, y)); + x++; + } + return ((int) (scan.size ())); +} + + +int VHScannerO7::nextOnLeft (std::vector<Pt2i> &scan) +{ + // Prepares the next scan + if (clearance) scan.clear (); + lcy ++; + if (lcy >= ymax) return 0; + + while (lcx < xmax - 1 && dla * lcx + dlb * lcy < dlc1) + { + lcx ++; + } + while (lcx > xmin && dla * lcx + dlb * lcy > dlc1) + { + lcx --; + } + + // Computes the next scan + int x = lcx; + int y = lcy; + while (x < xmin && dla * x + dlb * y <= dlc2) + { + x++; + } + while (dla * x + dlb * y <= dlc2 && x < xmax) + { + scan.push_back (Pt2i (x, y)); + x++; + } + return ((int) (scan.size ())); +} + + +int VHScannerO7::nextOnRight (std::vector<Pt2i> &scan) +{ + // Prepares the next scan + if (clearance) scan.clear (); + rcy --; + if (rcy < ymin) return 0; + + // Whenever the control corridor changed + while (rcx < xmax - 1 && dla * rcx + dlb * rcy < dlc1) + { + rcx ++; + } + while (rcx > xmin && dla * rcx + dlb * rcy > dlc1) + { + rcx --; + } + + // Computes the next scan + int x = rcx; + int y = rcy; + while (x < xmin && dla * x + dlb * y <= dlc2) + { + x++; + } + while (dla * x + dlb * y <= dlc2 && x < xmax) + { + scan.push_back (Pt2i (x, y)); + x++; + } + return ((int) (scan.size ())); +} diff --git a/Expes/Testers/TestLines/DirectionalScanner/vhscannero7.h b/Expes/Testers/TestLines/DirectionalScanner/vhscannero7.h new file mode 100644 index 0000000..3e31ddc --- /dev/null +++ b/Expes/Testers/TestLines/DirectionalScanner/vhscannero7.h @@ -0,0 +1,113 @@ +#ifndef VH_SCANNER_O7_H +#define VH_SCANNER_O7_H + +#include "adaptivescannero7.h" + + +/** + * @class VHScannerO7 vhscannero7.h + * \brief Vertical / horizontal adaptive DS for the 7th octant. + */ +class VHScannerO7 : public AdaptiveScannerO7 +{ +public: + + /** + * \brief Creates a vh scanner from pattern, start and upper bound. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a start point, a line pattern, and an upper bound. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the discrete support line. + * @param b Value of parameter 'b' of the discrete support line. + * @param c Value of parameter 'c' of the upper bounding line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param sx X-coordinate of the central scan start point. + * @param sy Y-coordinate of the central scan start point. + */ + VHScannerO7 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int c, + int nbs, bool *steps, int sx, int sy); + + /** + * \brief Creates a vh scanner from pattern, center and bounds. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a center, a line pattern, upper and lower bounds. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the discrete support line. + * @param b Value of parameter 'b' of the discrete support line. + * @param c1 Value of parameter 'c' of one of the support lines. + * @param c2 Value of parameter 'c' of the other support line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param cx X-coordinate of the central scan center. + * @param cy Y-coordinate of the central scan center. + */ + VHScannerO7 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int c1, int c2, + int nbs, bool *steps, int cx, int cy); + + /** + * \brief Creates a vh scanner from pattern, center and length. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a center, a line pattern, and a length value. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the discrete support line. + * @param b Value of parameter 'b' of the discrete support line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param cx X-coordinate of the central scan center. + * @param cy Y-coordinate of the central scan center. + * @param length Length of a scan strip. + */ + VHScannerO7 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int nbs, bool *steps, + int cx, int cy, int length); + + /** + * \brief Returns a copy of the directional scanner. + */ + DirectionalScanner *getCopy (); + + /** + * \brief Gets the central scan in a vector. + * Adds central scan points to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int first (std::vector<Pt2i> &scan) const; + + /** + * \brief Gets the next scan on the left in a vector. + * Adds points of next left scan to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int nextOnLeft (std::vector<Pt2i> &scan); + + /** + * \brief Gets the next scan on the right in a vector. + * Adds points of next right scan to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int nextOnRight (std::vector<Pt2i> &scan); + + +private : + + /** + * \brief Creates a copy of given directional scanner. + * @param ds Source directional scanner. + */ + VHScannerO7 (VHScannerO7 *ds); + +}; + +#endif diff --git a/Expes/Testers/TestLines/DirectionalScanner/vhscannero8.cpp b/Expes/Testers/TestLines/DirectionalScanner/vhscannero8.cpp new file mode 100644 index 0000000..00703b2 --- /dev/null +++ b/Expes/Testers/TestLines/DirectionalScanner/vhscannero8.cpp @@ -0,0 +1,194 @@ +#include "vhscannero8.h" + + +VHScannerO8::VHScannerO8 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int c, + int nbs, bool *steps, int sx, int sy) + : AdaptiveScannerO8 (xmin, ymin, xmax, ymax, + a, b, c, nbs, steps, sx, sy) +{ +} + + +VHScannerO8::VHScannerO8 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int c1, int c2, + int nbs, bool *steps, int cx, int cy) +{ + this->xmin = xmin; + this->xmax = xmax; + this->ymin = ymin; + this->ymax = ymax; + this->nbs = nbs; + this->steps = steps; + lcx = cx; + lcy = cy; + rcx = cx; + rcy = cy; + this->dla = a; + this->dlb = b; + if (c2 < c1) + { + this->dlc1 = c2; + this->dlc2 = c1; + c1 = c2; + } + else + { + this->dlc1 = c1; + this->dlc2 = c2; + } + + this->templ_a = a; + this->templ_b = b; + this->templ_nu = this->dlc2 - this->dlc1; + + // Looking for the central scan start position + do + { + lcy --; + } + while (dla * lcx + dlb * lcy > c1); + + rcx = lcx; + rcy = lcy; + lst2 = steps; + rst2 = steps; + fs = steps + nbs; +} + + +VHScannerO8::VHScannerO8 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int nbs, bool *steps, + int cx, int cy, int length) +{ + this->xmin = xmin; + this->xmax = xmax; + this->ymin = ymin; + this->ymax = ymax; + this->nbs = nbs; + this->steps = steps; + lcx = cx; + lcy = cy; + rcx = cx; + rcy = cy; + this->dla = a; + this->dlb = b; + fs = steps + nbs; + int w_2 = (length + 1) / 2; + + // Looking for the central scan start position + for (int i = 0; i < w_2; i++) + { + lcy --; + } + dlc1 = dla * lcx + dlb * lcy; + + // Looking for the upper leaning line + while (w_2-- > 0) + { + cy++; + } + dlc2 = dla * cx + dlb * cy; + + this->templ_a = a; + this->templ_b = b; + this->templ_nu = this->dlc2 - this->dlc1; + + rcx = lcx; + rcy = lcy; + lst2 = steps; + rst2 = steps; +} + + +VHScannerO8::VHScannerO8 (VHScannerO8 *ds) : AdaptiveScannerO8 (ds) +{ +} + + +DirectionalScanner *VHScannerO8::getCopy () +{ + return (new VHScannerO8 (this)); +} + + +int VHScannerO8::first (std::vector<Pt2i> &scan) const +{ + int x = lcx, y = lcy; // Current position coordinates + + while (y < ymin && dla * x + dlb * y <= dlc2) + { + y++; + } + while (dla * x + dlb * y <= dlc2 && y < ymax) + { + scan.push_back (Pt2i (x, y)); + y++; + } + return ((int) (scan.size ())); +} + + +int VHScannerO8::nextOnLeft (std::vector<Pt2i> &scan) +{ + // Prepares the next scan + if (clearance) scan.clear (); + lcx --; + if (lcx < xmin) return 0; + + while (lcy < ymax - 1 && dla * lcx + dlb * lcy < dlc1) + { + lcy ++; + } + while (lcy > ymin && dla * lcx + dlb * lcy > dlc1) + { + lcy --; + } + + // Computes the next scan + int x = lcx; + int y = lcy; + while (y < ymin && dla * x + dlb * y <= dlc2) + { + y++; + } + while (dla * x + dlb * y <= dlc2 && y < ymax) + { + scan.push_back (Pt2i (x, y)); + y++; + } + return ((int) (scan.size ())); +} + + +int VHScannerO8::nextOnRight (std::vector<Pt2i> &scan) +{ + // Prepares the next scan + if (clearance) scan.clear (); + rcx ++; + if (rcx >= xmax) return 0; + + // Whenever the control corridor changed + while (rcy < ymax - 1 && dla * rcx + dlb * rcy < dlc1) + { + rcy ++; + } + while (rcy > ymin && dla * rcx + dlb * rcy > dlc1) + { + rcy --; + } + + // Computes the next scan + int x = rcx; + int y = rcy; + while (y < ymin && dla * x + dlb * y <= dlc2) + { + y++; + } + while (dla * x + dlb * y <= dlc2 && y < ymax) + { + scan.push_back (Pt2i (x, y)); + y++; + } + return ((int) (scan.size ())); +} diff --git a/Expes/Testers/TestLines/DirectionalScanner/vhscannero8.h b/Expes/Testers/TestLines/DirectionalScanner/vhscannero8.h new file mode 100644 index 0000000..85fbe8d --- /dev/null +++ b/Expes/Testers/TestLines/DirectionalScanner/vhscannero8.h @@ -0,0 +1,113 @@ +#ifndef VH_SCANNER_O8_H +#define VH_SCANNER_O8_H + +#include "adaptivescannero8.h" + + +/** + * @class VHScannerO8 vhscannero8.h + * \brief Vertical / horizontal adaptive DS for the 8th octant. + */ +class VHScannerO8 : public AdaptiveScannerO8 +{ +public: + + /** + * \brief Creates a vh scanner from pattern, start and upper bound. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a start point, a line pattern, and an upper bound. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the discrete support line. + * @param b Value of parameter 'b' of the discrete support line. + * @param c Value of parameter 'c' of the upper bounding line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param sx X-coordinate of the central scan start point. + * @param sy Y-coordinate of the central scan start point. + */ + VHScannerO8 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int c, + int nbs, bool *steps, int sx, int sy); + + /** + * \brief Creates a vh scanner from pattern, center and bounds. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a center, a line pattern, upper and lower bounds. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the discrete support line. + * @param b Value of parameter 'b' of the discrete support line. + * @param c1 Value of parameter 'c' of one of the support lines. + * @param c2 Value of parameter 'c' of the other support line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param cx X-coordinate of the central scan center. + * @param cy Y-coordinate of the central scan center. + */ + VHScannerO8 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int c1, int c2, + int nbs, bool *steps, int cx, int cy); + + /** + * \brief Creates a vh scanner from pattern, center and length. + * The scan strip is composed of parallel scan lines, the first one being + * defined by a center, a line pattern, and a length value. + * @param xmin Left border of the scan area. + * @param ymin Bottom border of the scan area. + * @param xmax Right border of the scan area. + * @param ymax Top border of the scan area. + * @param a Value of parameter 'a' of the discrete support line. + * @param b Value of parameter 'a' of the discrete support line. + * @param nbs Size of the support line pattern. + * @param steps Support line pattern. + * @param cx X-coordinate of the central scan center. + * @param cy Y-coordinate of the central scan center. + * @param length Length of a scan strip. + */ + VHScannerO8 (int xmin, int ymin, int xmax, int ymax, + int a, int b, int nbs, bool *steps, + int cx, int cy, int length); + + /** + * \brief Returns a copy of the directional scanner. + */ + DirectionalScanner *getCopy (); + + /** + * \brief Gets the central scan in a vector. + * Adds central scan points to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int first (std::vector<Pt2i> &scan) const; + + /** + * \brief Gets the next scan on the left in a vector. + * Adds points of next left scan to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int nextOnLeft (std::vector<Pt2i> &scan); + + /** + * \brief Gets the next scan on the right in a vector. + * Adds points of next right scan to given vector and returns its new size. + * @param scan Vector of points to be completed. + */ + int nextOnRight (std::vector<Pt2i> &scan); + + +private : + + /** + * \brief Creates a copy of given directional scanner. + * @param ds Source directional scanner. + */ + VHScannerO8 (VHScannerO8 *ds); + +}; + +#endif diff --git a/Expes/Testers/TestLines/ImageTools/absrat.h b/Expes/Testers/TestLines/ImageTools/absrat.h new file mode 100644 index 0000000..ffdb786 --- /dev/null +++ b/Expes/Testers/TestLines/ImageTools/absrat.h @@ -0,0 +1,71 @@ +#ifndef ABSOLUTE_RATIONAL_H +#define ABSOLUTE_RATIONAL_H + + +/** + * @class AbsRat absrat.h + * \brief Absolutely useless rational type. + * + * This type is only maintained to ensure the compatibility of + * DigitalStraightSegment::naiveLine function used + * for IpolDemo and AMREL (seeds selection). + * Better not use it anymore. + */ +class AbsRat +{ +public: + + /** + * \brief Creates an rational number with null value. + */ + AbsRat () : r_num (0), r_den (1) { } + + /** + * \brief Sets a value for numerator and denominator. + * @param numerator Numerator value. + * @param denominator Denominator value. + */ + inline void set (int numerator, int denominator) { + r_num = numerator; r_den = denominator; } + + /** + * \brief Sets a integer value for the rational number. + * @param val Integer value. + */ + inline void set (int val) { r_num = val; r_den = 1; } + + /** + * \brief Deletes the rational number. + */ + ~AbsRat () { } + + /** + * \brief Returns the numerator of the rational number. + */ + inline int numerator () const { return r_num; } + + /** + * \brief Returns the denominator of the rational number. + */ + inline int denominator () const { return r_den; } + + /** + * \brief Returns the numerator of the rational number. + */ + inline int num () const { return r_num; } + + /** + * \brief Returns the denominator of the rational number. + */ + inline int den () const { return r_den; } + + +protected: + + /** Numerator of the rational number. */ + int r_num; + /** Denominator of the rational number. */ + int r_den; + +}; +#endif diff --git a/Expes/Testers/TestLines/ImageTools/changed b/Expes/Testers/TestLines/ImageTools/changed new file mode 100644 index 0000000..9697e2a --- /dev/null +++ b/Expes/Testers/TestLines/ImageTools/changed @@ -0,0 +1,13 @@ +cmp absrat.h ~/git/2019-FBSD/Code/FBSD/ImageTools/absrat.h +cmp digitalstraightline.cpp ~/git/2019-FBSD/Code/FBSD/ImageTools/digitalstraightline.cpp +cmp digitalstraightline.h ~/git/2019-FBSD/Code/FBSD/ImageTools/digitalstraightline.h +cmp digitalstraightsegment.cpp ~/git/2019-FBSD/Code/FBSD/ImageTools/digitalstraightsegment.cpp +cmp digitalstraightsegment.h ~/git/2019-FBSD/Code/FBSD/ImageTools/digitalstraightsegment.h +cmp edist.cpp ~/git/2019-FBSD/Code/FBSD/ImageTools/edist.cpp +cmp edist.h ~/git/2019-FBSD/Code/FBSD/ImageTools/edist.h +cmp pt2i.cpp ~/git/2019-FBSD/Code/FBSD/ImageTools/pt2i.cpp +cmp pt2i.h ~/git/2019-FBSD/Code/FBSD/ImageTools/pt2i.h +cmp vmap.cpp ~/git/2019-FBSD/Code/FBSD/ImageTools/vmap.cpp +cmp vmap.h ~/git/2019-FBSD/Code/FBSD/ImageTools/vmap.h +cmp vr2i.cpp ~/git/2019-FBSD/Code/FBSD/ImageTools/vr2i.cpp +cmp vr2i.h ~/git/2019-FBSD/Code/FBSD/ImageTools/vr2i.h diff --git a/Expes/Testers/TestLines/ImageTools/digitalstraightline.cpp b/Expes/Testers/TestLines/ImageTools/digitalstraightline.cpp new file mode 100644 index 0000000..c7dede7 --- /dev/null +++ b/Expes/Testers/TestLines/ImageTools/digitalstraightline.cpp @@ -0,0 +1,460 @@ +#include "digitalstraightline.h" + +const int DigitalStraightLine::DSL_THIN = 1; +const int DigitalStraightLine::DSL_NAIVE = 2; +const int DigitalStraightLine::DSL_STANDARD = 3; + + +DigitalStraightLine::DigitalStraightLine (int a, int b, int c, int nu) +{ + this->a = a; + this->b = b; + if (nu < 0) + { + this->c = c + 1 + nu; + this->nu = - nu; + } + else + { + this->c = c; + this->nu = nu; + } + if (a < 0) + { + this->a = - this->a; + this->b = - this->b; + this->c = 1 - c - nu; + } + else if (a == 0 && b < 0) + { + this->b = - this->b; + this->c = 1 - c - nu; + } + int pg = pgcd (a, (b < 0 ? - b : b)); + if (pg != 1) + { + a /= pg; + b /= pg; + c /= pg; + nu /= pg; + } +} + + +DigitalStraightLine::DigitalStraightLine (Pt2i p1, Pt2i p2, int type) +{ + if (p1.y () < p2.y ()) + { + a = p2.y () - p1.y (); + b = p1.x () - p2.x (); + } + else + { + a = p1.y () - p2.y (); + b = p2.x () - p1.x (); + if (a == 0 && b < 0) b = -b; + } + int pg = pgcd (a, b < 0 ? -b : b); + a /= pg; + b /= pg; + c = a * p1.x () + b * p1.y (); + + if (type == DSL_NAIVE) + { + nu = b; + if (nu < 0) nu = -nu; + if (nu < a) nu = a; + // To match Pt2i::stepsTo method ... + c -= nu / 2; + } + + else if (type == DSL_STANDARD) + { + nu = a + (b < 0 ? -b : b); + if ((b > 0 && a > b) || (b < 0 && a < -b)) + c -= (nu - 1) / 2; + else + c -= nu / 2; + } + + else // type == DSL_THIN + nu = 1; +} + + +DigitalStraightLine::DigitalStraightLine (Pt2i p1, Pt2i p2, Pt2i p3) +{ + if (p1.y () < p2.y ()) + { + a = p2.y () - p1.y (); + b = p1.x () - p2.x (); + } + else + { + a = p1.y () - p2.y (); + b = p2.x () - p1.x (); + if (a == 0 && b < 0) b = -b; + } + int pg = pgcd (a, b < 0 ? -b : b); + a /= pg; + b /= pg; + c = a * p1.x () + b * p1.y (); + int d = a * p3.x () + b * p3.y (); + if (d < c) + { + nu = c - d + 1; + c = d; + } + else nu = d - c + 1; +} + + +DigitalStraightLine::DigitalStraightLine (Pt2i p1, Pt2i p2, + int type, int atRight) +{ + if (p1.y () < p2.y ()) + { + a = p2.y () - p1.y (); + b = p1.x () - p2.x (); + } + else + { + a = p1.y () - p2.y (); + b = p2.x () - p1.x (); + if (a == 0 && b < 0) b = -b; + } + int pg = pgcd (a, b < 0 ? -b : b); + a /= pg; + b /= pg; + c = a * p1.x () + b * p1.y (); + + int bb = (b < 0 ? -b : b); + c += atRight * (a < bb ? bb : a); + + if (type == DSL_NAIVE) + { + nu = b; + if (nu < 0) nu = -nu; + if (nu < a) nu = a; + // To match Pt2i::stepsTo method ... + if ((b > 0 && a > b) || (b < 0 && a < -b)) + c -= (nu - 1) / 2; + else + c -= nu / 2; + } + + else if (type == DSL_STANDARD) + { + nu = a + (b < 0 ? -b : b); + if ((b > 0 && a > b) || (b < 0 && a < -b)) + c -= (nu - 1) / 2; + else + c -= nu / 2; + } + + else // type == DSL_THIN + nu = 1; +} + + +DigitalStraightLine::DigitalStraightLine (const DigitalStraightLine &l) +{ + a = l.a; + b = l.b; + c = l.c; + nu = l.nu; +} + + +int DigitalStraightLine::manhattan (Pt2i pix) const +{ + int absb = b < 0 ? -b : b; + int per = (a < absb ? absb : a); + int pos = a * pix.x () + b * pix.y () - c; + if (pos < 0) return ((pos + 1 - per) / per); + else if (pos >= nu) return ((pos + per - nu) / per); + else return 0; +} + + +Pt2i DigitalStraightLine::getABoundingPoint (bool upper) const +{ + int sa = a, sb = b, u1 = 1, v1 = 0, u2 = 0, v2 = 1; + while (sb != 0) + { + int r = sa % sb; + int q = sa / sb; + int u3 = u1 - q * u2; + int v3 = v1 - q * v2; + u1 = u2; + v1 = v2; + u2 = u3; + v2 = v3; + sa = sb; + sb = r; + } + if (sa < 0) // should be 1 or -1 if a and b are primal + { + u1 = - u1; + v1 = - v1; + } + return (upper ? Pt2i (u1 * (c + nu - 1), v1 * (c + nu - 1)) + : Pt2i (u1 * c, v1 * c)); +} + + +void DigitalStraightLine::adjustWorkArea (int &xmin, int &ymin, + int &width, int &height) const +{ + (void) xmin; + (void) ymin; + (void) width; + (void) height; +} + + +void DigitalStraightLine::getBounds (std::vector<Pt2i> &bound, + int xmin, int ymin, int width, int height) const +{ + getBoundPoints (bound, false, xmin, ymin, width, height); + if (nu > period ()) getBoundPoints (bound, true, xmin, ymin, width, height); +} + + +void DigitalStraightLine::getBoundPoints (std::vector<Pt2i> &points, + bool opposite, int xmin, int ymin, int width, int height) const +{ + if (opposite && nu < period ()) return; + + int x, y, dec, r; + Pt2i pb = getABoundingPoint (opposite); + adjustWorkArea (xmin, ymin, width, height); + + if (b > 0) // downwards + if (b >= a) // rather horizontal (8th octant) + { + x = pb.x (); + y = pb.y (); + dec = x <= xmin ? (xmin - x) / b : (xmin - x) / b - 1; + x += dec * b; + y -= dec * a; + r = (opposite ? b - 1 : 0); + + while (x < xmin) + { + x++; + r += a; if (r >= b) + { + y --; + r -= b; + } + } + if ((opposite || r < nu ) && y >= ymin && y < ymin + height) + points.push_back (Pt2i (x, y)); + while (++x < xmin + width) + { + r += a; + if (r >= b) + { + y --; + r -= b; + } + if ((opposite || r < nu) && y >= ymin && y < ymin + height) + points.push_back (Pt2i (x, y)); + } + } + else // rather vertical (7th octant) + { + x = pb.x (); + y = pb.y (); + dec = y >= ymin + height ? (y - ymin - height) / a + : (y - ymin - height) / a - 1; + x += dec * b; + y -= dec * a; + r = (opposite ? 0 : a - 1); + + while (y >= ymin + height) + { + y--; + r += b; + if (r >= a) + { + x ++; + r -= a; + } + } + + if ((opposite || r >= a - nu) && x >= xmin && x < xmin + width) + points.push_back (Pt2i (x, y)); + while (y-- > ymin) + { + r += b; + if (r >= a) + { + x ++; + r -= a; + } + if ((opposite || r >= a - nu) && x >= xmin && x < xmin + width) + points.push_back (Pt2i (x, y)); + } + } + else // upwards + if (-b >= a) // rather horizontal (1st octant) + { + x = pb.x (); + y = pb.y (); + dec = x <= xmin ? (x - xmin) / b : (x - xmin) / b - 1; + x -= dec * b; + y += dec * a; + r = (opposite ? b + 1 : 0); + + while (x < xmin) + { + x++; + r -= a; + if (r <= b) + { + y ++; + r -= b; + } + } + + if ((opposite || r > -nu) && y >= ymin && y < ymin + height) + points.push_back (Pt2i (x, y)); + while (++x < xmin + width) + { + r -= a; + if (r <= b) + { + y ++; + r -= b; + } + if ((opposite || r > -nu) && y >= ymin && y < ymin + height) + points.push_back (Pt2i (x, y)); + } + } + else // rather vertical (2nd octant) + { + x = pb.x (); + y = pb.y (); + dec = y > ymin ? (ymin - y) / a - 1 : (ymin - y) / a; + x -= dec * b; + y += dec * a; + r = (opposite ? 0 : a - 1); + + while (y < ymin) + { + y++; + r -= b; + if (r >= a) + { + x ++; + r -= a; + } + } + + if ((opposite || r >= a - nu) && x >= xmin && x < xmin + width) + points.push_back (Pt2i (x, y)); + while (++y < ymin + height) + { + r -= b; + if (r >= a) + { + x ++; + r -= a; + } + if ((opposite || r >= a - nu) && x >= xmin && x < xmin + width) + points.push_back (Pt2i (x, y)); + } + } +} + + +const Pt2i DigitalStraightLine::centerOfIntersection ( + DigitalStraightLine *l) const +{ + int den = a * l->b - b * l->a; + if (den == 0) return (Pt2i (0, 0)); + return (Pt2i ( + ((c + nu / 2) * l->b - b * (l->c + l->nu / 2) + den / 2) / den, + (a * (l->c + l->nu / 2) - (c + nu / 2) * l->a + den / 2) / den)); +} + + +const Pt2i DigitalStraightLine::centerOfIntersection ( + Pt2i p1, Pt2i p2) const +{ + int sa = p2.y () - p1.y (); + int sb = p1.x () - p2.x (); + if (sa == 0) + { + if (sb == 0) return (Pt2i (0, 0)); + if (sb < 0) sb = -sb; + } + if (sa < 0) + { + sa = -sa; + sb = -sb; + } + int pg = pgcd (sa, sb < 0 ? -sb : sb); + sa /= pg; + sb /= pg; + + int den = a * sb - b * sa; + if (den == 0) return (Pt2i (0, 0)); + int sc = sa * p1.x () + sb * p1.y (); + return (Pt2i (((c + nu / 2) * sb - b * sc + den / 2) / den, + (a * sc - (c + nu / 2) * sa + den / 2) / den)); +} + + +bool DigitalStraightLine::owns (const Pt2i &p) const +{ + int val = a * p.x () + b * p.y () - c; + return (val >= 0 && val < nu); +} + + +bool DigitalStraightLine::owns (const Pt2i &p1, const Pt2i &p2) const +{ + int val1 = a * p1.x () + b * p1.y () - c; + int val2 = a * p2.x () + b * p2.y () - c; + return (val1 < val2 ? val1 <= 0 && val2 < nu + : val2 <= 0 && val1 < nu); +} + + +bool DigitalStraightLine::crosses (const Pt2i &p1, const Pt2i &p2) const +{ + int val1 = a * p1.x () + b * p1.y () - c; + int val2 = a * p2.x () + b * p2.y () - c; + return (val1 < val2 ? val2 >= 0 && val1 < nu + : val1 >= 0 && val2 < nu); +} + + +int DigitalStraightLine::pavingIndex (Pt2i pt) const +{ + int rmd = a * pt.x () + b * pt.y () - c; + if (a == 0) rmd = -rmd; + return (rmd < 0 ? (rmd + 1) / nu - 1 : rmd / nu); +} + + +int DigitalStraightLine::rmd (Pt2i pt) const +{ + return (a * pt.x () + b * pt.y () - c); +} + + +int DigitalStraightLine::pgcd (int a, int b) +{ + int r; + while (b != 0) + { + r = a % b; + a = b; + b = r; + } + return (a); +} diff --git a/Expes/Testers/TestLines/ImageTools/digitalstraightline.h b/Expes/Testers/TestLines/ImageTools/digitalstraightline.h new file mode 100644 index 0000000..fb4da72 --- /dev/null +++ b/Expes/Testers/TestLines/ImageTools/digitalstraightline.h @@ -0,0 +1,255 @@ +#ifndef DIGITAL_STRAIGHT_LINE_H +#define DIGITAL_STRAIGHT_LINE_H + +#include "pt2i.h" +#include "edist.h" + + +/** + * @class DigitalStraightLine digitalstraightline.h + * \brief Digital straight line. + * The digital straightline is defined by equation : c <= ax + by < c + nu + * where a >= 0, b > 0 if a = 0, and nu > 0. + */ +class DigitalStraightLine +{ +public: + + /** Digital line type : Thin line (width = 1). */ + static const int DSL_THIN; + /** Digital line type : Naive line (width = max (|a|,|b|)). */ + static const int DSL_NAIVE; + /** Digital line type : Standard line (width = |a|+|b|). */ + static const int DSL_STANDARD; + + + /** + * \brief Creates a digital straightline from its equation parameters. + * @param a X value slope parameter of equation : c <= ax + by < c + nu + * @param b Y value slope parameter of equation : c <= ax + by < c + nu + * @param c Shift parameter of equation : c <= ax + by < c + nu + * @param nu Width parameter of equation : c <= ax + by < c + nu + */ + DigitalStraightLine (int a, int b, int c, int nu); + + /** + * \brief Creates a digital straight line centered on two points. + * @param p1 First leaning point. + * @param p2 Second leaning point. + * @param type Digital line type : DSL_THIN, DSL_NAIVE or DSL_STANDARD. + */ + DigitalStraightLine (Pt2i p1, Pt2i p2, int type); + + /** + * \brief Creates a digital straight line from an antipodal pair. + * @param p1 First segment end point of the antipodal pair. + * @param p1 Second segment end point of the antipodal pair. + * @param p3 Opposite point of the antipodal pair. + */ + DigitalStraightLine (Pt2i p1, Pt2i p2, Pt2i p3); + + /** + * \brief Creates a digital straight line from two points and side shift. + * It creates a parallel line to the line centered on given points, + * with side shift. + * Unknown use, care the side correctness in case of. + * @param p1 First point. + * @param p2 Second point. + * @param type Digital line type : DSL_THIN, DSL_NAIVE or DSL_STANDARD + * @param atRight Rightwards orthogonal shift to p1p2 line. + */ + DigitalStraightLine (Pt2i p1, Pt2i p2, int type, int atRight); + + /** + * Creates a similar digital straight line to an other one. + * @param l The original line. + */ + DigitalStraightLine (const DigitalStraightLine &l); + + /** + * Deletes the digital straight line. + */ + virtual ~DigitalStraightLine () { } + + /** + * \brief Returns the parameters of the digital straight line equations. + * @param a X Slope parameter to provide. + * @param b Y Slope parameter to provide. + * @param c Shift parameter to provide. + * @param nu Width parameter to provide. + */ + inline void equation (int &a, int &b, int &c, int &nu) const { + a = this->a; b = this->b; c = this->c; nu = this->nu; } + + /** + * \brief Sets given values with the three central naive line parameters. + * @param a X Slope parameter to provide. + * @param b Y Slope parameter to provide. + * @param c0 Shift parameter of the central naive line. + */ + inline void getCentralLine (int &a, int &b, int &c0) const { + a = this->a; b = this->b; c0 = c + nu / 2; } + + /** + * \brief Returns the period of the digital straight line : max (|a|,|b|). + */ + inline int period () const { + int absb = b < 0 ? -b : b; + return (a < absb ? absb : a); } + + /** + * \brief Returns the lower icoefficient of the digital straight line. + * Returns min (|a|,|b|). + */ + inline int antiperiod () const { + int absb = b < 0 ? -b : b; + return (a < absb ? a : absb); } + + /** + * \brief Returns the standard width : |a| + |b|. + */ + inline int standard () const { return (a + (b < 0 ? -b : b)); } + + /** + * \brief Returns the manhattan width of the digital straight line. + */ + inline int width () const { return (nu); } + + /** + * \brief Returns a support vector of the digital straight line. + */ + inline Vr2i supportVector () const { return (Vr2i (-b, a)); } + + /** + * \brief Returns the signed manhattan distance to the given point. + * Returns the distance to line bounds + * Right values are provided for points on the right side (yp > line (yp)). + */ + int manhattan (Pt2i pix) const; + + /** + * \brief Returns a bounding point of the digital straight line. + * @param upper Set to true for upper bounding point, to false for lower one. + */ + virtual Pt2i getABoundingPoint (bool upper) const; + + /** + * \brief Adds points of one of the bounding lines to given vector. + * The bounding line is naive if (nu < period). + * Restricts the points in the specified area. + * @param bound Vector of points to complete. + * @param opposite True to get the opposite bounding line (ax+by=c+nu-1). + * @param xmin Left border of the viewport. + * @param ymin Bottom border of the viewport. + * @param width Width of the viewport. + * @param height Height of the viewport. + */ + void getBoundPoints (std::vector<Pt2i> &bound, bool opposite, + int xmin, int ymin, int width, int height) const; + + /** + * \brief Adds points of both bounding lines to given vector. + * @param bound Vector of points to complete. + * @param xmin Left border of the viewport. + * @param ymin Bootom border of the viewport. + * @param width Width of the viewport. + * @param height Height of the viewport. + */ + void getBounds (std::vector<Pt2i> &bound, + int xmin, int ymin, int width, int height) const; + + /** + * \brief Checks if given point belongs to the digital line. + * @param p Given point. + */ + bool owns (const Pt2i &p) const; + + /** + * \brief Checks if given segment entirely belongs to the digital line. + * @param p1 Given segment start point. + * @param p2 Given segment end point. + */ + bool owns (const Pt2i &p1, const Pt2i &p2) const; + + /** + * \brief Checks if given segment belongs even partially to the digital line. + * @param p1 Given segment start point. + * @param p2 Given segment end point. + */ + bool crosses (const Pt2i &p1, const Pt2i &p2) const; + + /** + * \brief Returns the center of the intersection with another digital line. + * @param l The other digital line. + */ + const Pt2i centerOfIntersection (DigitalStraightLine *l) const; + + /** + * \brief Returns the center of the intersection with line P1-P2. + * Care : returns (0,0) if p1 == p2 or if P1P2 is parallel to the line. + * @param p1 Start point of the crossed line. + * @param p2 End point of the crossed line. + */ + const Pt2i centerOfIntersection (Pt2i p1, Pt2i p2) const; + + /** + * \brief Returns the squared Euclidean thickness of the digital line. + */ + const EDist squaredEuclideanThickness () const { + return (EDist (nu * nu, a * a + b * b)); } + + /** + * \brief Returns the plane paving index of given point. + * Plane paving index is the index of the line containing the point + * when paving the Euclidean plane by the digital straight line. + * @param pt Given point. + */ + int pavingIndex (Pt2i pt) const; + int rmd (Pt2i pt) const; + + + +protected: + + /** + * \brief X value of the slope parameter in equation : c <= ax + by < c + nu. + * a is greater or equal to 0. + */ + int a; + + /** + * \brief Y value of the slope parameter in equation : c <= ax + by < c + nu. + * b is a positive value when the line is horizontal (a = 0). + */ + int b; + + /** + * \brief Shift parameter in equation : c <= ax + by < c + nu. + */ + int c; + + /** + * \brief Width parameter in equation : c <= ax + by < c + nu. + * nu is a positive value. + */ + int nu; + + + /** + * \brief Adjusts the provided area on the straight line limits. + * @param xmin Left coordinate of the area. + * @param ymin Bottom coordinate of the area. + * @param width Width of the area. + * @param height Height of the area. + */ + virtual void adjustWorkArea (int &xmin, int &ymin, + int &width, int &height) const; + + /** + * \brief Returns the greater common divisor between two integer values. + * @param a First integer. + * @param b Second integer. + */ + static int pgcd (int a, int b); +}; +#endif diff --git a/Expes/Testers/TestLines/ImageTools/digitalstraightsegment.cpp b/Expes/Testers/TestLines/ImageTools/digitalstraightsegment.cpp new file mode 100644 index 0000000..c2b509f --- /dev/null +++ b/Expes/Testers/TestLines/ImageTools/digitalstraightsegment.cpp @@ -0,0 +1,277 @@ +#include "digitalstraightsegment.h" + + +DigitalStraightSegment::DigitalStraightSegment () + : DigitalStraightLine (1, 1, 0, 1) +{ + min = 0; + max = 1; +} + + +DigitalStraightSegment::DigitalStraightSegment (Pt2i p1, Pt2i p2, int type, + int xmin, int ymin, int xmax, int ymax) + : DigitalStraightLine (p1, p2, type) +{ + if (a < (b < 0 ? -b : b)) + { + min = xmin; + max = xmax; + } + else + { + min = ymin; + max = ymax; + } +} + + +DigitalStraightSegment::DigitalStraightSegment (Pt2i p1, Pt2i p2, Pt2i p3, + int xmin, int ymin, int xmax, int ymax) + : DigitalStraightLine (p1, p2, p3) +{ + if (a < (b < 0 ? -b : b)) + { + min = xmin; + max = xmax; + } + else + { + min = ymin; + max = ymax; + } +} + + +DigitalStraightSegment::DigitalStraightSegment (Pt2i p1, Pt2i p2, int width) + : DigitalStraightLine (p1, p2, DSL_THIN) +{ + nu = width * period (); + c = a * p1.x () + b * p1.y () - nu / 2; + if (a < (b < 0 ? -b : b)) + { + min = (p1.x () < p2.x () ? p1.x () : p2.x ()); + max = (p1.x () < p2.x () ? p2.x () : p1.x ()); + } + else + { + min = (p1.y () < p2.y () ? p1.y () : p2.y ()); + max = (p1.y () < p2.y () ? p2.y () : p1.y ()); + } +} + + +DigitalStraightSegment::DigitalStraightSegment (int va, int vb, int vc, + int vnu, int vmin, int vmax) + : DigitalStraightLine (va, vb, vc, vnu) +{ + min = vmin; + max = vmax; +} + + +DigitalStraightSegment::DigitalStraightSegment (DigitalStraightSegment *dss) + : DigitalStraightLine (dss->a, dss->b, dss->c, dss->nu) +{ + min = dss->min; + max = dss->max; +} + + +Pt2i DigitalStraightSegment::getABoundingPoint (bool upper) const +{ + int sa = a, sb = b, u1 = 1, v1 = 0, u2 = 0, v2 = 1; + while (sb != 0) + { + int r = sa % sb; + int q = sa / sb; + int u3 = u1 - q * u2; + int v3 = v1 - q * v2; + u1 = u2; + v1 = v2; + u2 = u3; + v2 = v3; + sa = sb; + sb = r; + } + if (sa < 0) // should be 1 or -1 if a and b are primal + { + u1 = - u1; // necessary if sa = -1 + v1 = - v1; + } + Pt2i extr (upper ? Pt2i (u1 * (c + nu - 1), v1 * (c + nu - 1)) + : Pt2i (u1 * c, v1 * c)); + + int dec = 0; + int bb = (b < 0 ? -b : b); + if (a < bb) + { + if (extr.x () > max) + dec = 1 + (extr.x () - max) / bb; + else if (extr.x () < min) + dec = -1 - (min - extr.x ()) / bb; + if (b < 0) dec = -dec; + } + else + { + if (extr.y () > max) + dec = -1 - (extr.y () - max) / a; + else if (extr.y () < min) + dec = 1 + (min - extr.y ()) / a; + } + extr.set (extr.x () - dec * b, extr.y () + dec * a); + return extr; +} + + +void DigitalStraightSegment::adjustWorkArea (int &xmin, int &ymin, + int &width, int &height) const +{ + if (b > a || -b > a) + { + if (xmin < min) xmin = min; + // Caution, segment max limit is excluded + int x2 = (xmin + width < max + 1 ? xmin + width : max + 1); + width = (xmin >= x2 ? 0 : x2 - xmin); + } + else + { + if (ymin < min) ymin = min; + // Cochon, segment max limit is excluded + int y2 = (ymin + height < max + 1 ? ymin + height : max + 1); + height = (ymin >= y2 ? 0 : y2 - ymin); + } +} + + +void DigitalStraightSegment::getPoints (std::vector<Pt2i> &pts) const +{ + int xmin, ymin, w, h; + if (b > a || -b > a) + { + xmin = min; + w = max - min; + ymin = (b < 0 ? (c + nu - a * min) / b - 1 : (c - a * max) / b - 1); + h = (b < 0 ? (c - a * max) / b + 1 : (c + nu - a * min) / b + 1) - ymin; + } + else + { + ymin = min; + h = max - min; + xmin = (b < 0 ? (c - b * min) / a - 1 : (c - b * max) / a - 1); + w = (b < 0 ? (c + nu - b * max) / a : (c + nu - b * min) / a) + 1 - xmin; + } + std::vector<Pt2i> lowbound; + getBoundPoints (lowbound, false, xmin, ymin, w, h); + std::vector<Pt2i>::iterator it = lowbound.begin (); + + while (it != lowbound.end ()) + { + Pt2i p = *it; + while (owns (p)) + { + pts.push_back (p); + if (b > a) p.set (p.x (), p.y () + 1); + else if (-b > a) p.set (p.x (), p.y () - 1); + else p.set (p.x () + 1, p.y ()); + } + it ++; + } +} + + +void DigitalStraightSegment::naiveLine (AbsRat &x1, AbsRat &y1, + AbsRat &x2, AbsRat &y2) const +{ + if (a < (b < 0 ? -b : b)) + { + x1.set (min); + y1.set (2 * c + nu - 1 - 2 * a * min, 2 * b); + x2.set (max); + y2.set (2 * c + nu - 1 - 2 * a * max, 2 * b); + } + else + { + y1.set (min); + x1.set (2 * c + nu - 1 - 2 * b * min, 2 * a); + y2.set (max); + x2.set (2 * c + nu - 1 - 2 * b * max, 2 * a); + } +} + + +DigitalStraightSegment *DigitalStraightSegment::erosion (int num, int den) const +{ + int newwidth = nu; + if (nu > period ()) + { + newwidth = nu - (num * period ()) / den; + if (newwidth < period ()) newwidth = period (); + } + return (new DigitalStraightSegment (a, b, c + (nu - newwidth) / 2, + newwidth, min, max)); +} + + +DigitalStraightSegment *DigitalStraightSegment::dilation ( + int num, int den) const +{ + int newwidth = nu + (num * period ()) / den; + return (new DigitalStraightSegment (a, b, c + (nu - newwidth) / 2, + newwidth, min, max)); +} + + +DigitalStraightSegment *DigitalStraightSegment::dilation (int ar_width) const +{ + return (new DigitalStraightSegment (a, b, c - ar_width, + nu + 2 * ar_width, min, max)); +} + + +void DigitalStraightSegment::dilate (int ar_width) +{ + nu += 2 * ar_width; + c -= ar_width; +} + + +void DigitalStraightSegment::setNaive () +{ + int p = period (); + c += (nu - p) / 2; + nu = p; +} + + +bool DigitalStraightSegment::contains (Pt2i p, int tol) const +{ + int pos = a * p.x () + b * p.y (); + tol *= period (); + if (pos < c - tol || pos >= c + nu + tol) return (false); + if (a < (b < 0 ? -b : b)) + return (p.x () >= min && p.x () <= max); + else + return (p.y () >= min && p.y () <= max); +} + + +int DigitalStraightSegment::length2 () const +{ + int numin, numax, den; + if (a < (b < 0 ? -b : b)) + { + numin = c - a * min; + numax = c - a * max; + den = b; + } + else + { + numin = c - b * min; + numax = c - b * max; + den = a; + } + return ((int) (((max - min) * (max - min) * den * den + + (numax - numin) * (numax - numin) + + (den * den) / 2) / (den * den))); +} diff --git a/Expes/Testers/TestLines/ImageTools/digitalstraightsegment.h b/Expes/Testers/TestLines/ImageTools/digitalstraightsegment.h new file mode 100644 index 0000000..891b6a2 --- /dev/null +++ b/Expes/Testers/TestLines/ImageTools/digitalstraightsegment.h @@ -0,0 +1,181 @@ +#ifndef DIGITAL_STRAIGHT_SEGMENT_H +#define DIGITAL_STRAIGHT_SEGMENT_H + +#include "digitalstraightline.h" +#include "absrat.h" + + +/** + * @class DigitalStraightSegment digitalstraightsegment.h + * \brief Digital straight segment is a bounded digital straight line. + * The digital straightline is defined by equation : c <= ax + by < c + nu + * where a >= 0 and nu > 0. Seemingly b is unconstrained when a = 0. + * The bounds are horizontal lines (min <= y <= max) if the segment is vertical, + * vertical lines (min <= x <= max) if the segment is horizontal. + * The bound points belong to the digital straight segment. + */ +class DigitalStraightSegment : public DigitalStraightLine +{ +public: + + /** + * \brief Creates a default digital straight segment. + */ + DigitalStraightSegment (); + + /** + * \brief Creates a null-thick segment passing through two points. + * Caution: the segment ends on given min-max bounds ! + * To create a segment ending on the two points, use constructor with + * two points and width instead. + * @param p1 First point on the line. + * @param p2 Second point on the line. + * @param type Digital line type : DSL_THIN, DSL_NAIVE or DSL_STANDARD. + * @param xmin Segment min X-coordinate. + * @param ymin Segment min Y-coordinate. + * @param xmax Segment max X-coordinate. + * @param ymax Segment max Y-coordinate. + */ + DigitalStraightSegment (Pt2i p1, Pt2i p2, int type, + int xmin, int ymin, int xmax, int ymax); + + /** + * \brief Creates a digital straight segment from an antipodal pair. + * @param p1 First segment end of the antipodal pair. + * @param p1 Second segment end of the antipodal pair. + * @param p3 Opposite point of the antipodal pair. + * @param xmin Segment min X coordinate. + * @param ymin Segment min Y coordinate. + * @param xmax Segment max X coordinate. + * @param ymax Segment max Y coordinate. + */ + DigitalStraightSegment (Pt2i p1, Pt2i p2, Pt2i p3, + int xmin, int ymin, int xmax, int ymax); + + /** + * \brief Creates a digital straight segment from end points and width. + * @param p1 First end point of the segment. + * @param p2 Second end point of the segment. + * @param width Width value: 1 for a naive line. + */ + DigitalStraightSegment (Pt2i p1, Pt2i p2, int width); + + /** + * \brief Sets identical to given digital straight segment. + * @param dss Original straight segment. + */ + inline void set (const DigitalStraightSegment &dss) { + a = dss.a; b = dss.b; c = dss.c; nu = dss.nu; + min = dss.min; max = dss.max; } + + /** + * \brief Creates a digital straight segment from another one. + * @param dss Pointer to the digital straight segment to copy. + */ + DigitalStraightSegment (DigitalStraightSegment *dss); + + /** + * \brief Returns a bounding point of the digital line. + * @param upper True for an upper bounding point, false for a lower one. + */ + Pt2i getABoundingPoint (bool upper) const; + + /** + * \brief Adds segment points to the provided vector. + * @param pts Vector of points to complete. + */ + void getPoints (std::vector<Pt2i> &pts) const; + + /** + * \brief Provides the naive central segment end points coordinates. + * @param x1 Start point X-coordinate to set. + * @param x2 Start point Y-coordinate to set. + * @param x2 End point X-coordinate to set. + * @param y2 End point Y-coordinate to set. + */ + void naiveLine (AbsRat &x1, AbsRat &y1, AbsRat &x2, AbsRat &y2) const; + + /** + * \brief Returns an erosion of the segment. + * @param num Erosion value numerator. + * @param den Erosion value denominator. + */ + DigitalStraightSegment *erosion (int num, int den) const; + + /** + * \brief Returns a dilation of the segment. + * @param num Dilation value numerator. + * @param den Dilation value denominator. + */ + DigitalStraightSegment *dilation (int num, int den) const; + + /** + * \brief Returns a dilated segment of given arithmetical width on each side. + * @param radius Dilation arithmetical width. + */ + DigitalStraightSegment *dilation (int ar_width) const; + + /** + * \brief Dilates the segment of given arithmetical width on each side. + * @param radius Dilation arithmetical width. + */ + void dilate (int ar_width); + + /** + * \brief Erodes the segment to its naive center line. + */ + void setNaive (); + + /** + * \brief Inquires if given point belongs to a dilation of the segment. + * @param p Tested point. + * @param tol Dilation value (some tolerence). + */ + bool contains (Pt2i p, int tol) const; + + /** + * \brief Returns the squared length of the digital straight segment. + */ + int length2 () const; + + +protected: + + /** + * \brief Bounding line lower coordinate. + * If the segment is stictly horizontal, it enforces x >= xmin. + * If the segment is vertical or diagonal, it enforces y >= ymin. + * The bound belongs to the segment. + */ + int min; + /** + * \brief Bounding line upper coordinate. + * If the segment is stictly horizontal, it enforces x <= xmax. + * If the segment is vertical or diagonal, it enforces y <= ymax. + * The bound belongs to the segment. + */ + int max; + + + /** + * \brief Creates a segment from parameter values. + * @param va Slope X coordinate. + * @param vb Slope Y coordinate. + * @param vc Shift to origin. + * @param vnu Arithmetical width. + * @param vmin Bounding line lower coordinate. + * @param vmax Bounding line upper coordinate. + */ + DigitalStraightSegment (int va, int vb, int vc, int vnu, int vmin, int vmax); + + /** + * \brief Sets the provided area on the segment limits. + * @param xmin Left border of the area to set. + * @param ymin Bottom border of the area to set. + * @param width Width of the area to set. + * @param height Height of the area to set. + */ + void adjustWorkArea (int &xmin, int &ymin, int &width, int &height) const; + +}; +#endif diff --git a/Expes/Testers/TestLines/ImageTools/edist.cpp b/Expes/Testers/TestLines/ImageTools/edist.cpp new file mode 100644 index 0000000..eecca11 --- /dev/null +++ b/Expes/Testers/TestLines/ImageTools/edist.cpp @@ -0,0 +1,22 @@ +#include "edist.h" + + +EDist::EDist () +{ + d_num = 0; + d_den = 1; +} + + +EDist::EDist (int numerator, int denominator) +{ + d_num = (numerator < 0 ? - numerator : numerator); + d_den = (denominator < 0 ? - denominator : denominator); +} + + +EDist::EDist (const EDist &dist) +{ + d_num = dist.d_num; + d_den = dist.d_den; +} diff --git a/Expes/Testers/TestLines/ImageTools/edist.h b/Expes/Testers/TestLines/ImageTools/edist.h new file mode 100644 index 0000000..633c576 --- /dev/null +++ b/Expes/Testers/TestLines/ImageTools/edist.h @@ -0,0 +1,154 @@ +#ifndef EUCLIDEAN_DISTANCE_H +#define EUCLIDEAN_DISTANCE_H + +#include <inttypes.h> + + +/** + * @class EDist edist.h + * \brief Euclidean distance rational value. + * + * Caution: this absolute rational number may have a null denominator. + * It should not be evaluated. + * It is mostly intended to comparison operations. + */ +class EDist +{ +public: + + /** + * \brief Creates a Euclidean distance with null value. + */ + EDist (); + + /** + * \brief Creates a Euclidean distance from numerator and denominator. + * Forces numerator and denominator to their absolute value. + * @param numerator Numerator value. + * @param denominator Denominator value. + */ + EDist (int numerator, int denominator); + + /** + * \brief Creates a Euclidean distance from another one. + * @param dist The Euclidean distance to copy. + */ + EDist (const EDist &dist); + + /** + * \brief Deletes the Euclidean distance. + */ + ~EDist () { } + + /** + * \brief Returns the numerator of the Euclidean distance. + */ + //inline int numerator () const { return d_num; } + + /** + * \brief Returns the denominator of the Euclidean distance. + */ + //inline int denominator () const { return d_den; } + + /** + * \brief Returns the numerator of the Euclidean distance. + */ + inline int num () const { return d_num; } + + /** + * \brief Returns the denominator of the Euclidean distance. + */ + inline int den () const { return d_den; } + + /** + * \brief Returns the nearest smaller integer value. + */ + inline int floor () const { return (d_num / d_den); } + + /** + * \brief Sets a new value from another Euclidean distance. + * @value dist Other Euclidean distance. + */ + inline void set (const EDist &dist) { + d_num = dist.d_num; d_den = dist.d_den; } + + /** + * \brief Sets a new integer value. + * @value val Integer value. + */ + inline void set (int val) { d_num = val; d_den = 1; } + + /** + * \brief Sets a new value from a numerator and a denominator. + * @value numerator New numerator value. + * @value denominator New denominator value. + */ + inline void set (int numerator, int denominator) { + d_num = numerator; d_den = denominator; } + + /** + * \brief Checks equivalence to another Euclidean distance. + * @param dist Other Euclidean distance. + */ + inline bool equals (const EDist &dist) const { + return (d_num * (int64_t) (dist.d_den) == d_den * (int64_t) (dist.d_num)); } + + /** + * \brief Checks if the distance is strictly inferior to another one. + * @param dist Other Euclidean distance. + */ + inline bool lessThan (const EDist &dist) const { + return (d_num * (int64_t) (dist.d_den) < d_den * (int64_t) (dist.d_num)); } + + /** + * \brief Checks if the distance is inferior or equal to another one. + * @param dist Other Euclidean distance. + */ + inline bool lessEqThan (const EDist &dist) const { + return (d_num * (int64_t) (dist.d_den) <= d_den * (int64_t) (dist.d_num)); } + + /** + * \brief Checks if the distance is strictly superior to another one. + * @param dist Other Euclidean distance. + */ + inline bool greaterThan (const EDist &dist) const { + return (d_num * (int64_t) (dist.d_den) > d_den * (int64_t) (dist.d_num)); } + + /** + * \brief Checks if the distance is superior or equal to another one. + * @param dist Other Euclidean distance. + */ + inline bool greaterEqThan (const EDist &dist) const { + return (d_num * (int64_t) (dist.d_den) >= d_den * (int64_t) (dist.d_num)); } + + /** + * \brief Multiplies the Euclidean distance by another one. + * @param dist Other Euclidean distance. + */ + inline void mult (const EDist &dist) { + d_num *= dist.d_num; d_den *= dist.d_den; } + + /** + * \brief Returns the sum of the Euclidean distance and a integer length. + * @param length integer length. + */ + inline EDist sum (int length) const { + return (EDist (d_num + length * d_den, d_den)); } + + /** + * \brief Returns the sum of the Euclidean distance and 1/2. + */ + inline EDist sumWithOneHalf () const { + return (d_den % 2 == 1 ? EDist (2 * d_num + d_den, 2 * d_den) + : EDist (d_num + d_den / 2, d_den)); } + + +protected: + + /** Positive numerator of the Euclidean distance. */ + int d_num; + /** Positive denominator of the Euclidean distance (might be null). */ + int d_den; + +}; +#endif diff --git a/Expes/Testers/TestLines/ImageTools/pt2i.cpp b/Expes/Testers/TestLines/ImageTools/pt2i.cpp new file mode 100644 index 0000000..a9b63fe --- /dev/null +++ b/Expes/Testers/TestLines/ImageTools/pt2i.cpp @@ -0,0 +1,832 @@ +#include "pt2i.h" + + +Pt2i::Pt2i () +{ + xp = 0; + yp = 0; +} + + +Pt2i::Pt2i (int x, int y) +{ + xp = x; + yp = y; +} + + +Pt2i::Pt2i (const Pt2i &p) +{ + xp = p.xp; + yp = p.yp; +} + + +bool Pt2i::inTriangle (Pt2i p1, Pt2i p2, Pt2i p3) const +{ + int x1 = p1.xp, y1 = p1.yp; + int x2 = p2.xp, y2 = p2.yp; + int x3 = p3.xp, y3 = p3.yp; + + // Checks bounding rectangle + int xmin = x1, ymin = y1, xmax = x1, ymax = y1; + if (x2 < xmin) xmin = x2; + if (x3 < xmin) xmin = x3; + if (xp < xmin) return false; + if (x2 > xmax) xmax = x2; + if (x3 > xmax) xmax = x3; + if (xp > xmax) return false; + if (y2 < ymin) ymin = y2; + if (y3 < ymin) ymin = y3; + if (yp < ymin) return false; + if (y2 > ymax) ymax = y2; + if (y3 > ymax) ymax = y3; + if (yp > ymax) return false; + + // Case of aligned triangle vertices + if ((x2 - x1) * (y3 - y1) == (x3 - x1) * (y2 - y1)) + return ((x2 - x1) * (yp - y1) == (xp - x1) * (y2 - y1)); + + // Checks orientation to triangle edges + int pv1 = (xp - x1) * (y2 - y1) - (yp - y1) * (x2 - x1); + int pv2 = (xp - x2) * (y3 - y2) - (yp - y2) * (x3 - x2); + int pv3 = (xp - x3) * (y1 - y3) - (yp - y3) * (x1 - x3); + return ((pv1 >= 0 && pv2 >= 0 && pv3 >= 0) + || (pv1 <= 0 && pv2 <= 0 && pv3 <= 0)); +} + + +Vr2i Pt2i::vectorTo (Pt2i p) const +{ + return (Vr2i (p.xp - xp, p.yp - yp)); +} + + +Pt2i *Pt2i::drawing (const Pt2i p, int *n) const +{ + int x1, y1, x2, y2; + if (xp > p.xp) + { + x1 = p.xp; + x2 = xp; + y1 = p.yp; + y2 = yp; + } + else + { + x1 = xp; + x2 = p.xp; + y1 = yp; + y2 = p.yp; + } + int dx = x2 - x1; + int dy = y2 - y1; + int e, i = 0; + Pt2i *pts; + + if (dy > 0) + { + // Octant 1 + if (dx >= dy) + { + *n = dx + 1; + pts = new Pt2i[dx + 1]; + e = dx - 1; // middle point lies below the line + dx *= 2; + dy *= 2; + while (x1 < x2) + { + pts[i++].set (x1, y1); + x1 ++; + e -= dy; + if (e < 0) + { + y1 ++; + e += dx; + } + } + pts[i].set (x1, y1); + } + + // Octant 2 + else + { + *n = dy + 1; + pts = new Pt2i[dy + 1]; + e = dy; // middle point lies to the right of the line + dx *= 2; + dy *= 2; + while (y1 < y2) + { + pts[i++].set (x1, y1); + y1 ++; + e -= dx; + if (e < 0) + { + x1 ++; + e += dy; + } + } + pts[i].set (x1, y1); + } + } + + else + { + // Octant 8 + if (dx >= -dy) + { + *n = 1 + dx; + pts = new Pt2i[dx + 1]; + e = dx - 1; // middle point lies below the line + dx *= 2; + dy *= 2; + while (x1 < x2) + { + pts[i++].set (x1, y1); + x1 ++; + e += dy; + if (e < 0) + { + y1 --; + e += dx; + } + } + pts[i].set (x1, y1); + } + + // Octant 7 + else + { + *n = 1 - dy; + pts = new Pt2i[1 - dy]; + e = - dy; // middle point lies to the left of the line + dx *= 2; + dy *= 2; + while (y1 > y2) + { + pts[i++].set (x1, y1); + y1 --; + e -= dx; + if (e < 0) + { + x1 ++; + e -= dy; + } + } + pts[i].set (x1, y1); + } + } + return (pts); +} + + +Pt2i *Pt2i::clipLine (const Pt2i p, int left, int low, int right, int up, + int *n) const +{ + if (right < left) { int tmp = left; left = right; right = tmp; } + if (up < low) { int tmp = low; low = up; up = tmp; } + int x1, y1, x2, y2; + if (xp > p.xp) + { + x1 = p.xp; + x2 = xp; + y1 = p.yp; + y2 = yp; + } + else + { + x1 = xp; + x2 = p.xp; + y1 = yp; + y2 = p.yp; + } + int dx = x2 - x1; + int dy = y2 - y1; + int e, i = 0; + Pt2i *pts; + + if (dy > 0) + { + // Octant 1 + if (dx >= dy) + { + *n = dx + 1; + pts = new Pt2i[dx + 1]; + if (x2 >= left && y2 >= low) + { + e = dx - 1; // middle point lies below the line + dx *= 2; + dy *= 2; + while (x1 < x2 && x1 <= right && y1 <= up) + { + if (x1 >= left && y1 >= low) pts[i++].set (x1, y1); + x1 ++; + e -= dy; + if (e < 0) + { + y1 ++; + e += dx; + } + } + if (x2 <= right && y2 <= up) pts[i++].set (x2, y2); + } + } + + // Octant 2 + else + { + *n = dy + 1; + pts = new Pt2i[dy + 1]; + if (x2 >= left && y2 >= low) + { + e = dy; // middle point lies to the right of the line + dx *= 2; + dy *= 2; + while (y1 < y2 && x1 <= right && y1 <= up) + { + if (x1 >= left && y1 >= low) pts[i++].set (x1, y1); + y1 ++; + e -= dx; + if (e < 0) + { + x1 ++; + e += dy; + } + } + if (x2 <= right && y2 <= up) pts[i++].set (x2, y2); + } + } + } + + else + { + // Octant 8 + if (dx >= -dy) + { + *n = 1 + dx; + pts = new Pt2i[dx + 1]; + if (x2 >= left && y2 <= up) + { + e = dx - 1; // middle point lies below the line + dx *= 2; + dy *= 2; + while (x1 < x2 && x1 <= right && y1 >= low) + { + if (x1 >= left && y1 <= up) pts[i++].set (x1, y1); + x1 ++; + e += dy; + if (e < 0) + { + y1 --; + e += dx; + } + } + if (x2 <= right && y2 >= low) pts[i++].set (x2, y2); + } + } + + // Octant 7 + else + { + *n = 1 - dy; + pts = new Pt2i[1 - dy]; + if (x2 >= left && y2 <= up) + { + e = - dy; // middle point lies to the left of the line + dx *= 2; + dy *= 2; + while (y1 > y2 && x1 <= right && y1 >= low) + { + if (x1 >= left && y1 <= up) pts[i++].set (x1, y1); + y1 --; + e -= dx; + if (e < 0) + { + x1 ++; + e -= dy; + } + } + if (x2 <= right && y2 >= low) pts[i].set (x2, y2); + } + } + } + *n = i; + return (pts); +} + + +void Pt2i::draw (std::vector<Pt2i> &line, Pt2i p) const +{ + int x1, y1, x2, y2; + if (xp > p.xp) + { + x1 = p.xp; + x2 = xp; + y1 = p.yp; + y2 = yp; + } + else + { + x1 = xp; + x2 = p.xp; + y1 = yp; + y2 = p.yp; + } + int dx = x2 - x1; + int dy = y2 - y1; + int e; + + if (dy > 0) + { + // Octant 1 + if (dx >= dy) + { + e = dx - 1; // middle point lies below the line + dx *= 2; + dy *= 2; + while (x1 < x2) + { + line.push_back (Pt2i (x1, y1)); + x1 ++; + e -= dy; + if (e < 0) + { + y1 ++; + e += dx; + } + } + line.push_back (Pt2i (x1, y1)); + } + + // Octant 2 + else + { + e = dy; // middle point lies to the right of the line + dx *= 2; + dy *= 2; + while (y1 < y2) + { + line.push_back (Pt2i (x1, y1)); + y1 ++; + e -= dx; + if (e < 0) + { + x1 ++; + e += dy; + } + } + line.push_back (Pt2i (x1, y1)); + } + } + + else + { + // Octant 8 + if (dx >= -dy) + { + e = dx - 1; // middle point lies below the line + dx *= 2; + dy *= 2; + while (x1 < x2) + { + line.push_back (Pt2i (x1, y1)); + x1 ++; + e += dy; + if (e < 0) + { + y1 --; + e += dx; + } + } + line.push_back (Pt2i (x1, y1)); + } + + // Octant 7 + else + { + e = - dy; // middle point lies to the left of the line + dx *= 2; + dy *= 2; + while (y1 > y2) + { + line.push_back (Pt2i (x1, y1)); + y1 --; + e -= dx; + if (e < 0) + { + x1 ++; + e -= dy; + } + } + line.push_back (Pt2i (x1, y1)); + } + } +} + + +Pt2i *Pt2i::pathTo (Pt2i p, int *n) const +{ + int x1, y1, x2, y2, delta; + if (xp > p.xp) + { + x1 = p.xp; + x2 = xp; + y1 = p.yp; + y2 = yp; + delta = -1; + } + else + { + x1 = xp; + x2 = p.xp; + y1 = yp; + y2 = p.yp; + delta = 1; + } + int dx = x2 - x1; + int dy = y2 - y1; + int e, i = 0; + Pt2i *pts; + + if (dy > 0) + { + // Octant 1 + if (dx >= dy) + { + *n = dx; + pts = new Pt2i[dx]; + e = dx - 1; // middle point lies below the line + if (delta < 0) e++; // ... above + dx *= 2; + dy *= 2; + while (x1 < x2) + { + x1 ++; + e -= dy; + if (e < 0) + { + y1 ++; + e += dx; + pts[i++].set (delta, delta); + } + else pts[i++].set (delta, 0); + } + } + + // Octant 2 + else + { + *n = dy; + pts = new Pt2i[dy]; + e = dy; // middle point lies to the right of the line + if (delta < 0) e--; // ... to the left + dx *= 2; + dy *= 2; + while (y1 < y2) + { + y1 ++; + e -= dx; + if (e < 0) + { + x1 ++; + e += dy; + pts[i++].set (delta, delta); + } + else pts[i++].set (0, delta); + } + } + } + + else + { + // Octant 8 + if (dx >= -dy) + { + *n = dx; + pts = new Pt2i[dx]; + e = dx - 1; // middle point lies below the line + if (delta < 0) e++; // ... above + dx *= 2; + dy *= 2; + while (x1 < x2) + { + x1 ++; + e += dy; + if (e < 0) + { + y1 --; + e += dx; + pts[i++].set (delta, -delta); + } + else pts[i++].set (delta, 0); + } + } + // Octant 7 + else + { + *n = -dy; + pts = new Pt2i[-dy]; + e = - dy; // middle point lies to the left of the line + if (delta < 0) e--; // ... to the right + dx *= 2; + dy *= 2; + while (y1 > y2) + { + y1 --; + e -= dx; + if (e < 0) + { + x1 ++; + e -= dy; + pts[i++].set (delta, -delta); + } + else pts[i++].set (0, -delta); + } + } + } + return (pts); +} + + +bool *Pt2i::stepsTo (Pt2i p, int *n) const +{ + bool negx = p.xp < xp; + bool negy = p.yp < yp; + int x2 = (xp > p.xp) ? xp - p.xp : p.xp - xp; + int y2 = (yp > p.yp) ? yp - p.yp : p.yp - yp; + int dx = x2, dy = y2, e = 0; + if (y2 > x2) + { + dx = y2; + dy = x2; + x2 = y2; + if (negx == negy) e++; + } + else if (negx != negy) e++; + e += dx - 1; + dx *= 2; + dy *= 2; + + int x = 0; + *n = x2; + bool *paliers = new bool[x2]; + while (x < x2) + { + e -= dy; + if (e < 0) + { + e += dx; + paliers[x++] = true; + } + else paliers[x++] = false; + } + return (paliers); +} + + +std::vector<Pt2i> Pt2i::drawOrtho (const Pt2i p2, int offset) const +{ + std::vector<Pt2i> pts; + + int x1 = xp; + int y1 = yp; + int x2 = p2.x (); + int y2 = p2.y (); + int dx = x2 - x1; + int dy = y2 - y1; + int e; + + int num = dx * dy; + if (num < 0) num = - num; + int den = dx * dx + dy * dy; + int nabs = (offset < 0 ? -offset : offset); + int steps = (offset * num) / den; + if ((nabs * num) % den >= den / 2) steps += (offset < 0 ? -1 : 1); + int floors; + + if (dx > 0 && dy > 0) // Quadrant 1 + { + if (dx >= dy) // Octant 1 + { + e = dx - 1; + dx = dx * 2; + dy = dy * 2; + if (offset < 0) floors = (steps * dy - e) / dx; + else floors = (e - 1 + steps * dy) / dx; + e += floors * dx - steps * dy; + x1 += steps; + y1 -= offset - floors; + x2 += steps; + // y2 -= offset - floors; // useless + while (x1 < x2) + { + pts.push_back (Pt2i (x1, y1)); + x1 ++; + e = e - dy; + if (e < 0) + { + y1 ++; + e = e + dx; + } + } + pts.push_back (Pt2i (x1, y1)); + } + else if (dx < dy) // Octant 2 + { + e = dy; + dx = dx * 2; + dy = dy * 2; + if (offset < 0) floors = (1 - e + steps * dx) / dy; + else floors = (e + steps * dx) / dy; + e -= floors * dy - steps * dx; + x1 += offset - floors; + y1 -= steps; + // x2 += offset - floors; // useless + y2 -= steps; // useless + while (y1 < y2) + { + pts.push_back (Pt2i (x1, y1)); + y1 ++; + e -= dx; + if (e < 0) + { + x1 ++; + e += dy; + } + } + pts.push_back (Pt2i (x1, y1)); + } + } + + else if (dx > 0 && dy < 0) // Quadrant 4 + { + if (dx >= -dy) // Octant 8 + { + e = dx - 1; + dx = dx * 2; + dy = dy * 2; + if (offset < 0) floors = (e - 1 + steps * dy) / dx; + else floors = (steps * dy - e) / dx; // < 0 + e += floors * dx - steps * dy; + x1 -= steps; + y1 -= offset + floors; + x2 -= steps; + // y2 -= offset + floors; // useless + while (x1 < x2) + { + pts.push_back (Pt2i (x1, y1)); + x1 ++; + e += dy; + if (e < 0) + { + y1 --; + e += dx; + } + } + pts.push_back (Pt2i (x1, y1)); + } + else // Octant 7 + { + e = dy; + dx = dx * 2; + dy = dy * 2; + if (offset < 0) floors = - (e + steps * dx) / dy; + else floors = (e + 1 - steps * dx) / dy; + e += floors * dy + steps * dx; + x1 -= offset - floors; + y1 -= steps; + // x2 -= offset - floors; // useless + y2 -= steps; + while (y1 > y2) + { + pts.push_back (Pt2i (x1, y1)); + y1 --; + e += dx; + if (e > 0) + { + x1 ++; + e += dy; + } + } + pts.push_back (Pt2i (x1, y1)); + } + } + else if (dy == 0 && dx > 0) + while (x1 <= x2) pts.push_back (Pt2i (x1++, y1 - offset)); + + if (dx < 0 && dy > 0) // Quadrant 2 + { + if (-dx >= dy) // Octant 4 + { + e = dx - 1; + dx = dx * 2; + dy = dy * 2; + if (offset < 0) floors = - (steps * dy + e) / dx; + else floors = (e + 1 - steps * dy) / dx; // > 0 + e -= steps * dy + floors * dx; + x1 += steps; + y1 += offset - floors; + x2 += steps; + // y2 += offset - floors; // useless + while (x1 > x2) + { + pts.push_back (Pt2i (x1, y1)); + x1 --; + e += dy; + if (e >= 0) + { + y1 ++; + e += dx; + } + } + pts.push_back (Pt2i (x1, y1)); + } + else if (-dx < dy) // Octant 3 + { + e = dy; + dx = dx * 2; + dy = dy * 2; + if (offset < 0) floors = (1 - e - steps * dx) / dy; + else floors = (e - steps * dx) / dy; + e += floors * dy + steps * dx; + x1 += offset - floors; + y1 += steps; + // x2 += offset - floors; // useless + y2 += steps; + while (y1 < y2) + { + pts.push_back (Pt2i (x1, y1)); + y1 ++; + e += dx; + if (e <= 0) + { + x1 --; + e += dy; + } + } + pts.push_back (Pt2i (x1, y1)); + } + } + + else if (dx < 0 && dy < 0) // Quadrant 3 + { + if (dx <= dy) // Octant 5 + { + e = dx - 1; + dx = dx * 2; + dy = dy * 2; + if (offset < 0) floors = (steps * dy - e - 1) / dx; + else floors = (steps * dy + e) / dx; + e += floors * dx - steps * dy; + x1 -= steps; + y1 += offset - floors; + x2 -= steps; + // y2 += offset - floors; // useless + while (x1 > x2) + { + pts.push_back (Pt2i (x1, y1)); + x1 --; + e -= dy; + if (e >= 0) + { + y1 --; + e += dx; + } + } + pts.push_back (Pt2i (x1, y1)); + } + else // Octant 6 + { + e = dy; + dx = dx * 2; + dy = dy * 2; + if (offset < 0) floors = (e - steps * dx) / dy; // > 0 + else floors = - (steps * dx + e + 1) / dy; + e += floors * dy + steps * dx; + x1 -= offset + floors; + y1 += steps; + y2 += steps; + // x2 -= offset - floors; // useless + while (y1 > y2) + { + pts.push_back (Pt2i (x1, y1)); + y1 --; + e -= dx; + if (e >= 0) + { + x1 --; + e += dy; + } + } + pts.push_back (Pt2i (x1, y1)); + } + } + else if (dy == 0 && dx < 0) + while (x1 >= x2) pts.push_back (Pt2i (x1--, y1 + offset)); + else if (dx == 0 && dy > 0) + while (y1 <= y2) pts.push_back (Pt2i (x1 + offset, y1++)); + else if (dx == 0 && dy < 0) + while(y1 >= y2) pts.push_back (Pt2i (x1 - offset, y1--)); + return pts; +} diff --git a/Expes/Testers/TestLines/ImageTools/pt2i.h b/Expes/Testers/TestLines/ImageTools/pt2i.h new file mode 100644 index 0000000..57ac7ce --- /dev/null +++ b/Expes/Testers/TestLines/ImageTools/pt2i.h @@ -0,0 +1,195 @@ +#ifndef PT2I_H +#define PT2I_H + +#include "vr2i.h" +#include <vector> + + +/** + * @class Pt2i pt2i.h + * \brief Point in the digital plane. + */ +class Pt2i +{ +public: + + /** + * \brief Creates a point at origin. + */ + Pt2i (); + + /** + * \brief Creates a point from coordinate values. + * @param x X-coordinate value. + * @param y Y-coordinate value. + */ + Pt2i (int x, int y); + + /** + * \brief Creates a point, coincident to given point. + * @param p Given point. + */ + Pt2i (const Pt2i &p); + + /** + * \brief Deletes the point. + */ + ~Pt2i () { } + + /** + * \brief Returns the X-coordinate value. + */ + inline int x () const { return xp; } + + /** + * \brief Returns the Y-coordinate value. + */ + inline int y () const { return yp; } + + /** + * \brief Returns the nth coordinate value. + * @param n Coordinate index. + */ + inline int get (int n) const { return (n ? yp : xp); } + + /** + * \brief Sets the point coordinates. + * @param x New X-coordinate value. + * @param y New Y-coordinate value. + */ + inline void set (int x, int y) { xp = x; yp = y; } + + /** + * \brief Sets the point coordinates. + * @param p Point to copy. + */ + inline void set (const Pt2i &p) { xp = p.xp; yp = p.yp; } + + /** + * \brief Checks equivalence to a reference point. + * @param p Reference point. + */ + inline bool equals (Pt2i p) const { return (p.xp == xp && p.yp == yp); } + + /** + * \brief Returns the manhattan distance to another point. + * @param p Distant point. + */ + inline int manhattan (Pt2i p) const { + return (((p.xp > xp) ? p.xp - xp : xp - p.xp) + + ((p.yp > yp) ? p.yp - yp : yp - p.yp)); } + + /** + * \brief Returns the chessboard distance to another point. + * @param p Distant point. + */ + inline int chessboard (Pt2i p) const { + int dx = (p.xp > xp ? p.xp - xp : xp - p.xp); + int dy = (p.yp > yp ? p.yp - yp : yp - p.yp); + return (dx > dy ? dx : dy); } + + /** + * \brief Checks whether the point is connected (chessboard) to the given one. + * @param p the given point. + */ + inline bool isConnectedTo (Pt2i p) const { + return ((p.xp - xp <= 1) && (xp - p.xp <= 1) + && (p.yp - yp <= 1) && (yp - p.yp <= 1)); } + + /** + * Checks whether the point is colinear to two other points. + * @param p1 First distinct point. + * @param p2 Second distinct point. + */ + inline bool colinearTo (const Pt2i &p1, const Pt2i &p2) const { + return ((p1.xp - xp) * (p2.yp - yp) == (p2.xp - xp) * (p1.yp - yp)); } + + /** + * Checks whether the point is to the left of the segment (p1p2). + * @param p1 Segment start point. + * @param p2 Segment end point. + */ + inline bool toLeft (const Pt2i &p1, const Pt2i &p2) const { + return ((p1.xp - xp) * (p2.yp - yp) > (p2.xp - xp) * (p1.yp - yp)); } + + /** + * Checks whether the point is to the left or on the segment (p1p2). + * @param p1 Segment start point. + * @param p2 Segment end point. + */ + inline bool toLeftOrOn (const Pt2i &p1, const Pt2i &p2) const { + return ((p1.xp - xp) * (p2.yp - yp) >= (p2.xp - xp) * (p1.yp - yp)); } + + /** + * \brief Check if the point belongs to the triangle with given vertices. + * @param p1 First triangle vertex. + * @param p2 Second triangle vertex. + * @param p3 Third triangle vertex. + */ + bool inTriangle (Pt2i p1, Pt2i p2, Pt2i p3) const; + + /** + * \brief Returns the vector to given point. + * @param p Given point. + */ + Vr2i vectorTo (Pt2i p) const; + + /** + * \brief Returns the straight segment to given point as a point array. + * @param p Given point. + * @param n Size of returned array. + */ + Pt2i *drawing (const Pt2i p, int *n) const; + + /** + * \brief Returns the clipped straight segment to given point. + * NB: Always returns a non-null array. + * @param p Given point. + * @param left Position of left clip bound. + * @param low Position of lower clip bound. + * @param right Position of right clip bound. + * @param up Position of upper clip bound. + * @param n Size of returned array. + */ + Pt2i *clipLine (const Pt2i p, int left, int low, int right, int up, + int *n) const; + + /** + * \brief Adds points of segment to a distant point to given vector. + * @param line Vector of points to complete. + * @param p Distant point. + */ + void draw (std::vector<Pt2i> &line, Pt2i p) const; + + /** + * \brief Returns the path of the straight segment to given point. + * The path is composed of relative position between adjacent points. + * @param p Given point. + * @param n Size of returned array. + */ + Pt2i *pathTo (Pt2i p, int *n) const; + + /** + * \brief Returns steps location of the straight segment to given point. + * @param p Given point. + * @param n Size of returned array. + */ + bool *stepsTo (Pt2i p, int *n) const; + + /** + * \brief Returns an orthogonal segment to the segment to given point. + * @param p2 Given point. + * @param offset Orthogonal shift. + */ + std::vector<Pt2i> drawOrtho (const Pt2i p2, int shift) const; + + +protected: + + /** Point abscissae. */ + int xp; + /** Point ordinate. */ + int yp; +}; + +#endif diff --git a/Expes/Testers/TestLines/ImageTools/rechanged b/Expes/Testers/TestLines/ImageTools/rechanged new file mode 100644 index 0000000..b3cfe00 --- /dev/null +++ b/Expes/Testers/TestLines/ImageTools/rechanged @@ -0,0 +1,13 @@ +cmp absrat.h ~/tmp/ASD/ImageTools/absrat.h +cmp digitalstraightline.cpp ~/tmp/ASD/ImageTools/digitalstraightline.cpp +cmp digitalstraightline.h ~/tmp/ASD/ImageTools/digitalstraightline.h +cmp digitalstraightsegment.cpp ~/tmp/ASD/ImageTools/digitalstraightsegment.cpp +cmp digitalstraightsegment.h ~/tmp/ASD/ImageTools/digitalstraightsegment.h +cmp edist.cpp ~/tmp/ASD/ImageTools/edist.cpp +cmp edist.h ~/tmp/ASD/ImageTools/edist.h +cmp pt2i.cpp ~/tmp/ASD/ImageTools/pt2i.cpp +cmp pt2i.h ~/tmp/ASD/ImageTools/pt2i.h +cmp vmap.cpp ~/tmp/ASD/ImageTools/vmap.cpp +cmp vmap.h ~/tmp/ASD/ImageTools/vmap.h +cmp vr2i.cpp ~/tmp/ASD/ImageTools/vr2i.cpp +cmp vr2i.h ~/tmp/ASD/ImageTools/vr2i.h diff --git a/Expes/Testers/TestLines/ImageTools/vmap.cpp b/Expes/Testers/TestLines/ImageTools/vmap.cpp new file mode 100644 index 0000000..25861d0 --- /dev/null +++ b/Expes/Testers/TestLines/ImageTools/vmap.cpp @@ -0,0 +1,769 @@ +#include "vmap.h" +#include <cmath> +#include <inttypes.h> + + +const int VMap::TYPE_UNKNOWN = -1; +const int VMap::TYPE_SOBEL_3X3 = 0; +const int VMap::TYPE_SOBEL_5X5 = 1; + +const int VMap::NEAR_SQ_ANGLE = 80; // 80% (roughly 25 degrees) +const int VMap::DEFAULT_GRADIENT_THRESHOLD = 20; +const int VMap::DEFAULT_GRADIENT_RESOLUTION = 100; + +const int VMap::MAX_BOWL = 20; +const int VMap::NB_DILATIONS = 5; +const int VMap::DEFAULT_DILATION = 4; + + + +VMap::VMap (int width, int height, unsigned char *data, int type) +{ + this->width = width; + this->height = height; + this->gtype = type; + init (); + imap = new int[width * height]; + if (type == TYPE_SOBEL_5X5) + { + buildSobel5x5Map (data); + for (int i = 0; i < width * height; i++) + imap[i] = (int) sqrt (map[i].norm2 ()); + gmagThreshold *= gradientThreshold; + } + else if (type == TYPE_SOBEL_3X3) + { + buildSobel3x3Map (data); + for (int i = 0; i < width * height; i++) + imap[i] = (int) sqrt (map[i].norm2 ()); + gmagThreshold *= gradientThreshold; + } +} + + +VMap::VMap (int width, int height, int *data, int type) +{ + this->width = width; + this->height = height; + this->gtype = type; + init (); + imap = new int[width * height]; + if (type == TYPE_SOBEL_5X5) + { + buildSobel5x5Map (data); + for (int i = 0; i < width * height; i++) + imap[i] = (int) sqrt (map[i].norm2 ()); + gmagThreshold *= gradientThreshold; + } + else if (type == TYPE_SOBEL_3X3) + { + buildSobel3x3Map (data); + for (int i = 0; i < width * height; i++) + imap[i] = (int) sqrt (map[i].norm2 ()); + gmagThreshold *= gradientThreshold; + } +} + + +VMap::VMap (int width, int height, int **data, int type) +{ + this->width = width; + this->height = height; + this->gtype = type; + init (); + imap = new int[width * height]; + if (type == TYPE_SOBEL_5X5) + { + buildSobel5x5Map (data); + for (int i = 0; i < width * height; i++) + imap[i] = (int) sqrt (map[i].norm2 ()); + gmagThreshold *= gradientThreshold; + } + else if (type == TYPE_SOBEL_3X3) + { + buildSobel3x3Map (data); + for (int i = 0; i < width * height; i++) + imap[i] = (int) sqrt (map[i].norm2 ()); + gmagThreshold *= gradientThreshold; + } +} + + +VMap::VMap (int width, int height, Vr2i *map) +{ + this->width = width; + this->height = height; + this->gtype = TYPE_UNKNOWN; + this->map = map; + init (); + imap = new int[width * height]; + for (int i = 0; i < width * height; i++) + imap[i] = (int) sqrt (map[i].norm2 ()); + gmagThreshold *= gradientThreshold; +} + + +VMap::~VMap () +{ + delete [] map; + delete [] imap; + delete [] mask; + delete [] dilations; + delete [] bowl; +} + + +void VMap::init () +{ + gradientThreshold = DEFAULT_GRADIENT_THRESHOLD; + gmagThreshold = gradientThreshold; + gradres = DEFAULT_GRADIENT_RESOLUTION; + mask = new bool[width * height]; + for (int i = 0; i < width * height; i++) mask[i] = false; + masking = false; + angleThreshold = NEAR_SQ_ANGLE; + orientedGradient = true; + bowl = new Vr2i[MAX_BOWL]; + bowl[0].set (1, 0); + bowl[1].set (0, 1); + bowl[2].set (-1, 0); + bowl[3].set (0, -1); + bowl[4].set (1, 1); + bowl[5].set (1, -1); + bowl[6].set (-1, -1); + bowl[7].set (-1, 1); + bowl[8].set (2, 0); + bowl[9].set (0, 2); + bowl[10].set (-2, 0); + bowl[11].set (0, -2); + bowl[12].set (2, 1); + bowl[13].set (1, 2); + bowl[14].set (-1, 2); + bowl[15].set (-2, 1); + bowl[16].set (-2, -1); + bowl[17].set (-1, -2); + bowl[18].set (1, -2); + bowl[19].set (2, -1); + dilations = new int[NB_DILATIONS]; + dilations[0] = 0; + dilations[1] = 4; + dilations[2] = 8; + dilations[3] = 12; + dilations[4] = 20; + maskDilation = DEFAULT_DILATION; +} + + +void VMap::buildSobel3x3Map (unsigned char *data) +{ + map = new Vr2i[width * height]; + Vr2i *gm = map; + + for (int j = 0; j < width; j++) + { + gm->set (0, 0); + gm++; + } + for (int i = 1; i < height - 1; i++) + { + gm->set (0, 0); + gm++; + for (int j = 1; j < width - 1; j++) + { + gm->set (data[(i - 1) * width + j + 1] + + 2 * data[i * width + j + 1] + + data[(i + 1) * width + j + 1] + - data[(i - 1) * width + j - 1] + - 2 * data[i * width + j - 1] + - data[(i + 1) * width + j - 1], + data[(i + 1) * width + j - 1] + + 2 * data[(i + 1) * width + j] + + data[(i + 1) * width + j + 1] + - data[(i - 1) * width + j - 1] + - 2 * data[(i - 1) * width + j] + - data[(i - 1) * width + j + 1]); + gm++; + } + gm->set (0, 0); + gm++; + } + for (int j = 0; j < width; j++) + { + gm->set (0, 0); + gm++; + } +} + + +void VMap::buildSobel3x3Map (int *data) +{ + map = new Vr2i[width * height]; + Vr2i *gm = map; + + for (int j = 0; j < width; j++) + { + gm->set (0, 0); + gm++; + } + for (int i = 1; i < height - 1; i++) + { + gm->set (0, 0); + gm++; + for (int j = 1; j < width - 1; j++) + { + gm->set (data[(i - 1) * width + j + 1] + + 2 * data[i * width + j + 1] + + data[(i + 1) * width + j + 1] + - data[(i - 1) * width + j - 1] + - 2 * data[i * width + j - 1] + - data[(i + 1) * width + j - 1], + data[(i + 1) * width + j - 1] + + 2 * data[(i + 1) * width + j] + + data[(i + 1) * width + j + 1] + - data[(i - 1) * width + j - 1] + - 2 * data[(i - 1) * width + j] + - data[(i - 1) * width + j + 1]); + gm++; + } + gm->set (0, 0); + gm++; + } + for (int j = 0; j < width; j++) + { + gm->set (0, 0); + gm++; + } +} + + +void VMap::buildSobel3x3Map (int **data) +{ + map = new Vr2i[width * height]; + Vr2i *gm = map; + + for (int j = 0; j < width; j++) + { + gm->set (0, 0); + gm++; + } + for (int i = 1; i < height - 1; i++) + { + gm->set (0, 0); + gm++; + for (int j = 1; j < width - 1; j++) + { + gm->set (data[i-1][j+1] + 2 * data[i][j+1] + data[i+1][j+1] + - data[i-1][j-1] - 2 * data[i][j-1] - data[i+1][j-1], + data[i+1][j-1] + 2 * data[i+1][j] + data[i+1][j+1] + - data[i-1][j-1] - 2 * data[i-1][j] - data[i-1][j+1]); + gm++; + } + gm->set (0, 0); + gm++; + } + for (int j = 0; j < width; j++) + { + gm->set (0, 0); + gm++; + } +} + + +void VMap::buildSobel5x5Map (unsigned char *data) +{ + map = new Vr2i[width * height]; + Vr2i *gm = map; + + for (int j = 0; j < 2 * width; j++) + { + gm->set (0, 0); + gm++; + } + for (int i = 2; i < height - 2; i++) + { + gm->set (0, 0); + gm++; + gm->set (0, 0); + gm++; + for (int j = 2; j < width - 2; j++) + { + gm->set (5 * data[(i - 2) * width + j + 2] + + 8 * data[(i - 1) * width + j + 2] + + 10 * data[i * width + j + 2] + + 8 * data[(i + 1) * width + j + 2] + + 5 * data[(i + 2) * width + j + 2] + + 4 * data[(i - 2) * width + j + 1] + + 10 * data[(i - 1) * width + j + 1] + + 20 * data[i * width + j + 1] + + 10 * data[(i + 1) * width + j + 1] + + 4 * data[(i + 2) * width + j + 1] + - 4 * data[(i - 2) * width + j - 1] + - 10 * data[(i - 1) * width + j - 1] + - 20 * data[i * width + j - 1] + - 10 * data[(i + 1) * width + j - 1] + - 4 * data[(i + 2) * width + j - 1] + - 5 * data[(i - 2) * width + j - 2] + - 8 * data[(i - 1) * width + j - 2] + - 10 * data[i * width + j - 2] + - 8 * data[(i + 1) * width + j - 2] + - 5 * data[(i + 2) * width + j - 2], + 5 * data[(i + 2) * width + j - 2] + + 8 * data[(i + 2) * width + j - 1] + + 10 * data[(i + 2) * width + j] + + 8 * data[(i + 2) * width + j + 1] + + 5 * data[(i + 2) * width + j + 2] + + 4 * data[(i + 1) * width + j - 2] + + 10 * data[(i + 1) * width + j - 1] + + 20 * data[(i + 1) * width + j] + + 10 * data[(i + 1) * width + j + 1] + + 4 * data[(i + 1) * width + j + 2] + - 4 * data[(i - 1) * width + j - 2] + - 10 * data[(i - 1) * width + j - 1] + - 20 * data[(i - 1) * width + j] + - 10 * data[(i - 1) * width + j + 1] + - 4 * data[(i - 1) * width + j + 2] + - 5 * data[(i - 2) * width + j - 2] + - 8 * data[(i - 2) * width + j - 1] + - 10 * data[(i - 2) * width + j] + - 8 * data[(i - 2) * width + j + 1] + - 5 * data[(i - 2) * width + j + 2]); + gm++; + } + gm->set (0, 0); + gm++; + gm->set (0, 0); + gm++; + } + for (int j = 0; j < 2 * width; j++) + { + gm->set (0, 0); + gm++; + } +} + + +void VMap::buildSobel5x5Map (int *data) +{ + map = new Vr2i[width * height]; + Vr2i *gm = map; + + for (int j = 0; j < 2 * width; j++) + { + gm->set (0, 0); + gm++; + } + for (int i = 2; i < height - 2; i++) + { + gm->set (0, 0); + gm++; + gm->set (0, 0); + gm++; + for (int j = 2; j < width - 2; j++) + { + gm->set (5 * data[(i - 2) * width + j + 2] + + 8 * data[(i - 1) * width + j + 2] + + 10 * data[i * width + j + 2] + + 8 * data[(i + 1) * width + j + 2] + + 5 * data[(i + 2) * width + j + 2] + + 4 * data[(i - 2) * width + j + 1] + + 10 * data[(i - 1) * width + j + 1] + + 20 * data[i * width + j + 1] + + 10 * data[(i + 1) * width + j + 1] + + 4 * data[(i + 2) * width + j + 1] + - 4 * data[(i - 2) * width + j - 1] + - 10 * data[(i - 1) * width + j - 1] + - 20 * data[i * width + j - 1] + - 10 * data[(i + 1) * width + j - 1] + - 4 * data[(i + 2) * width + j - 1] + - 5 * data[(i - 2) * width + j - 2] + - 8 * data[(i - 1) * width + j - 2] + - 10 * data[i * width + j - 2] + - 8 * data[(i + 1) * width + j - 2] + - 5 * data[(i + 2) * width + j - 2], + 5 * data[(i + 2) * width + j - 2] + + 8 * data[(i + 2) * width + j - 1] + + 10 * data[(i + 2) * width + j] + + 8 * data[(i + 2) * width + j + 1] + + 5 * data[(i + 2) * width + j + 2] + + 4 * data[(i + 1) * width + j - 2] + + 10 * data[(i + 1) * width + j - 1] + + 20 * data[(i + 1) * width + j] + + 10 * data[(i + 1) * width + j + 1] + + 4 * data[(i + 1) * width + j + 2] + - 4 * data[(i - 1) * width + j - 2] + - 10 * data[(i - 1) * width + j - 1] + - 20 * data[(i - 1) * width + j] + - 10 * data[(i - 1) * width + j + 1] + - 4 * data[(i - 1) * width + j + 2] + - 5 * data[(i - 2) * width + j - 2] + - 8 * data[(i - 2) * width + j - 1] + - 10 * data[(i - 2) * width + j] + - 8 * data[(i - 2) * width + j + 1] + - 5 * data[(i - 2) * width + j + 2]); + gm++; + } + gm->set (0, 0); + gm++; + gm->set (0, 0); + gm++; + } + for (int j = 0; j < 2 * width; j++) + { + gm->set (0, 0); + gm++; + } +} + + +void VMap::buildSobel5x5Map (int **data) +{ + map = new Vr2i[width * height]; + Vr2i *gm = map; + + for (int j = 0; j < 2 * width; j++) + { + gm->set (0, 0); + gm++; + } + for (int i = 2; i < height - 2; i++) + { + gm->set (0, 0); + gm++; + gm->set (0, 0); + gm++; + for (int j = 2; j < width - 2; j++) + { + gm->set ( + 5 * data[i-2][j+2] + 8 * data[i-1][j+2] + 10 * data[i][j+2] + + 8 * data[i+1][j+2] + 5 * data[i+2][j+2] + + 4 * data[i-2][j+1] + 10 * data[i-1][j+1] + 20 * data[i][j+1] + + 10 * data[i+1][j+1] + 4 * data[i+2][j+1] + - 4 * data[i-2][j-1] - 10 * data[i-1][j-1] - 20 * data[i][j-1] + - 10 * data[i+1][j-1] - 4 * data[i+2][j-1] + - 5 * data[i-2][j-2] - 8 * data[i-1][j-2] - 10 * data[i][j-2] + - 8 * data[i+1][j-2] - 5 * data[i+2][j-2], + 5 * data[i+2][j-2] + 8 * data[i+2][j-1] + 10 * data[i+2][j] + + 8 * data[i+2][j+1] + 5 * data[i+2][j+2] + + 4 * data[i+1][j-2] + 10 * data[i+1][j-1] + 20 * data[i+1][j] + + 10 * data[i+1][j+1] + 4 * data[i+1][j+2] + - 4 * data[i-1][j-2] - 10 * data[i-1][j-1] - 20 * data[i-1][j] + - 10 * data[i-1][j+1] - 4 * data[i-1][j+2] + - 5 * data[i-2][j-2] - 8 * data[i-2][j-1] - 10 * data[i-2][j] + - 8 * data[i-2][j+1] - 5 * data[i-2][j+2]); + gm++; + } + gm->set (0, 0); + gm++; + gm->set (0, 0); + gm++; + } + for (int j = 0; j < 2 * width; j++) + { + gm->set (0, 0); + gm++; + } +} + + +int VMap::sqNorm (int i, int j) const +{ + return (map[j * width + i].norm2 ()); +} + + +int VMap::sqNorm (Pt2i p) const +{ + return (map[p.y () * width + p.x ()].norm2 ()); +} + + +int VMap::largestIn (const std::vector<Pt2i> &pix) const +{ + if (pix.empty ()) return (-1); + + int imax = -1; + std::vector<Pt2i>::const_iterator pt = pix.begin (); + int gmax = imap[pt->y() * width + pt->x()]; + if (gmax < gmagThreshold) gmax = gmagThreshold; + + int i = 0; + while (pt != pix.end ()) + { + int g = imap[pt->y() * width + pt->x()]; + if (g > gmax) + { + gmax = g; + imax = i; + } + pt ++; + i ++; + } + return (imax == i - 1 ? -1 : imax); +} + + +int VMap::keepFreeElementsIn (const std::vector<Pt2i> &pix, + int n, int *ind) const +{ + int i = 0; + while (i < n) + { + if (mask[pix[ind[i]].y () * width + pix[ind[i]].x ()]) ind[i] = ind[--n]; + else i++; + } + return (n); +} + + +int VMap::keepContrastedMax (int *lmax, int n, int *in) const +{ + if (n == 0) return 0; + int *min = new int[n-1]; + bool *fired = new bool[n]; + int nbfired = 0; + int sleft = 0; + + // Clears the list of fired max + for (int i = 0; i < n; i++) fired[i] = false; + + // Computes the ponds depth + for (int i = 0; i < n - 1; i++) + { + min[i] = in[lmax[i]]; + for (int j = lmax[i] + 1; j < lmax[i+1]; j++) + if (in[j] < min[i]) min[i] = in[j]; + } + // For each ponds + for (int i = 0; i < n - 1; i++) + { + // if the right summit is lower + if (in[lmax[i+1]] < in[lmax[sleft]]) + { + if (in[lmax[i+1]] - min[i] < gradres) // gradient resolution + { + fired[i+1] = true; + nbfired ++; + if (i < n - 2) if (min[i+1] < min[i]) min[i+1] = min[i]; + } + } + // if the left summit is lower + else + { + if (in[lmax[sleft]] - min[i] < gradres) // gradient resolution + { + fired[sleft] = true; + nbfired ++; + sleft = i + 1; + } + } + } + // Pruning + int i = 0, j = 0; + while (i < n && ! fired[i++]) j++; + while (i < n) + { + while (i < n && fired[i]) i++; + if (i < n) lmax[j++] = lmax[i++]; + } + return (n - nbfired); +} + + +int VMap::keepDirectedElementsAs (const std::vector<Pt2i> &pix, + int n, int *ind, const Vr2i &ref) const +{ + int vx = ref.x (); + int vy = ref.y (); + int i = 0; + while (i < n) + { + Vr2i gr = map[pix[ind[i]].y () * width + pix[ind[i]].x ()]; + if (vx * gr.x () + vy * gr.y () <= 0) ind[i] = ind[--n]; + else i++; + } + return (n); +} + + +int VMap::keepOrientedElementsAs (const std::vector<Pt2i> &pix, + int n, int *ind, const Vr2i &ref) const +{ + int64_t vx = (int64_t) ref.x (); + int64_t vy = (int64_t) ref.y (); + int64_t vn2 = (int64_t) vx * (int64_t) vx + (int64_t) vy * (int64_t) vy; + + int i = 0; + while (i < n) + { + Pt2i p = pix.at (ind[i]); + Vr2i gr = map[p.y () * width + p.x ()]; + int64_t gx = (int64_t) gr.x (); + int64_t gy = (int64_t) gr.y (); + if ((vx * vx * gx * gx + vy * vy * gy * gy + 2 * vx * vy * gx * gy) * 100 + < vn2 * (gx * gx + gy * gy) * angleThreshold) + ind[i] = ind[--n]; + else i++; + } + return (n); +} + + +int VMap::localMax (int *lmax, const std::vector<Pt2i> &pix) const +{ + // Builds the gradient norm signal + int n = (int) pix.size (); + int *gn = new int[n]; + int i = 0; + std::vector<Pt2i>::const_iterator it = pix.begin (); + while (it != pix.end ()) gn[i++] = magn (*it++); + + // Gets the local maxima + int count = searchLocalMax (lmax, n, gn); + + // Prunes the low contrasted local maxima + count = keepContrastedMax (lmax, count, gn); + + // Prunes the already selected candidates + count = keepFreeElementsIn (pix, count, lmax); + + // Sorts candidates by gradient magnitude + sortMax (lmax, count, gn); + + delete gn; + return count; +} + + +int VMap::localMax (int *lmax, const std::vector<Pt2i> &pix, + const Vr2i &gref) const +{ + // Builds the gradient norm signal + int n = (int) pix.size (); + int *gn = new int[n]; + int i = 0; + std::vector<Pt2i>::const_iterator it = pix.begin (); + while (it != pix.end ()) gn[i++] = magn (*it++); + + // Gets the local maxima + int count = searchLocalMax (lmax, n, gn); + + // Prunes the already selected candidates + if (masking) + count = keepFreeElementsIn (pix, count, lmax); + + // Prunes the candidates with opposite gradient + if (orientedGradient) + count = keepDirectedElementsAs (pix, count, lmax, gref); + + // Prunes the candidates wrongly oriented + count = keepOrientedElementsAs (pix, count, lmax, gref); + + // Sorts candidates by gradient magnitude + sortMax (lmax, count, gn); + + delete gn; + return count; +} + + +void VMap::incGradientThreshold (int inc) +{ + gradientThreshold += inc; + if (gradientThreshold < 0) gradientThreshold = 0; + if (gradientThreshold > 255) gradientThreshold = 255; + gmagThreshold = gradientThreshold; + if (gtype <= TYPE_SOBEL_5X5) gmagThreshold *= gmagThreshold; +} + + +void VMap::incLocalMaxGradientResolution (int inc) +{ + gradres += inc * 5; + if (gradres < 0) gradres = 0; +} + + +int VMap::searchLocalMax (int *lmax, int n, int *in) const +{ + int offset = 0; + int count = 0; + bool up = true; + + // Gets the first distinct value from start + while (offset < n - 1 && in[offset] == in[0]) + { + if (in[offset] - in[offset + 1] < 0) + { + up = true; + break; + } + if (in[offset] - in[offset + 1] > 0) + { + up = false; + break; + } + offset++; + } + + for(int i = offset; i < n - 1; i++) + { + if (up) + { + if ((in[i + 1] - in[i]) < 0) + { + up = false; + int k = i; + while (in[k - 1] == in[i]) k--; + if (in[k + (i - k) / 2] > gmagThreshold) + lmax[count++] = k + (i - k) / 2; + } + } + else + if (in[i + 1] - in[i] > 0) up = true; + } + return count; +} + + +void VMap::sortMax (int *lmax, int n, int *val) const +{ + for (int i = 1; i < n; i ++) + { + int j = i, tmp = lmax[i]; + bool on = true; + while (on && j > 0) + { + if (val[tmp] > val[lmax[j-1]]) + { + lmax[j] = lmax[j-1]; + j --; + } + else on = false; + } + lmax[j] = tmp; + } +} + + +void VMap::clearMask () +{ + for (int i = 0; i < width * height; i++) mask[i] = false; +} + + +void VMap::setMask (const std::vector<Pt2i> &pts) +{ + std::vector<Pt2i>::const_iterator it = pts.begin (); + while (it != pts.end ()) + { + Pt2i pt = *it++; + mask[pt.y () * width + pt.x ()] = true; + for (int i = 0; i < dilations[maskDilation]; i++) + { + int x = pt.x () + bowl[i].x (); + int y = pt.y () + bowl[i].y (); + if (x >= 0 && x < width && y >= 0 && y < height) + mask[y * width + x] = true; + } + } +} diff --git a/Expes/Testers/TestLines/ImageTools/vmap.h b/Expes/Testers/TestLines/ImageTools/vmap.h new file mode 100644 index 0000000..a94c2e5 --- /dev/null +++ b/Expes/Testers/TestLines/ImageTools/vmap.h @@ -0,0 +1,393 @@ +#ifndef VMAP_H +#define VMAP_H + +#include "pt2i.h" + + +/** + * @class VMap vmap.h + * \brief Map of 2D vectors. + */ +class VMap +{ +public: + + /** Gradient extraction method : Undeterminated. */ + static const int TYPE_UNKNOWN; + /** Gradient extraction method : Sobel with 3x3 kernel. */ + static const int TYPE_SOBEL_3X3; + /** Gradient extraction method : Sobel with 5x5 kernel. */ + static const int TYPE_SOBEL_5X5; + + + /** + * \brief Creates a gradient map from scalar data. + * @param width Map width. + * @param height Map height. + * @param data Scalar data array. + * @param type Gradient extraction method (default is Sobel with 3x3 kernel). + */ + VMap (int width, int height, unsigned char *data, int type = 0); + + /** + * \brief Creates a gradient map from scalar data. + * @param width Map width. + * @param height Map height. + * @param data Scalar data array. + * @param type Gradient extraction method (default is Sobel with 3x3 kernel). + */ + VMap (int width, int height, int *data, int type = 0); + + /** + * \brief Creates a gradient map from scalar data. + * @param width Map width. + * @param height Map height. + * @param data Scalar data bi-dimensional array. + */ + VMap (int width, int height, int **data, int type = 0); + + /** + * \brief Creates a gradient map from given vector map. + * @param width Map width. + * @param height Map height. + * @param map Vector map. + */ + VMap (int width, int height, Vr2i *map); + + /** + * \brief Deletes the vector map. + */ + ~VMap (); + + /** + * \brief Returns the map width. + */ + inline int getWidth () const { return width; } + + /** + * \brief Returns the map height. + */ + inline int getHeight () const { return height; } + + /** + * \brief Returns the maximal value between map width or height. + */ + inline int getHeightWidthMax () const { + return (height > width ? height : width); } + + /** + * \brief Checks whether given pixel lies within the map bounds. + * @param i Column index of the pixel. + * @param j Raw index of the pixel. + */ + inline bool contains (int i, int j) + { + return (i >= 0 && i < width && j >= 0 && j < height); + } + + /** + * \brief Returns a pointer to the vectors of the map. + */ + inline Vr2i *getVectorMap () const { return (map); } + + /** + * \brief Returns the vector at pixel (i,j). + * @param i Column index of the pixel. + * @param j Raw index of the pixel. + */ + inline Vr2i getValue (int i, int j) const { return (map[j * width + i]); } + + /** + * \brief Returns the vector at given position. + * @param p Pixel position. + */ + inline Vr2i getValue (Pt2i p) const { return (map[p.y () * width + p.x ()]); } + + /** + * \brief Returns the squared norm of the vector magnitude at pixel (i,j). + * @param i Column index of the pixel. + * @param j Raw index of the pixel. + */ + int sqNorm (int i, int j) const; + + /** + * \brief Returns the squared norm of the vector magnitude at given position. + * @param p Pixel position. + */ + int sqNorm (Pt2i p) const; + + /** + * \brief Returns comparable norm of the vector magnitude at pixel (i,j). + * @param i Column index of the pixel. + * @param j Raw index of the pixel. + */ + inline int magn (int i, int j) const { return (imap[j * width + i]); } + + /** + * \brief Returns comparable norm of the vector magnitude at given position. + * @param p Pixel position. + */ + inline int magn (Pt2i p) const { return (imap[p.y () * width + p.x ()]); } + + /** + * \brief Returns the index of the largest vector at given positions. + * First and last points of the list are not accepted. + * A gradient minimal threshold is set for the test. + * Returns -1 if no max is found. + * @param pix List of pixels. + */ + int largestIn (const std::vector<Pt2i> &pix) const; + + /** + * \brief Keeps elements that are not already selected (in the mask array). + * Returns the number of remaining elements in the selection. + * @param pix Input array of scanned pixels. + * @param n Initial size of the selection of pixels. + * @param ind Selection of pixels. + */ + int keepFreeElementsIn (const std::vector<Pt2i> &pix, int n, int *ind) const; + + /** + * \brief Searches local gradient maxima values. + * Returns the count of perceptible local maxima found. + * @param lmax Local max index array. + * @param n Count of input max values. + * @param in Array of input values. + */ + int keepContrastedMax (int *lmax, int n, int *in) const; + + /** + * \brief Keeps elements with a reference direction. + * Keeps elements with the same direction as a reference vector + * in a selection of pixels (positive scalar product). + * Returns the number of remaining elements in the selection. + * @param pix Input array of scanned pixels. + * @param n Initial size of the selection of pixels. + * @param ind Selection of pixels. + * @param ref Reference vector for direction test. + */ + int keepDirectedElementsAs (const std::vector<Pt2i> &pix, + int n, int *ind, const Vr2i &ref) const; + + /** + * \brief Keeps elements with reference gradient magnitude. + * Keeps elements with gradient value near a reference vector + * in a selection of pixels. + * Relies on angleThreshold value for the test. + * Returns the number of remaining elements in the selection. + * @param pix Input array of scanned points. + * @param n Initial size of the selection of pixels. + * @param ind Selection of pixels. + * @param ref Reference vector for magnitude test. + */ + int keepOrientedElementsAs (const std::vector<Pt2i> &pix, + int n, int *ind, const Vr2i &ref) const; + + /** + * \brief Gets filtered and sorted local gradient maxima in a set of pixels. + * Local max already used are pruned. + * Returns the count of found gradient maxima. + * @param lmax Ouput local max index array. + * @param pix Input set of pixels to process. + */ + int localMax (int *lmax, const std::vector<Pt2i> &pix) const; + + /** + * \brief Gets filtered and sorted local oriented gradient maxima. + * Local maxima are filtered according to the gradient direction and sorted. + * Returns the count of found gradient maxima. + * @param lmax Local max index array. + * @param pix Input set of pixels to process. + * @param gref Gradient vector reference. + */ + int localMax (int *lmax, const std::vector<Pt2i> &pix, const Vr2i &gref) const; + + /** + * \brief Returns the gradient threshold value used for maxima detection. + */ + inline int getGradientThreshold () const { return (gradientThreshold); } + + /** + * \brief Increments the gradient threshold value used for maxima detection. + * @param inc Increment value. + */ + void incGradientThreshold (int inc); + + /** + * \brief Returns the gradient resolution value used for maxima filtering. + */ + inline int getLocalMaxGradientResolution () const { return (gradres); } + + /** + * \brief Increments the gradient resolution value used for maxima filtering. + * @param inc Increment direction (-1 or +1). + */ + void incLocalMaxGradientResolution (int inc); + + /** + * \brief Switches the direction constraint for local maxima selection. + */ + inline void switchOrientationConstraint () { + orientedGradient = ! orientedGradient; } + + /** + * \brief Switches the direction constraint for local maxima selection. + */ + inline bool isOrientationConstraintOn () const { return orientedGradient; } + + /** + * \brief Returns the occupancy mask contents. + */ + inline bool *getMask () const { return (mask); } + + /** + * \brief Clears the occupancy mask. + */ + void clearMask (); + + /** + * \brief Adds pixels to the occupancy mask. + * @param pts Vector of pixels. + */ + void setMask (const std::vector<Pt2i> &pts); + + /** + * \brief Sets mask activation on or off. + * @param status New activation status. + */ + inline void setMasking (bool status) { masking = status; } + + /** + * \brief Retuns the mask dilation size. + */ + inline int getMaskDilation () const { return (dilations[maskDilation]); } + + /** + * \brief Toggles the mask dilation size. + */ + inline void toggleMaskDilation () { + if (++maskDilation == NB_DILATIONS) maskDilation = 0; } + + /** + * \brief Tests the occupancy of a mask cell. + * @param pix Pixel to test in the mask. + */ + inline bool isFree (const Pt2i &pix) const { + return (! mask[pix.y () * width + pix.x ()]); } + + +private: + + /** Default value for near angular deviation tests. */ + static const int NEAR_SQ_ANGLE; + /** Default threshold value for the gradient selection. */ + static const int DEFAULT_GRADIENT_THRESHOLD; + /** Default threshold value for the gradient resolution (filtering). */ + static const int DEFAULT_GRADIENT_RESOLUTION; + /** Size of the maximal dilation bowl. */ + static const int MAX_BOWL; + /** Number of dilation types. */ + static const int NB_DILATIONS; + /** Default dilation for the points added to the mask. */ + static const int DEFAULT_DILATION; + + /** Image width. */ + int width; + /** Image height. */ + int height; + /** Gradient type. */ + int gtype; + /** Vector map. */ + Vr2i *map; + /** Magnitude map (squared norm). */ + int *imap; + + /** Effective value for the angular deviation test. */ + int angleThreshold; + /** Standardized gradient threshold for highest value detection. */ + int gradientThreshold; + /** Gradient magnitude threshold for highest value detection. */ + int gmagThreshold; + /** Gradient resolution threshold for local max filtering. */ + int gradres; + /** Direction constraint status for local gradient maxima. */ + bool orientedGradient; + + /** Occupancy mask. */ + bool *mask; + /** Flag indicating whether the occupancy mask is in use. */ + bool masking; + /** Type of dilation applied to the points added to the mask. */ + int maskDilation; + /** Number of neighbours in the applied dilation. */ + int *dilations; + /** Dilation bowl. */ + Vr2i *bowl; + + + /** + * \brief Initializes the internal data of the vector map. + */ + void init (); + + /** + * \brief Builds the vector map as a gradient map from provided data. + * Uses a Sobel 3x3 kernel by default. + * @param data Initial scalar data. + */ + void buildSobel3x3Map (unsigned char *data); + + /** + * \brief Builds the vector map as a gradient map from provided data. + * Uses a Sobel 3x3 kernel by default. + * @param data Initial scalar data. + */ + void buildSobel3x3Map (int *data); + + /** + * \brief Builds the vector map as a gradient map from provided data. + * Uses a Sobel 3x3 kernel by default. + * @param data Initial bi-dimensional scalar data. + */ + void buildSobel3x3Map (int **data); + + /** + * \brief Builds the vector map as a gradient map from provided data. + * Uses a Sobel 5x5 kernel. + * @param data Initial scalar data. + */ + void buildSobel5x5Map (unsigned char *data); + + /** + * \brief Builds the vector map as a gradient map from provided data. + * Uses a Sobel 5x5 kernel. + * @param data Initial scalar data. + */ + void buildSobel5x5Map (int *data); + + /** + * \brief Builds the vector map as a gradient map from provided data. + * Uses a Sobel 5x5 kernel. + * @param data Initial bi-dimensional scalar data. + */ + void buildSobel5x5Map (int **data); + + /** + * \brief Searches local gradient maxima values. + * Returns the count of local maxima found. + * @param lmax Local max index array. + * @param n Count of input values. + * @param in Array of input values. + */ + int searchLocalMax (int *lmax, int n, int *in) const; + + /** + * \brief Sorts the candidates array by highest magnitude. + * @param lmax Local max index array. + * @param n Size of index array. + * @param val Input values. + */ + void sortMax (int *lmax, int n, int *val) const; + +}; +#endif diff --git a/Expes/Testers/TestLines/ImageTools/vr2i.cpp b/Expes/Testers/TestLines/ImageTools/vr2i.cpp new file mode 100644 index 0000000..18f4258 --- /dev/null +++ b/Expes/Testers/TestLines/ImageTools/vr2i.cpp @@ -0,0 +1,70 @@ +#include "vr2i.h" + + +Vr2i::Vr2i () +{ + xv = 1; + yv = 0; +} + + +Vr2i::Vr2i (int x, int y) +{ + xv = x; + yv = y; +} + + +Vr2i::Vr2i (const Vr2i &v) +{ + xv = v.xv; + yv = v.yv; +} + + +Vr2i Vr2i::orthog () const +{ + return (Vr2i (-yv, xv)); +} + + +bool Vr2i::orientedAs (const Vr2i &ref) const +{ + int ps = xv * ref.xv + yv * ref.yv; + return (4 * ps * ps > 3 * (xv * xv + yv * yv) + * (ref.xv * ref.xv + ref.yv * ref.yv)); +} + + +bool *Vr2i::steps (int *n) const +{ + int x2 = (xv > 0 ? xv : - xv); + int y2 = (yv > 0 ? yv : - yv); + int dx = x2, dy = y2; + if (y2 > x2) + { + dx = y2; + dy = x2; + x2 = y2; + } + int e, x = 0, i = 0; + *n = x2; + bool *vecsteps = new bool[x2]; + + e = dx; + dx *= 2; + dy *= 2; + + while (x < x2) + { + x ++; + e -= dy; + if (e < 0) + { + e += dx; + vecsteps[i++] = true; + } + else vecsteps[i++] = false; + } + return (vecsteps); +} diff --git a/Expes/Testers/TestLines/ImageTools/vr2i.h b/Expes/Testers/TestLines/ImageTools/vr2i.h new file mode 100644 index 0000000..09f675e --- /dev/null +++ b/Expes/Testers/TestLines/ImageTools/vr2i.h @@ -0,0 +1,156 @@ +#ifndef VR2I_H +#define VR2I_H + + +/** + * @class Vr2i vr2i.h + * \brief Vector in the digital plane. + */ +class Vr2i +{ +public: + + /** + * \brief Creates a unit vector on X axis: (1,0). + */ + Vr2i (); + + /** + * \brief Creates a vector using coordinate values. + * @param x X-coordinate value. + * @param y Y-coordinate value. + */ + Vr2i (int x, int y); + + /** + * \brief Creates a copy from a given vector. + * @param v Original vector to copy. + */ + Vr2i (const Vr2i &v); + + /** + * \brief Returns the X-coordinate value. + */ + inline int x () const { return xv; } + + /** + * \brief Returns the Y-coordinate value. + */ + inline int y () const { return yv; } + + /** + * \brief Sets the vector coordinate values. + * @param x New value for X-coordinate. + * @param y New value for Y-coordinate. + */ + inline void set (int x, int y) { xv = x; yv = y; } + + /** + * \brief Sets vector value on another vector. + * @param vec Vector to copy. + */ + inline void set (const Vr2i &vec) { xv = vec.xv; yv = vec.yv; } + + /** + * \brief Returns the squared norm of the vector. + * If intensity value holds on a byte, gradient holds on a short + * and gradient squared norm holds on an int. + */ + inline int norm2 () const { return (xv * xv + yv * yv); } + + /** + * \brief Returns the scalar product with given vector. + * If intensity value holds on a byte, scalar product (SP) holds on a short + * and squared SP holds on a int. + * @param vec Given vector. + */ + inline int scalarProduct (Vr2i vec) const { + return (xv * vec.xv + yv * vec.yv); } + + /** + * \brief Returns the squared scalar product with given vector. + * @param vec The given vector. + */ + inline int squaredScalarProduct (Vr2i vec) const { + return ((xv * vec.xv + yv * vec.yv) + * (xv * vec.xv + yv * vec.yv)); } + + /** + * \brief Returns if a given vector is on the left side of this vector. + * @param vec Given vector. + */ + inline bool leftside (Vr2i vec) const { + return (xv * vec.yv > yv * vec.xv); } + + /** + * \brief Returns the squared norm of the vector product with given vector. + * @param vec Given vector. + */ + inline int squaredVectorProduct (Vr2i vec) const { + return ((xv * vec.yv - yv * vec.xv) * (xv * vec.yv - yv * vec.xv)); } + + /** + * \brief Checks equivalence to the given vector. + * @param v Given vector. + */ + inline bool equals (Vr2i v) const { + return (v.xv == xv && v.yv == yv); } + + /** + * \brief Returns the manhattan length of the vector. + */ + inline int manhattan () const { + return ((xv > 0 ? xv : - xv) + (yv > 0 ? yv : - yv)); } + + /** + * \brief Returns the chessboard length of the vector. + */ + inline int chessboard () const { + int x = (xv < 0 ? -xv : xv), y = (yv < 0 ? -yv : yv); + return (x > y ? x : y); } + + /** + * \brief Returns the CCW orthogonal vector. + */ + Vr2i orthog () const; + + /** + * \brief Sets the vector to the CCW orthogonal vector. + */ + inline void setOrthog () { int tmp = xv; xv = - yv; yv = tmp; } + + /** + * \brief Checks whether this vector has same direction as a reference vector. + * @param ref Reference vector. + */ + inline bool directedAs (const Vr2i &ref) const { + return (xv * ref.xv + yv * ref.yv >= 0); } + + /** + * \brief Checks whether this vector has the same orientation as a reference. + * @param ref Reference vector. + */ + bool orientedAs (const Vr2i &ref) const; + + /** + * \brief Sets the vector to its opposite. + */ + inline void invert () { xv = -xv; yv = -yv; } + + /** + * \brief Returns the location of the steps between the vector ends. + * DEPRECATED METHOD: DON'T USE ANY MORE. Use Pt2i::stepsTo (Pt2i) instead. + * @param n Size of the returned array. + */ + bool *steps (int *n) const; + + +private: + + /** Vector X-coordinate value. */ + int xv; + /** Vector Y-coordinate value. */ + int yv; +}; + +#endif diff --git a/Expes/Testers/TestLines/Images/couloir.gif b/Expes/Testers/TestLines/Images/couloir.gif new file mode 100644 index 0000000000000000000000000000000000000000..9489eff401ac290ddd0f796930b747a59ec43982 GIT binary patch literal 113589 zcmZs>XIK*K`~S~WKt-k^E^u#g?*d#n!<m|uD^s)5(zLPy#JzBjv~Z8C)U3>`8@HB~ zHcZnB&CJZY(X4FTe13iZkN?-h>)G`_uJbtF=j(X6y17_eg#$nVz#jmBgoK2oq$CUm zgTvtn1VTzmN?KZ4Mn(pSM9Rv_qEIL_8jZnVuvjb(hr{FXa&mI=^6~@%K|w)*NF*vM zDk>={DJv_hsHmu_s**?~GMTKVrlzj0uA!l!si~=@rKPQ{t)ru(tE;Q0r>C#4Z(v|x zXlQ6;WMph?OrcOrOiWBoP0h^A%+1X$EG#T7Ev>AqtgWqWY;0_8ZSCyr?CtFx92|D- z+O>Q4ZYq^ZqtWPex}&3`larIPv$Knfi>s?EgTZifb8~lh_wexW^z`)d^78if-m_;9 zlgae)@$vQb_4D)d_xBG72nY-e+`D&gP*6~CaBxUS$i991LPJCM@85smz=4AY4~B(> zg@=bnL_|bJMn*+N9XfO<Iy(CB;lnX8F|o0+adB}+jvR@Pk3V|!=&@tRjvqgskdTm= zn0VsEiIXQ!CM6{$Cnujebt)w#B{enm^y$-Q&YU@W_H0^O+PQP*&YwSj;lc$Li^XQM zIUG)UdODZO&B(~e%*@Qn%F52p&dJHi&CSir%gfKtFDNJ|EG#T4D&p~Y#l^)XB_*Y$ zrDbJh<>loS6&010l~q+$)z#HCH8mG6UaYOHt*fiMbm`LN%a{3letms?LqkJjV`EcO zQ*(23OG`^@YinCuTYGzZM@Pq%D_1%@JFi~7dhOb^>({S$b#--jclY%4+_-V0x3~A^ z&6~Gw-MW4Ic3)rLojZ5>`}^<Sy*n^4aPQu|`}gnv^Ups5fnacOaA;^~czAeZWMp)7 zbZl&Fe0+RjVq$V~^1*`#4<A09nwom_=+WcHkDokwGCe&#Gc)t_>C<P=o;`p5{KbnG zv$L}=U%s51o134XfA#9s!otGh;^OPquiw0R^Y-mqp-{NAw6wgu{O;Ymm6etE@87Sk zu73FNVQp>g<HwKd>+7FBecIUA`26|vmoH!b{r6vyNVK`RxwW<R_3PK|?d@;hzJ34x z{l||VJ3Bi+fByXS>(}q!zyJLC^WT5}iN#{T|KBnA|1ST|0stlk<Pj8tnqX)(@VQ<d zuqj&xqY<_P0If)($S#G_3TjRaXR;IbkIsn^22GP$qcgcl&l-}|J|Rt}_pB6`fZ!#b zC7an84L6qF3_4x12xq{uXPY+Xmg@&zikK{Mma5PTYW1x5%|MjtI47apB8EruRER~S zshAZKM&oFD8V<7}iB`2pU1QcqjH55@DuS4-r@IynXQc@FCXEt>1cSI<LilXvAtmc$ z&nLPYGo=;GlU4-bj$`?wh7ltSG6<TlU>+BA&F5XF%$PZKxP_*knfF|e(ZwP=-jtFz z;Pt0&HaO?4==<jH@MuGsXiZN(rFa>Vg4b^lw^HgO&gS5Zc3JH~FAqr|m8}y*3H5BS z6owGLLveJ&Y5`1VMp)jsxe+W>ZD+*S;~ai8-IsyRz=imPllKNWzkiP38@5!R<saKU znjKm;=PZ99b#qicjD^l5MDWOs`G;!5)rqlf-DAY~-kC8)>KJ)k>Ev^tEaem-J4@xP zXvUSqB{eYAd5j3S*2N@Hu1-^J!vno*0#gse+iN`@#sY|$w8<E$amsYc`rVY-w3DNk z#cXK0jOBdH4KFL!`L8n8OWA&F*6(<Z-ZrbX?{sWGI(0m5`qVj$wEG;@(Qa3J;@4xl zFKK$~Z6bkLM;q+5KgP=y2Xs0k-i2d3!qo@D(9Seeq8~#Ey5xv*{KFqc#TTzQVcj-F zbfj?|CEh!s-Imt@(oDo++%@VAnNA8fIf&i%<P5((i9rE!D)jgMdqYn{Gtf4$M6Qf> zpTO~o*83~y?gSaKJWBC}CggzKil7pCTp8$(p>GSosH~r?s)*Y&taRz(dsGgbGI2{G z`r>ASW1{&69~^5|>*9FkN&$~9-TfjuJ-+D$YG1tW+3!OLLqXRNOn-z~EB){$pL5cn zlUJLf)5!$E2tiqOaL=<Tpo4EuRe})OJpPc>s6`40_T}{S;QY3Uhy_Jk^9Lo(b$~)v z(*InAY;S`;k{Flv!y%lD3!KAc`rzM?v)6nBUdU-(%5IQ1<NzB9;S<&p3ei=G5{0oV zeUU}S27r=Ebc|b8vD<2WmP*<VP*W|>+V_xVRl@S5c4HaGU9XdCqh)kQwCqtcj63OR z`hfCmsy5mC<l#&6VT}cs7n9x<SfyuTkqy~;57rvjsJ6Pz<#BQ6Tk9@$+;4Bb|7@2% zsJ)|t>I|XP%8F{3pRS)^C;6f21Uj1YEuFnYf;s5AVJi3#l|FzZl{YBqNy3)ubZQ(o z8QRB7z{*G*3a4)7Aj7)ADqU3Qu7n|6C)LQH>Y<)W1UZo6lmRD)OK$bk?tQU9m5v-| ziJgUEj5Sb6SdNDASsD$Q)^Ii6qEnJ8V#}T<bz^4pC2Ux<F-8K2ut+kqfknzB@hi(- z3!<vSrI8wrO87nl3A3ts8S}{Mg}HpIyE(LzzBfQiRnZb>(tuJyBnCFd^+K5s8*a~H z!xO^d7HBjLKat^P8{bZY`b0WRP>Z7;1uLiSk=zx=mY@Q5%g#|@@m*X1ybV6iImF&- zA>I3CNXORDxxK#RYd9l4rDarhFB6Y8x#kpVJ(C->ogqKicA`>0eRW!3qujkzvkOtj zTOnmVd$EK}nMk+3xZOk-JVqB+jg(t1!I}9(xHhJ<{sDAe@Z7T8U9oV~_^FfhJMx~> zPf}gXo5(V8LF#+b8dR*VeUM2&xhpLT-!$A$DHP=t`BwHnZ!yi-<z2n*zj5+ly9=?% zS4Jb8p#uwL@oQ8FG)Zj~O^Q$8)*yk}PN*=nhE|CZgsdHBLdTR%gXLF99FoqU%q+(j z8OJ=YkQm40Y{?(`%$L;OU?VPrJj1;;rF=Hu_jjP`qtH^4S_y2Avq8Lk03};OAp#AQ z;1V~&B=xf4x61es3Cn!R-D_-Ib83}ROthIv5L?E4)>$#i#oVTghLmos#$1XwG0X0h z@>j2`oDrHP_sSe6l=dtpY+3F4`tGnbJYB9&a9EQihF$GyoRBr&DxNo4f%l!_;8<D4 zI(;i}chOCS2_e)-q#<D`1j<gdg7Fm1^CZ!PV(*D#;}JvMa!wxiEY|eX(ox3Ackoh& zV0ykLmCJ2(HB3Fs`C(ZoVfE~tnAw<<wF^F$uZ5X2i6@b*ju@4($*fuUP;IwItETDT zV<xWw*B%7bUvrb%C#1{&f@D(WxcN~Ak*B9hGrxA!w{nj)o|%34roMk~p<KGLZ^=-b zi&go3X8WEdwG3Cc<j|+7BaY#U8%tE~^iwqvsl{M!@al=1SE@BMBzKGO-7NvA%Q_H_ zIgAF6qjnxSic0r>PvWr+qBXVw61HnJv6Qa>j4#Q{$oQ?7HHN_JF&S_>f)vtQ1o~6% zG@*&6qxL293a2z+D&1F*f&7=6)-`Z<5-0q8LV&JVK(Xz5L^#Cfyy0m}Si#=yH*=U* z!EI0)?$C~{?tto{u22(x!PI(;S#WE7sl$;%IS&Eo9S9?FKOEVZb%cQ}13)hg2q4$| zIG9@D{Ju}0kKeA~Y}SrLS@B2&-%?eUavO|`5xmj@us;d;<w<$(ySFO%_r5)%9W3Sr zg7&_VF23TZOn>R$MfF6-(wqwYigN?a-%dVaoV0uuTG)B#pmoAY22kPy>td9UuE9YU zF%z`nwR2Nk`tV)Z&Kcsw#Fm3?Im46tRO(>A1~9)rj?50fvFeqd`BL2IgTJcN^pv*C zqfYi5zkic)dgGn%=ehCC>55yYUSP=+010~?x~xBmgWq7O$ycB7p5IDuv^^c7i-%#( z3pt9<10b4Rv<@mvzNG9Y$Lc3Z;<N~)gtLI!hH-G`-LB~gFFJ;Q`;BkwD%W&6rx}|Z z%ak%*tN!zoqy(>WjSn-G;{zn;B1qtzguSYZ7G0RHw3fpa=?ZOpiTnb#jQ3bN#(kDj z|1Yg$@458AC;XzW!g-~`ekS%U`K4ns4e%gQwbu~qruNFi7dsIfM-Q?<N+jB;)O|<Y zGPUi?HoW11ViD)y?XU#}R?+<9K%hkJ2@rak1v|ky7Bu90_eSUYH}^l01RqO8@<R_U zHuwA+er)OO_;YLh1~nu4!AeJoLnJTw?Sx<N)v0t_l&d78#MaX{>;egv$%A#P!^;xj ziXY&}QwY@`h#socO`eoy%88>TCmvC?XDBkySlVZwYA*mzzHE%Z+|*fRowU`~{lrHq zWauir@Ui-ibk0h$k4|!(bMpUom_dP<v!YYFL1aG2mWq~Rqre(eO%up27J5%P_9zW= zJpxQk0IN|!<_Uh0E&&eY=!FJ7CG#u?X38!h$fO(MIhbNL5|GwP-Zu!bV?sQ-z;<P5 zaX-kPNW($~(ZChs6XAR9Mdv(LVy-qp9K%e@Zh<k_AbSe2$LEY*m)i$2*qTIa(K5B7 zx;TiyYztzC7Wn2Su|*qnXIxRm%<Sgs*>Pxm?KUX47nPrwHvR+jVhrTcXs*u!J)Tv; zO#>BAGVV892+=BDjpy`4L=ynmhy_wj03OB`PHP@})_nf-UMq0J{v`Da9-AcOmK6?T zz2j=FICLS~&vPuv6DTS1$oyb9MIw?U0m!k5rg~X7NW=*w?4L^fd4JH+SL1D&ZO1!} z3lx~b1Z?d&yJi}G8O2eRm+GNv4L+07x~(-X)OzqvI(dteazf?|JzeLNIGyXOle_F- zALL`wf~+X<`7A^_RPUm@`nt4o8Mh;k&+Jv$mQP3ooAAM4GljtH<lQgyfJrpyVD^RY zj$kyzI1Ef<fz>G>Gf{F>H1=qpVZ!Di_k=9#gsjS!kb^V0^DprMU3gY2`CKdBYZ~kn zmZ~EF{V4HOevXaYNzHb_s-Pi<lTxh0Am`r4RJF+mK+a&)i8t%P<_Tvg3Gw_>aeFO@ ztrK9bCNU3v#0mh;UvfF<cVxg-v1RG(84dHR??JgvY5DcU@ybwrkz)Fak{(a_Ga0B% z0V#fs_fmJepK)$5qu_N)$U-JiSs1z$bRNY7VucK~E-+AR>hZ7qg~be#NQ=xW^|-g< z@y|+tDn?B`!gD{*Q@ccshm|ngQu|iUI>eGdytJ|Hk(l3RHAiZIw7joGvgJ-E775su zUB#D4i2g>zJ-<C_r?rTI_Tyi}ANpyl&6b=sby%FPH~GNSuh7Z&MHV)kln>CgJFTk> zEo*9sblO`+S&y7G^@oF_T*AN<5|%m*QsaS`nfiM_rYeo3Zl+`7*{Huc)A`_DH8AJ< zVapJ#BDdn@mi&cHJu7CGcG&)940QE0`Ms~T_Y!$y2x3oxME5%;j0P&Nq+a2~fHW|< zrvqMRgXyDt)5>#VXU}Aq1ZlquvY~==p<uB(3se^zhy3bX8&0?!6Etc9Zk;6T1|PAE z1lP1E++pXnI4hZv7`?%u_RX`4RiV1mpo#Y(13%-{`SFUZQ%_thW>)vPVU=f0LPwKB zHh^`iDAl{fV;@q0WD>}s3z9rs*VRN~hIxFMuwr40-n&%_MlO7E_8i5rLWR%=BWgyb zHsETOm9H0kUVZl#3n^KADooP7MDlFcwKQgN7DZDs1P<@e3|-c|r>-TJ89sa?T)Z7V zWyUl~Wu}2j*$ojEhfD4IPQGg_wYTBcFY7j#CK<nqG|5d`Xn<HQopNTL_5P{nkPE)e zL4#LzA9clSrW@*JWP`Us<|r}5jtWu(fMsSJ6RZpojY%_V{_<rIst6Q}0BePT#e+Bn zHOD>0(6#d3^uvcer@?e#rJD;TzFA+%9P7JpHwlfwvBB=q`KS8#(gsi4@CX>Jb4Ga3 zL4R=iE24?urga!03ms?62Wv)|X1-9o+<eB8np~!**dL=fmTac`HDqB9bhjEb@ZI%3 zqr;e)Kfb9HY6iM(VezJ3In?jyOZm{#&Y>S?`riHk5>a&nh-0gJfjB;JYz+J{sB_bb zG`Vgy`HO^F?p(<r|B&<i0b_~BthG;E)or*6)!{{kU4G`|1)Ee~cE6Sg2t!+ibvSyn zD6lLZtN?XANKC)pOyc~o)jH&RVu)$4bGn}J%ifI9H3x1W+xlp}O1Ez<wflL};K44s zO*dXqc1wh%F8M2j8(IUv)+hrC38WnczLtsx75h2zXqHk@H1zJoO(Ja?tVIGDg~{i( zVIArXJ%s*SC$XWiU<VXfn+bLkLH3ayKUm=176SI0K)nQDS{G!OsM3~n6TBp6HG_75 z<Uma*y9GCQ1HgJL@Gf?1RjMg+IEVB8?}v4<5P&REV24+?a_gh6#st|=Zl_ofZJ1zs zdgIBF+ud@C^@q&v7MqUW40%Gkcpk#&%TaQ;QP2v#^Xufk*qHcGr*kh--6}`&=Xa9C zS1l}w)1imKAY}?r8U=jv61eXfcrAw1V}9Y>R|wX3vft@AxAnqtiBzNQfSsx$^)2gv zu3RkzdsV4>K~M8w)x3Fg+r+McH|A`Qp=;-a*F?7P-crqdCHJ<RIQUaigJ$+|Pqh+G z)B_Z|Uh-tjceGh2?&}AZ=9hHY1nbKDE=4UgIDcsPCe^sm1F=l>b>)L)SBy<5Aa#2* zARVPRb4WX&@#o!SA{x>`bJhZYj6@ldgK|eJ_4brgj<N@7LXZV&NSg|F<)?h2TOWK+ zXU<IT?Z$gk&`!>7)+i^g?#<t7H=V-#$R_Auy(f+a{=9Zbcp|tekl?=}K3$ED+oLq< zu<jh=6ie*_ALN2-XGYj@#5_&KjhWm1YbJe(uH&~0dMD4Fb4m;M%fFU!aSAxDLj{%G zjZab9SH7@sso31>#+?g)LJSi?&rRI7y02iT0T@vlDS^I=cP!<`u{Wcn^Y04pCS8zM z3nX&x-njw|rKk;jx(j~kS)x8*uI42%Z*v)J+wkt-)Ne0$(=hy+BnzeK2ZFDUgoSK* zw{;;%+EU+>!WC|X>(5CW5@f&u%+#$Cgl4I1a+l$)bQ{S?hsp+rk6o5p-LD27F|=hj zdH>APQ4UQ8R#bU2s)Bh}6cl~hntVz*s(g<-#6T0hn+3vcfCI%3<-Bgm9nT(=8!0yz zfYsusJ%r7v!AAZ|^w0okR2+GaFxOqQXuzEDt}=F9-5r-$1&%@=#zD+@pi{r93SA(b zhvVvWoS~8J?2aHa6lj0cGkXB7wohp3aQmc`DIY!B-3<zmBvL<{+|JA!_32CRH9PMF zE;ypZRb&j@iPj{66sW)sCk6kb@qu;bmHy`zv@h1K6cB{KyRU$mo<Jmj0uKO9Kq2co z9_!!hCSQ@hz{sLGk9{*KgGtsojn@3ByNyaV3kcqf|3Jqg>re9BqLU;>CoKQ9Mxht% z-&Zi=xVN(8o+{-N-?^M2tn{VN;iOsn$Ld{La;0MAH>N>Lx@Me@iNwN&uv7`^s}a!M zx4$g!OJulKlvSs}0bPg%wbqQ{Ol48QN$6wEzOg_3T~Jg7@)Hw^vmOAXFYNv4S*17= z$aIgfNknbggY5ymrU?)aVYJJ|;|!_82G7ZcwQqLwAWyD>tf&U|#lHD55OKteBNc77 zl(8$}ZA%ImVfNHz?4}l2nA7c2@oVH#{UPx}#i<XJXZLf1Yhy`ZMIULR9y5c2HQOp2 zYqWSi@g8*7Em<ltbZVyW{#?jJYQ8QNM4o0S0zh-*b7QOVLX#^KnFW@YLT};gwtyfN z)&x4NUyT(?7r7Uub#6p^Ea8tQeipCP)b^@!tp96yIk95BUj)_jO<)zU5LFUCn|b_a z2d_;HFfjbiXOf4JcK)XGx3Gu`8Lci<$t@P=KC@(GI$Um@qn4>X{q@9arh}>LNzBbx z?E5D_Uu>vaE~REYx^?f-D@^9Eh$v$w*dhS@R2#hZ$L}IC)7&oW;3^ty0@*X3qbC9e zR_Sxb<m&%!K6?)NOHe7puQgF%F09g@<Tzyhh8O|$pC)sypdUYjT`9v%1M1|i-43<- zTALdmH}no?JGLBdbsXL65e8AaF?v-y=u+0L{fnU6#hEsUp{S(T{U3rt(sGMOzg!q0 zj*Qoohk%<i6zS=q6QiJBv94dj^S=MS<Q*Bac$z|os9F$NqTUka&?Jio6XOeHC3F!; z1$ADj3%GD$LIwqtqX5Y$uptVfB0K4#vh@}ZU9z~;d*s0HtV@7m%{(~{^mhExP_u`; z@qzm+Al4<K$|PbUpB%Y5rC=WWv=exzrQo8K=F&*9S(dl5UCI~XV5zI@<z44NXnOC2 zPm^1PY2q)9xUzQt&d|YJ=ZAsU_TM`X#ky<+^&Fb2b;#55des{@do6sp<*1_7vDX9V z92Jdf`x1{%7V97WvS^c(Jk#va>ixPudE-ydkq_P?yCLq9vWwE5&4|-ma=Q<7I!zX9 zFnGAZw5DuXf;mT8d?&nto3FB)1<|EPO?*&u3B7$QZ3R}M?ve{2M>sL^<!#eCcX;&i zBC=z4Z^ga-Wb`YSIK|2TUQO@_W~mbtbmK&c7PBL$o8F9&V+UUgvbn6{QLgKMX=f*K zWE_sS<Wh{)?2^}?{Y<_n*U+bWzK@ngifJpdnJfz$%2r93V`nL)3J0@P&diash{pu= z5_0i;8W$bfSD&eD3K^1AJ;kpVC?`#mC1nnE)k_ef`9LVjp9dT##_@p+Vzj9EFAd2a z%#aUD2+xr7C)8&Uq7rCs1YVRcbcR6)=i&o($Pg)wX%Yn8x46Z{`3JExjQ3E<8Cdrq zF-=0DA`J+YrL3|3qEitI3@5Vq90Y|_4<S{>aAve>S=%$(wN<#M+C~Lx2p!W${)j!z zF-5uG@Bc>Hf9oO1N~I&|ZBj-9VQb`U0V=)0gre6dZ8j~mkg<3klwMHme&$`1wSPw= zvNG&Jq->2CHNvY^$>CxyvoIMrKW6Y~c~DgQ`-;%i&G+r$O${B@hr6EU)?B+bsioNc zU~9GWxWMO#(ycdo+D83rL;A+|c7Ex14KC_VQAUwxr%WaYex8>!%DOsD7eeipv(`Bz zHAyTZyS`WTi#i8J{&Mnj#3M?JOwMwW2S!pQ2mpjCv8?YzP5Iw7{^wPQ*u&;MIhW8d zt{km1m_g#jsO9QCzOo!^FmZ#vu78bG<nTknX;~7tj^$*lHw;VPATh5P!k*=;tHDSl zTtg=Q7&8HW*KGDLu|8qFIi!**Sjtp1kFS?d>`SX3R!kEt(N)fO4gTdD`H2(6@Ewzk z8k5UQ<B!$<HJMPj#$AGH><=4+;u*Y>N%@Lizf3t_)K#vLPuL(AMX3r)H*?hqe~h^= z-txfv?u7K(9%8X4al0n?Ph`wtNgN4{J|PI9vnG&qy_kyg-PFGNJjbhk&ivW7LtVpv z%tlKKht5fCHlK}jUwdJ6e`koCJ%n-u7?)hxpE#m^A2Hh{&dn&XA>|<rb0$@414^lk zJhWZ?i01j_9A=CM{z@v;K#(D|U(_HkfQ?ad+4)hFMuo>Lm%x@Kqa!S5#jop`!Cx|r z6St=g6RPv}gIX%@Z9^#>+Hx48v?|lVAfh10HBzsXn-e=`eFILHjuC;?HiQuMRCaC} zwdaNOQpBxb@8}4>9`%heC^lkHGX8ry3DOodZBwmK4X(>m9=GTq4@pM}8Zf<o9~nl+ zjeu2l!mq}y(VpOY!ZN<o2F*QC9E3j;Tw5+<OT~i5;~OnWgb$X-pRrM>BycyGO_TY> zMyi#i5BygkX$M;r#xwhdo!gx(f@Ts_#I5NQGZ+`;hQaqCO&nAcHP2L3`ytIgU18wb z>49+CIuq6WEcltXNf2;LxZREIq^1lkP+|T4pqZ*LFTIIXgg=Xuc;+L+uSR*>QrIf9 z9m_-u01^gFwyePepTZL$`lze!cC+z09{|2>(KSOPFg{;W8}eU|pyQ8j6Zy|?Re(MR zZMx>Jes2q}=)W#eGC)S^6oZumBP8vFG}$ve64Y=F<`%`o&gqTn`VZ!!9;=0F{tcOH zA{|L`R0urnB7NJpASDBuWjpdR<drG%{G?`fGMaEOf38qnVpSPtCOu37%T8Fgm>fT+ zk7?7X)@T~DVa&<Mu$W?qauW+;j0%2|nPAE<zZzw1#yOOmaG5;Chvpq&OESBxYcq}> zuHSCNn}C(O=Ubx8vVpQOR1SU{0AIxxwT+y#_-c)j3S^egUZU`|qD-N+N;Rtg#&B&< zRg{HeIk6HceEeJ(tSp#^j&LJ~d=b)(9}EhU64K?89z)b(fXD<2Lv=;?cfO99O5yes zHA*Fny9Q<a0ieUgkFM_xjf+|4wnwxky^KXiqzYF!PhYpy9F1UWeGGn#<qARf%={YM zQ3EM*$1mwn*`I>|uXOKF_b3QkCH`mtB^e|zv5y6%cj<`At<!}2)|!YNx?Z#;zb|JG z;&pyah<_eRB0=Uka-vFJMw)pOP^tae<E|@(X&OO8abTsgG=uU9|KOAf#~WHOL&yJP zg*+!s%I32@(#~y81)~P>?7aOvsf>}JCDQ|h7oE&>;(@`;fw7X|GxZr+`#!JleU@0s z{?=^#8IXjT>F7$OOoe-M^+QX$xX#8j_%pZ8=rb!|we9j`yPZKof#29vO@55#5lzfa z5M<rx3zTHdmT`Z!P>26u{cE#PyWT8l`U_t|lL?e9>uT3^x_WNUaL5^^#cp?BGD0_G z2n7lS7b+jNxnnaFR`kpgAR`B(W`B{5VTP*pmC)ShJh^88jj-1k7t>e3WV^$6$HnU` z3G1CfZbW+naULMKauh8?sRTc22Aq74#}uzfHLc<op@u>1b%a9t(_lZIR~&GCMYk2t z4@<%Rg@TW_yM?j7rJG7!TZK{DQfC3f7eHkaT0#~)Z3hIA;pFU5+T~Gve6)58=hRA? z@$m^C+w>^!NXm~a)Z>!HzlWS$+%xm5mYP(6awYrvqoTT3G))s{Z!|P#>;Krs?1pCO z+-lAZO!b)ZOms&yFZ;{)oF;~{Zv?G|UcV7lZWx-8XET5ZWaDRyqh#_2CA?y^E#p;k z&g<|0JQg#bZObzi4Wdr*IOwi0NzvrH`OMYJ&!sD(QubG)lc;J1=fnbtdFo;;a-Y&( zyTiMmh#TWv4j&2qB!pco;XG*vbT9%x$F2DUI5(oWDjL7trrvaZDpLIX=GtmJP)!pV zFG?qBv_%vK9y5$R{cvA2!^K8GOO6fW=)6{Rx{&^(??M+C<%V=pdYU4WFn#=gM5OBv z?WeqJlZsm1fPXR!>hyv!v^ch$P93OB8yCa|y{j`tv!PfZmS1EMTnZ@@U`Rl@uuCeF z*wS|_Sx*~yD$8*a@Ocw}IZ}qR@7`qNw!=*hr0eWKmw(^1$t$_sK3Jo8*Mu8WzBdsT zoRbZ}r0>(!EfB)Yckn!vT!m>)RS5h_9$fdCt)tM^;EI@g{S(({p(22!7tBPMdf4G> zay$AE3Jv7cI~}&*EW2T|6VrF^0IKe*69fS1K}Y@6RLRWnD?@o|BkB5kORhlc3+4$R zBFs=t$i}1C_`ZQ+IFH%WIm?v!Acb1lsaBf+%2(Yd?65E_;9C)}7I<@bOMUMV3%915 zd+cj*6F~I@P@Pn{b4QJHE|J{_x||0huK_igmbmFF&)W(vCnD9_3~nc0TxMP*t)bTS zjaJ(T318VEYE}rgk-`j6DS=J0VM|NbDpdjB_OZi<%u^P4M0oAjlHJk1?DaLJ9ZcU& z#HAz4#?RS6!`M1`;dMp!-}WkaNmG^+?+}%3C~tu(<~i6irfh3BpiNE}CJd;tB>0eX zNp^)rMGqnp?!b3gRgoaA1P%s!*|oUL;|^~7G-$h+Ls+>(&@aCNxBMHq5jDFo7at5H z?IY`|FT&cS;84Aava0r)h>F^EFqOb{WO7|tT!RHuq*{hAHKQIwZl>BD4C;s=IJR*l zViU})OU-&HdHwIms>>aH({|(%NA(WJK?-4*afN<FfA_W{HPpPnMn;1KQv3*1TVvx1 zK>P%7%CEqdShI&Xbwt0}uA|`H9D8UJr~qJNQrU`Yz@0D|-{)8D*X^eWo%BB=iaNk0 zYtEM)9P=l#bEZb@a>meS(s{o+M~=!?fPwOt2L9-9K1em&B6vsqT*BxmMVN&i;x^(m z`opV>1lny;x)yGNdiEJxCK#QX=YmILhVP8+JY;{{y8Q>-x07p}xv%8N88&`rw|Hw@ zbE!a%`b0j49dE0G-2q7RSxKNvlH5|5B3m+)fnzYFp9|LeunzMqS^fla4uBkHpsuh` zD3Ivm#J@io#qppmn@RlHwlZ!vv5E6FTf?BQrhSI<d$L>kNO|K$Jb*}V(H|;UD{qSg zSE%KbGIDIf;II;S?U{;3Pe^ku#Bc$!2ar*p?Y=h^nt-lsNky33NgQI{NG`7LD3R}a zF0NM&$-A2_b)i`5zIsD<$irKn`Xh70S;Y^X9GmW(=$#`dDyFiPNg&lQHag6CM@NBd zT`;uWxh(XWO+Y?cQ}fzj|4sUzezvmkQJ0OuLr&q7x!y<Xp6Bk4EzPhss+e+(gU|A7 ze1W?Zu5f1kIWIkd^CN_pk?L=rdJXx@00!BHJwQC4ear*&pif#Aa9#T6#Y9K%5X3)^ z=e6X7yt7x><7}R%;yAe)s8n^0NHtapGRF0x6;O4gk7zddiFjFN3sQPs`U%2Jpg%Yc zY7)Gz2Oyc+>Gr^_hfkJlRL(4y%#urxkTTgd7Lqhgs|UzT1K32i#8I4!c#~1xh`V@F zM<*3j8>#yGtBt5nXI8BU^}<ADk)x81=k>ecl)<2g88x#WOM;w&Lq+wOoGO72LJiWi z;-i}E-atX15%MiMm2Cp|jxY)By84cq%D<(iyZ7n9!>4x%YMfw6!HwZTKYb_1fnkqc z=PS~h{D!?Z3&_l8N<lL6B=#i2dDhS2ut&iYLFbcHi&q4Y%7lvwz#o&U{=rtp@$-~> zyyC6)kx>NdTq!!`0b7HQbDy!v?I-SRGUse%LzOrxZTeCdfky7t-EO-++_iMn1leLZ z{@>UESJ~ujnZ2eUsTsx4p2dCT&7PhuML&6G9|DQfKoaA&7%`~`f@A&??YIv1#vc|o z6=<h`iNQ3*6u3SM?~4mi#xnP4o>tEEs1pylp0~e*qjV+60FB&mVkIC3g~sq@&%`n0 z!}_*{jAa33sowyoITkz>Ahm;KW7y<W=6UEOrg0kazRde{x5CCv=MvSq9uuX~NtJDy zF(AF^Vm9uOE!m7@!2nw_(>3GUtyy4gF5RBTuNI~6?t_<{<6fHJsy>oySgWAy3pSII zh=}QE^{ph-kgs>ncQrk|dou6*X~%xRm0kJ-s+4*Cp(~UEk304Qr>i87-;kaR0=)_{ zP)q<S5kMps8_lp#jkzUMt3IsmSr}~aZchA6ZI0d5%%(oN1~ZB1ebnd`X)zSiX-l6y z+o-K%ZCIi-w|IjnELHtp#nD-7(4c_iDL?{gvHNu48folC`10sbHK`BywCz3-1-f&h zS#yGbSYjV(LP_TopS?R4-^nf_qg}kUc3`#tY|5nE0212K+fRX?xj;KI=CI6rmGDb% z@0AczOXf|0Z}(%?GuYXYC3=%uFM=81D*&W*X~AzbbnJmsxuuEjM2+qPxE;XyCM!|` zC@VT3dJSN!0oCSSZWS;^x|d4^?v%wJWTIsi!2LwOa>XM-88LX(v#=aMPs!F>IcNnW zLN~I#zk=0sv4*r8z%34dxQba^l~IVdkk8l4ZFmrNkcB`-+a;g&MQcf&nI|W#krPRh z1CC4AmxJY69PK=-?8}xEj9!hkg|E1WNGyeW{#km#1EQ_jM1EuEy<4&GJe_YgzJT#Y zOgTEY3U$Y>DTx$o=X-1C6`U;;dPl1jv!lelKn)S2l*>6_=Y{;d@b<`M9UYKLU%vbV zaybb31T?lFc=xXB7U_+VN*nMXjx%3Xu=hVBkFk&c)N6{&v|Pni<3y!Ux&vtqcs6hB z;Sr-Py7BqQ>$+@X-(4ll=swOn88434o6+&roc%9jG4B#f5=;+#USb)Ud_3`@Z}q>b z_KdC*h8nA6Yv4DQSat`1VnicYEU6s<#(=H1HW3p@!~LF+!v)DJ&FReDk<WAHCQnv& zs{S&ZX*`l0jHc%a@Z{m3u-J6@qv<7KBw_}=^JIm@d7(4Y-L0>J&vG|1zZ*g#pKz<M z)EsWBnLo0VTk~F`|F^`=m^IaPdF$uCxd$GmyH(Y{h$%d;uUL^)SXj_pmw$PNJyLI> zb_=NPU0{Ev0CfD;vLJsXzgHyz_^A(wjs-HKLJe16J-%U3YEmflb&+pl-#29w!)g%! zF<ODQzU?@KJ*&~7fZ8oV1RXX(M{~Zfs_ssH>14Idf6}`ufGK`#1xjN@eU)mLCSedI z-R`3BQ7k>JRD3p__kn8_e*?JMvncKMg0LgP@jCU%v%eK>Xv_Z`sZqu=-)G;@lGk~E z&K_v(gSE;jMNP00?rlifmpYjMXLb%fZaENRd;Nm~GYZK@wgFgMSeXeHmIA`ZAH_v( z_g`hoCz&i1w&j#KGk!7s(SMmX$WUmKZFo3$6yIt$RG8gg9CKLVsA)x*oYISa`UG71 zmn}c{L11bIMbT{k8k8+J9L3C(FVQ3R)OYhM`ya~R7Tm=l8!iPrw3Y6?QS)!TgE{fm zQta)eyJh-KGGpp<K!Sj6i$G(m*eb%R!4CCdwG`$j`>HRUs%=Ip*$EpbjPUj9vtlVt zzmS5F_=`5p>h<TX@H#+6CMdI}Ofvyw7TxqlUG@cMU70UK$bL6*vaoDY=J4;2QmuZr zqwM3I_Z3K-2RwZjFvzi@SbcY~^Znb3gFtD|+Ov7Qi9vH!9Ugwr9G+vOINk7b;RFEs z{*~r?v0fkG2v+P_C(7Udwz<wcw)7ovudMaUqSFUXVN@(XJPA0aqBMy`2?3{-Kchq} z+ISb;6*z18_Fv%Sg{#x0@h0VqcianVE`PnJ)TG*Amz}{stGaMdoNQZQ%}<^UvSOI} zSL&up%){5gl15yoAV}OK|ME2bSS+-O@2>GWtTXDV?3-MxEA`i>$i02?;%}Bqw|P?6 zbB7HB9`g6AX<__!H=T<6u?F2teXz!!lx`+6%6PH2uCB&?i2V3*A|ytNqxg{xlLgA_ z$eh;ptUCXZ84~M=2dO`HQee(v`hZ%fIk#}o7iLpyarbw=$?H<k(aQw{k&_X<@Xv&d z9aN*P{TXQjxOSVpr#w{eM6brqHCVNgw8`y%{kG(mO00Uugpx3#RVrYKojzWuWS1nI zNF2_TR<!X>qHL;-<tvz)K3LOHmyjkH(}xneIitC9x{R)brHGZ$N+V{Kj!y&^jyLda zL$L=rl4!NKLKJO?n}gN%<XP8)MsqOQp@M8$<d_sr#X4z6EFwE)BXMdmYl86MOq@%N zdH7J%(S_UIwf5BuD{$NZvq4m#_Ws|4dYpFuZu;m0ynb#s<uOMyuVP}K*72pM8F`gc z2k*1s>Z@O?Orj>LOioJ3)R@Q3c7?xgd8BBausl)i*IKA-mnvGBxzV~u^)FP#yN^ux zXt-DYg}UYsa2)|x4@@>B>&qJ-6mZb`nFjMc(N7xOE(Sh&Wf4E$d-UDxqlMeYg@2)w z_A#aNCsrPmo27i&ZM)WF#<%@MnDDlP-+K2~*|gq4azz5{6Yl1SdQpb_gB$hsUlXil zv&oOsoiLhuJ`orODUpkI5QI6RXmc#Av;%}Tf)djJo9QS=99tbl$C+ppI|+3lXcuX; zqx9|#DjjCD)1{6I_)g%Wf{-(fs9=f7L#SP_Vz88nkk5fz82Ien=dLp|g3Hi#8O8bf zC(hxMe9@z5TLPOatuY{SEO93IjG!}r#f+c}RDLy9P}iuANXsTtI!dFz9fH}l(FLYU zlFPvD)>SoNX|u(IQYo2_d`G(ZG;3IPxB1F&yRCIny0nGob0DHU8aM2sihii!W;`GO z-O#NUlz93NP7g^L>I6Jv+BWkzd+=$LAs?#(Cf!$uddLs4s^uJT-FzT{BdM4s9F!z+ z1vDt8EIBa5m9hlccWiQ0PAoY2qIoZlsWVlH^Lswu6&AoSFp1b3I#?waOqHva3r&p= zmajfNs6eR8K@$}s%X~6pTib8FI&!QJy%2wMhJ8^vWu<srB~65`Rb}ID!%mckHo&#{ z_Lcu9<zn)Z`!}9SQHGMT@=d3*CB4l5x}Y*=Ki`vooA&*6<=gYCJCc0X$2LEq?FOlh zpDyjPQ^>_c_gQ7B=I;DJGip;Kax~Otmh))WLWZR5Atn$s9Z=s~98524tluryeW=7` ziWw=Z#p>b|?B3V`R|NYW7+I;7?bby3t#^T>t^Vq+G7O044#o#%S%velwr?HwArw2J z8B>BCSx3`c@fy#Gs=8C}f<ia0*iJEZ5hk!=?nR+K&<WYK#RJPatetU0wl0h4PFndQ z5OSmPiKC3!bAEc0)s7(jKc$Ft!j2x41e<k<rW!y4p}~_KayNXZLAZP>^to?fK-&=K z2(2Ar8w|XCe#`I#J_F)w14Q_;aM-FmEiHa-Ks2TF>eNFp2?dnYqRPouWq|cWFZT(D zD#(wUv-emx9FQC9%CwjY=hjz67({oEJ4Hv9{tS|*->NRj7&MEtR!|5EBtCDVSzId) zj$cM)*-W#McP6VJe<zI#0rZ%hpb>SAaPubJN4WN|(WEFJseLOE@~^sUOiGF^4(E#x z6Ib2#SoX}Q{1CYA+D4DhkJYQ*y!u(`=*fkJWi2bnok*JO(7rc%No1*SkB(o!`UtH9 z`tbjKgzaY0WH?NS(%cBN(C(>h8s9_xJ<M%Jy#$>v>T{d{BrV1PWx14Q<yg1mrwgH| ze4g5b+ha-Z2%1#<VGvQN9)5BOmvf0i&|`!f`;j~^<yLZ)Q9!tP*rj@cGuOrWU*>Z^ z|NKk<SDCpkF^CyL3$oO<2Hnp&1i{*sPITnWu>z2~Z;|II?ZN-?B_%h3otS0R6(I{s zrVct626T2zkTf2gVSCgxWAQvH)YwOk;nFkMoRrYwFbQBLsO_%hqr@2>JKXyHt;^-7 zSQ4gHOA_&^j+j(x7I6+CX*~_n7-+r%(XX<RX{l8Ik`XR@WI0SLuqto=h6(XT#38+t z3Xf9j&9v;QAB?Q^R9<CU#3z82BGe@>{-01g=XE@7Hf#5vaMWn5lS;vu5|g)#^E_EY zx;~tD(7%8%Ms@kCWU9o~x;lh^8k<}3Q}tYSEnN|6EIcKX`W{_XZt7e}_jRkk7u!~@ zj6plBp&N!R+?_Qdd>UC{coK>xxfMw>FTinC=GI*IJuO)+jCCMu+>j{vN<+}6IVy~6 z?k$eKg)Cv?1UU)L0AOY9Utkow_z*K4deG~OYFgC%I&m6#s#c7FYHWnT+`8D#HLgu* z$d4!TdsLdbnr8l60zi!bLzn&i8%P3oNqdp#=c||Zf4vu~ne*OCY2@CYn-BZzyt1z* zp{BCd5t4+#K?I%0;iYK=sD5NfmuX_FZt<Z8(}8f`6VEC#ouD2$?l1<4c4=n?L=w2c z^Gk5J7E!1+W6;+C^v|Dr0*EqC9(JGpqC1Log*YcbuozBciF_mP38~=niOL)F_z0iQ zD%|ThXQL5|JhcT+`C(QMrDE0muuxxdZaTBN*#~*lpffy0%k}clC0UL&H*t#r_l#-4 zmfehh7G{zcBBaONhe@b)&q}+TDj*&d?<<-c@R{tX^V%KzX+@iMn(w34b__*t*0bq- z@TMQr_mwYc_&@&!O$~Aa1ICe#5vG&<Qz8016Q1oEb*1JnH8Z7e>PdUK#6GIzE+!4e zc=?D_WyH6oAFnJ)`AqQoQ7>#V{xZvv*Xo-zWCA?x&i$~<@&Y0l(`-~(0yuA5;3^a7 zzd|&)SFqdL)s9a=gox6mrH;Yru#Z{q?xMcG=D)Y<W5{@u+LRXolGiaU$ha`Df?1QK z9cN7@J6Q`=D^w$*<w|#PXwt+xP`zn5qGS<N8LFXS#E|#mYaA*U{MJ^XEWsKEa3{|x z=y~}d?zAm0w_Xw&9QY}I02{%uEss;d#F#;J1*wY#^)UjNX<z!$aQGj-MMSQRy<E#g z)mY3@?!Kxk_2}0MPntZ<_3gak4rs+zLn|!f;~S45^CaH_j#_8JZmO7c!~KeedO8K2 zhR5pjHU#~Iae%BD0ba1>4=&lLU%6&n?Izy-up0n6#GXT2^)FS>L{!bUrYj5oz~!9= zm5XzU&|O;oXPq7Ava6C3K<G}A<T`%se@Q=W&mXt1wk~;trlh2w`X38QV$#e%UTtKp z#<*X9IAif5w{ROk=R3*t3Sbig^r8Z;Hv@|b#hUwwcD#od-8OaDNC<l*7$W-z)@XpR zQbw3q5Guve&&6w0M3+FeGdojv5S27XM+vhmxb3}4Q$9SYX(ePDLKcQYGDiL<q{>2` zUFqG!n4=bYL8ap%E;`CR`CLz&f)xL6XuOgoe~EU%EvGtID$1S9gQl^(vH?Dc)p8sK zDR#Ag+kSB`3!cD|c+Q07vn0FbgJY-?!q;!AOZQ0^+hpJ|y7{Cp8LV%WBLKo^SHQk| zFXYVM;Mw`n3FsK^*aP#Lhn-`@5$UjY^wCAn$0C>G@gc3%m((aAjixv28T3O}hdzxC zb-W%jiVvhnr+?OUOr5SV=T-ch2o@DT(!5!5emm=xlIoVi#8x%t*z1BfA~}68RdGVV z!GvZ;Vm0H3?qmSvC6Zf-5DF?s3ZGn6{cSk+TVZk)1w{6;@^1oQO;q;ledYe8AOEQS z?;DGPhqUzy5K{oq7z-^Ned{oY(P3k$Q^VFicuFX43X9SJA_q{iVcs@!z!I3-%7FK_ z(}VGI?g%k85$=tV2Fj{IWb;>$=G=!%fPm)!WW@@V<zBf>eKgbf$mivi=T8{V0RzDV z7nV~|fs?n1@{J7$BA$2q4&Wv!PrA*iGWNA+q7*ZS42O+YoST1EPoD3dW~|A|^s%ZF z0r#FuDf_Y@VS?Z&mP8+5@8`O`P&8?Eo@C@B!l{hn#zJ)8j9H79)id)a5`5&(jm4CW z5t=2myonzkWqw$iIws0g5e=&3FO!rwYUUsfPr7E?rS^ady<X0<PNLDsvax_@vUniX z{;!~vezn<kPNWtWTr%HVp*1LHn~Gs`OBUzkOka6%S7gjg7c!X#6aI@%P}rMU56dI4 zQPu%pMSy{$k-Rm=24!zy*xprCPEqGx)YRqtdjd&502wNO@5R0|n=oDzGFgW7OR0KS zE<hSLSogwth%$(gZo+!8MFZG6b5EQOkVYDGS`q%%okRu6t_oov{JqaC$<k|`WV>Ke z=U0#u{GVwEnOn6o10&Kx65zFfJ`q$=7L{f)(kd=1l4)_@Gxr=L5gcI8^C%lZ)Q<Qu zmv(}uJ$O#iEYzN9e^?n6!QsIG<&ybipYn&d+UotLohD4m{ccj>&+8=3bzz;skWhg{ z6f3x)`HhT^>X0*f#`!=}I|-u_Mp?uHt8m=`k%E9jsZ)oN2Ib203Csh80JS(+%aK1X z6#Z+Omt0!DjYUX|t3rk%^*HLa5r<mTqI-t)+lW^ly*d1J*<`Na%lqY6&Ld0Dl553} z;((E_y1ZEY;e}~APM4#p%Ee<}T8{rWdjqJQ5U7|=l$RnNmYJZ+qJXk11Vq*4l`3yu z=*!CZ#rK4qZ&V~|k!t;ub?QxAr9WJZ+F2}EiAz48gF^97pF_CYzxab14Ei~`d$zSo zj0~d;A`PN0>}nc4&EJMi{x5jtQ|ObV89*D~NmiruCK-5@9`lR({93P4z%&(+FGvk| z(3(0|F};ZVv**!#=x>PfpKWeNa}2nc%gBf1Jt*_5rTX+c@pK>FsvU8cc1Fa7m(w)+ zx|m+fdIXE<i~8uXox7_9P|9IRPK8T)vLI2^*WoOQ0cwZ@TJT2-eKZ!cIXcLWy)7=p z`_roRRQ|SyP}=fDe!hlLjb`*4qClGX`XliX`)Js!wVz{8VB@MNP?RNIgW0lvnxof1 zkM8H__q=*;-eIzMX_hCBepxH2k@M*6M$P<U)^&JfJpP?dafz<-zGGja&AzYdx5p*O zwcdQ;+dmJG-VT%P1kAh^tc)PvGrZU1CqEIetW!Ct+IKR4sM4td_#!nw^;wTMykf+= z2<E?@=3c%-{n<9so6VAvreSrY4=~tRgF(z96l>vAYww9O0MfQ|;8d&)5s`BBV6yma z(Ke%en{{_f;60+>pn<#h=2O<`IeJx7`+%(UwBQD{6uA*9@b-3FWyz-cKPn&~!<?m8 zG;UIY$NF&+qkvtvbYS%YL{MkGZw9X!bwd~fm)4xfMfv6D`SJkX(y`Ca1^aVDJ3^sS z`GAlls-$?t06rBD;k_@5W<8cDe)C_Q>Z^>wY3Ko`L9B-7!R(KAuO~ybGN1aQ4^5O$ zI5kXiGavL$)z%artS=7lDZZo<sB`|aDgZ<#(WAUuG^2*J_=dVpk`43%!`=|%wAB|H zj$dpXV_zB0{W<U^Lhr~;xeE!tt%!>{=!@MJus77p?YBy9H0vd(Reo1SHflUTEdr#c zpUL)8VYO7FfPYsQFYh@ZKXhD7j)l9=3S=h)l2w3w+(_B<yOj}GkpTH!!|CeuHDn(P zF~XAK0;FJrh~0xYdm5HROT#Q*y5|?Fay;$Zr2iVrS<8*(UesSm`ruh8+jm4(7%552 z72m`I+qi9XfxC=v4C6T!QAU+A;L6M`{;VoQBEx@G6gh=MrMZ>xE^4kV%8jWEsdvh2 zA$fJOJo#&oj}Z4oCy=TFVuuT5vAnFirV2a|vE)0})o{i%^q~NeEx4F0@_!Kyds!De zM#XH0zukN-AfXQoP{<YK5L=%SB^TU$*O)LN3RQDRi8xv1`G6<!uy!!Eb1W`iW%kS^ z?dTenZC%x*5K^|`OxaP~Nf7=rJ^DU<;nb?!$*hz)HM4xztFLP;MG?jo1xLQP+7K?s z_j}n&Up)FoYiWJ9XR}3L_selh_r&OfiKiwXz6YQIgHl|9b-lOiXylo2v1}#v-y)~S z?|5*TKL8mP3%<x&nT~WtJLNO6+MgzV?y$lui*zKbVO>;|XbD55)tLkH{^SXPnHQi> zFw*tt?1Oi|Wq*aNt;SETa&IL&YaeJW(s_nGQ9Kcye!V?D#&M4I_+IGG@A~FTMHXl0 zWJN5QMb_QAJ#=1>$0T5&8t|FlCXVJwL4t2IokVmBZs=4qVx~`8NkMK*BMcG<6##e@ z>Q#XNW<7eVtaHkZ7hsJah@}Q53uFoelAZvFzu-|E%Ta1drRV*Vo-X0Jj|Lv>OKs(~ zN!xg9moe+*@%-%roWY9?Q?j?G9`<fG-Rqs5Wh*Mou6>(&BHpGc<A=yA+Zr3&+HDPc z_GIZtcN@ibzetr#oqF}9y2T<aJ+{5&+MwjQImuV3x33@*@mdFumSc{|eKFmVQ{H!Z z?{39!lm}OO7!`aCqyGt{gkPmopZ)fJ{L9O`vZ-)A3Y!NjLu~-0rdZ#QCyZt;S~=j> zw^fT^w0ltC|D)-=f|_8vuASa!2{k}~gx-4>FroL}MGU>FfQX2wX&~KzG$|UYfT&af zMGaLzL_kCo)KCRQ1;nmAf8KxQJI+C7GIP&luYIj+KmCRKv_VnM9S#Lk-&SV{=~oHi z2GEd`gZ(?#T=2qpt;{Xr*gE^(2VJD++P>iC>0fN#@Hw7(%60F{Ir?Hv+Qa-gP0jm; zjt>Eket)EYH*96*IL}uVOG2KUhj2TFJVbTpAc(VyQ=zzcr}7ADOyJNXNDd8cdWU^l zKmn1@K5*mcopG`OuR=sx+(CLaDD|G!VE~H4COv<GQ|*E-ys3QAa|CiSi1VZLgVCTm z@8GX9!vNz@@5!+GbM`l4)(V}E)$%@pBuec2C<->lhu>2AV5|H+?>dT4Hub0LkA1f% zgX%$#v;VWqx%gP)ZZFnJ@Gb0mKD_RoV3;!B;!?j(^XF1ksT1$a>iiBl>iS&%8t<CD zOj$ECK@JnIM$N6JB7;6=0p8l{ijeZQ*-1Yre4JB!&QnT<i}UL>KQOXyf2Q#LJpCtY z&%VtXy?Hc;S6<kR+n^+!)5SwSLy3b}!5udD<?=5f)|AIBPM=75@VSJ6Z1~AGk`pM@ zKgemgbm!@V^Z&Y*{-pkP?4tacr<@$hIr$%vf3xaO%jKm%I+|DKkX`re<<BtRct5YU z1{})xtN+KNVEFEs_nA(#InSRI`VSP9r+5F9w^`fSmqfh*9#}j8ev#syWs~j|QyFt8 z{k2iG$js5MDEx-aF-G(G1OB779xKQ|IqBY-h)j7^i8^4Sct~yiz^Ej9|EYg(<3HO+ zMmjS@kb*x*QU6lbf63Wmf-i<&=g@V|>i_iw%j8GJzT*$OF(tZ`HA@p-yezjkddh4i zGs_PyR&?0$8xf1yCVtwq=M54k4sY(#AM@i$nF!>b90E<Sk~r8&WR4Ze*)Rah%Lk#h z0+Iy#Nn>ny&1ESw9B6BqUVk~8n6khjc)<C^RD$~P%a&tB3Jyia*?Ys;SLE%g^gROi z9fVa}YV^gk7>|*nCN#8)=h_%r);3FCeoti8pP>=8xd{YA3vSuu`RjMA))z<`C$EiB z!H@YQ&mZeAFQh?uaAxrfV;|{v1f&h3=Xw)=jJ}mHiEI7o9tzFFYMH_L`F`8vimLn8 z8z#!yAO%$eDFwhGC`v><;U<<e2tkRey63Uof!4^EO0LCN9ZSf`#c`K%<-%H<JPEzv z;>`q-6`Wt%GH+9xK+E7Z{u7vm;Ygt$U#_Zq+|zHqqV(fBapuvxm;>SeJR4cEMAeq^ zL6lmH@lK9f8(TIP-pvW+(NwN)<I#Hf6O^YtyvfYdoiHBC)0-vA@fpnf*X0{7rLN{1 zuQ9{;O+U1)@|*2VhY47u?ym|EQCud~PPS&a$l3Upys)QhKtqXNXw67zP|DW2SV)!* z>}q&v$f$U9?bS?)xZ4AYlE-@;Iweo`|AJK|42s*iCT;4!uR1hCQmRg#^N6ZGy@2eh zCM_nuueQQuz^|oZj~9EK#pfqqbJ%L0n|0c1AFI8Ux4Dmy%fVY-5{r+v&)gw%2IT4X zIb$^p%1@GPKn8_Josh;sw7-c&Sk@G|?inD;>^}e-WKs^ALf~a7z01(5Amy42{A(x6 zY(-X<<za;)_lX;d<*A`4mHl(INSO<)4-C=dRAukBOros%olI<P79o$)<RcU9{xRe3 z-B}b;zJNo5OI3`7aDT9lkPb0pQ~^{njXjWwOa57N2?v`1c1Xl>;<O}^YDpOa&i+(= zEE#<(qLh#V>=yAPE<=R#yaqC=BI5`dA_+%GMsJ;o8qj4;<pRjtq9eCWggot+A%d>j zR7h28-7-|jtc`Vu-vV*c3^94=4qbeK{>(h#Rb;J2{O+pTk^c^^SQ1qct)P=?w>gyq zu+C}O1dY4pZMi%;bK1Q8BUpN#fSvWwu+YIva@dIIvAo1liIbBa0_HzAxdAf-nPl&v z*asPRvnD`Ad;+O9qAJbXP;5x%jHh^H#aWoM&P^4)%HwyJ8!HVSCF<c4hp%>4C2gB2 zJv0BYujfvBGyZnk`khCzhn-Bm_ce#{$HHFn1)wMMuFLDo84~st1P0pk+cHOnF?M*~ zPckXyB7-9+D$`4ZrNm6}?)9LvcgTvU5VDo)9ymY9IZP*piXsk)$Lf%fCr*Kcuk8u_ zPzEADCDMd5K^aQgAqozg)RV78RK-!tULjZgnnpdfj{T#+Om=`r;sGtUa198vu>t;A zPw|Yck<cq4;(YGh-t8PKRP5^k!P$78-oI_5Wzt6FO_HSxr<0FTb?TnjU%4X2X^b@V zA|;Wp2t5l*fZq7UhjgS=&7l>+q6?F=zI8Z3^(a%%C#!SGqF@PTUL4k*Tf@hLcA4)% zo8tg@G+X*t?+x?VILq_fVb}jmx*T>b98`H6VFX&%Affgo=`cTI%^*agIA|!-*g8|E zIK(<?r$NGf81|%ZfG>e!g(F+bO)cBoneTz|kdQ2E?G;Q>6XQdXevYU9mV-xyX9)$9 zr%nhv^WdCqc}oaiT)Bhfh+c5z(thK{_}1Btw&(jl=ah&?@3_CV^8V2ee`uS1GJRd| zUBgO;iYrpb+l)dw;G+N_5)2|llM9e;4l@5bwD~CPUbnsEJ|u&lrpRRhErmgl$NJic z(V`NJ-(I=obUjRr*LmUs{`z<QWh5YnCRD&;C@|a1e^D)!uX2jy{{o<DY!W&lTGahT zZm_QWplz59821woQ!1zW-tB0XBFAf;`Q<be%fyyG#pkFtQ8892^sCyo8d#07fdnF5 zoXvvj5~=W<`3`myCNeUaDix<_AW}|-%oWy6lEW%QtlJ?se?6ViX6oO>xG<j(fGFO+ z3XH#vwq648W$$yKfOQc>x|{;RHB+7D*@1_kWv|P3#@$C<GHe?bRj9c=Kpz4L`9V_j zfc%R!=n`ET+{@H3S|bF1-{Vz8ZLDj<>%?cqY$umIHq(U>F`6rw!rn&3qPcsDeIQ5A zL8x(}UtXAUnE2F=l<D~J)QXhWSx$n`*DCmfbFO#X=7qcucm8<xS?9yKWw~e)Uz&Rw zyCE}-Oa7&??2%Ix6OCTwK}AbJq_n)8V8D25@M(xFgc=bX?#k;a8~Rw=x<St)TL4*v zO*Y?9)X<}%(x)AIs5`~(_ysq|^2I;GBS<HIq~HZ6Xb2FZZpdiCq-SWgWGXbMR#OG; zJA&^pzIlCr;&e>#3jIE1Qmd<Q0BOwxMVxhhihDjLsFR}oh#PC`L<5hQ9k_!;*?ESw zCadHlL_LwYcxYBUmB)VrRMSrE@W?Chk<g$m|1FG`k1mKJ9qF#jXB$C^Nj%OwRL3;R zi>HalA-(i#{0n$w+|Z|Un{`M2Vtq1_b1hZB7-j%4JtcSInW6}#Cjs+<W%;L;lI7se z2L2~(j#Gwmivr-L3j_Rb`KmWrDlV}VKLjf_A<C8>IWI@1D!cE$G2U9t)2h*{{%!uY z;x(keXgmhD*7V-|nTPq|f7w#>B)h}^Z9RbT6tmVUFEKPmnkfOjUV*=Wyph@%iDEoN z&HV{~S^~PH_6dP+Yhc_Bx1t6QF$|U`A%fkLFxRUTR9{gc-AxBAIUX}QhMS2glqKn! z@B7789)lR;EQo^qNYM3onqaX6L%50496C^jHvPs7Tz5l+VA{)iCQ8NUcr?L>Xbw}` zZJo8RvLS26Jki9m`>7uAO^G-$J43Me9QzqXGmlJw>9B9^wpkw8e}-0hjO`^t6P@*| z)c%BVqD^CHK~M2;mGMeaRIJM<HD3P&5ZZX}kTd{@v>*bJ-k#t$eN;DN<aO7n$6L-D z-!61OgF?99(`tsMi9(#hwO(2V?|`tnUA_F-z@$i`)oHinqm$xZwx^ngba$sGE%uk~ zX1pN=N*+k(^2*a6e4%Q=I`>Zos$lz^Y;{xLA3BHuxTSvb_IkKJe)6J~fE#39iG>*9 z2=Yn;VwZ6?p7`x${B16Sc)qLFizDlO0ng2J6k^#B(}FP96}<lFXylZ?dx8Iw4I-Qa zRUdsG>U!+=`?Zwl0B;c7H#F?+gukX7G&;<brfzY2<ZvVBMk!#c6mULCO(iK888ikj z0w8KPVE(k0i9v*L?MJ~{U*A?je;zV~0|{pZcda0pY(y90&cRBkgrd#~j$R}H?1%rH z%+X3B!!#8eM^$hAc81B)4HrW7`~mt&p;6OK0#0%UjK*enPHakJgl5)kN+y&hXL=zI zWAGGIoEfA2tn&m6S__V<n1Dtegh9DOQ}`#auM;FF?ptWwe5})r*J0m_LlIWlD&B}6 zT~LOn!$M|mFaL0_1cZmFVW~Xam#3A8eVmAYTqY0aqdAoOui0=|zhmp~n?(uS{c_b& z2_pd%9K5q+TJzU^z#;sG==>M4*KTJ@NpCOQkf7N;%DeQa7|#Cf!mfv3M{TE}3f%v0 zxeL3pR%(`Ic~_LTy^Pm^R!~GJ9_Bv?a|A#<S)W})WtkBH*ibIC0+|`{v$To(O<N7( z!11AVxG-4;AV9G;^urX~47OIZZgfHwnntuJCU0#hGK%8K>9ym@C}cVd&Qgz!rzl7d zs>QA&?%nn^6_h*Ji;_(z!%mPj+{ru-2EFpgKHHX`u#vi-LwDb{uFlr-?S{gBZo~Fg zV7IMcp=6l+sxCqqYRQTQ-O+f4Knw91EFq#2WcB@t6QNd->bGLk<#M!%f<|hls)<Ja z;3&;!M!_6CiR1f*#BS%y!akS31-V@b)v{23K3^I-UXY`_)_4LC{=>Jqg!bhm>{3FG zmX-cm=_o6)Lg07pouo|UVmRaeXV$6jWINmUb}&+fL`{V%LCA1<sjFEs%<kXYCtO(o z?!nrb(eUaiM(W#f$-g|V6_;E&yRJ>||Ks202*v|M)&c7wte+l0G3MkNtw!D*Mkd2) z0JsI_0BQ^y)`x{eTKeW?P&Gf)`pVY!)53p)z(-kdbZxMA>|rs`8Um#5#t)Ar!g)7z zwWH%aDe#~qq(xH?dmXV5MmANpi2bywIf1wUFpgy*hd^>&ys~>x!fjQ#=>~W>QSq)K z?B0~uLw8ki{ihXUAyaM8NaDUPUL%#Q6>Irra?iA2zcrrK=ugbEW<#WpNBz~--qY0* z_}KOq(N#}3C2we|@P%5Q?E0D1!5F%X<+)rOW~7dR^6Htm61l1b9UThTl-O+nfCS%) z=T=b@So5l0V+?h@tMNUlpL#65o`68aevP6!0DT2d^?Nu8*KWS@7qV`8a?kUCVUxA> zni<1yc;kL@xSh3~(ODtM5iR3BOv>wzmFa}4%o^-n#+hw3iTV&{MpWu|p0jHXxXs~r zwRUM8r#PtF(iqL<hgX(|AG5~3vNo1w2s?w5mT~SNxH}&5TK4_6m&LIU^6mxHAle#( zqx*RUVtO0C2bc9;sg>V^f6yeI&w}rv){<F`u6S)%B2>AUZn{Y?lC_Har2M^vp7~*$ z`(K?KBsFKr3xjNG+zGvtYW9{5`iihM6wDa_yZ0IRgf}FX2IE#oCU;;zKiC~6L&G`R zrE??=SorJ*7&E$c&k>ejl}7|PF7CDA6%8Hfjscvu`i<cHI?aQ_V1qPK=gXNc=fXN< z+arN7ov;`aR_0fWn^9sX+b!^5LyD_kT#>IE*pTdYie+{3scy_=i}IE48>4#1cSS`Q z`j_rlmh#;zzT<f=CTSL6Xz;?OE7$M~Uce0Ae=P>hKOo^EB4juqL?j7M-l>ef;W(`0 zv~knvAw*(W{!trAa(b=$cih>HalbF}($Fb3%JrO}x(AjwU3zl4)Mrps6;$Ixh$frU zIk34sNIbcfI5j!vBOCm^*4OdyOvbT5fcnjD?nbG_xq!O!Sl)YGM;R9U98{+XhWf+0 zQi)ByHse{f$O~K()c3#&;#Lu0B9+YzV4%@tye#UVWmKKeQH!v*JeD-86H3$}vmn8E zwNcrc$;nVN!|<PjT7qCTuczThQyd~db;hNds8FqV@*Q_D%##%vCg=Fi-SI|p)R(=; zw@naj`!>O+`XIS|Gk>8NTZkrWuVTKlGz?NZZ)|!}ASSHcl2i0yZr_q@=6e}zOa>G2 z@qU<>Z|Ndia)<MNS~?&zC{88*4jy8V1kk0v(px+%wR@u!>{EQwEIHSU@j9<r+`Pna zm%aFtoD86%`9m<cngjtc;jZk;**l4^zWDUiJ5^9kK7KO!a`M#8=cX48-1e>g7ekVv z?gJoHfWpYKYKA9&3vjg-_->`;0t-eyAi`;Qcx+i{K{TG(WPy~`Qk>Nx?i@Ksfdz6> z48X_Euls%W@<oIO6s>c|$|tBn*a1}k1T5?EnPXp8x71fQ^C(tV*@)hV=47I^QXDMF z=)%_<ApyxH<`eSYr_S5sFOF;0EEs6T<8^V3TEnwiAbL1oZ-D5QwyW#mD_Nv({QKS; z5cg4i&aatJ+k9AD)b}w%XClO%Xh?jfKQ|V2feVdT#b^-)%OW8`br}K@u?dQo9oa@{ zksX$-LpZ&7V@`K(7}$^vruZLc@ao056{l!qr1NFOV@_5Wi;2~H)cL=PRWUGOV8_YU zRRZE(^+}~O<=^M|KaledI^F(UQ$a)g^4oXqY0vX}9DEj!iv=2(j5{Pf)2Y6_?7S`T z?zP8%91QboHYe+wbiR*_R}V{c+LN85#y}K_)xv^VN0Z4kC;aCG%8{?K>!YZUJO!m) zJteo>jVlFEOCnU))<0e|%saMK6QxVkghvx^7p*HM{DLG9w>Xwf<$33jk?E_T^F=HK zWw1OOfRJ#xsvuk2^*LBKRDH^zHu?m7I{_X{h6Di+w|{wcyvp_W4H1;PHTN6(cLHo$ zNBVcB{flMzFgIK+if|u-TDwg<uC}ecqWyO^>Oy3Pna6b#jDF7dh!ZQpV!3D!|IU=5 zShKj8)FEb5=7CYT@r<^%`KE9YkyER{HVga$PUa}kf9j6Un-gPRehxdlx7suRz5d6> z4;J>7{BQ#Q<I>`1i76_%H3H>3DPDyR{F~&(dpve~M2S8D^b{5&Otn`WXv*+<<GJh* z`ChcC-e*z3w{O^Ks@eCsj%31VsjtV+wxzN6!*Bj@0Psbe6Koh5d_I7;Iqmm@HR`N6 z=$I)wSlVt0*RO*}%thow!@M)j=)l%|E$=AJ`aA!+6@-U);^E;6wKyv+OU+826;qsd z`$?mK0|h0>Z&)$Ex1)VeuMsj!9GOEV!!i-m@QEby$Xk2Z@Df2zRBpxS7<pv4-b8~; zR-znmf|Z@E)rN9q!TiVnTqJDRnG>NO#IlKvcoAfHX)+Au*+vwgFZD&cg0JbMwpE<V z_XHb@#z8Dnqi=ZMQ_wP{gh5ch_fv)pDpq555i!sHxEz5QXzvuI`ggxQ0-9Jg)2BR) zd#Xh1y|PvppG|x83H;-GST;C(A?s=HZI;Km_5}GCKRxyC`9J5iv`(u3)OhN5dCTQR z)Ay$UKLkKjv{`!okbp79FELxMz+)+K^&_^3B);}(<OZqboBydh_uh>ST>I_D>8*I| zBqsCdIY34o__#uPlvh=O-}eL?+7=4;=YVaAS9e!#&#g4Xg2JL*pYYH$qbbnhU(kaU zUby@A6#=>;|Ef;fCaEMMI1a2IYddAMh={$_QAzHEXR4`N2J2_&WCFUn-8DX(x_Ddl zg2y?3{YZsm3g{F7X3K)gny+QE<%pTEaL%V^MRUi0o%dvc%^n;}CVy$83w?6GGq$G( za*IBQ(4#aK2zx{ZpKtSA)oh55ZHwG}*ro4HjDEWx<@QsHder4o^+Ms@j%mqIWf_Qa z#U*nh#E{di4ggPsAt`tfpvd>6k5@vE^;rM@{_uP@aP34w`^lwwcCJ?92^HSksbx7B zFS);b1u9pEjqOed_3xY83)f(Bh6Q=W65lO2%nIP*>~N>x%TKbGrKC@NxpOcIoHRWw z=FhXeqI2+=4{auNweaz8!b1>fAl8P8=RUc{6gJe`0=5kb0m$0%OPQtrT&Mxt6-Yt( zzHbi5trSQZ_)ks)<sdndu=9T@<*S(mG8V~8<mgpd0V#t>=dPI*1`k%%b;+1kZ;w%M zxn>-*!f+6jwJ*l$gx1>^$=Vhiuyr7aTp?YA=r(YT+mt6U0OFwxgx@UzrzvKk91JOX z<RU`clVN{ZLVs$aochu>U&8Rj5@nZWo5Qb4ZmJ>;W#)-#k;hVTa=KwyZU5F(>WUTe ziK>5F^l~jUTCDyU^Db|iO^%4NeSI`RZZua!6MF-nC_55+FyH5u*OmZ;WMkx=v+%*S z5Tu}jJ4@RGbT3;}C#bavX=!!o6V8Ic(I(NLyb}5hPEC0BZw2S_wvz&n&I_@)^lRT` z8Lo!)&o8H6hoK^#5PKV{k9Wuj*I!y_Ll2A}fQlVWUd|9ZM!~=`L$gS>;t^c;r9^an zAvfr3oyn9u-oFdOB~9ztNu6FwgiD{@Z0AyvKX)I?P|>t8*#c|JY=x@{D-Oza<usJq zEw(I=M$g*+`AZ+mck=2@5ry*g=e)h18Z1QDJvIFQKFoIvaDnk^9#>tuULH1O^1l8` zf$4?Y|5KOpzAiZQr9W)5hKs<u9n|Gdd~Gr-n$UA=D4nIpG*(1P45P_XHC|0N*5By- znoB!i0Zodwr4MQyjpg^-r3~@$eB%@fpv{NDmIkf(p<{$9@{pDMHwx2Iev53yZ?;6F zx0=kki#TbNb1ZpP%H8Q0do;;f(CH_{TgVZeXf5b4a(WnTC$enCA2j0aV=Ys-2F4H} z%kxdaKo8&k_J;0Kf#3%sVgmZpK4QUo3*7dM(E{-`uW~Yl7GknbwBj{XhWUpV+bBv# zs0){hBdeR+22tuvLy4H0fF4k^N>$q+Qm$Uxi&tX~7l@SQ++9Q}8XFBl2lCln)3WkW zg9*LWfYXT@Y~sOJq)hlU5H6L<k%9|mogu*_C~SJ~$AKTx=bZEhhSR@2J$~%MZyT!7 z+yD5CGlacy(KaF|%`viQpjqz-Lo_%m1RI(+vwSJ4st_g-+caRip>mI_GfkYtgh{2$ zWx)&5_jfPJQ2u5i<g7%`h8H<q>zKIW;To`BG9=s?QTEgfoUi}fJuLrfbW2~O5r-mF zP?h|;`Mt>|>-i1Sk3lk<tk3a}{?&X@mk_8cFg0ziFFTQAal?wD78_It<D#dU(>orv zvi;;DhdU-bGyEpXUGxrIF1-IWmCqjy4oS?V2#|dFD;i3qvKVla@&UfI1}trX167J1 z;PECOfF!UXDz;>SHx=5#a>^v75FJoZ(Fz!cARu(I2Sz3KFfs`?h{Fg=&r}4XV4n!r zvu6eRv+3d;ZkpLtEMGKQ^1=2x%pd^>@?cYiu^Ti)3UxRZ4aPE^^V(}Bd0a*5#SN4Q z%`2}at-p#tOKpQHv#%m;DYTo_yLy`LK%_MuB$S*9eU?~&&_!1X;*}s)VN*%AWSWq+ zoyqgvfG4g1(Cy>z<K7`J&rXWcq?Jj?PXU#SOL8)OFCHu1Os8G=oYM0E*hIYe=Lik) z9<Px=NE8ZP%D@?A>ZBvA396Wlg|<G?5fVSEZfG+$Sx)L3@H7Q&O`z@HJ8;5n2|21l zRbzjk0_Fxq?={V8F$x?0SBwu%vn_8TnZ=XkBx4+}RhGZ+H(hpcn_Io|4WOX>NnF(< zNAO+UsNX}Ur*h}F7GEC8VhJTsbJd6oBr;x*j%IM60?zi~h8y$}E{|tQwzo^pP&;aV z6DpcVO^8Hef?VX~jd#X6MLkK9t=2lmtg&t)cu-t;8DZ^CIRAk@NI+1V5)xnX<O3k$ zs2ZpOh1v?*L`z#MWRWOMemf;CxM=|FqQ_O=AKezRKgqCjA#q2;iDfFYoG4v(C@Pfg zi+soqGiu|B7*-Y^4SEk%`N^L0AMG$6Ol;TS;X_=u1?3;KQFJwa@=9^>v+C|`69eP` z*VmxB!h0#E07t?%`{L|7IC(EsNGT6DsQl>wh$NE@q0p(kEDiII9+%I}XHHz#URK&4 zxcufCs|N_oBHry9)ZmHg)gWeBih}5wh7glb*Qs(P#~DfEo=KBwQgYoBtF5FtxU|pL zirZI)<@1J-Cp1+=IQ>U8Q)hCIk+aWy3?Kc%*3XX>K6&liHmtJzrJ3oAE!V&M@2>+C zray~#r^9TTekq@}$c$A*>{wwvS5{Q+Yy)8W>?VXI2ZURo;EkI|Zlq8(8V7ZZKM}#- zM5pH!5n;S}V~(*t%&2Mz{Pfvz-n76G{?LO_hz{V%?^Mekh1P^->rLUHVOnw1)DBcJ zm1>_%RtHU?$wZ3sqvQrP#xw_dH2Eoi?W{<`KY&hR15c3H_jXm#$55pNXTD@6SZ9`! zBgM0d1%9Q8Hc{F#ZKx>eTeNBQyB*gP10qiQf*d|i(o34fd4yvEF)Vh3O7s-c>>L$s z?-D&0FqI%{GESV@4ObjngBc=e0@37nb{_yXr8lv^<2&)lJYs3%l=t7crzL5*G~ud$ z+bZ_=`~2EKqN2&YLoPjB%c~`p;*iOI6=*G|IwPSEe{0Mr>#x^4n)aW&i<P{keGzL| zK_BzZy_n|EO&8RNCn+4;^gF-?e)w{+4#QcApkIp(y|PzsbmRS}Q)&F8!9*T~<uB(? zrtea`>lGuKGc>)2uBJT^px_x<IW}A)1Wmx67bBjBhiEj3xE`N=E(6vk2X6J{cb2T8 zivbrSs0fxHZZ`WCL`!IZ3cZy<dvW6CxF)ZdyZ<YOf-ni8`8Nm6{&xEOI$KRsd7xP_ zwDnTnD|p21y>{M}N9`%`Q71pwSietye^<g5mfE<0>RP8-e0@-_o8Ww{M!93Gcq2-a zy9~%`q#5Ll&ayE1BGlNbO>I7CcgU?;m$=s<|9GK|qCgn&kKH<8wy;j4AW3-qm-UfG z$h{59r$^s`ybJT7gMNVm<yU%+oVyw&hp$$;eFkUT8_+#D!RJmv?+2-CBN8#SpT!<> zGERp1l@H-&)}7}vMY<9EPv!6A?<Lg5wD_U-f+6zq+$fa`4ll`PsBFgFzv!j%=+B!X z-O}A-gK}i9r)_~^#xKVIR8EQKu3MUCBH#r-tte5VM_D8?WJ4%PzD(ttww01t%C!|0 z+(uF+iKr_EQrFfoA5mA@4Fk1<mDVv9Vx(6|U`+#%IvLD6phoZjONJ5E3^h~<zOo)* zo%TRAcb}m5D)!nzZV9Kk1LI+Urt*)|js_V<OcC$XDdc%@bEr0vDRN8)5=gKH0c}s+ z0PP7z9XJ3XsX!b4RjkeqSI8!%%i`*-KsxL*@&@2@BVyj())XMNW+1S97bwrcsZc;F z9PylW@f#sFMf>`$Lg#9h#Z{OAHFh9Pt29M2s7Vng$4YZsjU6h)<;d$-McH)O9^opV zdcGToYa5EV14W5IscEo-cKpzH=_ZKLkWczZiOkr0nRBc0&%YZ@pK;<Vg3cNoo&Vu< zS?GjxC`@+c{QG%WJO{=ZabAsprzBkXJSs<JU&v-7zC<DS>@NOJLH&d)bW;w2E-64C zyFL=)Lnx6^e&jbhd{3^B1<RCXz0Bu@lwWx%XRVlvL{r7GLP~0>6|+gccTZQ#9?&dz zl625SeRmL{4Rpc7^LdisflwH^R1H=rVsS$iX8_LY0_hWps?cc7c{QO`HGOTbcm-eY zZjd&Gp$&leC~5vkjCTU~BpF&cmLz$MVTc^i^FsPmsF!)Mi21ZnSe7Tu!&AirtWB{q zAwtyQ9yS2TX`sy+f@H|>nRB}&Z6=8N18Zk%t(71~hv~EvB&+?h3XDK~n6uT(x=kJd z+N+?&mDn8lSQg=2nMmx_Zi&T}Y}uWFnh{*Sa9pjuL9H@Kx7|+7J?_pJP@)aEP6c9! z!2YP*-lkm7(IW$5GJA>f>y}1iKOE11kEdJVDSE~kb;tXy33&%~gaP>zFYHaq==o~1 zCLa`oYr!tYvM%S_OzHTO`8$X!=!=!;W3@z76BE_SLA7i1cnT)m^Wq&PCmfTdL?kJ` zX7j}jUPj!d@G6`C+joPCy9<^oi(oBqFB6d^Q~?J8{%#B4M3M$vI|LGGu#>Gyydo^G zqi5i$kP*o@mtp<`lc%Gh)Clq+)Epv)n~s2G>cEqh;3-eiMPG&@g{V#jX`>;ZbRqUT zh9r!UGZ}J}Pzv~P+M=yk%N?YVAgGWbqGhid|6ZlnQ}jV7F&z%>1&ZKAOG)lxb0G0^ z5m?OgR2jyllPZF7jhC`@bWV<7y<^4m?v}+3+a73qEh`eWE80&x!?3Hv*qzZ2qzq>j z#Ne)%1hg399@pS%HqxFXmY;)I5&#vqn}FgJdrZQS>k&X1X09i!nfXDw9uky5j~|H& zx@}$AZF2k>{5-?UcvgWh=X3NC(nLy7RyOp+t4)~t9_+eY@VhK{XA8V>NN&dvafyY< z-?^}BbdiNeH6)-~*wt@iczReoKNYTh*uMxjGTR(a7=M2GzcI?$S-0~Y{Qn74F&pOZ zz!tyzuH(x>1dy3WfwYr4R&j8w2?uOI0ZHuY7%S+=Ac<x~LBSADG9uHQVj!S@Dli#r z^Zs!1K&ZJrMlJ`P6{4XCP`~~iq;c<nQ6Ju%a0TMK5K>J`;wm-`$!IMi$l}9cptlFi zD%00i6tZ^s*`TlPG|s(UO9<MKa9Yf!%P-a7QnG%zR061w0Dd~yaCAZ}?IpHDr;!eH zcra6zZy!_gO>a6x|7nfA3L9JY`08!LQ&-0&+`%9bB2XqF)cF*ywkfS;BfDJWT*=7Q zE@;Jih@lv<!g1&vfdg(9Zyta*2Z+h6gDPDC(A)LR|A5f{FwohQ%Ihuo01EVev+<kA zqa0C_E2r|`H|F2^V6w8F|0FxO<!jZwiJM;<3fLc9x>r#h+B`eutq%dbo5P33r&~8n z%)p|zmJ-|&W%<|0_jzp-UmTn=M`tCzK?z7kxl8{@{JV91-(BDj&DSv_$^LATIUa0a zB&JNUOD5cr9jK9y@I)_%!D>Ws-;-2Qz=hq>hF3igxYj+O2DtE{+OD;LwT~)y5e0{6 zR#fP{mKKqO*s!&vO54rc!M!W(#+!%b$8Z0&jMQ60+xDQ(H(>2ffrDp!5_L+`qdIdj z;#pLz<}|6`WmH<UTAZP7x|oi;ye)<M_&KR=LJmqSQ0{_mqP!h_D_e=Ie~SPzlPgzq zFL##1ErQadh(IAWurCFdXB{|H7%(1kw`N8czYo0QFJ03I6x#s^0eVEyAR;UG9yb2; z$0N;<%CZT^gFC}AXZ3o|>m8>G8KY#+&nUFyc(oK9avq8~p*#pv?{J=!m(!<Rn7kTH zq1>$EL0-F$yaqtkaa>xaQFq9#y$SaQhzU23T>dO?HXNonGokpr!i{&-ZO?%T%}#`@ z-}<ehvaEWWKcN42>}_BFh`*MCx@?d^1h_mE#AAhiE~};(qGnp|1DQLe42S3@s2aBw z2CfNesTAq8vE@{2CFOi4`boTMlja}5_)^W3ClE&p#A86jADF2Gha4P1Yqn)ygw^j_ zb~Mk5Xr4i%)JX%nVE)M@O#kU9{{e#BrSvF<0;{tf=%=nDl;TjDnpjFn)Xi&*)$h+b z-E|;Ye7Di=`5=wUrn2=qjLNSthVkeeg7y)qoq&uFK#8aJw_*0R$_99*T@@)!ngzTY z)r6w}eYWFPNua>VkvP5hc)hx@l=z!-j*<OF(dm^_--7xe1VK+iV&2h}(w5VC&WlaX z>vnR6D<{l5Od0sGFD1x~^s$<cs5~>CyPUCp%Duv+LkBkFcjEYX{M?S$n$5*30ngqN zfK5EeW!5=c3_iL3&mrksLjQV;H(@j+d=uP;Ycrx`D@=FFyb>F)MeAg!3(ctU4Wt|< zFPJ36W)Q#~p=jYbzl)`UvOpbqM-A1{IyG(Z+7jqRp0+(+vq9g{iUY3JUHo%^I-?JW zTw5wKCVJYw7fmQ@7r+p$ygkpq@GA@lZ~e9;wUtP9c67A+o$yaF?FTxGiYvaxX8b7S zlhcKr7e9^!HQu$I+KtYS7FTc6=&O+s%%8n6kZosnN&;%HG&-}sJlj?hhflyMiM~Cb zGg~_|I5Y!{{0dZmY9I~(`lhF0wLv6w{LQ{xHdJOf!~02N^O(<Qr+07ftI?-FN9Ulh zmtrvX_?+zC`Kxqi^%YsImHf^$xin_*>X>N`2SMjO<`8<ZnKhR8_hguP!Z4XP>YqZ$ z-o3J=`M>3q$~>j{59F@_*K1puoY;w#!2SzwP6;DBNyTb<oVsVVpo78o1+bbRp&iYO zAqrt?F#`_*Nnqn9urdG?2mzaps7(%@A<T3NbiGmo+N#39yo;CSg6fW*hScjfd&0D; zEo&gd4<V!}m7OT?jG8GRv%0!n6)LzXsm?u-W1#))qt~;{nFc+IK{0he;&050f7mm@ zV5=vcdPl57!X<KS{RtXxbD|qH>pn7yW9moSx{RbsmvJ{>clTW0)s#tz@8lf25(l+7 zBJmF>wWF^G2T3LCGR9{m0fseOKrEBfTLP3vLmb#61B#l1qRkJp;@id@ANMp*8TNK1 z<7r{XkM~zT>otC7xUw7xi<q7(qn}7shiipi=&I=3`F<dG2`_J*w0db$zGdgd_mbA% zB?_ds_lnIF!I_F5ua5uJSNbjgGM#n{pg>KjX5#!Nh8CG4xhiC6=`oWM9b-SW6tEgI zKxZdv<Isj&#D@I`n!;%j*vy9ERdI2;UyrVJPR*pz&NDLu%#mL@I)F{>HgU7*37c=P z>x!S+vP6PsFjrixO*TPtZ6NcX3kP2p;kzQ5FV*;qOQJo%Mpj_TQEyTkY09Oudl?%w zKvKO2R7em_`bnfjh~3oum}UF%xHw3yH7k9q>{<=zr6;c1Blh6{kie3BRx|9}S^iby zUEdj?$0Sgi_?rv{5dfcp7q<tb&)yoC)inQo^<W>cLIw8ufGpV~M_M694=Nh;Wyn?W zVa}r@^SP;!(M+ME_Zf4dMTCWQLQ!3dqHNz=k>FJWx#d*TFTO7R4|dw<@-@uWZw(iH zX!k@e^9IfGS{=0hPNlq?zP!RQOW|6HKduRmng7VX4sY1wGf5KIdZ_|MA7&uLE#(nt zyhYm29a3EepR}<r1HVhlERZsaU0R^7at|b#S%04{T$ST?_|w$4b?Qb&M5JAZnV6x> z2`TZI=^bpCEx)AhiJfT=a3-Imj(bx%kUpFvWkdUmPm*^KRPZZN))5?r7c$JPLnjIJ zC?1STB!~Da>{5XY$@eoF5w3n&>cAv<-#%7w1Sx@_A_$?=u)Ic#kltt@IFsuM2r4Ig zSXv>+bYk1`fpkR|@tUxH{J%jckBGK+Iv{b-041#G*<{bH_2-DF`<G)$D~udL+0=6Y z(x7#=pj`1K^h}U-78>vG^o3kw$yE^O$8)A@=$8bRhv>|Q1K=D{jrcn34B-##Gq(*v z!4Gi5)G~U|(GHQ1^9sL(+OieOwcCh_Rne9tC5iMEq_S+DWsWk78H(b)!Ky{6nRcz9 z)Z4}^dDQQ6R)#h2?X2)<vk_K#I%CWkt}T=(m#;t1wS^ikC4l*jSIX%HrXPC$k3apr zGbBKS91tLdHn?nV2B*8ccE#OZN1?<g@T`q!;EC~wvfvBANNi{hN%3k#MIx*sy0ND5 ze-x^1<?-I-M#+;8k#@MmQNpFFqzRq(xa4UEC8^VM0hg*%7HYbr&Mcjc@=7($P`Z|C z0TuB+>uBI}?W|`*l#lO`h;C{Bvt&dTKC|s~3nLvpo>{gRO6n7fopzEdFdGbk7cvdD zV3DWnk*`X9f2t@7hq9=RxZ#QD%<CuE-(lh>R8<#Pp2QkOLC>*}EJmg=C|jPBFNTz3 z{0u?#rH^g-<IL^@HM%V(3~%(NCOm-?wq;RwaJ-*CW(qr(6Ep*}u`8D(gR|82o*&@A z$2;C=uKMatnbFldXG|{uVc)own_fY8ZKDh!&+_AjucPH^`Y;8OH4wqHkNFH1-M7FD zZMHLYNWgf3>oCw}pE$&CJk2`P@aQXXYkT^6K2+F#6My)#F+zY2-@cDOER?x0@u&yt zT}!;j7e9CcdOdnN8!p2DSt_emF;RTY3vDQV60Zl3M#sXSwU%%Ej14wSoj$6U;BEET zFfAQyPk66A%P(KDG~{sjAghCE)sn|3a?;;|6uUc`D2Vt5Q6owNxj|wXc|m^zYrI~6 zsv>r0SW(jAzwHf4E@&H7W&Jduc*ahyI(gP<@l~-`>RU{S_gP!>Ztnx1qdxz=NE7`{ zxQ%BZLR)_Lt<=C`oGjxQq8DbV3pfrNh97qbE?+OaV*F7<&z;Jfzy@9Qzo~~NOz_Fl zi^c#Wfu_niJkiWzp>hsX2Tw&Mk+m4&%Un4&O0a;;$Zd~<|DLc1xP*a48AOfvaZ!78 zQL8j=m`p%&Zj%$hlGDyG3*y)V%@pu(Y9y>WabO~J`l@I-8LBu9K-$`ZqsG~K7A65v z<Tl;MTrN!0o_fX^ASv>9BvSW}6XL8KXdV9;V!c{7vxAjtFgbq6`?)JjQ5(q9ssW}m z+~n?j2MPpn=K&)1MA8-7cV_+}P9IlB?oREFVM7iqdmUBYrAz=`<RcNWmTKZ%Mh|T4 zO9cm+%lt11BM~9iijIY1-ovtD83fz#UYvw(S>4m!F}~}$bmk@zK3j6a;l{by4^80; z3o+SF^~M>hzBPy5{?c?AK#yuKd7DL<$w|zzG7VbW`!|3Sk{rZi7QfGfQ)B_pkJHy0 zG)tPlWZ&_s)0O!(UDmRH>yFPimYun95mN>LJO<}tqQaqckztm+mU}^$dnjE1l|Uh! z<pEWT5HCa8)TNIPz@5pW;{8Mz6cWR0VF`vSX24E1Q+XoEba94;fr5J(8Y>79dB}rw z^k2r*>oPOQ5C!7|s#^daB;2|V<2yJsFR(iZ#{UHboLgoM1#`!}5f)rTB<sO%g;BL@ znEZl(JW-c6uI;J~rcg70I6tT%;k=tiKRzJgGhK~gD;g>o^C#tX+F8ijb&Tr-Cii=F z^O2RfF?R*b>>SX+0ss<eBFbv*NPmtd7fNihVaj+aZ#lQs-dn#g=4-Iy&%RRs0HEN2 z)ao5MegYU6LH#_+xUbPv!t<e$0TP35x++^`{+SlJ&3baw;&z#OXd#wt?aVa|@ILmo z(mnLU#CVd#Z`A_cD(ZqOnmC-MDGwVc6h3uLw-jz5LO-(u6p!u<im`=kb@Fx_jm@p9 zw_7u>bzz<nXF9ohgoC+SiINAW!xygq_c!>)u$U^X;!Ao{#pB=D532s%&)^~vE2P1| zM<q-6x-`Nv!k0s2#Y2=0?X&%p*`oZV!vXPINS^uGSs}*9K%Hp+;M?6VSO~NZPJhCU zu1XVPQ<{v%UZQij7m?UcGHf?<tRouKuXrcI{LBa376$2=Ho|h)voamtG)&I|r`5on zR2tE}5(ot5RA=9uFx#NLt0IGCnIyhRkf3tKq!KkhPu!)GCd$Ge!lS8SRA11`&BAu2 z^7tfMKy@QBiCJ;9K2nEV{n8Ww5@JzK3uaTFr=`SlZLJX5HlRie1*&1sMJA^g`I6c3 z($k*~l+^IuLcBG_{xvC-mB>$EaYWzkrAj(}pRwt91|_D8yVRm4do+o3|4~)dnM>kt z^%j)*V#!0?<xQs1aL5B|Q$gvXdbowY%IW!VMu{rs{;N+tm3_H3#-|9JW5v(eWA3(9 zRhWF!H(S+{AB9*fC37nk)$3w!qcZz0sZeOoya<MyztAo2!b211zvM>yi`Syt&Csx^ zoB_n_3cGg4nsRO=+kr?ct4h$Oa3+-R1fCMMTV#a1TuJ1M_oQQz+U_hD0a2FjwDBqy z>@d^Er$S=mOyOC))lQ;=*$Qa53lDMF2(e?MAcsCu2vB*H!#UKf#bXM5UlM^nNFWM5 zo~zIC#Sv=#C^VZP_KexGi9`VY7dvN0Piri6nDdqT7WWuJVB|}=B>ZL>Try+E-%7a) zmU$++_d*PR9I8w{2R9>wy5IjhKd)#oWs3(%7z0lvd&LWc--yL6R6ucPYTyq;=!ruL z-S583taNxTB4cT9_c!nXC|8uHcF^+V@uS<ZAv}TZJvs7reIdb`Z;c!`B+TG&leJ^X zGv2dBYlq(b4rSOe8IEkot6vNc&K+;GkZv<H2%&upkV%fd0``JMMrTGDUrV`{89$bJ zd?E8hs&oQV+lo~Dv9HbE1D;Y|oDx0YJX6dsq+A+j;^m+$d_*}j_TYL>MW2Y=q4w!A zNq4Fk0Eyk{6Go$0=z9|A()PWq4p~$<AqP-F&1<2S>Qd|4fOeJC_IsisHNf%vz+S8D zho&fTV_=Ry@MRP5=oYY(r!$qHr<*H7z#~<xWoOHQVhCV1raX(4iyuQ8OUmX^^g38T zG5<jeI#8Nr^6M8@rAKhM*)m{SC#q~AJPklI&BoAJ^PsDuJFNVGyjQA*Yiv0|<p<Bt z<v%hmm_P|c#65UPzwEoJ9&}&et(*o|9sr+yuy|j9%ximO7=UB~Jaiw5*i!{}D1Un; zHoj6&sSjt1EMop#Zf$zF?AQQsxV;|Bgf2^N>`87ObTE0E8;<<`-}T*ZM~udi;0YCQ zsNfxmt3BbN^axbZ`M5h5Dix{pB2v>G8w$fgndq`m^v@FN6Akr?Hik2Z$oMOv_L(_D zMAMKPW+BpuKd=OR@s`d&;i=-HLw#b-CMA;BQ<bk@N$wM*_m!V&61i`Z*`(a>pDo=B z6iom!H~Y{{qQy->g)XY4dbT7R5iOqG&Z3Gi&BVBN*Hob7bk=lqIj-$qH(*F&y0dc- zSZAE0<^*gbaX%`P#)9Mko$}f=S!J3DF3&JM_p-mZ^<UG<*Q&;gKn8la7|~HR&0|~# zl&hoK#1Ch%M27Cm&frG|Wb@V2`VaUJz#2{|5R~KYEa%y(`Z~>QM7M0R_38*n;`)^W zfh?Ozn!wE&gK5=U)`J2;_W{pe+aRL>h$aIvL?fumWic0k1Q?2-4Mp8;Blp#Bc-)P~ zY5ZLPh~PnSRh68<hkY}5y<Z!iSgqVZ_3X;^BrT$YERDV$!e}NuBBx<O@8NbW>3g%7 zKSMp~PweD=^~4n2QPzQ2qlpe}5a$I3WAzRrJ>yM#23%O1jLtMPf#uw1L#?ivy}DAs zBb*O0Nly}H77L5k7mM*KQzeu=2~8NUl9KT8OqxhWs!XOoB4Z3$^@Qraq$-XDqS=GM zK+DU-97Vl*Ol>T^=D?x7&7m>@hz8ux2dIjVJ*oH4z4lvWYE{SD?4~%oekp-ZVv`zA z$dk1{B&VDwiwD(J<Q#gU)0+;oFsr>h#&drk6@l!CG7&XC(kV`y;BGm^B_^B4fX}dX zPI`Q^#(Y97KE9t6u0D=EtD~m>@D1-Av74`IfbVrv-628XFZ+sH;}t|t!=s%x(-Nak zYh6CJJwT?qz**ffU30I(T~KTn{?xkHn@=P9Z++$fP|Q*5*Ax+DT1@A|k3yP|R~G#- z4PRC(5x^d#irrVMDz&EDqpw_r&mR7n`*`<W;n~H)ua0itR}Py4jH!R<L@dP0o?(k+ zJB~42b{OslSg03q%$EWU;8Y)Q!A}j?N;^9_PzjuI1M;sIdH+WGgc9!|6XXkFY%%Fd z|8#NdfZ(EghG{$hKOiPT?dq^z#u#vx=pk5szct7*vW9vYU#oS?BOo9fQ<Bw5qe`8o z0{3PzXQ=gUDy@}3WnEp1Te2EK)Ei_fx`HYrX{PK$=;{(tlj<j?^GGdZw*+<y;DK5r zK&!m#h7e@KD!$-nyTRA&$8@TAQHkOQG0U6CZ}tLHUIMkVT>@mF%$suTrryhh{5QjV zo#u;gf?BH!1sZ??J$#pM1z0TH^qxvG;M{caucJ60r*xUfqnIvab&cj%BLN>N70WIM z!pc+ufBDb$hA)!<Nc0k~@s#$H9RKROUa?c}^);ctY`6a2eNWR&x(V9eW+qSG7IrAq zA{UB@gP#oGelOzAS!tb@6Fk%g{=N-<lTk!OL%1^Hcm*POn{krwrM9?GAm`apw04qg z<}xWWf}ELB0t2tJH+D16$__9Ok?gjVoCbu87yWjw4qy%`ONNgNgJ;SiGd<-FMcRA? zEbisBp{zH&fY)Wfj?O}KZeJ>1o{wwHrRMii*|EATQ`y!|6>&1~ii^PYAgW;R_$pZ! z7p<eLL8G-{e{E3*(Aj`JhdIO|ANutb=HNLXjS1+GS;d1^ykMnNW7<I={iqtg$sxby zCbsu=NLZf8rnpcqh0oY>loRsCc&<ar&<W-3xhi+v%X`cJfl}lH{vGuf69wDuw^TRO zr(dr){aduk0U$U>9(`MR1P$UHYeh8)yy@qg^S50GPw|yg-rqOiOMQ_#ljB)b=KmCR zT>kdjYr~UsJ_yX40TrWRlHI59N1wN8FaFWAqmPQK&~@$SBN~jJWrP2o^Nj%+&(-+G z`9N&Z_P@91S8Os)(lY$*-<~=u^!}*dRt7AF6}%oIeR$XS(>HtDtzd^;B{AGM|3|-4 zuko)Lef#<2-J$J0=-hVYSse4&Zz&d2mdr}$xe|@-$r?O-1Sxf3F92%H62)>rZ_8BD z93QiRWxb|Ti`j1wHlwv<{@W!q_A6O@76Z%MuAMF6-zgkT6>Q^{;9p(FXXA)i;exCh z;f|{>vPD_hH>zs_9J2EIYI~EYOZEb9h(K2%;4@Xi)xP{3-^)2ue3lB`=7JLMnF5=D zr%#e37IGihc1YYmD(Td1u4Pg0oKIO=oqK)X`{=Mh@X%8}<E4<?@{u?EQ%M!bXo}l} zfk>98PGwol^DwW8pyNRe>!F%TKP%bIQ-dat(c>U>|Hn23IJ*?1CpF<|tv!Zpqx53% zqK4G-W%`^)wHtrL@+V(+fZu<X!HIhr8|K=^g>R#>)(`j?FArb6k)~RFZiPGNPJc!o zb6plj^R(|_BB=@+{I}qh^zpc0wG4_L7R_lkqpkp6)mYUE$mYgsTK6N6m=|I{8-Qk^ zRDs#7ZZ?m_i#&Td&gjm#m_P8e1r_sJPV4yaK$}RF2Jf%J*Yg)9px5{?EDBotc&$b@ ze(-))Z+3w;zNd?-nnQid0QRz$B~lRCqqSBIb-zaB*8Ne2t^%WN(M!|L*I!*#jQsMR zS<o0$Ultj2kn-SZ%i@dr2SHut6;s{;Pi$Vv-wFgPY%?jyP0FE&u!z5amKgwVTZK=4 z5YHIDD4wQ$Ma-N((YQD^wsSPFyYgg}>z3?6)hS4!dM~JbgEqEHBe&poyD*Z?kDN|4 znk*cSp=tHGRul1zIKBwSHpq!j5RV-}uX!om_6$Alk{6q~jNCQ}xN&GkDDZBCpmlN5 z&oTIjU-2JlY}d5-U%#(X8-4*L!t|0hGO`Datdz{1hSf>fQ!g9LGXJrPnKM1gc*%e9 zr6k$wVL3OG^$W5P+CjN9WA9b>#$TaMh-FdDLD8$F%D=|RM+Oe@Pw^Cu0L91jg?>_y z0AST$#1jAW`P;zQ&sjA;ujh%^3<`d!<hd!GMHSx3Q&nvh(k>`hF|TYFf9ovp%dpJ- zPnV)Ezl}@G^gd;@p`xL1@rQpm;2_H7$dm7Xc0wx(x)=W11ofN0J){v@4B-FC7jmvY z!uytOMX1T!2P^SFblC^=b+N9SXE2Y0TBk>SEj8hoqsJOR0;ES6!hCRS;r#idyRuq_ z?uzMOcY9m~6=I4keu85a7*>E_kNHN;zUrk{)oayBoRBN0a^9-<TmgS$56zEh*$La4 zT-x4!E6V#$uZ+aoKoYzkzkmKz&>fFM{h!;7&0b$~G*_b5oSFIa|Cl=Ou%!3LeX~IX z1VqId&T!`58t$3l%pLBLnJcrh0!5q|&djuMkF;=PHV?R0=FZGISY~Er+OE^_{P=wT z`u)!Z*Tr~WZ=Tol+&5I%9^_E=iLeAfqCca>9V5Y&WZOPtlabY24(;8In@5y;HXq!) zflvt2V{$7PBt&MH=)Fxu>EpL<1O_^fE*8rG+g3)X3)kvE7USEt8hUTXgCVtC7_bYJ z-@JEK#piKR!==8>TX)f8CQ{={K#q&mCIaYR9zGlV$fqLcMzRX(hfl(F$DsM3qYXy$ z7py(dt1rQ-s8#^t6TR5)5|{CP<d89%O+WPQA&SNjZckLG?PtYyC1jGD?nUfvUP>79 z{i|(S)a)-YzK*#1QhFv%FK=BYKO{P&IrB!yIr~!8x{O62)5er(Px-vO203wCUYGIt zCGOW>=gUNE#J99Rz`uxpkmrjuToW()_3_t!deZ(KdH$&#%!nKDu)-h<^VX)(<dT9A zV5+?|=`ZFireKktF;v$8$L_O8+vMT0hf#9IK1?D%3sx*+v==<rU!vTEF4=c712WzK zpH<NJTj&q7g%l7C6DGLH`c7z~UgAuKX9P>MGr;=+haWDabSVce&#Vo?u_Rk21c;AB z1d}Y&{w#+nk6x74r!s%Cfdg<!<Aha!dTFPaf@S>I<&ucei?Ze^r1P`BIr);N+gohQ zhT(j1b+1-h-8~1<MP0-D)W_jD1%#$U<-9{#_9FO5hrFLV{OnweoGD@d%dlUp4cTJZ zw*pHrY)eWv_%Dw9eW<9^y`!z9Yaplyo?LW_R0le}yRGSu{Ui^DBE(Ir{AR-)kV5(` z@u?hK+L@z}DHSWo!a3_^K(d|X)<{g8xqsMRwv=@hljTD0f{42r;_AgcEP+shr?VbZ z!q4B+LD)=X<VXf4bva0xoB=u#!$0&#{GX6rCa$$>L?)qkZA3O{NO4pyZ6PdINbRiq zAGLE<SYrg(jwu!xf<#m+BL~L!*RxZEYW99mq1Nr?(n+0%fAB)5l5j<?KV|p@WjN>V zebMMCt?Yk&)cMCIf78XMO<%LUi_PBFWMj<VH8xC}U+tdvXaV2#p0U{>t6Z`bF;fw< z7Y|v`u#({idQtbSv9rYvW~2pR^n0n<7~>A?JW_j+GVgb`sZrc@^87l{v$!*DUPQ45 zkbTH-NN@w=&j)0q4gKBr?2Z^eq}$%oVPb8O(REEroyUQ@*}*4<7r~ek)uus|UKL3m zjw7UlF_pX2V`BZU90;x!0@7Tc@dF_wRbBT@IZAH`U?-~4$_0ty|JtdJ-uD+L;Mnat zcDKl*e2VR)Rw2NaHmdm4CS?-3S(7TW(_sU%j-ZCX?BEw)oP5D}`XXbqB-aosW<R4a zzg;(a4NCOE8o|{X1-r=oKU)}SplGZO_&xtJU(`Xjb&V<R6>HCQ91Br=k+v_)9`6*} z3dxl_Gset2Tuyc<@b=*=N$N&-4NC^60Z$iO6zGi*Bg=Oiq+?r4SS8MCzZ}aB&k5sf zzb$_5z6*c$X5U;6^RR8cQrCF5S+ncG=|Y`UkT^;&L#YgP{XEF^G3Qxq1G@gDu1vA% zD~juMV?}8DJM#~;zl*<vax^q-Tk5N~+HaPM9kcpAboQyw?c&B`oldhm$GFO^B(R@} z{QY@e*}sIqo?^2NvOW)pv<FnS(2l`=gD?9<V=Fs)X)s@xi~Vy^S)gfq?a#;ovAoaO z`-I_w4fiQEK1(>}7C<at^PnI#Zpls*yuaJmW%((0QZ70|lMF;S(yNQlO+iD{1MYMP zXG57&5ps5n#Pi}H?3fmWFFl>Y>5&u5B$<ysqr;4Mdai%dDzf|XY^F6rXqx|M5nJ?x zvDfPjY%OVt{}3Pp6)N9RkrzSo6r1>HaGzLGR}5G`2%<&-inboNNot_JET%&4S>aEd zx+?opg~>!Gx7xX4gYyvkp@g<PWd5M7WH+T@>?B|G=+CU;A*$I_#Tv@iHT&%{UB!gl z=Hj7IE5F0e)%aK^7Wk_4GLMk27gaAFqmfg+3@Hqlc_n#QkaOV7sTNE12<dYLj=QiN z^l`a%*{NfMuVc8jcOe?yOYeOw`kz{Psxy6BTr1j@Y#;gYCltQ@WVoc4X0rmKgOu8) zP=45KMi-?(qrc!#0#_o+mvy1eW!!^_=SiU0;rnM?yM#{ff6Zo~hJpeVX^(6}Tu{>_ zVfj!e-3~dq57{NpT-=|(0%yOYt6`NHQ1Z+$wu7U8`!l~Uol^e9kaf;vQ>0J$$p~}p zthTU2M><lvilVf_d)BIsyMhe_jiQi2Pmkn;N;wP>9`hvN*pvQN0}QhyW<f6^{ZVSI zLHvHp9pI&OEx_paqER+0^2y0=HQ=AL$Cs2h0Z(*!Js4~&kY(ApQotL#KmHg|q6LaE zDhzN=?<>W{%L*z5b!zNQdzSZXVR)qLsEbF%QO+uHf`0cgy3Lt0*tA&<X;S~y?HVvQ zw-4kw<tX<BKW^#$F)-O8Z~-KB|3-5y{Z7kqkSp~mMZu)<>7M1LjjPwfq)+qB(dqQQ z9uiDi;^)8*?Aez+O46q*9ABNh68MJn*MCzk-LV`Hac3B+w4;dPmX?)#hZvau8gqdy zamRn2fQdc?x>EsQ8B;^p0b(mShyqSFQw`T|MP9Jf7;Y_~K()<?PJK;U3LQ<^=)xkj z3Nah!P6J6V1E|W?*$=)#RL;KR-;&eFPK>KC?gd`KT}_ZcD|Z>cFVL85BxiBGk`tok zJtVET4%M^n3CEfO;OQGRKt@-QjWE^UE~u^lcGAM32LPECA2r>`u#{NVtNk=+bgPyI zQNTV&!c~Cd-_xGxvf0`CqAp5Fl|A-VUzL@Q$L%SZ>i0<AQxQO%>i2WV$htIKkr1vb z>ylVOOKN%(f8Q+dOd|R?;zenVVJ`WFV!xl4{QIJRRGn0gh)>whtNYCs92p9ioUOfS zl%I^ULSMSNFtcrPN72Kv<csIqULW(xs}DaWAAa~b;gjvl$@ZTUPcvnIG5+~nSb9P= zLlf3nBpTT_J4nlDG_RY;I$T;R_P5Gk`)~rc)~L=C!<>-ScpI0aR4*czHsoYxBTA!4 zuIj#Nu#a<(!ZwI2?3A?{0mjjx%Go#{3U~x4<Q%NxhB%R$!{#Xofr2b0WS7x_oylWo zFtSTrKonKg?Ovm4mS$mv>ENzZT3)t8%ibk)XY57HbmMkZ^CzvF$^R<NZpV()`?i>; zg$n(&WpA6pXkbb5`jY8}6HeF9-118}Lxs(_7EPO#Dh*OG3W6x1>JWDH-X>M6IOk)1 zB^TGzaxD#&s10O?dY>BgyVxMV?Xi?Q+QKh})cb(@K#Y60QF+S0Zki&gCmG}+MM>ZX zSDNkeq<xNtKjTBdvhBSE2upYS>xcK&$};na1*1Muk58<@({HrkO&U$pheE_w*VM*- ztY|*+t(AT8YbvipO(W@=4~wHk=ksVuZ^Zj~Y6d_3JoZjk)e4ck230^_&Y5!14FE)K zYzZCDyHWXd->sr5(IGmxi_*8(F(lH#6a4@aqS{9I<pWw3c8iq5sNoQZG=@?mS_7c- zUcei9VTMyKKmY#2=VAE(ByM(CE?}I?6F!CL^6L2x3c9`sya)4_2${pduQ=pRDCQ=S zog}Q_(*pMZiP44Gslen}$);ydGH}u*v%xs@vghbZ$&0f9zf3AU!k1E<4A{3QKvqE` zPkNHjj$jIJ(FXt<qNpCqi(xFS%_=r6)(LVdoAvVkPRnblKoE!+iH<P|-dI~es771h zMhXA;W3P@EUYo-<a+V@D->p=1U@j9tO8uc+^?9;xnTA}D){O1;JCG4HTldejp32}g zQ@b7Xbq=9m@3RW>i^@?mmnV&lW#%2?7I($JbxGZ=2(sfz-SZs!zBm}zdg-rsuA_=9 z{{;W>+I>!)*B_zXA7xzs?;MdQA?h09zgI!Oqa|@G!T*rTYL0JBzZR*F%kzFnIk60b zV4#D`_I6fK_tTK6HxwJ2#fp4h@`vlF4~3|i;BkcKcm`o4qnt7qd?XSzx<Eow7AN}M z{W}RsJA%t`vkOfUm%j-l<m((|!X)C>@z<oKDoUQPJqe|_<u?^qLwKi|xu-rX!`XEI zixqG>JuQtAGc}U%H5Xn~5*!AA=+e*8M`ewjl|y6%@-MNdm)_=4K8{WDtV(8VUfk)x zc$Ynewi0}TqLOCUic<Wv^h{~VSfK0!eyMZ4oFMS#FR!gu45>pwNjFQ`8KfGV^&x*? z|H?Hjfr8G5EcN>EYbt~Kx^|kbSJR~@>U5u&s9f)J5o=1j+Dv=W(r;(+aQ+NX*$Un8 zz3sWR(TM%<(evo3Fxs%FAM4k<qyBQDS-TEsAA&1!`L+-3zq<0lHXgj3?x*F89wT}0 zx*`W?ejOA@CnT|-a)J^$EC4tQSBVmnxt)Jwl4?L<j;KZo;)CPCXaGV>5Z}SWR=AI4 zEId+K|MPg?2w+{*IRM!)RdEhLu=1SMmQTx+eoQODR#(iwh&-Rgn{V=U&WgOUr&l-K zTQMCO^7LWAOu6UP93I+`L6pnd?~|nzoF(TE6#CW0mMK06pbQnHCbWzB^Ce<|l33uX z5&TIo0MfcfivoCg;Z=UUSQFt3fY$^LZ-O2`1+xxoR&%k6418K%o+*&rhIsRM>~$JY z^LFW*Kh9cck&;%vcGH{9f0yK`E(#xPbidi$!hw{?1HyOt!@DSTPS(dfC3T_eXD3v{ z_NoCTdk1$^>&%h99H}oCkDR_D6^$9PNPE&>=h~K}rq}%Z*QU24>2>$&<@*)QZU|4W zs=wh?lzzEo)F#u;=&cX;C{)4m@G=GU=Q3)2p9nqT;zJ&{pNU$P+dC3mCAvB4e497X z0Z2?@2^o>7b$SGK*jZw7>NYcCmF~Szh3ZvCd>yH%pdcY7*ptJLqugCiZ{41uQ+_+3 zy6NCb-mH};A^FDn_!8%pss8$k`8P4=kxuv8k6l3K2AqzRh+4;o*9M)OT}`f)_RNJf zQgCdb@=TVVT(%i+&}2DZeVrkl36wPCXGPj5A_k3kSxTfVRRPFiXi$>|L^A<W=qB)T zZbmRqw%G3_psm>BP2)%MrMcmY=BjI|p)Vp!<Iotgh*F^;rfj0Jn8|+=SzL}8e@n^_ zbXAfszxIy*=JG_8+R~elji~+fto_aJG)T|MTOL<e_FOb>h8v8<g$LTzG3QMg;EXHc zI)M{k9Ik(DyKKY0+7^5?=4F|qN_%}ZGj_}MkB9`C`S#FiRpKMMoxHzCx!RvyCd6#P zE9W}%<0^9Jqh&pHC?{epa(J2_KV-%Z=-)4*Tf*NJ!xRzzm1q6FSc6#rBxImOmmMea z>f#Y4W4-MVRj5Jxz8jyNIK%F<615I$FOth87Cm2VZ{3`K)|f;o!PaX!X}mbU9dkhg zUR4sgAmU8wRDtDDa5FZlyez$pL9<A(aqysa21pG%Ae~?%;Vi_W+KAH_3Ow5b!C70B z0i7|h_0XWk0#H%~h)UztNz6gWGR;QSF?&|{#yTxQSHc90ALOiB>=oGPFvj|-7|&SA z=T=ILAri*@h*XRgG@spSQ&Q6uIKe3ERaMvo1}D1Asb#%)$x@{asF`XnWWCZH8z66m z)^fu2{)AqKTsI_&)g@gvJq<QxYDbo8H+PhM-+6UCej^&G{9QWUYUc8<H0po6qYk?c zhZg{%c|PNdks=&u*10N=Wv1X78<KO~&2S3UNEc1YNsovA(`o<rdC~Qi8gLzp;^8i` zx!Zk>o>=;KRJp)=bBOnIIBbmPSIa7W%o?5H^suIWAEHGK-DE|*_Zlji>L?d~w%$K* zEct=&f_xYpaX`&(cBfC%W2JHLF5XE!^~#~Ww}<pcuP_2|#b}voPo$-dy#Ij4I>;b0 z+vrc$euOQ2(uQcs$07KrUv&Hf8!`tZ&-Rj`WtmQ8oA_tRGZo~DfLMFpX>P78o*(0~ z^0(*8Mai9sxk5w^_EPtUwUd5elhFNF@>&(A3s<$)GZ`A{;TsT_x3i_%(Bqx?x=Fqw zTZ$ipbwF3D+vW?h)Mu`x=D*SgL|^@^aLw|n@o{ajBZI>0Yb_@EYZX|hT|Dai^RE*2 zmh1w~Jt;d=J-zeMj&i72#CBBw6CMH=E<Ed-Y0PElH7^Z?{+#EoJZV9?5B|KITPG@# zw{Tl7<<^4#our)HLwoK>#C}XMK%^Cb-06&3r`!gq{M+`o$j0g@<OTj2?Oh+MCv=%~ zVe*l^yPpAW_Km<-{KbV7{e@?53vSLxs5<SQr_g9AcT%2-*QIEVzRhgb?_X(=c;;UD zYf^ffIFkmz@fZraS>y*<+N%SaRFFKAFOk8Q%>d%LvVI-=geMh>!M1Xl2&tq2V_LQ< z8z|`vl&oPWAhNKnA*Y7OYrZR>|6X{vcbMf;gId3s@8fzSfk~5x@WFFxr&lc9Y#xX6 zUkkoyczV1kdh<lVWi#*E2?v)0wJ$3&<AgM{5bFWG+dHBS>g0ie>J3}<6<y5x{4k{r z!>>uVU$lMMi@lmr@$t*af+&q${H^29t#{pSK}+=9ve%AukC6_I(J$2Gq&c?mv?>lR z4*faTKI*FseMpx*=+Kok?9SPmeh6^m5Qe31AT2qzwRCtMAW*Bq-{O5wpCejF|H87P zlDF^0I~1V)?U6{M<=~Sif{jk7yjQ)Rl3DB<IQw4O7Jhm7t>%B<#6v!d%cm2Dr^>;7 zncXQDWE88+UZhZGQx|Ty;vXYS8ALWv4x6Po29y*IV%+(7_Z8e%%77xS_z49|w!tz- za9CUORj_6x@RAIlFvgcyrNA?DVPo`3zv?wrU5k0QpiFG@WR7rIV`dSh!Z%1W0j@dk z?5+Fit~cU|n`6WWc=l9jqH2Dr?zqYlqw@D$#ZF+dgsl((f2Er%I$vxjdlltWRx>kx z6_gOBuwmew?I`a0`NN&%g3Dh{KR^Ag{s+hKXHvA=*^L;<oJg&o2d$P5c0yR=6p=K# zd**W9>~)?G=R8#4ZR&H_u*fY9-8Tf0b&AOiBOc*)i4qB@Wk=rActi#TK~bSozYPzK z5YId&rlUqpBa<ftSVhmnt|;D9mcl1k>19WI8M81=`LaR%)HK|e(2}II_nF(I@YK`y zg0Fl8_1&`$(fCXMg2s!g7!~pFQ&Jb{VkEvmgzqLtI?^G!?`}+7&4|c43(4D~C3F;G z$hHR(KvJ=!=4~F9XoKybrzcPvbHBU@mYJ+Jc(~vYTyFm$0lV^L6X+fI;)9}J`kc74 z0(5>%G5i$FKuP;FSC(rNxaC#pSHIO!Od^7A{qy@wE@%PsPc=xbwx<4MT-kNRQ8DEw zPmRSQf8W8C^?v6ZiEcmMckG{U((#0|_r<0NFN7zzf5eA6ucN+pxz8N+nL66(Lpr}m zT*H6M22OIvc=X5i#>U&fb9@H031{mmydH`O6OgjQN@fFw{=*h)N@Gjh@3h8@$<4;q zI8J0*CfYa7HPFvKYN5%dXIh8g2i~8z&GqlfB%HqcF-zjl9m`DQfy;XacPGU^VOc7v z%x9}WNG@8IvM{#$3Q|B&ab!E!fpSE|)$!M=s_KXGaH{@Xj=vH_glHPh&8q^vLCV;M z()xK}BUmMeBEl7yNM{d4+pG^?r6I9c(!tC>By++TRcPqhXpCD&;8k2Ib;IV?$HmEB z&Hnt-sL4v5&<B}7amOj4&1s^_EERUB+UVGFZ}Jzl=^C@Nt%a`Cw;tE5&i?%Q3+zm- zv%g4kX)li}YoRu{HU%ysE42deBz@e1YE^N=N7(8?OH<OlvGeT#_gROY8z;Vaa1Jcx zvD%-!K6b?*7ZY+XZELABV)SN6%Bk&_dKZ4^C_LEQeKp>&7a9_pe)>!QlZGGinLl^F zZ9P-I@aQF`-XcXXR$`a?&v(<!*Yqx1)<vS4WuCuY1WY;}u)~s#=Q1A2#k0d$;A5sc ztRE@;VL60IwhaWkcZ0zKd@AI_WN5KAFexg1?GZD${}Kdi`I8P2Cu*!Q2?Q_TsD%Ga zNseS_h9ZkdY^)n3(vs>LBz!nykU|Yx;Hb)#NV1*K{se?**>FE+V=Om$le$*KGI!mF znY-Cq1}Mg3EjujZkm=t98xP};X{l}&w->yhh*|&a&OQh<5~nhZb<4Bu;UHjv8gm9X zF7C>~IZ60YyXRC7vk5s8f&P3Y(>Prv7Ix2)L&zZ(kZp62S~H~P7Qfpf`)gR6`TI#( zb0^9BGPNh3?qlJN)k*0x;eN_R&O(+z&G7+|{nzrs3pBVhwuRdLf_hOQPCBBqZ-!`s zHrnW(M4P^1XJZ}#29?FE5bp=YhEM^hQWp(Av&=(H`3*r>npLe7Ho`ijjxuo<dm}yE zj^AFoIj+|C?7{9v-)e^DTN43GdAtAhN<iY>w=Wq^_ul&~dEDRb7Z-~@Gjr9<YTp?_ znDx)J*iWz@N!&GS=|uJ#M9fi`xe#M%GcfQm_6Z8&sz;LbC8iwzAOKjd$daT9c5)<& z?fE%UKI}2C0%Ae3caE2!D`!t((h)aIjNaVYmJX-PITlh2$aS&@hLc^*pDj2mn;U#c znZlUHW=<JG&&|4uDO{NKz;R#79eZr?m%$ga_n8K%CZASIC&$MVR(I{hPRhA6Om!JW z2bX8+3$$8^SuA8g6~AylZMn;fKyB^HkqmaWX2};gXTk9HrVN(q%{Sp9I5iF}+dnSQ zSM7TGR{oK?QvI!5g}l$c=fVp0HQKJ_Rk;KxH`>-eQ?2I3Yiit_0cl0-f`|`PVR~!^ zDpB$B-K80p@dKNX_tk?(x9M=Ju^YPITv3ly!bHxW1QE(@a-xE9qJeVmQV(_<bXhh< z5hP{l37TVkBfmO+&%GnOw+o$^ZigwZ9gNr_6(^TA)RbDmqcnn~C;R4WKV|HX{<}{4 zOd2oy>f;v5&K2sDM1Y|LJug-s!9Z>cA*G16IP{h@rgDiS`#{gul#I>sP|3#8Nl@TQ z;BqiUMdcwKx_`#=@0E1R*CFl7`CoCUtOZ4-5JV}tAP8AqjxBH0b~4t#*62!6eNw_H z%NMn&hC$U1{%T99D6F|Yy$-~Zr&DFZoZqg0bu}6vPfD98d*uu_*Q&**G^51fa{DcG zFRE8)QNb$Yx;$&)!*i;J$RK`>S{fhY=079KZfulX7l;IsvKTeF5OOeI#A^p6_n8N` zEZPGi6j}jLLu&Sw<wmtH$^#y0svRWWEq}+Z0I}t#;B0)_xld((|B1&-v>8rn%#~*S z*Ui>GezA$Zrk@R(jMBOP0C*r}MpS+ebW8Mmo!Et+cHsnmm5aZv_*KEsetRI-hdUs_ zts0K@+7zWBB8bD}$NH6PMKP3Yqb07Rai5~Be6+jCo1bAx){^?l3cHbHw|e}Q&2Dl5 z+-l=WfCPF%;o5jip>eAXYA709pDX}Jx(Cw3soAnK09z9THuS2ok?{Zz#>fPsw(~&F zp$N8CKif$s>Vdn+w3k)5&9e;{31gLK7sCXaltijO(Rk-&-afPI&Mq-40qm8dU9{u7 zzE67H!&k(;@~ZTB*D}(S?~(VVX5&?4|I2~7q+87S3R|iD+T%-oH^jZ`tK8guke5Al z$aX4}RuR_+hJ-$@gDRo~Zr->iN^NCuGpG)V!F4EqDhQXwW~%4eU{3af9|G2Lty}Bh zvaLdj6D7NaJgH_T4LS_j$_|CPap1mO8*z=9rqwdF9}$(lRl1$==Tw89Z}|~TJ0$!1 zRnhXtIU%p8;U(YSgb=@G!_c#HnITHzLb~T6w=m=+&Cvk<W$J*xGb&<;E%aoK#J48f z6`0*NzBBtc(Ueu3nu&@WyW2kQVJxp2o*U)yXDg%GS)sd9s&nsiNrcqXi7eu#1T<8? z14ZMQ0G|mGPcVC4kiPa@TjV@uXgx;=TZ_b`$Id!C8!e!>{`T#Z5^I?b_OiRmgO?W{ zPrfc`DZTta-gol^K1{nNRDF_oXriq11^$Z1FE=4uz^&E$n~rRKkm)SG|B;oP&KlvE z^^5%SP}f8~@skrr_WV4w#s-v3^^o30wgisAUL$Qc--%?4eHj8KTw~{GxGN(4;%o`+ zRH%Av7z$lvC;t<lXGhFJo3{!BJT_1o3*_DCUi``Wrun_=Nr8ock4cPnfIdf^8(9(g zx7?`Wk8f4rk)N&(cO5Ew8lzBA<iVg<;WzK~D~6ojdscW<gLun|^Hn!Hw#!W$c*Vnc zWVsbf<a;bjhwk_jhp_E&MKb6(!s&4L+sl~L!Sz&to~bS53eG87zq2_ZKjhq%3=ZPj znX!(^Z(~5QiL*<H+qp6EwLno?(o=%vHU#A*^As~gf`{Ze!;zlzV^bh;k~%~N%MjgE zxHD_Lej?+k=ZmsCo1%JWdWzPgKf^9J6q7%ByKhDA_tdoVKf&BZuQeriG1bNcUb>#} z^1f;)wYr*aoz|Zwe!#o4<q7x{B(wea>%3p}i^tXP+`M_a#)l4dw0|XT>;dJa@rwfU z!tr^_gK{sEU^+}$%n6#E%u8EXPh%Z=t9maR3S7%`l2byP_fx<M6b62k2e<8S=}ZZK zM3W}`{m)DO@3!d^7yh}*LCTw2i(X@jSTF)Z$3gx>UXqxWH-pCU!3z}h)+_X!L&$Q@ zYxnq2lpS_E2N$Z<Y-*$LTu1Q1v>=-$6BTo;!L7lqhE;2=N+U9~!M1yhw`qg9Q@~AD zgDKl?!g*w~ui!?9vc*PSes$txtYgQRGCZ9Yb1(1XMyvI9JZxx$;v;bI;MuL@*-dTR zFjYP-6x<rqf{q}dwluIU$B{6QL4Bo|V(%oGpV;4=7*6lGH{PS5)T?{QrRZ&j@vxGs z=4WinE=5~8)<rDnSh?%rMbo`osrZ&fdl9VXC9!iN-=YsxTDH0Rd~|!n1nZN82*^Xx z%-v|0GqgAl9}ZHh4rdrfpoU5KDM}WF$z+B}m;&*THOvQ!*d_@zMG}LBVF7hwTL5%F z2l;@GcGeKvY7)ws@E9OEjRV%AgG!UZf0SV<9C#K;CI@s5S+JGHlj$t^7d25JxOFbA zX@6w6FVaJ#oG!9+0!hFO4uos*1Z$Yh!D-Tv7Us2p@_S_%v-c`XCaEc~2qzbYrzMCh zMd<6y=!YsA7=xwK>ZH!EwPd1L(XnIA3>nv*u~=|KMr?#XyPY-G?mS|~&V!X<;I|Vd z`#a!I2Cep9*&#m0U#yO{h1o+xN%np;cXzr?DDSa(q%+Lkd3(cVhxIs=1N8tvC@yDW zhe}ZgTr@fpKCqkt5cJ1HmsJ+}xXLH8ixQ~CrO0XiGxfIQElf<?7t{U8^vSRG@MA?O zSc_XrGy30FAa1Yp-4qTlMT#V!5m~e*;Z=7*S{zWQ62v$QBYUD!<!~}S2HHnQJ zafDz+sdAu9BA=kYhC?Z0h43<17@A8%0T|dlI)=v)$pTmxb3|X##UM6VgbiMSfgPg2 zZ8=~=0Z2GPl;HtYcpyswB#;J6;lb(k{w&bHi7>>i<bNy6{uPJ`UOD~Q7TMEi&eEs` zAgA?2Z>nm^Ob5-_Hb~~_=wh;#at^p^gnp%q{UkLh=;D5NW$Od=O@V~?34%`|&9Ffd zpCXmnKI$wy??(uEMMhkkw>mI6o=t?`&Rgan)M2CD+Uv$m)MR4PJJ-*aoZ@I?WXw9E zr=M(tWWszn&^#ve9upEue+;sR<qfG9GN*MSd&~=aRR$o%F)oz%JHMx)7`GE29AQFI zZpY^n&85A_@lt$NQgpfQeg`F6k!16?nBmW5v2h-lT-}DRE+%ikzEnxxqo$E>X@|ME z9ea1VqftmPR2A~h3`w3-_D@qp%U$1NK5};>DQIKi-)08p-W~<(p(w>xlnDkB^lk8R z44h0D4#mK9kdUt^Vgdjvi)K%yLwp4AW&rv-2@B&>fpwxO9M|cuAQb^n5&*=KfHD-2 zAqNu7fh}Z{!%uRSKFOS9`CaZQ=-52>Ao={YSBPSsP(o+^?Z9Avl>EL#Z9**I-GI)f z=j&Cdm{uzgXtN%;g;N2JgcpJAYzVH3v?SLSV_m6qEyJt{Vr1(^;`CU!U9N6vgrWYX zDc&rSJz<M~*F7bS0_^S=!LatKi`$gVhM4MRm==`cPjh$XL5o!4R;C_vyiQLzYP?WO z(h>0<p`P9oQsg0X%UX%)>nDuI+0|)YdmCKQwHdaSlJYjyQLPE%``*>$xK==+Yxe^A z;Nz&L&APsBxO0W`rUz2f-LMhp%6pMuLkd_$Px!vNIk%+ltycxB>k4I~u5KP}-y!K7 zF*TrMAOlm$!8kJ{i*01W!wBX!;yQf1h7DeZFUA!@mj&o03et7~=0mdcAt5?AqAMIR z%o_G5O*9cu*}(+s0!*X?00a$y1OVk|AX5&6?hQvwr2P0ObCP6cblL2gnBb2Kf>)we zpA}F;(|iC5>LUf|wp$EyQ24x8>$^Jmq}SWHP*`^8cihoXjS<7Tqp?#Xetd~g8%dO! zqVJ}Z^-hjQ1SA==m#gJCo}LY5Y(|78g<i@tQ}T&w%+Bv?$ltVqH;}i+8f?xd*}g>d zd~ol48SC@`7G^|(1Om{Hb)Ef~SN%C&12Y<zRlM!pPM=?YvhBFv(|#_?C-SYXm}jAj z=D3R>$ve0Yb0b~%mZ4VA#mm^Ycs);s&GGEi%J|8-%fZJF7iqeQ8R&|oCPS3s3@Bg> zG!@lyd%4?{<M*^6g>f}f^EBNX$B-beVYF-{6W55X3`q~5(7Mzt<gUmmipi*=dTDTX z61<g!3<Q*iQ$z-6$W@LQj4yVN2a8odi8%$5AptQw0D^K+Q~;2q`ML?FOi$VU9TBsv zoZ7k9C4<|7XOZ?d&Yve~)ZV21$IBA!jiN8lS{ywBn58r1VlBVahX9ntET?rp3=fkP zgAUyUz4xZ2XpD}WRJ;&xXiFW-O9;8cFy^$I&~-vPPfbwfCOSIKx}?BI?<njbt<!mw z^&(X<$gML@(3J$JkbO&`zgMZ8vlc}?WM9y9=uMAwAin!VY_x}HW1dHQyG}-5Ux<1; z!?*-rQ27*n5ifS5(xvw6S3{@lTYcI4f7aZn)IFE}{e!-EPp=SZyA(W>;@6)8e#3m~ z<z{4?7WvrW!Ft8AV`M5tEYDLs+D0;MO)6uJDBMu^g%PaR)-7!sOc;2GkUT|#?+MYs z#T@ju097uCKF<+d7NEb<v5+v~7A!fGiGMZ(HspcS1i&dGa4ti5^#{NN06C7yA%N+1 zM{BO0^>)?Ps{`t=n@2vvFKD1FG~+Gq^41=421_d%kUOv3-4B@F%+e({wO6bQyn|F5 zf(|p7d8Z_esZ!s|MyD3Tm|B#8*G6d=D6V|m+|iUdW_nZVhCuNKTjw3wFmw26Wxk5) zCuEPp{iE<9N4g3$=^ofaFh?3V{#gq1&mAZI?@oU94zuhN<bg<SrQ6raJr?lMH<n7P z?7gUysjiin$!Af|e(lcb$Ea309{G+raW(#_+IM{J?N|1dDc^O?oc3yeQ4d_|%Xzj? zA?YdPAl|+l6f1h90`prblY=F$Vbp+9#eCv<MX5YR>2L;t?LT6v*kHDX$MaE%w3sR$ zx_(@2vr?o3AoiUnh6=;p6hPhE4rbi=EbRVh(1f~Gpu5oHNCALIt->m%4E=0zG%SG< zlg=tW>3Bp0Y2F~NQ57X}O&}+wGw6Th2HLq{ieB;t*SM4uvKrO#MzskPwjPSI)>9Ne zf7$w(Oz_{oB~xq^rS&%o)`(l?O8Y+>X>2GNIV%5mW>cY;YxkL(N<I3c;%FS@(MiEg zzppk6TQ_$$AR4!!uB5nw90+s45fO7?H0ktjjI&=_T-F}*s-Xj>HgY#@az0+t<4(6y zH9=hQfCtXhF?=ZADkD}lE4#<bQ7x<N{PWvAr(G@^mZu!^uzZ~LW3>!*3ao;QTPtwO zw#aTMj}E$cTjeko8tB%sUg1hZNb>QLeDPSmRJ@`DB}{^*h==eW?a|hifX$|SJV6mX z@(MWw!2AXX(XN<wn&=M20H=s;=0VfdW4cC?%m8tE0BL1mfm2X4UzNNsxK)}1GN(Zv zA#9Q;gBNE0&T=?nD<x-_Ipnt5e!!L9D-*m>uPsm-+IXcya>jVLKLTZU{OD>@0+ty7 z#nUpyqZ9N^rGpP4Zk`TPJo_Oha<g?|(P*z&Ydj^QJ!|kxL=yMXG`G#}oyLM~g9pW{ z^rETct<1aU-2jleRfsXjHPHuk*{S!!XD3);;wFqO^nAzm-uYIO+G?z9-FEd&8Wz_w z>+-!%>Q*VhYVog9N7ozdUTY<if7V;B%MKgTEnR!6Zz(qao<VZ|G$HQt_hbqnTl&QA z(#U&<bK4UCMn~jpeHwG1P!KW<A=EnPAOkN4#Jh$`$kmCx5}*)>;5iy*kAzbQ!>=yY zZqU)w9E@ZgZiRySO~)b`*xv$?VA5q&h+}JGN_fk>SSCn`#(&b!-)Gqh=K+W`&^qQI zcHQdhp-VO<X)z%G_b-qT>Aj#{TF^u|eP)3E5~uSZ(DKlff`v}#!n_!4O&IOs$Tp*k zlH!?9#W%_brl)C`IZ0D$j?CxLNQ4n6P3f|Rtky`#^%~{Eyim~~TTkmZ;<6p}(+VGe zr0^MGTU6NcG>Al$17GKeN^1HvnwHo?3kuaWZpN++42wz|A+~pBBXZG1@=;!Fm(s9< zxSDHuUO<+R8>*DDA;<R?j^8a#d?sjs-XiKOFTGR&8mQ&tonop@J-y=(HJDxZyO<w- zZ2pSWfoq&N@96xlqsNf9^S|xS!`dBR`6@jC&P7Y>CoBur>=1W-qs%M*@i{its~Z#l zu+6V+w!!YAWS!66ginjdbxOW|l;%^idx?y?(s~O<Rxjuthi~BQ+2F|fIi^_l7S~`w zN1{PKa2L^9<>_lgi%RCSkUWqk;Mmx~P!ekPNjwq|1cty+2rPgO0E?=`lDa@6XnBh> zfBeN%oUw}bc?9*8$ow+0?@iixchf<s14khUM`BnJ4kjI06$TTwMENiwwG@{tmpWx5 zEQksixe8}@_@n!>iLX|a%Yw7URV#x_$JJ^w@e|~xno^<Q{Q-Vb>$Wgy`~O;^lcrzX z4Hu{p|D%=;UNjNp8DPvds{YpvZS9#h|B&{q*n;**S<P~1C?eW2>YDoAlj_e?^Bb05 zW|ZI7q)h}osm<8PZ$W|A=yqtd6~xnC)4p+_PP$20+0^O>WK&GA%uq3_9U2>TKz9Wn zc-SmzAb1RT+uN(LcxC>uIfLrs$#gMVaxZW(k_gOKl&J8Ho;CFKADWGq>=;Dv7Y|EW zQvo4P041It8a+>Y3VVE<$A>#SCUgY2^UqXv=>1w*@QHh4{^1r?{bZSlqfh-rG$&0- zMDarKTs|qyJ$5wr{MXr0h1AA12dP9kFA<gOuWKwB200v|m<<8t$=PuMAP;w_MR4cH zY())RUQoe@O4yr(oP=X3bifNBfCqpZl0rqaXSiT7>py!lFR`Zcx%PMu=aTE^{~YH# zNQKhFdWHB=J=p(<vX0pp+n+3E%8ZSZkVsJCq#*x&H<!dxnM0_khKIWdE4%f=*q5%4 zRB!$tPvIsHFRI-q%zh6%0tmaI!=!AK!T-@n7kpA58$C-9R(99i1E$T6v^S_;{?OZS zx+?sUomzF=HEFS{#|8r4zDwI`yNA*z5e7wBu8p9&-05kusJ10C&gPx(y+O3$1K!+? zA<VG^Pi4~O1TQ76Vat%Dcje;kfop+F9lZ+ayEmwcx0ZbAjKW#_6>nARU24U*hsopM zoUMq1>US>p%4Kl2IuCtwt)S(rsu2U9?}48V^BlyF%}yM-t6{6`o}BNkrGyR?3=mOk z2-A!@q$lsNL~9aM<R%bvnwOm=sh95nd4)ONFGYgsA#5N6Xloj9ShyeRB5{M~Q<qXW zO+b`C4}@n6ZATnBgvbFrcX>8}5l+xD1P~oA;|x=m`IESYaP5Ecgv-g%idD?|PhDni zh}YX(3q;5J56Y~}z)W~RjPHFJ)(#tPwZj)XYGJQLwCN-GX5rNXJqttak<y)6iCngW z_UAs)&_5s|#e9Ta71kK*Zs)Q*#wvS+Q%!N-b`2Pivsm3#ojzUc9#@*X>3qceOcmy2 zi+9Miz?m!?iT3o~!998H5MGJY5D8|`YJ*PUW`}PB;075CqzA3+I<Luxr|0`Q(^g~5 zKGk>Bv!Z*!<mOg@)>*?p$e<}-$-ycB^tq_0JC%(~+dgqFx5Ops=A8IN)tFov`ckjj zNco9hU$lDa!?>TH64Js?(D^C*ZT~W(hFswwSQ(F1?_r?_s@8;iEr`MbfVDuc6ZMH5 zz^!^SHKuH^r#OSk*tJ{(?piTP3G!f$lP!UQo_(NH{NF~sp8QKyu+;;=SmPIfQYbv1 z{N4lLza(6%0-$<hKx}*7u$o{Ld89u}D%-eM2SP?gMy_NL%N^VJVYU*buUV!G@PiOr z`{GUIJvFkTh-+2s@oPVsWJjEdZ%4g^as9B;k0s?WdwuE2Se5}N%w^c<or{1tTHe7_ z)k$_qZAx=`OATnAY0FQ$#Z=Xb3J@!dgj_BhsMRs64-DK(ff<v>&@`SKK`^8}meL$l zbGJR-SWMH}CNy%hwCo>kQFXc*!{Xln%G%dUEq$fFXwM{%^!n6HpEiXE<XB~jz8K_5 ziidRzR7xiF$5n`BYs%@Il5Yh$wL1B?+{j+Iu6`A*FTOg!W1nXZ{;9*3(gwtb=qwZG zSNO~fJL!V-kpsbsNOxUZu{1iw6am6E#SKbM%)qZm4q&qcV3~B%V0VX!=UXH4kG*Am zt4Q@0u>7DXOoswQBD#PmDgcC}bvLHO01z2Gs5K``<|`fHmIspHwqHN+J2&<yS;l|( z46DT~@1l31UEq5f9JSbGcOGzcSt;zP+ssLPy07eW3J%#{8=gq7$f123F`*&ANo88W zhB9Z-`)yoYed>eB2MPncQ<i$JM~T<GVwyWXX;b)0I&LMn(P}&Ud9FW*;gs|PRI#J3 zc*PbdvU*u--E{K6iR7+U_I(Y{+cNqiuZ|yHDkbpNw*sds#?QcCKWHU1hv)KdxwTtR zZxe*I23usx2c?(7o&Ev7%Vyf&79Zb-9#P7-?fM|Fuzof!<=O18n+e`q`AZzZ7t5hA z_cwAO+BIwFlChzE8%ZqfSmAB0DOjo<098fUw1>@rl{Tob=A1h8IU;0mj|Vr};WZyl zzLlB!TcgEL?W*F;8s>|VPl?1ypol5p5}fS=&@vh$5`n1mYzWy0cq3q^eulLH1)#}s zhwQ-F3Is)wx^K1t4a!yg4VmsbXM72JNJS(0snMOgw=U!iYemmWhkPB7I*9brFR$|- zakWdncs6)_#1o^LGv}7G^r31-<z8P;%543`rbf+rP*5V1T38(->hsyWvQ^+cq<XvX z@V{j^lAG^Xgrw)0e=HNszw~t!h;~c_R0WIpXnQLP`CL7F#J$ec=Yj;(iGW37O+rHM z+?nr3e@jcA>U+fcQG38~IWCZpaX-Dq!N;Ln+rsYS%X`Kj3VwY)1(NSj#O4um<VxjO zX7(U)X1|?GH!VkhjE|-v-r>#*viDahisk2lrHm~(rayt$3jll4@=Ug5@L|uizt34P z4$FN^KhQssS{ZWwx16ZG6G)h?kWTriwD^Fi03-!y9}TwQLA+?t;6-R?lp&36=$;CT zX2XsF6ccw~C(8XoDizProWdlHBfO)XFQ>V+C~3_Z*8@ya29dW&Co_7K9|}x_2(M8# zirM2|8HrxzqE`WC^yu`!In@SxS6Y2~jgk3Yd!$;&SG79TK(<=ksc?(Ser{SNpzIVf zHVmgqJG@dJG`<Ur%F~)rwz_1awFHsl8e4rDJai9c?U{Ufu$1^EjrdrVxPnjiNj|P^ zV;vc$`7}+=dKjo1eQ>st%4-S#5rr#nKHHs2#@U=Y9plzhcB<FF;`V%g`I5)PFm=Bq zM1l-2-Vq5FK>UOf^D@NgB$&e>>@)=)p$qlkL8yYn6t?U!I@Dhm76pLaz(u15VFzbG zFH%6|d8eLL9yexM5$9RIeuSS|le1n=UcjX&?+2`s0a}9qi5;M(FazF!9MMe;{RRbh z8%DEJ<9Zzvv5J9ePJx3)`;_(&@0JiMDaNJ3-9IPGy+x#&E`0#6+?wQUEEHJ{D5pE3 zO6^s0QcV|TR9*<sC3dQuH0G5aChNP|@wdzZ*s0_nv3~wyZ4PcZc#AKpheu1bM#JKB zomdmu8Y{i5zgP)YVOrb~@zzrD6@9JYIdyYr`M9y>W}3un5Owk7(Vr)^M>dXGdB*=( z(94TG_}D<s-Q{S{(=$uM<t>YNR@SM)(!f(k-1;Ib)y*oq=JOMzkN%9pP2_o)3t*>c zP#RaHlneEx!HQ`j7wPag9xROuKTkQqtAZsZ!Oze$&+b6|c~Ba*D4HfrHN5FbFe?FQ z!}pZ>I9c%hEP2lnM*!-a>(5Bs1^&JxcMz&i&;??50A?(J(q;LVt@2<B0I3UnRRwm~ zf%sDloxnwr)TmF(j^3$$zJpN(&lHb(Bf@(wfnOso?OckD7OCP23lPf5;wQT~?7KV@ zeo~A@mH#w1W_~7yZ4^+^P;&K)YOR4<Yoi)Ty7Z=DX}VYGWZv;`PjT%lK@(X)ZLqSt zi={%l)Ow`OZDYc+t;Z3DCB^e}_h-#TyTjtY)%VH?&$~SUy_%BV5*tc&AIs!^_*7g- zmb~77c6q62Dd%ii9w_WeU|*U=k*%A-gj<%ETg14KG(-*=%dgFF%PGZe7>FMLK$A%b z=x33=N*bKO7P+P?lA#MvRP{c~hM!M5&k)pb!{E^rSS-&mo&!rJd1uYQkMh6=s3Co+ zrRJ{WqvQ2oTBv`6%Aa_1TJK(%NdfM?t>&81;Ys$;ejg|gfE)vvCqX>9P#VWDdVzFo z!7<TL(QiJ=_AOjd$=K0Jq<98Vj6G7qO<T&Z)d4B9ApWPyOwPVm&ULspql;deF{SDU zWQ|<A+K|5UtK^Hz_3zSgrv`3xH-_us>RaDQS&4843#w(aJA}xNCkw2{cE?I$j*X76 zMjdqi@jgVa<2+bWhen3fWl+#>s5j>h8sR{TKNF5z!Ksf|zW?1&mgjcd)q)wmS7|um zCYKy%yn>UTC7V_^M4z_60l5ZSSCRs#!Oma@3iOyR;=BM>$w6`iN7`t{WxBN`JBVAj zBe8vmb6i+354tc4jitg*CV8=FhzmTZ#SUoac5`IhDeHTcd7sb7#-FZ;k=vfvnjQek z@scG;@>mjmDcK%My}F{;N&tYgxnLIxG*lNR>gC6lfq%&`Qfh^t+wm)$K}7nAR0t4- zJVcFo?P9)2hEaPnwcRkU-6Yn3I0=P4t+I$^UXYCSx4p6b3*(*_o9|~{Y+U*<u+&mD z<5F&N%LedVa*I4oO7683kAHojg2M+AP3~tT^TjjASX!)v!o69IKt0Luu<`}yJv5Z^ z<J+Ns0*Fd8J<<Z8auP^3?AD#0qPT$jmht!R4C1Rx@Bdzj!>rwxU&H>c&Kc5dFu3RT z&ji`__vyP}aOME`5Cs}Qft}?c&L<%{0jRqqQIizp6&#YI+g?u>$<~DlkE2Wg<eXBl zF9m)^7m-ItWbHuBd7$20i>%W7eK#ttwvN}Sw=8FCe@zQ&5s9!|w<BPIHnLYQxB^zY zd>}Lc6A99p0lU$mLXKI&3}Y-#F?C2W%n#uJcdl+n6k+>rm86|)QmUb_>#@p0p6JPJ z<yt<QrK&PPadDoDc`@U%K{2b$HoF~vqh&o{ubq%n`2CKLf9W@Yd$g%l((-ksGw~-F z{ijOuzBe2S;9EC$6SfRI7QYhjczI~Gl_BEAuiNH6Ru<oc6gb`0TxiObnW*S7Nf`P< zXp;?g$KC%?eg9tMgLWhCPxA*C=Wt{1akY*?=n{*8W4DaY+`11LfAk(pHai=->4vZY z8&SZ9GhpEl2oPRSD6|gEI1f-!_W+{Y9b^X`$)Sj3QDNo+A$<_kTcNj~3O>SvGdYNS z5+Z~H)}RsyjK5g!4cOV7$kX>fRM)rMu4vHI0=aA4goRG7$w{(#Hlis`k@~&wE<(8g zmw!OkGmu~^OxFE&Dy3W6%SrX;UQHQIq;v+6&p{N@2a=&8rEH~YuE?_hWD3|nzg*d3 z&g4P_Drgk-B<b49j;YsYmw<Xq?B#%~!*{4Z)06dcQZDcRPI8NNemtI%F)=UY8I~cJ zJ$!C0zT^f+WI{{(VrOS~a`@`pU(pHAPlm6ihrV)^Fbx@Pk<fZtMtxib(&bynbb&H2 z<7*usoSSkpYQIJOi1T`JpWKD#Js;yAyA{xS3vDxQZk}Jw8n2le7lz?iK8%O5VX?Zf zqdZsv09mSw<Z?wHQAAsHkymFBXDBdt3Rp=Xr#u7F=Ykv<paUGRHy57EMda%uG(^G4 ztLhp&-0149xHk3py>~aSX1GmHl}|>69^F+R>DHUl^TAPl7Q^fz)ZP~vS4F8nB>>n4 z01cs6y|;8?=3lD1msZMK{+940T=vLGiS|9qc0esMPbDU8$>deO|1=3*WWX!|&t5Jw zvwLu-zxqaJ?~RPP+4cyr?|@-we9M#QiYL7Zg;)CrHLN;p@7k}$PkL#X{|cIsu`0%m zIPFUOeiKUAzjFR(=k@XY1B#Tzr3xwj7478mSj%VA|G52SIDYo&n49)u?b3aX5`4&u zMZz|j0eTP|ve^3#Tj889H@$x#a;Z9aDf%MT8UPiRof5dPC@wUTD{^0e_!cZukR(zF zfcugl`jmyejo(1>G{{LCY|n#r)*%W=h)@A|zoI2R%=!aisX~72d`AwJQTg8l{=KWk z?vE=^ECn86XGk-^$8{~neLT^f7UdRs5O+mj3Np}z`0B!rQ{l3uw*y^`E3m>>LgZ1O z{Lt!~R~Ly*t=;R8&Xkxj$WuNuuN(ls^X6Ck+@bWId#a(`%unx15~XsEci(svDE6|` zd?+w}aw|MfG$JamKkvqcso#=`%j#niBQGV6CG{T~{>++HwtDjQU>))Cye|dV6dc-y zyQ-uWoA80GmQa#+YWx9cL6xY%JB6GzN3IEOeIc*j#6MbC%r9C)1GirnuRS7wpX~_- z=J%Ql2I4wXUm3B-qdH$@X1#KyLLq)p5!`*nY*ZZ=8lVe5%T9{}K(B~ssBt0o)FtD1 z@F5=juTDhH4wQNgi>MQlg57iL$T%I#p<`p0il**0E_z&?s#S0J^Stu4v6l3m9$A3A zcv8e_jr>a2idqB!odi^)fF0@37^?6V1#x!$Et`$Vx`xP4dQv#kwwD*}o+~o^ZyNBp zNrw2lzev}z!RXYp?-n}WePlDkKw{Ux=8k{TpQHQzmS^uTe!g|H-@>QjTuJ<x?WBe0 zk4Jry%w6Kbg7s)U>+?tAsGdejtaeL~-LM|02UIYkgS%sxu;p#ne=6|Wp|ciy_s9X8 zi*q^>>fBSj#_^h(?UiEuGt8??DQnLYx5;%MI~uT<x`qmt#U;G^wZFzAUzlG(eX2<S z2UFqEJSgsKZ#qw8LLeH>fgZzNDwu)!?SPHHNviTdZ^yvp^;lCbl)m#kpNfbOfK7Pj zHCpahi`2g#lkB^5Di8I!{NJ%+)aQZ1#f`uBs1Fy}nQlu)L~CHE)mh+87yIR|71+*y z08fg54uleeb?XN6tIU})F700tvvqcPSoOf6u~}v35*5$t$ah}NhiWWyWTG?{+x@uF zi}!L0j~t*4<jDtpFOumy{&L`=attQK{N&bBm)*+r3$Ih31pLAuhI=m;+CF!L-j+F` zV0AO(VS4A0OQHVVksqJQDuV3ugyZ`(`g`C|ZeQZZ6=;cxJpy;mq4D4U$I*F)CEfpR z7!VWz6%ZA1;l`OeH8sG!x2dV2nVBnRsf`;D_Z}IR70%3@nPz1LICAGmvodp*nw6D# z+qn5Z9M5ZB00-aW`0)FDuj@RI@RzaZ43T1oyRX_$u<0lvo=jT82{c_;OxKe;kSk8` zh_?#m@`JIrgwO6*la03|G{`3;`^++7Z|-~F_lteJ|3%_Sz<t0Ma&NwrE~PJ$Q)_fu zLN|Pzg}OA9EUh2zlN~B&w~y7oclJFG^!MK{hRqlL>7)<BB(wstw>;~zM1C39J0Jd^ zg}KNd<JDJePJy#zT3vzBfq%GoVB?l;q3#C{Yq-hkLM>FrA5Q{fLZD%AwE_nvsC-#` ze~Ar-G*Chw*yRXr@*)pj2+G9ua*kpHJvfYUH;<JkQjd;&F<lKIxS1+tYZW={Qg`^7 z%LkvnJpaLPiF@)cNGlYKG#zHqg>e3O7rtJK|JxvmY$`|)Nu`4LH#&4tiXJ`R6yQ0H zwAOY~sVg+k3omoB;ey3o&CN7aef$zsB?DqOs!|~r`6$$r6$ufz==)oO)d{00m|Wew zS+iUNGyS<nvtOb8O~3YQ&zoDH)?T@LFRuHx`~@<Alkw<F?O1Ccwbl7h$kB!TLjwqz z7zT3YO|)+KpY#}mk>5`H0b{T(g#LultmpG_1MygX=ybNF>#Yxw_Vzw_mcJeLdfU3R zANivY!n-tQI0IQ;G87$^IsPLCA{Dm$!V24NcvkQevts4QH#%eG;ioWS5kw-w`_SWq zh(3-WZ+}h9JkwD=;Av?Rajo3X?)AF3Pap?Ub=qzPBx1D$6+jrW`Fl@IdTN}YOzm<Q zjL<O*5;=-=94I-mi&a4=#8FYFq#rbj*~z+^>Odq#1-%><b8Tc|36CFtF~{D{c)ZfP zwlCVJble{ZL0fI&1rWL&%XEa%0wn{X+e8FG*)D7V2w@7C0IQv4fzWZLwRr{~*j?^+ zxU}_BqI!)@y))uneYv}b0iVKvyD?aKJb7i~DkaZxNcv36NTYYc5Gs%0_4-UUFV)98 zGjFcBgPt-U_~emsdhpih-PRLZtAE-aW_@P8>g#zwc5f80mVYSb%71t7Mc=uB2$6d( zb!%~4v+e|s3w1sgtgV$V2}bX|_XqfPU0YnvH{lC=(l2{Atp6Q5A!<~Vx*#YZ)e&tt zL1JFCneWJ-`+Q_A4a=XS*I)lt6Z|F|06SIRQJxZOwRh*m{I;@!EK5<mEfu0m^(+Ye zOP6YATBD!F=h$tohy)`uutQWqU0ERX$N^Al6EDP%l!#u!GZc9MAswb7G9<E;|182S znfNGZ%JM8!>Y*o<0w98aP-z2oJv-Iep)YBZ`S&Gn{l|_!`k{uE=z`>d#Nt^33P_T$ zLszGG_?=@iY$}9+q#df-a)J2+1hB|P7e4@4A4`^)p$d@+tL|m^VE)3-+_j=!oJDlh zketi&?*mIcW?}S8rVHLBY|9XZZ|)X_^)=Kx`T8WxeGgH+n`ZPQtp}ICHyTzfw%MRF z!@YUDIroWZq_}_IsPpe@8!b;<vX12g$A5t`u+KY^0#<*(|1RY|`(4;*bZZ2CoSS=8 z98qE<Q0)qUyt%&LB1C&8T@)_~?O@<?kIYZIr-ZQdKimg|8W@R{Y^TZ>JS3hJ*qJV_ zwZ4UGjgMAb3&d?HD_2x@)(6d!aGHuRTPzuU9Y3fhOA_W8A~?dA0fTA)RF(lV6n>0F z$JDaGYV{nkf0hLl{vx54!z4rqJ_>(-DuXfcF;g2NUFa7$B<+EOteDdlGzF)7NH0U@ zt0ph+)Y*%)RU9?s-n}@QS0^spcxyKR3;E`U6R+RU!3|SYRCfVFkzT;-$y9;S`v8bf z5!lFsF15NVMAqXfH}u=6IdF4bQ$dyW9>UH+UiruQqM_%@8;uu*nv?3SQyml|^ly|| z_?_DDodAC^j8&aD-n!wxaM#)79q4M@Q%FEGkOnPkXc_XzzY*nG&@=T=`}dPiH|D}z zj>wfOjt@8+*ChBEzarD8S(KAq8imnbUBef6D5De40-By;on*5or=KlvvG~Xrec7}| z`vl7XdF69;Kf%c{dV1HS-M8nki6Vuk2RNCtsdjyn!*@-VIXF#Jp1CXy>gZstu-74M zVWB7-zYNxp)ECqXCkY>20HQkB5Z!S_<UgAN3OnqqLy@7PDZ>Id9yL=wXXkF{6FpI4 z#Ej_m+w{+xq`+glpa)L~N1<v}qrbamDkA*Du_ud9r>fk<EmDMzOwmLWsdiG1J~f)e zcIZKbDO{zfGdwRwKuIgWl?)Kj*rcKF?PggbK<LV0@U|0t%(FtN+q41_9eoTDVN-`& z*u?1B`c{OZXPLwKdEbNP`j%n5tGy4*;}S2;JqhV<5XjoQlHU1N>%<x7i)+pb-FwpS z&XLs&zF=G^sSUy31I`Bo?7M{SwM?A;?@;URliR1Qe!KuR186(1Ux9vJf4{za)fAI4 zLx19Z<8Mc6>`UpIsKq35DQdy#ufe47N&lga#MW`6(Ou~?6&WJ%i~ZW4fie-AIci3V z!Z8$vqLE^*sZXeAwO*#`{4PZ603digge<eIH)nx?!jDntGLp1hBH=YG$T;9Ssd5E= ze&M}d1*(9+JUqRaHAcvg8TH!tt*WaKG$39t4krr8?k)?FsOHGa{8PNk-$dN(@{h(( zuXo&SPugM&8c}<NJ?`;3GcE(bPb(a)uM!21vO%(f-*TKI>Eg}sOo9XL&6WGB5>-RC zhq7uAH^NWKyhjR|P3l*8mX7*3nf;8r4Z7lM=q}Grxq0_QaPhWp(xG|pxWv*iOY6yv zLu=cT_eamX>-W#UMAdJ*H}W86v;Yv4IX9Dd;lF3UK13MrZJ&!p_fGu-x$Oj%aEmWf zA$?E1?I>VXGq^4pD%GHFU$+(A=Z{ydS1u?%4~d2Wb?2q0W&4fn>#Cxkt|0!!i<Ds@ zDm^8Fx_>z{7ao8W<P-AzShZrO;{`PM5uj9>`jh|}$uPrbm<-dkdNW84498)Hb!vuH z6G+}ex&zkxQ=IqAsLVh*$`JSZkxZ!64EB0M9j#5ePJaf!f48*Gxp*lah>JH=K=SLw zAY}qwPL|G37%G-L!XE(fyEJ~AvjuMYKXwr(N(zlv5uKtsT%!)o)C*Dp-CE+1DKeyl ziedxR2^kg+45yr`J{gFYDa6Mk(>FEKzbP{qniYIa@Fua3)zn(62V0zJ^)82{_U3Jz zkze_pH*q&R=8pV_NAjOvtDQhqe(!8c;Z~-Q5%w1g&TqljK<YpA6;4mX!<7r3FUyE7 zQ$;qZr%8neAOZ;1^p+yFA6a}+--`OWeHkmtags^CtK6e`%_2|hM~SG&8*{!LN=Ld< zjEARTOqo*OikNphI=-{NDVxd;)6DvTX4ktSEskERvy(lb86d%~yI_kB0rDmzu&Kh| z0pjVA>7N5R#s-_j3m8#B1g8v=9-~r5z#0IyAcCElU>79#Fje3vi>`L}?zemcDVt}f zlQ{;>qQ&X9`M-;$ersO&UW~;m{!FDw^IMKcx{@hK#Q~(O2U0Pmm#a7`<LOsq=~q-h z3K%jT&sDzIW%d?d5KwFvmhV|X)&`fmDv6K2;R^NwFuOp{)eO5~hPy+qw}Z7wb>{H{ z$jQz7(WZR^N3xG-8`qoOyuGY5de`E^h{dsdwKH!dewIm!EdWG^yMweOdE3+Z4_P)p z%zC~)8VE<x5T$J|>g1mh6n@s~o{N$U5q{jg2oTe=y-+3<>pb{onYl!Dc~8Nskjj)b z%EtP}*C<cbMv8ThR9%8sZ88b3kcEfS#U%A;v(CVLT-AJwRv*k%W`P}DF=RSRL*#dz z8SjX-IeP66&}c!xmdTh!W%>_io@5H1U}bJpLjtIoJ`N0%NC6WnU3W91Ntj_6nPDFf zo+2?UpkRYt(2-36jmUf7`}Lv_RfJi)C8K9C;NCMBk~H5a@b>9P=ELgprk|!*qyR_> zIiiZ3Qgr~8y3j$sAORhZQp89(Vnp^ON$G%7wN87Bm>@dwXx#LA!j9o!8o+h69uiN5 zm{JdG03c0N@&DhFAEkobOH_R$g#(u}L;VHaX9aI{Sl41DR`t8zt+pOJbLA9WA`q1y zs5o$HOp^Tj=I1K-i2(S{R^M0QTd^86=OB2(Xvz08UYf=>J%rmQyz|MLvLci*B)(vB z$|TLxHm$3D_$Bj_##2aC)5h%hE>=u9{b^4qwyefV+VLo^hQ{wtC!P_7I+OkoA4#Mt zh%ej|&np!xccs3ah#m);y`7Nj@QJP-dV7E340sMt6(AECwk2RA2IROyRx~mzs5di^ zndwi>yqX3+>Y-2cXV?%JM7<1Se}?6-fIY7%L!*~&fVH<O0m(BTzH4#yNO3>I@OuaL z-AnQ_z0mcx1z6xC`tPd26V2-lV>AUUUD*Sqiu6z;zDhQJrRwl1>m*$*_1O8$V`_wB zs)Q+Jj}iHbM+%#K@tV(XJ+V)-sP10l{)Ye<lj?L&<+(Hf6u$uC=i^N8<4crmh8bRz z%z&v(IdWFyu;4AgO^VW$P;-g(Z%*OOFr_Dboi8oAa^xKuk}===VmP`Qu{Ke_9tVc{ z7FW&XuROuH@`Q7K57w5eXlvti)p)~XDEZLy0+;wPsf!oHmWfY~V-&m0in0^RvR~Ui zprH)VroT&o#lH*ghXJ7%3|RS09(k?+voNgwgB|Jm%;~d_+Y_MMXCKos*S8puu_@ST z7wX1lxFR7TIf6kYnE{b*0iTb0vKfv{hC@8qc41Dxgl@#5tL}nyhUt<z4Dzx7VUs3S zVR!n9-|UTfH`{l!PkjElWO(-z?d}*57YXE&Y5(Q`Wfp)k+9SZ&W67}~b>z~rM2LDS zeeDv6Kv-J$T}lh4gKg;*+VrbCB>eg-n5}ZQG;=1to}9<cC<Kf|Rr6CEF4wC0Awx(> zxpXAmU`oJ24-zz`8qtyEVVFJGrQ@X~F>DyHK~n1se|`E*Uz)k(1D}GY3u@aV1H%rL z+B!XvB;DSNUaWV#fm5ZrwR2R2m5U9IHWArwTh@MIy9fMT1U-LMe4oIGVkLfR-;Hld z>8k5V<kvoi=`t^ikBefkGM8q}^LCma=M`a_f<IgPWaPWY)*p}74*sWm!gThyT3oPF z2mKHm{6rk$9M5p+g`D(<xE(OOu@Fz>n#VHOd5ZD;5_nWj^X@DSUqUxXWm|ioEK}*z z4L;EoU1|}lmP03_A->U<x`<C7$?0+BcY&AZXgDe`bf-Xq2~;3}&ZU6T<U&?1f%rrQ zm&tX)@cO!Ih}(Ynx?ITl?pGIV>E1)Hpg%bZCBag_`t|ri1dw)I<_Mhdcxbi$`8}1I zaH=p53Y2N0o9BRicOj8eg7L`gM555p+O(IJT6+ad5n>Y%5I|zwyw@i&Cvf{?tvtbY zY$2L*sGAZ~D!Kcx5qk4AMR{D5*un`I+XP8X65g8V;EU31&w8`gyz#3^tVC;39`{Yl zyV>pK+1g)$hItdMG83`lGK{Sii5V@)AdRXEHFe5w>1njbxT8+3t`;Wmu8pq|dt9GL z&{a2SM;$WxW72Dv;j@r=l91^D$aQEs;!S7RM>3pC7$Z9T6Of^x2hzl5nC)h0&xn)R z0_vMUnpW^>xvl7wcf`wG3v%Idy^j`*$?|&Z){T#p2Wa`>GGf<@WdU?$e-Q7(-1^$* zbvL^Z0(8UmGM(TN-D9$(9%-6->R7VGRJto&nYpA0pf6l`k$#;3A4S$?stkBm54}-w zP26$8i6i`h${rcEdXN*$ta$c`|5RU>rNBmHyvB~d1_;78)4F%dbYl{n&#Lw46gqGJ zhW9L}oxF4NfnI%Fx}<Bx=Z+sT2yC@=dZqTC<$J!~X>h_Z#`sV6b@7O}zk#7yWrLSB zioW5CyVIFjX)=XF9XJgw6SPQID-#ob$Dq~$`8VqN^~g%|N48d4@(H4VP`{|wx9*wS z7V~TKa&)x=nz1SW#AVodK#pUxB6=ZSyI}WThWixQ9?Gy~fo(f@42MXD4W4n7lA*Iu zZFwL-I#{fGDxkg#e3-KBJm;e(A5PL;Uw8++TW8)Z!@Xq5l?rFd4Ab60K=>99`WTJR z2v?1yuN8tk?3SE)3EiTJ1n75_tDuI}aMiPkil;+Xe3u*}=%9&XP1nua+a9bfYuWz> zcouL6Xn>&}uKrnVQW6!qOvQE3EvLZ8DOs@up-H#AvP_|oKi1Rd8r$)o?+twDAoe)i zmw!mOofL);6ukYoB>&BqM!V<TQBJWvdJBAGD?-mZJhyOaO5xWV6B?|E&Ta4XEWQ&Q zYMz4cu8Ny5LLcdlqd%?d#ho=z!uG_zl`H}NLUOM1Ru6wad1uK+nW1*45?xh%<nFUa zDY)Bji_{X-E{9pozr?-L*K`o@Vlh-Gbo(iWS4pNH3&I~HzNQfG1F*9{!#17)Jq?Bl zfi0T^jw}cq1@Pxelr<iFghFp<^HJY_bvG&a?vL|#Ox9aX)}^HA_!4u41zLNLC~h$U zA337HFwc0N$T*#lU<^`=2e~bUtGo!;V1kPOJs@~!7XR;{SpAm<F|F(>NR#-h>~x~r zIZ(3BvGPUE)ryhkF30SEO)kzmqm_Cug9`PK72fp_N~PYf1E6++26|wxWk}>fR?@<W z#s%v;$k>SOA0JB_KfM{#HCBHbDS2~4jklhj|A7v-uFVh4XrrtE5X1%8Fkrw!WL2>6 z1#u8{`AuR{oTS@R%+jGziw9<)O=ZW!ttr=e4t2k<FMn%arPF-!LTV~j9(-H+vS6b@ z++hFQxrU`EbEj7F%f6P({~PiCxSB4=6q#X6$aKMj<R}70kvXPpa3D6*9?u{%86Le1 z((<k~tNHdDQyoHvWzN5E4}sI$1yr%`ibB49ILwv!_k3&CyM>g5Dg;RQC+Uv!0XFJD zae?#fvLk-+)$KpD+vVn$vul-h69|B`a{Dw*hh|l&w1e#bX(F#_bo}qP*tNe8{;7HR zKUE>9h6l@3qc=Pkt?E#MQB(rw@QUTl;}@8U0(SWl1eXHnw-tUA6szsS(hDUsgpeu@ zIRKI>1Fp_sdWLTwyC1*3_-*Ic7deYme*0Zn^Xukxn$ySW=<U6&*>tblULq(EBj>Jv zS46k@s#b2hk`0yc2gS%1mA8n8zBc*%rr6Iq95TJqCWQ1O#Z7h8^k*YgU8;$mOg$Jz z!>vBDW%b*dgqDv(MCj|pd`WGi)8|&`nfa18?VrVet2N>?O5M6lTXBLhSbgnDmVTXG zq0Hf!g{IKX!BsTZyMyW}b67;iDDlTo;bL0hdS8n71Df>%k#`TzF85xRyHL3F`s@^y z0~&;)RDAjzmMn$-%JR%VjJ@i#wilKCw({a|nV>7K;UYQ{eMXODui#dqm?!V@7e9cx z<nbLs4lN)Jh$lI8(uM68zuDt6kX{aojHw!i*zec31;i}&*!`lG!=`lwy8FxhVn+dr z14tWFBO4^@Gu1j(AEeJfIhxoEAT7RQ!4)Kc{;49;?t%)^atE*`W=-JN`h7e+Gnrv- z2_G;g{X8=zVtURDO2h{OKT4#`=khI4;WXRo*h;fZe*AmSM%-B?!N%I!<#ny-;RDJ3 z)A<QDeu0+)h$LsUhX)BF8KlW^#I;ZcqzgO9<HqY9Ym@Foh_R2Sonkk}XYZx=UGE28 zsuIB`Q+R_{pp;U*dEcj%5U$Xf?m?2!ShMeZP@v-Vm>~a4s*)$hFXOqC0@ZnDm_`%6 z?PQr_<Lgt`W9O?Pbyy$TPPg4TckRr#nY)1q{o#(UF=LKG^7We4_-bLoeKmok*VD^q zlY8fS^pfHK8<QD)^4Vl5c`?QQU0m<yMj2aUHd|4c-f7=2@_i%z{YCLR1qzpbqE;AU zC#%2bVvg;WGL)`r>qC@%r>6Qv42?*`sI31ixWfK~Z>8u|DnTiiF<v?#_Oo-dRxH?l zPoT&gq-auXgGuPIKFaPDcx$ogU{mJ!+Z6ncyzsdmd2}hZ;hm+)OAln=BiDheUM(XZ zR>FCc8tS(KOxrSV2F8?rl)Q3ma!`_KFZ%1|g?YyhRUQ`Kz~WHFK$~)kv&}R@KU}3l zn0p0LCMccSOcKHcdCY)yNGuY<Ynhg-Jsg=-bc8Gdx!dwxd(fxV%l6dWw#UD>qoI_B zR}y|na@Sh|CwdzELWTo<=L27C8%uTiL7YK<XWKS7Lds0M?x~Unyfp7aU=Qe#sq(ey zfiWXJy$N_2;5xQC!njJK5GZAOUD{*nq-Z$@4)z0~E0)2U@tj!Q`Rkhw1%d|tMTxc6 z9JCX{Tp1xYXcZw>m~uO7K{|9ODpEkYV;L-~L4v(0zjtNs^wHlV?w3k18Jfv-p14g3 zT|N()>q`uU<xuD%AE*#%C=KaO<iObcryRaU{Ph}FzR|80JqQ%mxaVG=M6c`If;yct zJdUcUZ<>4kT<8dn`?`RwqW1H1QHG5SUTCY%_GYW19f6Bd+CrMaR-lwH@mmIIx1T%e zCI$05h7YG8hp6}9!3SKaz7nTH%zIaAU&^i@_^4}EYrx#&$>L1@oKXP^(=qfn=Sl=N z#wo@%XK3uKOzl!8SU`q#0_xHMk{YH$!vGBR_-Vx1`Zc*R>(Q2qRm|pV&Gg5$qyB6! zdeM|>;8NXt`=%>T#hO%arq}o#d@q}%#I_&X$+#7t1EGuj%{<Pd1Oee{0@9gEn3_+c z;{ZhmVp^-3iCzf}v@u7#H21+&*=PlB6b2G6VQ1*%&@QyiR&N-Mn>={v>g;&XA#o35 zzCsHfOmw<Y7#d@-_)Ft_6h1?eMY*9$<iI0XwFjH_haDI%kd+h$zF-AqPT|Uiv!I^i z+UO8}kn%n;Ls6TKAmjk$)~Dzw{}nh_(OLn(mqK3I6p6bpCf4Vtsu6I<-m2_+#eJWC z*<Ff#yfvnLvvFZS7hOy<;-c^}BfZk0iuV2vrw&~QGj__LqyrK%Cmzgyf0#d;ye@kc zGWO(q#R-5wjHhsxUXCK7N2EXI<R8f5kl&R%yuT_0vtzf-Xl^ji5>O4SfuUUQ+EH{5 zYUkrYr=8<RRSr0XUuI<v{<4I_31E#>ph$d62H{8i(vQf@yG_;Jny-G3{;D-@4IiAn z`Qz@Pz?noTcFd<sp<C)52O5>A5~`j$b^&U&EG2r8RljQRDnr4gDmyWv7MfgW`$JXc zb&QXRL6d8eY*y)D{1jNzR0DQuvni`|>V#OAt8;8ud(oT5OzkY}K-@L5T)s!cYm~d_ z<Js#qwad@cP0yr0?bqM-Ka*`Xl_63M5RxEr#r5%Yb#r}Tx8)kJ1;qwywG6TtSA@v$ zkBiAJu$RXt9AQz4f-70dfEwG->pV2FHd$4{(ne{)F+i+noj<!P;|hYxUYB7;GVv?- z3cjeKQ#H;ws;da|k(%NLg-B%r3~J*5!ZfooJ?@>WXk2WKjXrW*KZk}mnQAOOjL$mO zZ$97C_!ApuDd;yl-$NlbXgWU3^beuIK#IX=urLa{!HR^{b4Jtk7%u8H(3H6S#B@NW zF<}+M?yxgkq=f|LCOz4RIcr(8;nvmB5SUJq>=P(I@n0fdC;i)3|E#&EA>YI=gR@T1 z9;YmekGBQLLrJuMh1d+g4>dQp3ZivmJQw=1`e6f`*_YO2aYRj1Srt*Iq}iCf8pL76 zrBuj~H(}CAFB`Nje&Y$N?st6bILOq)gr1GB_AVG<LuUSS6851~e@j-kI1EJ#hEX$) zyad3`MgrByogk$Wx?~4sP)1uX!!`&AQtj=RF$K^xf=D17!CIkF@U)qXqVr)o^k$uk z>JF@2$pqgbMqhp~u;1Eg6}7IcFj8;fv_IYF13Hbmb&w&;5N=4a5U<uVr)$4bOg{m_ zw6QWh1&>_mYWUo82L8#Qqy`aT`b2s(UeKuH=FcdPeq5Ns1(S|Yk?w5Eo!OEs<0jI- zk){G$!L8f6E|V~CDoFg|R=^{%q|n35G-SaPgMlGIt(Z{J9u<ZrI~3hU$TA+97Hw=g z|Kvf`s2Q()^~taF^^3CxV@HI4SF#UYy?^TTqWhwR<j=^nddeHD|49pmCNmCF0S}Nu zHZ(z-ZfBdCp{V8jtc|g!<Jbb#Y#1rep%8<80^o)}KktJ`V@<hkG5fU<u5-+Sl$T&H zBqVe(yK_1Jb;ZaA7EvXe$YebuA_kX-JW#TJi(<y_1P3uBiBz^&FBR2674Id%<GJe9 zY?MD)y@0FP!U4zJz-R-6*Ew?h>yZKpa6DVO{$0?OVf=j>>MyZiF=t_YcmX==w#9GJ zzhk{~eYX=VFCu@6R2hDzvMvC(Y8j%tfnFztG#ZmgDz)pDB#aG^qzw#{0HRH4yrwix zb0@B4BXH}^($}q<UpPy+`y`oOj!Yj=c7Vj+duq0=%HCJfnxtuR!(`bU{!4@&(&tVZ zu9_zOvQjFBblrv~?z6PeQgm^LXtPN~#`vL;G%>`;`Aq=Qg(jm%*F;*IummiOAZE5= zKX*obFM=&d&ZaZRLaN0qQvjqp(*FUg_tzC@cV?df7kPJP9e&-SN!Y=l%R5AI<Q;Yd zGwU&bf5Nbfr1(GG%zxUqoS5vAyjN`peNN%y{8%rG1MCOa{gVJyNf$9LD=gTPAh;Wa z{D@_H(SMAy3>(3~?4H{zKF_~0)xR!lhY9GcX2Vpb5O|W@08bva=O&WMPqBm4N{DDE z9g_@HXVSonR3%dmh{={mfKWz6a1a}m%Er2h%pthA`kGRzU(qhH&P6CxE;oE@dQq?P z<PEoFRmVD8uS2eZWPAvjg430-X|%l=;<Th>McV{NP~@eqnUcP5c%t=y67fK60d4CB z@KD>5bl=U-4}lstS2cQ8Wna?dlGWrFf%1%q$PXm>95wk(ntVybnI(?gOW^4@B)KV? z)=aHT3kk>Mw9$Z4nBy2DP7SN*U3ej)U<~1K9Jxyr-2<Ha1`v&xwcBE2#(`=txdy2j z<^uw44lfS7LHeGYFl#RU+Y9+;dXUQ3#_$r5MBt*JV|{!9b*E1~w4g$4;<gnX&PfCR zhg{%UAfLR1XZcou@8O*{g7iB9xgvrWHsNl@LX!_whEfre8bYG)SosqR4rMC#p_o{I z8`#t3tR6~iH4>pK<dRuG=E}<MVZq~veX)2XiY7+{sfBZdZK$vyAYzduo61J-(4b5r zK8FTgXDbpoplYi80SE0sgji5PR3fgVCa13o-$X)zbGP+ALf--MQv($=b6=}mJE<YO zV(S%dj5}l*SpV58*kB1A9!Od&Q_@w!7_AbquM#%45~Po@Ivj7f(De4p&=wpCDiG(> zNO;82sIBFnXt}-*yvW)Q@~lRAzM6NJq_7KYjMG#&;3$MgDeQ3?qP820qM|NG$c>X^ z`$(T}(C*TJ-$F?e79=#7Bs$JM-9eo>bu|xv(q@+}LXj2WyB{uc6p}%@ru~OoGAxLL zJ~1CHC!{R8o9@N1|DF=#1&i^3!RbSj4ueHD>Cx<83Y^80LK7eDuvwucV^)daz>5Ir zf*3K+Zji(RyHQ-LrgBh=6c*btv*a9unY!rVraCUt%1mXKI9IW<Cx_s=>)_Vqf*QhL z6v2MlZoHe7pSf9ZW?^733zV4!iyyG(>#){=gh;Yd3s4eDMir2d@c=0RS15*BVM7!f z=g~w7Y{7*PVIu!Jh7HQ0;`V3?U^+f$7!M}n2(SS2(PtP*JOsUH;TNvxq_R<3HPldN z99XY25Cp1SHC{rSE-5OlZr1BiOv02wPAov)+s2Wq5@9vpB8^yDi_%q0I1SrEl8yf$ zR}pb0FG8NBb|)&TNv&-<y?L5PTjSA!fb=yGXu~TGAJm2qZ^MJ>>ma&9PL%utEv8LV zw-G2E$+yF=Rr=>fZg~DYCHxlZEoL#Wpm+OhwH<t&D6vRVU*H-fTbolp{@QSQ)KJ&= zM63IyOyBq8yw7j{ApIZWn0xZfi++b5i7v7!XZZlx(2MX>MuPSXWiIcR){_v~X9Z0j zh5nwd;n9m(Ww{>qx|`?zR@Dtm*?yrohrR@dY7d$1z`|laX{$UVgp)%sPT$A=_hHPj zrwFcz91LCPi^s+sED8hok`oRgm8?P~6Ifg|C`d<xtG)whO3ssL0tyiT;20X#1t3Tz zp^X5-9Y6)D!;C0*<{GVa2_rgOvt&HOI`g^KEbx^)t={%pgWSyqIhzKZ&Nan>>tTWG zBWK>sZ{0*UVycC8?g-y_nac-`NHxvGlmfR4fh~y~sR|ly04Sf_ymmQm^*2rSXxqA# z?s|ys#}5e~R(Ivs^hBS%#z}J(v2?r)T{`ZI!U0fW2gs8jph?FACGe}5a3Jm<pcI9J zL5`vc@2#n>3*gC%FWGQD3%R~nsFx}}PEuQ>8&U<14EOKO3-n!sI4~H$cV_qR7gKWw z{{b#O`0Zf@GW<F8k#;9KO!kNV`F4FNyMOJ5U|^xkf?2uUfEc}Sc;%2GT<9d`xiD(y zKQ-+V8tcEO?Sngu@G6h2%S`A0z7A}k1*7A^VHGiaGk}H)D9MK`xf^8~pXG>r7PElf zr71JXYU3n%3$8jFq+0?au<6<!Ail;>)PuwSKJZjFlr{5}0YvMug{L}1{v_Z__+*pv zx9cLc!ZVA2kXK*O4f>tVuNqveJ6+!L8Wip1-z?3nx9Z%A`nD1TjB+d@be6?DnQw_* z-MT@-b<hZ_t2h=(x+z5FB?-5ZAZM++J}M!9@(aFKTH(*P>xEFo@*KPZNC{8oD*=^M zxJr6lr9R^zb*>_mD?bdxF=$c+KwK}WZW5^TWfgmoBZ-m0xZL^<bS$!1(|bY{Zh>?m zUkX*QVLJfHO`7@+*O1YFFemUIgYoyqNosKMpL%h?Bjt4T_(i9$4~9<v#M$Yq|9Bw# z!>a95%;Bt9YrC)*<RxLj1RL3S1R``tOvohsc?+}1mEh(plp0^)X1OUEk$tvT)|G?L z<5H}yhX3z-lAUTvwj)b`_wh%zV*HV_k$KlVhDboAWbQ;gO`?}3r%l%k*V7#4YEsF{ zMj+K>km@>(KlbI~IRcSvDFjd;hm9*)5rKp71)MTy*{4<1sj52>6ROwMz?fMh@yBPf z&K`s)g}DhdN{FjTjE|_L%n(`w+iZ;wsTouM+T%Rbyo*<5>T8>yl4SO7;_mYwdVu)U zl<HHuGio}Di+aJp#JP%YMLfBzt~>CVv9c+s;wV|ghOXkmRdJvz%YyKWq<@~%<aU8G zAzu{um(9zx%^_0kqvp5}Aj_P#rIPn^i-=%l1IzNssu=iRfYe{02DsmN`2F{+o{K-7 zEGE~|pMK;~;*p%zeYRtJ%-55bCQVK*?Cam9hWVI3EXaRkC3sN8emn#5W4m+if1P;e zA!7b~P<ylNA=3WZBIDYR!DX!VHH!V6dfSDh!R@aW-m=5G$l<U>$2SWJ@D|s?Y)sCs z29@7>;z$pW@Zli-hgJy!$ro^yD!3X2Ts1sM5ldEzp$P<qBDATHiyV2`So$9t)}jn) zgm!lBeO0Rw{H9^`iYVEl^NC@|lcOR0{}DtYN%FR%M73npsUpG?zh-_=l&tpY((0$5 zAv=~9wSj7~;f>K;HMs%K=T4IBIticr$N$-Dh4PdDu|!2#dVn%{^mcckAz7LDP?^Z1 z^DmZFedwzGbQL|2;vOlA{!8(GigIuFs0_EnxlQR4M=tztVI7H*M3VI3U=Sw{341Mr zKSfv6r&%P0^|6sToSqo1BdsqY%S{0H)O*uEMM)v{pY}wb|7!L0zu2|n*cbbCuX-Bb z%e0@xb|rul1vY{VqFe2_(9ofkBmc7L2=~}U*Evl7YZhW=akPqcHirOTO={CxZ_8-0 zEM9k7of`c6@`9ekMTXTFk)uQ<YqIz^-px2!keb6U6&1Q<Q<OT3OK_n}>=7lAASqKe z$cXyYohn&<q4>u)1qP{j%C$=O4eG@O*RL0zo(O#H_6+k(L+SUQw{Ac+UMOv&+AHQ= zrCQ}pX`)&<PYAcS^p8j5=fPC(;}IHu7MhK<vKCRxwQBM&X|fBZvPR7cNb;zb84;@) zzKK-I5EPSex$jY0Yg>RfNdya*)jL*d1~-E>P<YF<17x5|u2+Loxr!})Z`f1WE)R)Q z!VJHVw>-B!oHlFcDy<iM&nNeL{RmpcJ`en>##%@eXTX}|?;|037!A8{2}LEFW60nK z5ppDYOfFhwyjCYPtTjOVtb4T{>e!du=JTx|TuUEp`%3(CtmWXbR3_~4v0tmM`q;s@ zUB|6+mP>&Bf+&rUsov1Fp+cNd<U)sM#{JES8woLzvDcDL%NX$-`qeCDlDBYmC;yoj z!I>0F_g#9?^i5{3;Jrub7_-#E4k0~JH>Bs*cW!IWwBzH{(;H{b%fEm2XLCBkzF6UC zy6lQ!)D<niM&c>D(VY?-tMkl@Tggrp%Cbl*(xwQBXW}zNybi*|j`E;3B|`rL2tXBc z2;3ay5(gz=Ra2iZVKK`f@&L*<JWN>4!==+ktva%HP@R=S&ePzFj`FqcFVORKdv;gz z4{@Q^2)<+}1EEx?!WTL*jRXpfST@!o#xovkBO0&#Gek_@#IK1Q?qgVwo31na$IaGR zCyUItdj-Tw4wm~HkF0Md79H6~u9swrZX2R9B%SJOc-gX5&@u;yd+TUtwKD@q;_ri# zNIL>YrQG>Y07lu#)K){)At+F#)EXhJf+YT(5)eD`AvHt9coNwU*J)vb@@h-;YN3jk zmuv3e!Xj5PrNiPC4cgdmoEY~fHOKUOMgyJOx|b6XkVj6v(T4`0k4M{|0mNM(39WCm z+7erFJI&&Dn^D^09{zleXrKd)fi&O6gT?)ZUAiZI#9IYl1xA0)dI=5r+(SFoAKR1J zdJ-G<K2_#R*PFaCX79$$q_~G$moF?Bz4Yo=6p~9^2o-8fU06AC^_k3z>>rZ?N;$%r z$grFn-To>P{*iYYDp{e^;BvbIQ!w(7#Y#J`%%oYbu7osG0?O042Urua#DZ=3#91H% zp^#$=>K7peb>`MG{V(KpzdeWg(tU%fWT(;8^es=TW2zH=s1Io{taL@YdPKF#LiU@% zxT!#)dJP|~Ffun>=4`^ocqbWrWbrLopV%X!M;6De#Vq$1`1&mDUR|-p7nZW<;nk+P zVlvow-Q9*4GyHEhKa}c`Z+=MyWepatf9j|^9bW$N){Ag)SECWWGpzl^9@@!+UaxA$ zt2W;``g>}vsCvm?K*WH}^379cdaO1DPmhK6V*UAu+qNNGKBJR4SlPqe(2k)w2QMe; z^aYLSzEK(%mK0znbW$MNfUCR*l0S%0x-*1No_cJzdj-WL^2xui;1oaxwtfntsYinn zcWKCKHpHNbHj+ryn+G5$%E|$3)028xzy!kwDCTMZ{;H**7V}%E$rxEl+l*4}BO0@` z(Q`!k-Cd%E)$XbkZC3!ltPM4Tlac5Az;M-2VHpDfB|8-S__C4|8z86~*$MNiUd2={ ztP#vf!uCWkir=)}amdcL{L6{X(7Pf(z#2lN4cjCnk@JIWFjB#2J5MSd6H)&_J0_IB zGo?$h^;DGjY8)5y3~5T5V1S8eWnuY-)~Q^<=UZqe&*qtmp85t8#1H3?^nT{}o|#Fh zZfRQlhb^9$)NQMdFJc9V0b_4lA=6%<=(*(#McI4M1cNYCUQUj(JSx{QxgGUfc>piU zfjSzArBAJa)y+%YFQs$EfA^{XH1HMv;<$<`j2vzf{pRNLjdSr$P=@rt6hzyD1k;I9 zMsGgIklB6l&vfG0aj+>HJv>O=y*Bo6DwCOb%HvSJunPnt&J_K9>bNxc70e<M^k3St zz?eCwmyyDiLKtUiGV<U}N+2;R75pD;f|HojG0V!zHn&(f<8@N6)osrBf!P95M)LX` z5<yXm&O7H(Ij^;k&kvAwPYXAZ|1qUq5O%+6g5fcAL!<_3v-|0URyadMe?{0chb}S0 z)5~#V(~F+Q@94&P<_8RLEt~Lz>L#3~b7&9}G1!0oV2af)y($*dLC?<n-s5V%BHY^j zR{b_nS>_Z8e&jc*WPnw#È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 G0RTIuSIDFQ literal 0 HcmV?d00001 diff --git a/Expes/Testers/TestLines/TestLines.pro b/Expes/Testers/TestLines/TestLines.pro new file mode 100644 index 0000000..c9f19c8 --- /dev/null +++ b/Expes/Testers/TestLines/TestLines.pro @@ -0,0 +1,74 @@ +###################################################################### +# Automatically generated by qmake (3.0) mer. nov. 7 21:11:32 2018 +###################################################################### + +QT+=widgets +TEMPLATE = app +TARGET = TestLines +INCLUDEPATH += . \ + BlurredSegment \ + DirectionalScanner \ + ConvexHull \ + ImageTools +OBJECTS_DIR = obj + +# Input +HEADERS += BlurredSegment/biptlist.h \ + BlurredSegment/bsdetector.h \ + BlurredSegment/bsproto.h \ + BlurredSegment/bstracker.h \ + BlurredSegment/nfafilter.h \ + ConvexHull/antipodal.h \ + ConvexHull/chvertex.h \ + ConvexHull/convexhull.h \ + DirectionalScanner/adaptivescannero1.h \ + DirectionalScanner/adaptivescannero2.h \ + DirectionalScanner/adaptivescannero7.h \ + DirectionalScanner/adaptivescannero8.h \ + DirectionalScanner/directionalscanner.h \ + DirectionalScanner/directionalscannero1.h \ + DirectionalScanner/directionalscannero2.h \ + DirectionalScanner/directionalscannero7.h \ + DirectionalScanner/directionalscannero8.h \ + DirectionalScanner/scannerprovider.h \ + DirectionalScanner/vhscannero1.h \ + DirectionalScanner/vhscannero2.h \ + DirectionalScanner/vhscannero7.h \ + DirectionalScanner/vhscannero8.h \ + ImageTools/absrat.h \ + ImageTools/digitalstraightline.h \ + ImageTools/digitalstraightsegment.h \ + ImageTools/edist.h \ + ImageTools/pt2i.h \ + ImageTools/vmap.h \ + ImageTools/vr2i.h +SOURCES += main.cpp \ + BlurredSegment/biptlist.cpp \ + BlurredSegment/blurredsegment.cpp \ + BlurredSegment/bsdetector.cpp \ + BlurredSegment/bsproto.cpp \ + BlurredSegment/bstracker.cpp \ + BlurredSegment/nfafilter.cpp \ + ConvexHull/antipodal.cpp \ + ConvexHull/chvertex.cpp \ + ConvexHull/convexhull.cpp \ + DirectionalScanner/adaptivescannero1.cpp \ + DirectionalScanner/adaptivescannero2.cpp \ + DirectionalScanner/adaptivescannero7.cpp \ + DirectionalScanner/adaptivescannero8.cpp \ + DirectionalScanner/directionalscanner.cpp \ + DirectionalScanner/directionalscannero1.cpp \ + DirectionalScanner/directionalscannero2.cpp \ + DirectionalScanner/directionalscannero7.cpp \ + DirectionalScanner/directionalscannero8.cpp \ + DirectionalScanner/scannerprovider.cpp \ + DirectionalScanner/vhscannero1.cpp \ + DirectionalScanner/vhscannero2.cpp \ + DirectionalScanner/vhscannero7.cpp \ + DirectionalScanner/vhscannero8.cpp \ + ImageTools/digitalstraightline.cpp \ + ImageTools/digitalstraightsegment.cpp \ + ImageTools/edist.cpp \ + ImageTools/pt2i.cpp \ + ImageTools/vmap.cpp \ + ImageTools/vr2i.cpp diff --git a/Expes/Testers/TestLines/main.cpp b/Expes/Testers/TestLines/main.cpp new file mode 100644 index 0000000..82a01e5 --- /dev/null +++ b/Expes/Testers/TestLines/main.cpp @@ -0,0 +1,305 @@ +#include <QImage> +#include <QString> +#include <iostream> +#include <fstream> +#include <string> +#include <ctime> +#include <cmath> +#include "bsdetector.h" + +#define DBG 0 +#define REPETITIONS 100 +#define MIN_LENGTH 10 + +using namespace std; + + +int genDssFromLines (vector<DigitalStraightSegment> &dss, double &cl, + int height, int minl, string name, int nbi) +{ + double val[nbi]; + int i = 0; + + cl = 0.0; + ifstream input (name.c_str (), ios::in); + if (! input.is_open ()) return 0; + + bool reading = true; + while (reading) + { + input >> val[i]; + if (input.eof ()) reading = false; + else + { + if (++i == nbi) + { + Pt2i p1 ((int) (val[0] + 0.5), height - 1 - (int) (val[1] + 0.5)); + Pt2i p2 ((int) (val[2] + 0.5), height - 1 - (int) (val[3] + 0.5)); +// Pt2i p1 ((int) val[0], height - 1 - (int) val[1]); +// Pt2i p2 ((int) val[2], height - 1 - (int) val[3]); + if (p1.equals (p2)) cout << "LIGNE NULLE !!!" << endl; + else + { + cl += sqrt (p1.vectorTo(p2).norm2 ()); + if (minl == 0 || cl >= minl) + dss.push_back (DigitalStraightSegment (p1, p2, 1)); + } + i = 0; + } + } + } + input.close (); + return ((int) (dss.size ())); +} + + +int genDssFromImage (std::vector<DigitalStraightSegment> &dss, double &cl, + QImage &im, int minl, bool isNaive) +{ + BSDetector detector; + int width = im.width (); + int height = im.height (); + int **tabImage = new int*[height]; + for (int i = 0; i < height; i++) + { + tabImage[i] = new int[width]; + for(int j = 0; j < width; j++) + { + QColor c = QColor (im.pixel (j, height - i - 1)); + tabImage[i][j] = c.value (); + } + } + detector.setGradientMap (new VMap (width, height, tabImage, + VMap::TYPE_SOBEL_5X5)); + if (minl != 0) + { + if (! detector.isFinalSizeTestOn ()) detector.switchFinalSizeTest (); + detector.setFinalSizeMinValue (minl); + } + else + if (detector.isFinalSizeTestOn ()) detector.switchFinalSizeTest (); + detector.detectAll (); + std::vector<BlurredSegment *> bss; + if (detector.isNFA ()) bss = detector.getValidSegments (); + else bss = detector.getBlurredSegments (); + std::vector<BlurredSegment *>::iterator it = bss.begin (); + cl = 0.0; + while (it != bss.end ()) + { + cl += sqrt ((*it)->getSquarredLength ()); + DigitalStraightSegment ds ((*it)->getSegment ()); + if (isNaive) ds.setNaive (); + dss.push_back (ds); + it ++; + } + return (((int) (dss.size ()))); +} + + +int cardinal (const vector<DigitalStraightSegment> &dss, int w, int h) +{ + int nb = 0; + bool cmap[w * h]; + for (int i = 0; i < w * h; i++) cmap[i] = false; + vector<DigitalStraightSegment>::const_iterator it = dss.begin (); + while (it != dss.end ()) + { + vector<Pt2i> pts; + it->getPoints (pts); + vector<Pt2i>::iterator pit = pts.begin (); + while (pit != pts.end ()) + { + if (pit->x () >= 0 && pit->x () < w && pit->y () >= 0 && pit->y () < h + && ! cmap[pit->x() + pit->y() * w]) + { + nb ++; + cmap[pit->x() + pit->y() * w] = true; + } + pit++; + } + it++; + } + return nb; +} + + +int cover (const vector<DigitalStraightSegment> &bck, + const vector<DigitalStraightSegment> &frt, int w, int h) +{ + bool cmap[w * h]; + for (int i = 0; i < w * h; i++) cmap[i] = false; + vector<DigitalStraightSegment>::const_iterator it = bck.begin (); + while (it != bck.end ()) + { + DigitalStraightSegment *ds = it->dilation (1 * it->standard ()); + vector<Pt2i> pts; + ds->getPoints (pts); + vector<Pt2i>::iterator pit = pts.begin (); + while (pit != pts.end ()) + { + if (pit->x () >= 0 && pit->x () < w && pit->y () >= 0 && pit->y () < h) + cmap[pit->x() + pit->y() * w] = true; + pit++; + } + delete ds; + it++; + } + + int nb = 0; + it = frt.begin (); + while (it != frt.end ()) + { + vector<Pt2i> pts; + it->getPoints (pts); + vector<Pt2i>::iterator pit = pts.begin (); + while (pit != pts.end ()) + { + if (pit->x () >= 0 && pit->x () < w && pit->y () >= 0 && pit->y () < h + && cmap[pit->x() + pit->y() * w]) + { + nb++; + cmap[pit->x() + pit->y() * w] = false; // To avoid counting twice + } + pit ++; + } + it ++; + } + return nb; +} + + +void create (QString name, const vector<DigitalStraightSegment> &bck, + const vector<DigitalStraightSegment> &frt, int w, int h) +{ + QImage im (w, h, QImage::Format_RGB32); + for (int j = 0; j < h; j++) + for (int i = 0; i < w; i++) + im.setPixel (i, j, 255 * 256 * 256 + 255 * 256 + 255); + vector<DigitalStraightSegment>::const_iterator it = bck.begin (); + while (it != bck.end ()) + { + DigitalStraightSegment *ds = it->dilation (it->standard ()); + vector<Pt2i> pts; + ds->getPoints (pts); + vector<Pt2i>::iterator pit = pts.begin (); + while (pit != pts.end ()) + { + if (pit->x () >= 0 && pit->x () < w && pit->y () >= 0 && pit->y () < h) + im.setPixel (pit->x (), pit->y (), 255 * 256 * 256); + pit++; + } + delete ds; + it++; + } + + it = frt.begin (); + while (it != frt.end ()) + { + vector<Pt2i> pts; + it->getPoints (pts); + vector<Pt2i>::iterator pit = pts.begin (); + while (pit != pts.end ()) + { + if (pit->x () >= 0 && pit->x () < w && pit->y () >= 0 && pit->y () < h) + im.setPixel (pit->x (), pit->y (), + ((im.pixelColor (pit->x (), pit->y ())).green () < 200 ? + 0 : 255)); + pit ++; + } + it ++; + } + im.save (name); +} + + +int main (int argc, char *argv[]) +{ + QImage im; + if (! im.load (QString ("Data/image.jpg"))) + { + cout << "No Data/image.png found" << endl; + return 0; + } + int w = im.width (); + int h = im.height (); + + if (argc >= 2 && string (argv[1]) == string ("time")) + { + int repetitions = REPETITIONS; + if (argc == 3) + { + repetitions = atoi (argv[2]); + cout << "Repetitions set to " << repetitions << endl; + } + int **tabImage = new int*[h]; + for (int i = 0; i < h; i++) + { + tabImage[i] = new int[w]; + for(int j = 0; j < w; j++) + { + QColor c = QColor (im.pixel (j, h - i - 1)); + tabImage[i][j] = c.value (); + } + } + BSDetector detector; + VMap *gMap = NULL; + clock_t start = clock (); + for (int i = 0; i < repetitions; i++) + { + if (gMap != NULL) delete gMap; + gMap = new VMap (w, h, tabImage, VMap::TYPE_SOBEL_5X5); + detector.setGradientMap (gMap); + detector.detectAll (); + } + double diff = (clock () - start) / (double) CLOCKS_PER_SEC; + ofstream outf ("Data/fbsdperf.txt", ios::out); + outf << diff << endl; + outf.close (); + return (EXIT_SUCCESS); + } + + double cln = 0.0; // cumulated length + vector<DigitalStraightSegment> dss; + int nbs = 0; + if (string (argv[1]) == string ("fbsd")) + nbs = genDssFromImage (dss, cln, im, MIN_LENGTH, false); + else if (string (argv[1]) == string ("naive")) + nbs = genDssFromImage (dss, cln, im, MIN_LENGTH, true); + else if (string (argv[1]) == string ("canny")) + nbs = genDssFromLines (dss, cln, h, MIN_LENGTH, "Data/detLines.txt", 5); + else if (string (argv[1]) == string ("ed")) + nbs = genDssFromLines (dss, cln, h, MIN_LENGTH, "Data/detLines.txt", 4); + else if (string (argv[1]) == string ("lsd")) + nbs = genDssFromLines (dss, cln, h, MIN_LENGTH, "Data/detLines.txt", 7); + if (nbs == 0) + { + cout << "No line extracted for " << argv[1] << endl; + return 0; + } + + double gtl = 0.0; + int ngt = 0, nl = 0, nigt = 0, nil = 0; + vector<DigitalStraightSegment> gt; + if (genDssFromLines (gt, gtl, h, 0, "Data/gtLines.txt", 4) != 0) + { + ngt = cardinal (gt, w, h); + nl = cardinal (dss, w, h); + nigt = cover (dss, gt, w, h); // R = nigt / ngt + nil = cover (gt, dss, w, h); // P = nil / nl + if (DBG != 0) + { + create (QString ("Data/recall.png"), dss, gt, w, h); + create (QString ("Data/precision.png"), gt, dss, w, h); + } + } + + ofstream outf ("Data/result.txt", ios::out); + if (outf.is_open ()) + { + outf << nbs << " " << cln << " " + << nigt << " " << ngt << " " << nil << " " << nl << endl; + outf.close (); + } +} + + diff --git a/Expes/YorkUrbanDB/Scripts/precstat.sh b/Expes/YorkUrbanDB/Scripts/precstat.sh new file mode 100644 index 0000000..7d67ad7 --- /dev/null +++ b/Expes/YorkUrbanDB/Scripts/precstat.sh @@ -0,0 +1,17 @@ +\cp /home/even/Expes/YorkUrbanDB/Images/$1/$1.jpg /home/even/Expes/Testers/TestLines/Data/image.jpg +\cp /home/even/Expes/YorkUrbanDB/lines/yorklines/$1Lines.txt /home/even/Expes/Testers/TestLines/Data/gtLines.txt +cd /home/even/Expes/Testers/TestLines +./TestLines fbsd +cat Data/result.txt >> /home/even/Expes/YorkUrbanDB/data/fbsdprec.txt +./TestLines naive +cat Data/result.txt >> /home/even/Expes/YorkUrbanDB/data/naiveprec.txt +\cp /home/even/Expes/YorkUrbanDB/lines/cannylines/output/$1.txt /home/even/Expes/Testers/TestLines/Data/detLines.txt +./TestLines canny +cat Data/result.txt >> /home/even/Expes/YorkUrbanDB/data/cannyprec.txt +\cp /home/even/Expes/YorkUrbanDB/lines/edlines/output/$1.txt /home/even/Expes/Testers/TestLines/Data/detLines.txt +./TestLines ed +cat Data/result.txt >> /home/even/Expes/YorkUrbanDB/data/edprec.txt +\cp /home/even/Expes/YorkUrbanDB/lines/lsdlines/output/$1.txt /home/even/Expes/Testers/TestLines/Data/detLines.txt +./TestLines lsd +cat Data/result.txt >> /home/even/Expes/YorkUrbanDB/data/lsdprec.txt +cd /home/even/Expes/YorkUrbanDB/data diff --git a/Expes/YorkUrbanDB/Scripts/precstats.sh b/Expes/YorkUrbanDB/Scripts/precstats.sh new file mode 100644 index 0000000..63b1ae8 --- /dev/null +++ b/Expes/YorkUrbanDB/Scripts/precstats.sh @@ -0,0 +1,112 @@ +\rm /home/even/Expes/YorkUrbanDB/data/lsdprec.txt +\rm /home/even/Expes/YorkUrbanDB/data/edprec.txt +\rm /home/even/Expes/YorkUrbanDB/data/cannyprec.txt +\rm /home/even/Expes/YorkUrbanDB/data/naiveprec.txt +\rm /home/even/Expes/YorkUrbanDB/data/fbsdprec.txt +touch /home/even/Expes/YorkUrbanDB/data/lsdprec.txt +touch /home/even/Expes/YorkUrbanDB/data/edprec.txt +touch /home/even/Expes/YorkUrbanDB/data/cannyprec.txt +touch /home/even/Expes/YorkUrbanDB/data/naiveprec.txt +touch /home/even/Expes/YorkUrbanDB/data/fbsdprec.txt +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1020171 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1020177 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1020816 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1020817 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1020819 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1020822 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1020824 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1020825 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1020826 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1020828 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1020829 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1020830 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1020831 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1020833 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1020838 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1020839 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1020841 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1020845 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1020847 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1020848 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1020854 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1020856 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1020860 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1020861 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1020867 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1020871 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1020872 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1020887 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1020912 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1020928 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1030001 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1030004 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1040779 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1040783 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1040788 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1040795 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1040798 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1040801 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1040811 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1040812 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1040813 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1040814 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1040815 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1040817 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1040818 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1040819 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1040822 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1040823 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1040825 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1040826 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1040829 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1040833 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1040839 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1040843 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1040845 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1040850 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1040853 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1040854 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1040855 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1040856 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1040860 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1040862 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1040863 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1040870 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080005 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080008 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080011 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080015 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080018 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080020 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080021 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080023 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080025 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080031 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080032 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080033 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080036 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080045 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080047 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080049 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080053 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080055 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080056 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080057 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080062 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080063 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080074 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080077 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080078 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080079 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080084 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080091 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080092 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080093 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080096 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080100 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080104 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080106 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080111 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080113 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080116 +source /home/even/Expes/YorkUrbanDB/Scripts/precstat.sh P1080119 diff --git a/Expes/YorkUrbanDB/Scripts/ytimes.c b/Expes/YorkUrbanDB/Scripts/ytimes.c new file mode 100644 index 0000000..3ebf161 --- /dev/null +++ b/Expes/YorkUrbanDB/Scripts/ytimes.c @@ -0,0 +1,159 @@ +// Compilation : cc -o ytimes ytimes.c +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + + +#define EXPES_DIR "/home/even/Expes/" +#define FBSD_DIR "Testers/TestLines/" +#define YORK_DIR "YorkUrbanDB/" +#define DATA_DIR "data/" +#define LSD_DIR "Timers/lsd/" +#define ED_DIR "Timers/edlines/" +#define CANNY_DIR "Timers/cannylines/" +#define IMAGE_NAMES "yorkImages.txt" +#define SCRIPT_NAME "exectimes.sh" +#define NBMAX 200 + + +void tirage (int *ordre) +{ + int nb = 4, val; + int choix[] = {0, 1, 2, 3}; + while (nb > 1) + { + val = rand () % nb; + *ordre++ = choix[val]; + choix[val] = choix[--nb]; + } + *ordre = choix[0]; +} + +int main (int argc, char *argv[]) +{ + FILE *fim = NULL; + FILE *fcom = NULL; + char val[NBMAX]; + char *ims[NBMAX]; + int tir[4]; + int w, h; + int nb = 0; + int nstart = 0, nend = 0; + + srand (time (NULL)); + if (argc != 3) + { + fprintf (stdout, "Usage : yorktimes <num_start> <num_end>\n"); + return (0); + } + sscanf (argv[1], "%d", &nstart); + sscanf (argv[2], "%d", &nend); + nstart --; + nend --; + if ((fim = fopen (IMAGE_NAMES, "r")) == NULL) + { + fprintf (stderr, "No file %s\n", IMAGE_NAMES); + return (0); + } + fscanf (fim, "%d", &w); + fscanf (fim, "%d", &h); + while (fscanf (fim, "%s", val) != -1) + { + ims[nb] = (char *) malloc (strlen (val) * sizeof (char) + 1); + strcpy (ims[nb++], val); + } + fclose (fim); + if ((fcom = fopen (SCRIPT_NAME, "w")) == NULL) + { + fprintf (stderr, "Unable to create %s\n", SCRIPT_NAME); + return (0); + } + sprintf (val, "cd %s%s", EXPES_DIR, YORK_DIR); + fprintf (fcom, "%s\n", val); + for (int i = nstart; i <= nend; i++) + { + tirage (tir); + for (int j = 0; j < 4; j++) + { + if (tir[j] == 0) // lsd + { + sprintf (val, "\\cp ImagesPGM/%s.pgm %s%stest.pgm", + ims[i], EXPES_DIR, LSD_DIR); + fprintf (fcom, "%s\n", val); + sprintf (val, "cd %s%s", EXPES_DIR, LSD_DIR); + fprintf (fcom, "%s\n", val); + sprintf (val, "\\rm lsdperf.txt"); + fprintf (fcom, "%s\n", val); + sprintf (val, "echo 'TIMING %d (%s) on lsd'", (i+1), ims[i]); + fprintf (fcom, "%s\n", val); + sprintf (val, "lsd"); + fprintf (fcom, "%s\n", val); + sprintf (val, "cat lsdperf.txt >> %s%s%slsdtimes.txt", + EXPES_DIR, YORK_DIR, DATA_DIR); + fprintf (fcom, "%s\n", val); + sprintf (val, "cd %s%s", EXPES_DIR, YORK_DIR); + fprintf (fcom, "%s\n", val); + } + else if (tir[j] == 1) // ed + { + sprintf (val, "\\cp Images/%s/%s.jpg %s%stest.jpg", + ims[i], ims[i], EXPES_DIR, ED_DIR); + fprintf (fcom, "%s\n", val); + sprintf (val, "cd %s%s", EXPES_DIR, ED_DIR); + fprintf (fcom, "%s\n", val); + sprintf (val, "\\rm edperf.txt"); + fprintf (fcom, "%s\n", val); + sprintf (val, "echo 'TIMING %d (%s) on ed'", (i+1), ims[i]); + fprintf (fcom, "%s\n", val); + sprintf (val, "EDLines"); + fprintf (fcom, "%s\n", val); + sprintf (val, "cat edperf.txt >> %s%s%sedtimes.txt", + EXPES_DIR, YORK_DIR, DATA_DIR); + fprintf (fcom, "%s\n", val); + sprintf (val, "cd %s%s", EXPES_DIR, YORK_DIR); + fprintf (fcom, "%s\n", val); + } + else if (tir[j] == 2) // canny + { + sprintf (val, "\\cp Images/%s/%s.jpg %s%stest.jpg", + ims[i], ims[i], EXPES_DIR, CANNY_DIR); + fprintf (fcom, "%s\n", val); + sprintf (val, "cd %s%s", EXPES_DIR, CANNY_DIR); + fprintf (fcom, "%s\n", val); + sprintf (val, "\\rm cannyperf.txt"); + fprintf (fcom, "%s\n", val); + sprintf (val, "echo 'TIMING %d (%s) on canny'", (i+1), ims[i]); + fprintf (fcom, "%s\n", val); + sprintf (val, "CannyLine"); + fprintf (fcom, "%s\n", val); + sprintf (val, "cat cannyperf.txt >> %s%s%scannytimes.txt", + EXPES_DIR, YORK_DIR, DATA_DIR); + fprintf (fcom, "%s\n", val); + sprintf (val, "cd %s%s", EXPES_DIR, YORK_DIR); + fprintf (fcom, "%s\n", val); + } + else if (tir[j] == 3) // fbsd + { + sprintf (val, "\\cp Images/%s/%s.jpg %sData/image.jpg", + ims[i], ims[i], FBSD_DIR); + fprintf (fcom, "%s\n", val); + sprintf (val, "cd %s", FBSD_DIR); + fprintf (fcom, "%s\n", val); + sprintf (val, "echo 'TIMING %d (%s) on fbsd'", (i+1), ims[i]); + fprintf (fcom, "%s\n", val); + sprintf (val, "./TestLines time"); + fprintf (fcom, "%s\n", val); + sprintf (val, "cat Data/fbsdperf.txt >> %s%s%sfbsdtimes.txt", + EXPES_DIR, YORK_DIR, DATA_DIR); + fprintf (fcom, "%s\n", val); + sprintf (val, "cd %s%s", EXPES_DIR, YORK_DIR); + fprintf (fcom, "%s\n", val); + } + } + } + sprintf (val, "cd %s", DATA_DIR); + fprintf (fcom, "%s\n", val); + fclose (fcom); + return (0); +} diff --git a/Expes/YorkUrbanDB/data/moyennes/mprec.c b/Expes/YorkUrbanDB/data/moyennes/mprec.c new file mode 100644 index 0000000..627cf44 --- /dev/null +++ b/Expes/YorkUrbanDB/data/moyennes/mprec.c @@ -0,0 +1,136 @@ +// Compilation : cc -o mprec mprec.c -lm + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <malloc.h> +#include <math.h> + +#define YORK_NB 102 +#define NB_DATA 6 +#define NB_DETS 5 +#define LSD_NAME "LSD" +#define ED_NAME "ED" +#define CANNY_NAME "CANNY" +#define NAIVE_NAME "NAIVE" +#define FBSD_NAME "FBSD" +#define LSD_LN "lsdprec.txt" +#define ED_LN "edprec.txt" +#define CANNY_LN "cannyprec.txt" +#define NAIVE_LN "naiveprec.txt" +#define FBSD_LN "fbsdprec.txt" + + +int main (int argc, char *argv[]) +{ + double nvals[NB_DETS][YORK_NB]; + double lvals[NB_DETS][YORK_NB]; + double rvals[NB_DETS][YORK_NB]; + double trvals[NB_DETS][YORK_NB]; + double pvals[NB_DETS][YORK_NB]; + double tpvals[NB_DETS][YORK_NB]; + double *val = NULL; + double nm[NB_DETS]; // Number of lines + double nsig[NB_DETS]; + double lm[NB_DETS]; // Cumulated length per image + double lsig[NB_DETS]; + double lnm[NB_DETS]; // Mean length + double lnsig[NB_DETS]; + double rm[NB_DETS]; // Recall + double rsig[NB_DETS]; + double pm[NB_DETS]; // Precision + double psig[NB_DETS]; + double fm[NB_DETS]; // F-measure + double fsig[NB_DETS]; + int nb = 0; + FILE *pf = NULL; + + char *names[20] = {LSD_NAME, ED_NAME, CANNY_NAME, NAIVE_NAME, FBSD_NAME}; + char *files[20] = {LSD_LN, ED_LN, CANNY_LN, NAIVE_LN, FBSD_LN}; + + for (int j = 0; j < NB_DETS; j++) + { + nm[j] = 0.; + lm[j] = 0.; + lnm[j] = 0.; + rm[j] = 0.; + pm[j] = 0.; + fm[j] = 0.; + nsig[j] = 0.; + lsig[j] = 0.; + lnsig[j] = 0.; + rsig[j] = 0.; + psig[j] = 0.; + fsig[j] = 0.; + + if ((pf = fopen (files[j], "r")) == NULL) + { + fprintf (stderr, "Pas de fichier %s\n", files[j]); + return (0); + } + val = nvals[j]; + nb = 0; + for (int i = 0; i < YORK_NB; i ++); + while (fscanf (pf, "%lf", val) != -1) + { + nb ++; + if (nb % NB_DATA == 1) val = lvals[j] + nb / NB_DATA; + else if (nb % NB_DATA == 2) val = rvals[j] + nb / NB_DATA; + else if (nb % NB_DATA == 3) val = trvals[j] + nb / NB_DATA; + else if (nb % NB_DATA == 4) val = pvals[j] + nb / NB_DATA; + else if (nb % NB_DATA == 5) val = tpvals[j] + nb / NB_DATA; + else (val = nvals[j] + nb / NB_DATA); + } + fclose (pf); + + // Computes means + for (int i = 0; i < YORK_NB; i ++) + { + nm[j] += nvals[j][i]; + lm[j] += lvals[j][i]; + lnm[j] += lvals[j][i] / nvals[j][i]; + rm[j] += rvals[j][i] / trvals[j][i]; + pm[j] += pvals[j][i] / tpvals[j][i]; + fm[j] += 2 * rvals[j][i] * pvals[j][i] + / (rvals[j][i] * tpvals[j][i] + pvals[j][i] * trvals[j][i]); + } + nm[j] /= YORK_NB; + lm[j] /= YORK_NB; + lnm[j] /= YORK_NB; + rm[j] /= YORK_NB; + pm[j] /= YORK_NB; + fm[j] /= YORK_NB; + + // Computes standard deviations + for (int i = 0; i < YORK_NB; i ++) + { + nsig[j] += (nvals[j][i] - nm[j]) * (nvals[j][i] - nm[j]); + lsig[j] += (lvals[j][i] - lm[j]) * (lvals[j][i] - lm[j]); + double term = lvals[j][i] / nvals[j][i] - lnm[j]; + lnsig[j] += term * term; + term = rvals[j][i] / trvals[j][i] - rm[j]; + rsig[j] += term * term; + term = pvals[j][i] / tpvals[j][i] - pm[j]; + psig[j] += term * term; + term = 2 * rvals[j][i] * pvals[j][i] + / (rvals[j][i] * tpvals[j][i] + pvals[j][i] * trvals[j][i]) + - fm[j]; + fsig[j] += term * term; + } + nsig[j] = sqrt (nsig[j] / (YORK_NB - 1)); + lsig[j] = sqrt (lsig[j] / (YORK_NB - 1)); + lnsig[j] = sqrt (lnsig[j] / (YORK_NB - 1)); + rsig[j] = sqrt (rsig[j] / (YORK_NB - 1)); + psig[j] = sqrt (psig[j] / (YORK_NB - 1)); + fsig[j] = sqrt (fsig[j] / (YORK_NB - 1)); + + fprintf (stdout, "Detecteur %s\n", names[j]); + fprintf (stdout, " N = %lf (%lf)\n", nm[j], nsig[j]); + fprintf (stdout, " L = %lf (%lf)\n", lm[j], lsig[j]); + fprintf (stdout, " L/N = %lf (%lf)\n", lnm[j], lnsig[j]); + fprintf (stdout, " R = %lf (%lf)\n", rm[j], rsig[j]); + fprintf (stdout, " P = %lf (%lf)\n", pm[j], psig[j]); + fprintf (stdout, " F = %lf (%lf)\n", fm[j], fsig[j]); + } + return (1); +} diff --git a/Ipol/paper/Algos/algoAuto.tex b/Ipol/paper/Algos/algoAuto.tex index f35772a..bc0a6ea 100644 --- a/Ipol/paper/Algos/algoAuto.tex +++ b/Ipol/paper/Algos/algoAuto.tex @@ -1,5 +1,5 @@ \begin{algorithm}[!htbp] -\caption{detectAuto} +\caption{detectAll} \SetKwData{takes}{$\leftarrow$} \SetKwData{resol}{$\delta$} diff --git a/Ipol/paper/Algos/algoInitial.tex b/Ipol/paper/Algos/algoInitial.tex index aa1cafe..187bec5 100644 --- a/Ipol/paper/Algos/algoInitial.tex +++ b/Ipol/paper/Algos/algoInitial.tex @@ -4,6 +4,9 @@ \SetKwData{takes}{$\leftarrow$} \SetKwData{is}{$P_1P_2$} +\SetKwData{vis}{$\overrightarrow{P_1P_2}$} +\SetKwData{scancenter}{$C$} +\SetKwData{scanwidth}{$\omega$} \SetKwData{ath}{$\varepsilon$} %\SetKwData{on}{isExtending} \SetKwData{nb}{$n$} @@ -29,12 +32,17 @@ \SetKwFunction{addpt}{addPoint} \Input{gradient magnitude map \gmap, input segment \is, - maximal thickness \ath, + scan center \scancenter, scan width \scanwidth, maximal thickness \ath, maximal amount of successive detection fails \imax} \Output{a detected blurred segment \bs} \bs \takes $\emptyset$\; -\ds \takes \getds (\is) \Comment*{Gets a DS from input segment.} +\eIf{\scanwidth $=$ $0$}{ + \ds \takes \getds (\is) \Comment*{Gets a DS from input segment.} +}{ + \ds \takes \getds (\scancenter, \vis, \scanwidth) + \Comment*{Gets a DS from center, direction and width.} +} \scan \takes \fscan (\ds)\; \index \takes \largest (\scan, \gmap, \gmin) \Comment*{Gets index of pixel with max gradient.} diff --git a/Ipol/paper/Algos/algoMulti.tex b/Ipol/paper/Algos/algoMulti.tex index ed0a52c..590e6b0 100644 --- a/Ipol/paper/Algos/algoMulti.tex +++ b/Ipol/paper/Algos/algoMulti.tex @@ -36,7 +36,7 @@ \BlankLine \For{\index \takes $0$ \KwTo \size(\lm)}{ \If{\mask $[$ \lm $[$ \index $]]$ $=$ false}{ - \bs \takes \detect (\gmap, \vmap, \lm[i], \ortho, \eps) + \bs \takes \detect (\gmap, \vmap, \ortho, \lm[i], \eps) \Comment*{Searches a segment at local max position.} \mask \takes \updatemask (\mask, \bs) \Comment*{Updates occupancy mask with dilated blurred segment.} diff --git a/Ipol/paper/Algos/algoSingle.tex b/Ipol/paper/Algos/algoSingle.tex index 381e92c..e178dca 100644 --- a/Ipol/paper/Algos/algoSingle.tex +++ b/Ipol/paper/Algos/algoSingle.tex @@ -4,6 +4,8 @@ \SetKwData{takes}{$\leftarrow$} \SetKwData{is}{$P_1P_2$} +\SetKwData{scancenter}{$C$} +\SetKwData{scanwidth}{$\omega$} \SetKwData{ath}{$\varepsilon$} \SetKwData{minsize}{$S_{min}$} \SetKwData{crossang}{$\alpha_{min}$} @@ -26,10 +28,11 @@ \SetKwFunction{startpt}{startPoint} \Input{gradient magnitude map \gmap, gradient orientation map \vmap, - input segment \is, assigned thickness \ath} + input segment \is, scan center \scancenter, scan width \scanwidth + and assigned thickness \ath} \Output{a detected blurred segment \bs} -\bs \takes \fasttrack (\gmap, \is, \ath) +\bs \takes \fasttrack (\gmap, \is, \scancenter, \scanwidth, \ath) \Comment*{Gets rough initial solution.} \eIf{\sizebs $<$ \minsize}{ \bs \takes $\emptyset$ \Comment*{Not enough points.} diff --git a/Ipol/paper/algos.tex b/Ipol/paper/algos.tex index 47ca152..b478813 100644 --- a/Ipol/paper/algos.tex +++ b/Ipol/paper/algos.tex @@ -17,24 +17,27 @@ supervised control tasks. \subsection{Initial detection step of single extraction} -The input of this step is a line segment, the input stroke, +The main input of this step is a line segment, the input stroke, and the output is a blurred segment crossed by the input stroke. +The first scan can be initialized either directly by the input stroke, +or by the scan center, direction and width. A null width must be +specified in the first case. The process is detailed in algorithm~\ref{algo:initial}. Candidate points for blurred segment extension are selected on the basis of maximal gradient magnitude. -Because the goal is to provide an initial incomplete solution, the blurred -segment is bounded to a small size. +Because the goal is to provide an initial rough solution, the blurred +segment extension is bounded to a few scans. Further extension is considered as useless. On each side, the process stops after a fixed amount of successive extension fails. +\input{Algos/algoInitial} + Two parameters are used in this step: a minimal gradient magnitude $G_{min}$ to select candidates, a maximal extent $N_E$ of the blurred segment. Nominal values are proposed for them. -\input{Algos/algoInitial} - \subsection{Fine tracking step of single extraction} The input of this step is the direction of the rough blurred segment @@ -50,6 +53,8 @@ center line at each step. This alignment is only performed after a predefined number of steps when the blurred segment direction gets stable enough. +\input{Algos/algoFinal} + Moreover when the growing segment stops thickenning, the assigned maximal thickness of the recognition algorithm is pinched to a near value to the observed thickness. This pinch procedure avoids further inclusion of @@ -61,8 +66,6 @@ Finally, when any extension fail occurs, the amount of later successful extensions must coincide to the fail count to validate these points. This condition helps to detect more accurate segment ends. -\input{Algos/algoFinal} - Three parameters are used in this step: a gradient height $\delta_G$ to discriminate local gradient maxima, a minimal count $N_P$ of successive extents without blurred segment thickening, @@ -71,9 +74,12 @@ Nominal values are proposed for all of them. \subsection{Single blurred segment extraction} -The blurred segment extraction calls the initial detection, then the fine -tracking steps and applies optional validation tests to the output segment. -It is detailed in algorithm~\ref{algo:single}. +The single blurred segment extraction calls the initial detection, then the +fine tracking step and applies optional validation tests to the output +segment. +When extracting a single edge in interactive mode, the width input value is +set to zero in order to directly use the input segment as first scan. +The single extraction process is detailed in algorithm~\ref{algo:single}. \input{Algos/algoSingle} @@ -113,3 +119,9 @@ algorithm~\ref{algo:auto}. \input{Algos/algoAuto} +Only two parameters are left under user control: the sweeping step and the +maximal thickess of extracted blurred segments. If the sweeping step is +too large, many small segments may be missed. On the contrary, a too small +value has no significant impact on the result because the occupancy mask +avoids processing already extracted segments. It only coasts an extra search +of local gradient magnitude maxima. diff --git a/Methode/evals.tex b/Methode/evals.tex index 83788bf..ed2f410 100755 --- a/Methode/evals.tex +++ b/Methode/evals.tex @@ -244,7 +244,35 @@ Comme il peut y avoir de grosses disparit\'es de longueurs de mise en correspondance, on calcule aussi un \'ecart angulaire global comme le rapport de la somme de tous les \'ecarts angulaires sur toutes les images et de la longueur de toutes les longueurs de mise en correspondance sur -toutes les images. +toutes les images. \\ + +{\bf Refonte pour IPOL (d\'ecembre 2020} \\ +Le script {\tt specstats.sh} charge successivement pour chaque image : +\begin{itemize} +\item l'image York dans {\tt FBSD/Data/image.jpg}, +\item les lignes York dans {\tt FBSD/Data/gtLines.txt}, +\item les lignes Canny, LSD ou ED dans {\tt FBSD/Data/detLines.txt}, +\end{itemize} +puis appelle {\tt Testers/TestLines} qui charge la v\'erit\'e de terrain et +lance les comparaisons. +Les r\'esultats sont plac\'es dans {\tt YorkUrbanDB/data/} \`a +l'int\'erieur de fichiers {\tt <det>prec.txt} qui contiennent pour chaque +image +\begin{itemize} +\item le nombre de lignes, +\item la longueur cumul\'ee sur l'image, +\item le recall (bonnes d\'etections et nombre de pixels d\'etect\'es) +\item et la pr\'ecision (bonnes d\'etections et nombre de pixels de la +v\'erit\'e de terrain). +\end{itemize} +La moyenne sur ces mesures peut ensuite \^etre obtenue par le script +{\tt data/moyennes/mprec}. + +L'ex\'ecutable {\tt ytimes} a le m\^eme r\^ole que {\tt yorktimes}, mais +lance {\tt Testers/TestLines} au lieu de {\tt FBSD}. Dans ces 4 scripts, +on appelle la fonction C {\tt clock} et non la classe C++ {\tt chrono}, +car LSD ne compile pas avec C++. Dans ces testeurs, il est possible de +changer le nombre de r\'ep\'etitions par un argument en ligne de commande. \section{Tests sur images de synth\`ese} -- GitLab