Files
fluids/edt/edt.go

294 lines
5.7 KiB
Go
Raw Normal View History

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 (FelzenszwalbHuttenlocher) ---
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]
}
}
}