Skip to content

Commit 561a1da

Browse files
committed
PCLines class
1 parent da46ff0 commit 561a1da

File tree

1 file changed

+93
-82
lines changed

1 file changed

+93
-82
lines changed

pclines/accumulator.py

Lines changed: 93 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131

3232

3333
import numpy as np
34-
import numba as nb
34+
#import numba as nb
3535
from skimage.feature import peak_local_max
3636
from 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+
4754
class 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

Comments
 (0)