3131
3232
3333import numpy as np
34- import numba as nb
34+ # import numba as nb
3535from skimage .feature import peak_local_max
3636from skimage .morphology .grey import erosion , dilation
3737
@@ -44,6 +44,13 @@ def _linear_transform(src, dst):
4444 return w , b
4545
4646
47+ def _check_points (x :np .ndarray ):
48+ if not isinstance (x , np .ndarray ):
49+ raise TypeError ("Points must be numpy array" )
50+ if x .ndim != 2 or x .shape [1 ] != 2 :
51+ raise ValueError ("Points must be 2D array with 2 columns" )
52+
53+
4754class Normalizer :
4855 """
4956 Range mapping
@@ -64,95 +71,99 @@ def inverse(self, x):
6471 __call__ = transform
6572
6673
67- def accumulate ( x , w = None , bbox = None , d = 256 ) :
74+ class PCLines :
6875 """
69- Accumulate observation in PCLines space
70-
71- bbox : tuple
72- (x,y,w,h) format
76+ Wrapper for PCLines accumulator of certain size
7377 """
74- # TODO: Check inputs
78+ def __init__ (self , bbox , d = 256 ):
79+ # TODO: check if bbox valid
7580
81+ # Init accumulator
82+ shape = (d , 2 * d - 1 )
83+ self .A = np .zeros (shape , "f" )
84+ self .d = d
7685
77- # Create accumulator
78- acc_shape = d ,2 * d - 1
79- A = np .zeros (acc_shape , "f" ) # The accumulator
80-
81- # Axis normalizers
82- def normalizers ():
8386 x ,y ,w ,h = bbox
84- shape = (w ,h )
85- ranges = d * np .array (shape )/ max (shape )
87+ bb_size = (w ,h )
88+ ranges = d * np .array (bb_size )/ max (bb_size )
8689 ofs = (d - ranges ) / 2
8790 (x0 ,y0 ),(x1 ,y1 ) = ofs , (ranges - 1 )+ ofs
88- norm0 = Normalizer ((y ,y + h ), (y1 , y0 ))
89- norm1 = Normalizer ((x ,x + w ), (x0 , x1 ))
90- norm2 = Normalizer ((y ,y + h ), (y0 , y1 ))
91- return norm0 , norm1 , norm2
9291
93- norm0 , norm1 , norm2 = normalizers ()
92+ self .norm_u = Normalizer ((y ,y + h + 1 ), (y1 , y0 ))
93+ self .norm_v = Normalizer ((x ,x + w + 1 ), (x0 , x1 ))
94+ self .norm_w = Normalizer ((y ,y + h + 1 ), (y0 , y1 ))
9495
95- # Coordinates on parallel axes
96- x0 ,x1 = np .split (x ,2 ,axis = 1 )
97- x = np .concatenate ([norm0 (x1 ), norm1 (x0 ), norm2 (x1 )], axis = 1 )
96+ def clear (self ):
97+ self .A [:] = 0
9898
99- # Rasterize the lines
100- for a ,b ,c in x : # remove space wraping
101- t_part = np .linspace (a ,b ,d )
102- s_part = np .linspace (b ,c ,d )
99+ def transform (self , x ):
100+ """
101+ Transform points x to the PCLines space and return polylines.
102+
103+ Input
104+ -----
105+ x : ndarray
106+ Nx2 array with points
107+
108+ Output
109+ ------
110+ p : ndarray
111+ Nx3 array with polyline coordinates for u, v, w parallel axes
112+ """
113+ _check_points (x )
114+ x0 ,x1 = np .split (x ,2 ,axis = 1 )
115+ return np .concatenate ([self .norm_u (x1 ), self .norm_v (x0 ), self .norm_w (x1 )], axis = 1 )
116+
117+ def inverse (self , l ):
118+ """
119+ Transform a point from PCLines to homogeneous parameters of line
120+ """
121+ d = self .d
122+ x ,y ,w ,h = self .bbox
123+ m = max (w , h ) - 1
124+ norm_v = Normalizer ((0 ,d - 1 ),(- m / 2 , m / 2 ))
125+
126+ u ,v = l [:,1 ], l [:,0 ]
127+ u = u - (d - 1 )
128+ v = norm_v (v )
129+
130+ f = u < 0
131+ h = np .array ([f * (d + u )+ (1 - f )* (d - u ), u , - v * d ], "f" ).T # TODO: add reference to eq in paper
132+ tx ,ty = x + 0.5 * w , y + 0.5 * h
133+ h [:,2 ] -= h [:,0 ]* tx + h [:,1 ]* ty
134+ return h
135+
136+ def valid_points (self , p ):
137+ return np .all (np .logical_and (p >= 0 , p < self .d ), axis = 1 )
138+
139+ def insert (self , x , weight = None ):
140+ """
141+ """
142+ p = self .transform (x )
143+ n = p .shape [0 ]
144+
145+ if weight is None :
146+ weight = np .ones (n , np .float32 )
147+
148+ valid = self .valid_points (p )
149+ p = p [valid ]
150+ weight = weight [valid .flat ]
151+
152+ d = self .d
103153 c = np .arange (2 * d - 1 ,dtype = "i" )
104- r = np .concatenate ([t_part , s_part [1 :]]).astype ("i" )
105- #print(r,c)
106- A [r ,c ] += 1
107-
108- return A
109-
110-
111- def lines (peaks , bbox , d ):
112- """
113- Get homogeneous line parameters from location in the accumulator
114- """
115- u = peaks [:,1 ]
116- v = peaks [:,0 ]
117- #centrovanie
118- u = u - (d - 1 )
119- x ,y ,w ,h = bbox
120- shape = w ,h
121- m = max (shape ) - 1
122- normV = Normalizer ((0 ,d - 1 ),(- m / 2 , m / 2 ))
123- v = normV (v )
124- f = u < 0
125- l = np .array ([f * (d + u )+ (1 - f )* (d - u ), u , - v * d ], "f" ).T # TODO: add reference to eq in paper
126- tx ,ty = x + 0.5 * w , y + 0.5 * h
127- l [:,2 ] -= l [:,0 ]* tx + l [:,1 ]* ty
128- return l
129-
130-
131-
132- @nb .njit ("(f4[:,:],f4[:,:],i4)" )
133- def rasterize_polylines (lines , acc , d ):
134- """
135- """
136-
137-
138- def find_peaks (A , t ):
139- """
140- Retrieve locations with prominent local maxima in the accumulator
141- """
142- prominence = dilation (A + 1 )/ erosion (A + 1 )
143- peaks = peak_local_max (A , threshold_abs = t , min_distance = 5 )
144- r ,c = peaks [:,0 ], peaks [:,1 ]
145- value = A [r ,c ]
146- valid = prominence [r ,c ] > 1.5
147- return peaks [valid ], value [valid ]
148-
149-
150- def get_lines (image ):
151- """
152- PCLines transform of an image
153- """
154- # TODO: Get edges
155- # TODO: Accumulate
156- # TODO: Locate peaks
157- # TODO: Transform peaks to line parameters
158- pass
154+ for (u ,v ,w ),wt in zip (p , weight ): # remove space wraping
155+ t_part = np .linspace (u ,v ,d )
156+ s_part = np .linspace (v ,w ,d )
157+ r = np .concatenate ([t_part , s_part [1 :]]).astype ("i" )
158+ self .A [r ,c ] += wt # TODO add weight
159+
160+ def find_peaks (self , t = 0.8 , prominence = 2 , min_dist = 1 ):
161+ """
162+ Retrieve locations with prominent local maxima in the accumulator
163+ """
164+ p = dilation (self .A + 1 )/ erosion (self .A + 1 )
165+ peaks = peak_local_max (self .A , threshold_rel = t , min_distance = min_dist )
166+ r ,c = peaks [:,0 ], peaks [:,1 ]
167+ value = A [r ,c ]
168+ valid = p [r ,c ] > prominence
169+ return peaks [valid ], value [valid ]
0 commit comments