feat: Add .env.example configuration file and integrate Redis service into Docker Compose for improved task management. Update desktop application to support document indexing and extraction workflows, enhancing user experience with a new extraction wizard and stepper components. Revise README and documentation for clarity on setup and usage.
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
package pipeline
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// ClusterRows runs 1D DBSCAN on Y coordinates (parity with sklearn DBSCAN).
|
||||
func ClusterRows(boxes []indexedBox, eps float64, minSamples int) {
|
||||
if len(boxes) == 0 {
|
||||
return
|
||||
}
|
||||
ys := make([]float64, len(boxes))
|
||||
for i, b := range boxes {
|
||||
ys[i] = float64(b.Y)
|
||||
}
|
||||
labels := dbscan1D(ys, eps, minSamples)
|
||||
for i := range boxes {
|
||||
boxes[i].Cluster = labels[i]
|
||||
}
|
||||
}
|
||||
|
||||
func dbscan1D(points []float64, eps float64, minSamples int) []int {
|
||||
n := len(points)
|
||||
labels := make([]int, n)
|
||||
for i := range labels {
|
||||
labels[i] = -1
|
||||
}
|
||||
clusterID := 0
|
||||
visited := make([]bool, n)
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
if visited[i] {
|
||||
continue
|
||||
}
|
||||
visited[i] = true
|
||||
neighbors := regionQuery(points, i, eps)
|
||||
if len(neighbors) < minSamples {
|
||||
continue
|
||||
}
|
||||
labels[i] = clusterID
|
||||
seed := append([]int(nil), neighbors...)
|
||||
for j := 0; j < len(seed); j++ {
|
||||
q := seed[j]
|
||||
if !visited[q] {
|
||||
visited[q] = true
|
||||
neighborsQ := regionQuery(points, q, eps)
|
||||
if len(neighborsQ) >= minSamples {
|
||||
seed = appendUnique(seed, neighborsQ)
|
||||
}
|
||||
}
|
||||
if labels[q] == -1 {
|
||||
labels[q] = clusterID
|
||||
}
|
||||
}
|
||||
clusterID++
|
||||
}
|
||||
return labels
|
||||
}
|
||||
|
||||
func regionQuery(points []float64, idx int, eps float64) []int {
|
||||
var neighbors []int
|
||||
for i, p := range points {
|
||||
if math.Abs(p-points[idx]) <= eps {
|
||||
neighbors = append(neighbors, i)
|
||||
}
|
||||
}
|
||||
return neighbors
|
||||
}
|
||||
|
||||
func appendUnique(seed, add []int) []int {
|
||||
inSeed := make(map[int]bool, len(seed))
|
||||
for _, v := range seed {
|
||||
inSeed[v] = true
|
||||
}
|
||||
for _, v := range add {
|
||||
if !inSeed[v] {
|
||||
seed = append(seed, v)
|
||||
inSeed[v] = true
|
||||
}
|
||||
}
|
||||
return seed
|
||||
}
|
||||
|
||||
type indexedBox struct {
|
||||
Text string
|
||||
X int
|
||||
Y int
|
||||
W int
|
||||
H int
|
||||
Cluster int
|
||||
}
|
||||
|
||||
func groupByCluster(boxes []indexedBox) map[int][]indexedBox {
|
||||
groups := make(map[int][]indexedBox)
|
||||
for _, b := range boxes {
|
||||
if b.Cluster < 0 {
|
||||
continue
|
||||
}
|
||||
groups[b.Cluster] = append(groups[b.Cluster], b)
|
||||
}
|
||||
return groups
|
||||
}
|
||||
|
||||
func sortClusters(groups map[int][]indexedBox) []int {
|
||||
keys := make([]int, 0, len(groups))
|
||||
for k := range groups {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Ints(keys)
|
||||
return keys
|
||||
}
|
||||
Reference in New Issue
Block a user