1 #ifndef gevd_fold_h_
2 #define gevd_fold_h_
3 //:
4 // \file
5 // \brief detection of fold profiles in the intensity image
6 //
7 // Operator to implement Canny edge detector which finds elongated
8 // fold/roof contours with ddG. Then junctions are found by extending
9 // from end points of dangling contours.
10 //
11 // The recipe is:
12 //    -  Convolution with Gaussian with sigma typically = 1,
13 //       to well-condition the surface before taking second derivative.
14 //       Result is a smoothed image.
15 //
16 //    -  Convolution with Laplacian, or second difference.
17 //       Result is a gradient image. Canny proves that first-derivative
18 //       of the Gaussian is the optimum filter to detect elongated
19 //       step profiles.
20 //
21 //    -  Optionally estimate sensor/texture sigma, if given noise
22 //       sigma is a negative interpolation factor -k in range -[0 1].
23 //       noise_sigma = (1-k)*sensor_sigma + k*texture_sigma.
24 //       Sensor and texture sigmas are estimated from the histogram
25 //       of weak step edges, detected in an ROI centered on gradient image.
26 //       (see Step constructor documentation in Step.C - JLM)
27 //
28 //    -  Non Maximum suppression in the direction of local Hessian,
29 //       to detect pixels with maximum local curvature, above a priori
30 //       noise response. Result is connected contours, width < 2,
31 //       broken only at junctions with weaker chains.
32 //       Also obtain subpixel accuracy normal to the contour.
33 //
34 //    -  Optionally extend from end points of contours to search for
35 //       maximum curvature in the direction normal to the dangling contours,
36 //       above some factor of the noise response.
37 //       Result is a simple detection of all strong junctions.
38 //
39 // Input: Intensity image, smoothing sigma, sensor/texture noise sigma
40 //        and threshold factors for detecting contour/junction edge elements.
41 //
42 // Output: Magnitude, direction, location of fold pixels, forming
43 //         a connected network of contours.
44 //
45 // Complexity: O(|pixels|) time and space for convolutions.
46 //             O(|edgels|) time for iterative extension to recover junctions.
47 //
48 // \verbatim
49 //  Authors
50 //   John Canny      (1986) SM Thesis
51 //   Chris Connolly  (1987) use directional 1st-difference
52 //   Van-Duc Nguyen  (1989) add subpixel location, extension to find junctions
53 //   Arron Heller    (1992) translate from CLOS to C++
54 //   Van-Duc Nguyen  (1995) add noise estimation, use FloatOperators.
55 //   Joe Mundy       (1997) Added strength and direction arrays for transfer to
56 //                          edgel chains. The flag "transfer" controls the
57 //                          setting of these arrays. Note difference with
58 //                          gevd_step.h.. Didn't want to affect global use of
59 //                          DetectEdgels.
60 // \endverbatim
61 //-----------------------------------------------------------------------------
62 
63 #include <iostream>
64 #include <iosfwd>
65 #ifdef _MSC_VER
66 #  include <vcl_msvc_warnings.h>
67 #endif
68 class gevd_bufferxy;
69 
70 class gevd_fold
71 {
72  public:
73   //: Save parameters and create workspace for detecting fold profiles.
74   // High frequency features are smoothed away by smooth_sigma.
75   // Texture or white noise less than noise_sigma are not detected.
76   // smooth_sigma = 0.5-2.0, 1.0 insures separation of independent folds >= 2.
77   // noise_sigma = is standard deviation of intensity, in a uniform region.
78   // Optionally estimate sensor/texture sigma, if given noise sigma
79   // is a negative factor -k between -[0 1]. In this case,
80   // noise_sigma = (1-k)*sensor_sigma + k*texture_sigma.
81   // Response of white noise to the filter ddG is computed, and
82   // then multiplied by contour_factor/junction_factor to obtain
83   // thresholds for detecting contour/junction edges.
84   //
85   gevd_fold(float smooth_sigma=1,       //!< spatial smoothing [0.5 2.0]
86             float noise_sigma=-0.5,     //!< sensor/texture intensity noise -[0 1]
87             float contour_factor=1.0,   //!< threshold factor for contour edgels
88             float junction_factor=1.5); //!< threshold factor for junction edgels
89 
90   //: Free space allocated for detecting fold profiles.  Does nothing.
91   ~gevd_fold() = default;
92 
93   static gevd_bufferxy* null_bufferxy;
94 
95   //: Detect fold profiles with ddG edge detector.
96   // The image is convolved with a Gaussian to smooth away
97   // high frequency noise, and insure separation of fold responses.
98   // Then the largest absolute eigenvalue, and corresponding eigenvector
99   // of the local Hessian are computed
100   // using second difference [+1 -2 +1].
101   // Optionally estimate sensor/texture sigma and set threshold.
102   // Finally, non maximum suppression is done to find strict
103   // local maxima of curvature.
104   // The edge detector finds elongated contours only.
105   // These contours are typically broken at junctions because non
106   // maximum suppression is done along only the strongest direction.
107   // Return contour (float), direction (byte), location (float) images.
108   // Return true if no exception.
109   // J. Canny, A Computational Approach to Edge Detection,
110   // IEEE Trans on PAMI, vol 8, no 6, Nov 1986.
111   bool DetectEdgels(const gevd_bufferxy& image, //!< float image
112                     gevd_bufferxy*& edgels, //!< strength = dG * I
113                     gevd_bufferxy*& direction, //!< direction % PI/4
114                     gevd_bufferxy*& locationx, //!< subpixel loc
115                     gevd_bufferxy*& locationy,
116                     bool peaks_only = false,
117                     bool valleys_only = false,
118                     bool transfer = false, //if true, fill mag and angle arrays
119                     gevd_bufferxy*& mag = null_bufferxy, // d2G magnitude
120                     gevd_bufferxy*& angle = null_bufferxy); //Gradient orientation
121 
122   //:
123   // Find junctions by searching for extensions of contours from
124   // their dangling end points. Non maximum suppression insures that
125   // contours have width < 2, and so we can find the left/right neighbors,
126   // and deduce end points. By using a minimally smoothed image,
127   // we find fold profiles up to joining with a stronger contour, thus
128   // recovering the missing junction caused by NMS along only 1 direction.
129   // The junctions are returned but are not set in the contour image,
130   // to prevent incorrect tracing of stronger contours first.
131   int RecoverJunctions(const gevd_bufferxy& image, //!< iterative extension
132                        gevd_bufferxy& edgels, //!< from end points of contours
133                        gevd_bufferxy& direction,
134                        gevd_bufferxy& locationx, gevd_bufferxy& locationy,
135                        int*& junctionx, int*& junctiony) const;
136 
137   //: query stored/estimated noise sigma
138   // Return the standard deviation of raw noise, in the original image,
139   // either estimated or given by the user. If the noise has not been
140   // estimated, return 0.
141   float NoiseSigma() const;
142 
143   //: response of noise sigma to filter ddG
144   // Compute response of white noise through the filter ddG, or
145   // second-derivative of the Gaussian. Using a threshold of 3 times
146   // this noise response would eliminate 99% of the noise edges.
147   float NoiseResponse() const;
148 
149   //: elongated/directional?
150   // Return threshold for detecting contour or junction,
151   // which is response of white gaussian noise, noise_sigma,
152   // to fold edge detector, i.e. second-order derivative of Gaussian,
153   // smooth_sigma.
154   // noise_sigma can be estimated by finding the standard deviation
155   // in a region of constant intensity, and no texture patterns.
156   // Use short_factor*noise_sigma and smooth_sigma/2, when detecting
157   // junctions, to account for multiple responses to fold edge detector.
158   float NoiseThreshold(bool shortp=false) const;
159 
160   //:
161   // Compute response of white noise through the filter ddG, or
162   // second-derivative of the Gaussian. Using a threshold of 3 times
163   // this noise response would eliminate 99% of the noise edges.
164   static float NoiseResponseToFilter(float noiseSigma,
165                                      float smoothSigma,
166                                      float filterFactor);
167 
168   friend std::ostream& operator<<(std::ostream& os, const gevd_fold& st);
169   friend std::ostream& operator<<(std::ostream& os, gevd_fold& st);
170 
171  protected:
172   float smoothSigma;                   //!< spatial smoothing
173   float noiseSigma;                    //!< sensor/texture noise
174   float contourFactor, junctionFactor; //!< threshold factor for edgels
175   float filterFactor{6};               //!< factor in convolution filter
176 };
177 
178 #endif // gevd_fold_h_
179