294 lines
5.7 KiB
Go
294 lines
5.7 KiB
Go
|
|
package edt
|
|||
|
|
|
|||
|
|
import "math"
|
|||
|
|
|
|||
|
|
type Bounds struct {
|
|||
|
|
X int
|
|||
|
|
Y int
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type Coordinate struct {
|
|||
|
|
X int
|
|||
|
|
Y int
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
Represents a Euclidean Distance Transform of dimensions Dimensions with the transform
|
|||
|
|
stored in D and a lookup in L. The lookup represents the nearest occupied coordinate (x, y)
|
|||
|
|
relative to the current coordinate.
|
|||
|
|
*/
|
|||
|
|
type EDT struct {
|
|||
|
|
Dimensions Bounds //spatial dimensions
|
|||
|
|
D []float64 //distance transform
|
|||
|
|
L []Coordinate //transform lookup
|
|||
|
|
gx []float64 //buffer for our vector function, per row
|
|||
|
|
gy []float64 //buffer for our vector function, per column
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func NewEDT(dimensions Bounds) *EDT {
|
|||
|
|
edt := &EDT{
|
|||
|
|
Dimensions: dimensions,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
edt.InitializeD()
|
|||
|
|
return edt
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (edt *EDT) InitializeD() {
|
|||
|
|
|
|||
|
|
n := edt.Dimensions.X * edt.Dimensions.Y
|
|||
|
|
if n <= 0 {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
edt.D = make([]float64, n)
|
|||
|
|
edt.L = make([]Coordinate, n)
|
|||
|
|
edt.gx = make([]float64, edt.Dimensions.X)
|
|||
|
|
edt.gy = make([]float64, edt.Dimensions.Y)
|
|||
|
|
|
|||
|
|
for i := range n {
|
|||
|
|
edt.D[i] = math.Inf(+1)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
Reinitializes EDT using occupancy values. Occupancy dimensions must match EDT.
|
|||
|
|
*/
|
|||
|
|
func (edt *EDT) AssignOccupancy(occupancy []bool) {
|
|||
|
|
|
|||
|
|
n := edt.Dimensions.X * edt.Dimensions.Y
|
|||
|
|
if len(occupancy) != n {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//assign occupancy values
|
|||
|
|
for i := range n {
|
|||
|
|
if occupancy[i] {
|
|||
|
|
edt.D[i] = 0
|
|||
|
|
} else {
|
|||
|
|
edt.D[i] = math.Inf(+1)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
EDT must already have been initialized with occupancy assigned
|
|||
|
|
*/
|
|||
|
|
func (edt *EDT) ComputeDistanceTransform() {
|
|||
|
|
if len(edt.D) == 0 {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
//compute edt for each row
|
|||
|
|
for j := range edt.Dimensions.Y {
|
|||
|
|
//prepare our row vector
|
|||
|
|
edt.ConstructGx(j)
|
|||
|
|
|
|||
|
|
//compute distance transform for this row
|
|||
|
|
//rowD, rowL := edt.DistanceTransform1D(edt.gx)
|
|||
|
|
rowD, rowL := edt1D_with_labels(edt.gx)
|
|||
|
|
|
|||
|
|
//update corresponding row
|
|||
|
|
edt.UpdateRow(j, rowD, rowL)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//compute edt for each column
|
|||
|
|
for i := range edt.Dimensions.X {
|
|||
|
|
//prepare our column vector
|
|||
|
|
edt.ConstructGy(i)
|
|||
|
|
|
|||
|
|
//compute distance transform for this column
|
|||
|
|
//colD, colL := edt.DistanceTransform1D(edt.gy)
|
|||
|
|
colD, colL := edt1D_with_labels(edt.gy)
|
|||
|
|
|
|||
|
|
//update corresponding column
|
|||
|
|
edt.UpdateCol(i, colD, colL)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*Computes EDT and assigns lookup coordinates for given vector g*/
|
|||
|
|
func (edt *EDT) DistanceTransform1D(g []float64) (d []float64, l []int) {
|
|||
|
|
//perform 1d distance transform
|
|||
|
|
n := len(g)
|
|||
|
|
d = make([]float64, n)
|
|||
|
|
l = make([]int, n)
|
|||
|
|
|
|||
|
|
v := make([]int, n)
|
|||
|
|
// envelope sites (seed indices)
|
|||
|
|
z := make([]float64, n+1) // changeover abscissae
|
|||
|
|
k := 0
|
|||
|
|
|
|||
|
|
// top
|
|||
|
|
v[0] = 0
|
|||
|
|
z[0] = math.Inf(-1)
|
|||
|
|
z[1] = math.Inf(+1)
|
|||
|
|
|
|||
|
|
// initialize first valid seed
|
|||
|
|
k = -1
|
|||
|
|
for q := 0; q < n; q++ {
|
|||
|
|
if g[q] == math.Inf(+1) {
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// push q: pop while it starts before current segment
|
|||
|
|
var s float64
|
|||
|
|
for {
|
|||
|
|
if k < 0 {
|
|||
|
|
k = 0
|
|||
|
|
v[0] = q
|
|||
|
|
z[0] = math.Inf(-1)
|
|||
|
|
z[1] = math.Inf(+1)
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
i := v[k]
|
|||
|
|
// intersection s(i,q)
|
|||
|
|
s = ((float64(q*q) + g[q]) - (float64(i*i) + g[i])) / float64(2*(q-i))
|
|||
|
|
if s <= z[k] {
|
|||
|
|
k--
|
|||
|
|
if k < 0 {
|
|||
|
|
continue
|
|||
|
|
} // force a push with -inf
|
|||
|
|
} else {
|
|||
|
|
k++
|
|||
|
|
v[k] = q
|
|||
|
|
z[k] = s
|
|||
|
|
z[k+1] = math.Inf(+1)
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// evaluate
|
|||
|
|
k = 0
|
|||
|
|
for x := 0; x < n; x++ {
|
|||
|
|
for z[k+1] <= float64(x) {
|
|||
|
|
k++
|
|||
|
|
}
|
|||
|
|
i := v[k]
|
|||
|
|
l[x] = i
|
|||
|
|
dx := float64(x - i)
|
|||
|
|
d[x] = dx*dx + g[i]
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return d, l
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func edt1D_with_labels(g []float64) ([]float64, []int) {
|
|||
|
|
n := len(g)
|
|||
|
|
d := make([]float64, n)
|
|||
|
|
l := make([]int, n)
|
|||
|
|
|
|||
|
|
// --- 1. Gather valid seeds ---
|
|||
|
|
type site struct {
|
|||
|
|
i int
|
|||
|
|
gi float64
|
|||
|
|
}
|
|||
|
|
seeds := make([]site, 0, n)
|
|||
|
|
for i := 0; i < n; i++ {
|
|||
|
|
if !math.IsInf(g[i], +1) { // it's a seed
|
|||
|
|
seeds = append(seeds, site{i, g[i]})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// --- 2. Handle empty case directly ---
|
|||
|
|
if len(seeds) == 0 {
|
|||
|
|
for i := 0; i < n; i++ {
|
|||
|
|
d[i] = math.Inf(+1)
|
|||
|
|
l[i] = -1
|
|||
|
|
}
|
|||
|
|
return d, l
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// --- 3. Lower-envelope construction (Felzenszwalb–Huttenlocher) ---
|
|||
|
|
v := make([]int, len(seeds))
|
|||
|
|
z := make([]float64, len(seeds)+1)
|
|||
|
|
k := 0
|
|||
|
|
v[0] = seeds[0].i
|
|||
|
|
z[0] = math.Inf(-1)
|
|||
|
|
z[1] = math.Inf(+1)
|
|||
|
|
|
|||
|
|
for q := 1; q < len(seeds); q++ {
|
|||
|
|
i := seeds[q].i
|
|||
|
|
gi := seeds[q].gi
|
|||
|
|
for {
|
|||
|
|
j := v[k]
|
|||
|
|
gj := g[j]
|
|||
|
|
s := ((float64(i*i) + gi) - (float64(j*j) + gj)) / float64(2*(i-j))
|
|||
|
|
if s <= z[k] {
|
|||
|
|
k--
|
|||
|
|
if k < 0 {
|
|||
|
|
k = 0
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
k++
|
|||
|
|
v[k] = i
|
|||
|
|
z[k] = s
|
|||
|
|
z[k+1] = math.Inf(+1)
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// --- 4. Evaluate ---
|
|||
|
|
k = 0
|
|||
|
|
for x := 0; x < n; x++ {
|
|||
|
|
for z[k+1] <= float64(x) {
|
|||
|
|
k++
|
|||
|
|
}
|
|||
|
|
i := v[k]
|
|||
|
|
l[x] = i
|
|||
|
|
dx := float64(x - i)
|
|||
|
|
d[x] = dx*dx + g[i]
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return d, l
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
Prepares row vector (gx) from specified column in D
|
|||
|
|
*/
|
|||
|
|
func (edt *EDT) ConstructGx(col int) {
|
|||
|
|
if col < edt.Dimensions.Y {
|
|||
|
|
//we can be very efficient due to slice referencing
|
|||
|
|
startIdx := col * edt.Dimensions.Y
|
|||
|
|
edt.gx = edt.D[startIdx : startIdx+edt.Dimensions.X]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
Prepares column vector (gy) from specified row in D
|
|||
|
|
*/
|
|||
|
|
func (edt *EDT) ConstructGy(row int) {
|
|||
|
|
if row < edt.Dimensions.X {
|
|||
|
|
for j := range edt.Dimensions.Y {
|
|||
|
|
edt.gy[j] = edt.D[j*edt.Dimensions.X+row]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
Writes data and lookup values into EDT row specified by rowidx
|
|||
|
|
*/
|
|||
|
|
func (edt *EDT) UpdateRow(rowidx int, data []float64, lookup []int) {
|
|||
|
|
if rowidx < edt.Dimensions.Y {
|
|||
|
|
startidx := rowidx * edt.Dimensions.X
|
|||
|
|
for i := range edt.Dimensions.X {
|
|||
|
|
edt.D[startidx+i] = data[i]
|
|||
|
|
edt.L[startidx+i].X = lookup[i]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
Writes data and lookup values into EDT column specified by colidx
|
|||
|
|
*/
|
|||
|
|
func (edt *EDT) UpdateCol(colidx int, data []float64, lookup []int) {
|
|||
|
|
if colidx < edt.Dimensions.X {
|
|||
|
|
for j := range edt.Dimensions.Y {
|
|||
|
|
edt.D[j*edt.Dimensions.X+colidx] = data[j]
|
|||
|
|
edt.L[j*edt.Dimensions.X+colidx].X = edt.L[lookup[j]*edt.Dimensions.X+colidx].X
|
|||
|
|
edt.L[j*edt.Dimensions.X+colidx].Y = lookup[j]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|