Files
fluids/edt/edt.go

294 lines
5.7 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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]
}
}
}