Adds two unofficial env variables so advanced users can experiment: 1. PHOTOPRISM_FACE_KIDS_DIST=0.6950 (range: 0.1-1.5, -1 to disable) 2. PHOTOPRISM_FACE_IGNORE_DIST=0.86 (range: 0.1-1.5, -1 to disable)
This commit is contained in:
parent
bb09c43c49
commit
41b252d820
26 changed files with 395 additions and 310 deletions
internal
entity
face
embedding.goembedding_test.goembeddings.goembeddings_ignore.goembeddings_ignore_test.goembeddings_kids.goembeddings_kids_test.goembeddings_test.gonet_test.gothresholds.go
photoprism
query
pkg/clusters
|
@ -54,9 +54,9 @@ func NewFace(subjUID, faceSrc string, embeddings face.Embeddings) *Face {
|
|||
return result
|
||||
}
|
||||
|
||||
// Unsuitable tests if the face is unsuitable for clustering and matching.
|
||||
func (m *Face) Unsuitable() bool {
|
||||
return m.Embedding().Unsuitable()
|
||||
// OmitMatch checks whether the face should be skipped when matching.
|
||||
func (m *Face) OmitMatch() bool {
|
||||
return m.Embedding().OmitMatch()
|
||||
}
|
||||
|
||||
// SetEmbeddings assigns face embeddings.
|
||||
|
@ -125,7 +125,7 @@ func (m *Face) Match(embeddings face.Embeddings) (match bool, dist float64) {
|
|||
|
||||
// Calculate the smallest distance to embeddings.
|
||||
for _, e := range embeddings {
|
||||
if d := e.Distance(faceEmbedding); d < dist || dist < 0 {
|
||||
if d := e.Dist(faceEmbedding); d < dist || dist < 0 {
|
||||
dist = d
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -8,7 +8,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/dustin/go-humanize/english"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/crop"
|
||||
|
@ -273,7 +272,7 @@ func (m *Marker) SetFace(f *Face, dist float64) (updated bool, err error) {
|
|||
continue
|
||||
}
|
||||
|
||||
if d := e.Distance(faceEmbedding); d < m.FaceDist || m.FaceDist < 0 {
|
||||
if d := e.Dist(faceEmbedding); d < m.FaceDist || m.FaceDist < 0 {
|
||||
m.FaceDist = d
|
||||
}
|
||||
}
|
||||
|
@ -507,7 +506,7 @@ func (m *Marker) Face() (f *Face) {
|
|||
} else if f = NewFace(m.SubjUID, m.SubjSrc, emb); f == nil {
|
||||
log.Warnf("marker %s: failed assigning face", sanitize.Log(m.MarkerUID))
|
||||
return nil
|
||||
} else if f.Unsuitable() {
|
||||
} else if f.OmitMatch() {
|
||||
log.Infof("marker %s: face %s is unsuitable for clustering and matching", sanitize.Log(m.MarkerUID), f.ID)
|
||||
} else if f = FirstOrCreateFace(f); f == nil {
|
||||
log.Warnf("marker %s: failed assigning face", sanitize.Log(m.MarkerUID))
|
||||
|
|
|
@ -2,6 +2,7 @@ package face
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/clusters"
|
||||
|
@ -26,34 +27,42 @@ func NewEmbedding(inference []float32) Embedding {
|
|||
return result
|
||||
}
|
||||
|
||||
// Blacklisted tests if the face embedding is blacklisted.
|
||||
func (m Embedding) Blacklisted() bool {
|
||||
return Blacklist.Contains(m, BlacklistRadius)
|
||||
// IgnoreFace tests whether the embedding is generally unsuitable for matching.
|
||||
func (m Embedding) IgnoreFace() bool {
|
||||
if IgnoreDist <= 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return IgnoreEmbeddings.Contains(m, IgnoreDist)
|
||||
}
|
||||
|
||||
// Child tests if the face embedding belongs to a child.
|
||||
func (m Embedding) Child() bool {
|
||||
return Children.Contains(m, ChildrenRadius)
|
||||
// KidsFace tests if the embedded face belongs to a baby or young child.
|
||||
func (m Embedding) KidsFace() bool {
|
||||
if KidsDist <= 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return KidsEmbeddings.Contains(m, KidsDist)
|
||||
}
|
||||
|
||||
// Unsuitable tests if the face embedding is unsuitable for clustering and matching.
|
||||
func (m Embedding) Unsuitable() bool {
|
||||
return m.Child() || m.Blacklisted()
|
||||
// OmitMatch tests if the face embedding is unsuitable for matching.
|
||||
func (m Embedding) OmitMatch() bool {
|
||||
return m.KidsFace() || m.IgnoreFace()
|
||||
}
|
||||
|
||||
// Distance calculates the distance to another face embedding.
|
||||
func (m Embedding) Distance(other Embedding) float64 {
|
||||
return clusters.EuclideanDistance(m, other)
|
||||
// CanMatch tests if the face embedding is not blacklisted.
|
||||
func (m Embedding) CanMatch() bool {
|
||||
return !m.IgnoreFace()
|
||||
}
|
||||
|
||||
// Dist calculates the distance to another face embedding.
|
||||
func (m Embedding) Dist(other Embedding) float64 {
|
||||
return clusters.EuclideanDist(m, other)
|
||||
}
|
||||
|
||||
// Magnitude returns the face embedding vector length (magnitude).
|
||||
func (m Embedding) Magnitude() float64 {
|
||||
return m.Distance(NullEmbedding)
|
||||
}
|
||||
|
||||
// NotBlacklisted tests if the face embedding is not blacklisted.
|
||||
func (m Embedding) NotBlacklisted() bool {
|
||||
return !m.Blacklisted()
|
||||
return m.Dist(NullEmbedding)
|
||||
}
|
||||
|
||||
// JSON returns the face embedding as JSON bytes.
|
||||
|
@ -72,14 +81,14 @@ func (m Embedding) JSON() []byte {
|
|||
}
|
||||
|
||||
// UnmarshalEmbedding parses a single face embedding JSON.
|
||||
func UnmarshalEmbedding(s string) (result Embedding) {
|
||||
if !strings.HasPrefix(s, "[") {
|
||||
return nil
|
||||
func UnmarshalEmbedding(s string) (result Embedding, err error) {
|
||||
if s == "" {
|
||||
return result, fmt.Errorf("cannot unmarshal embedding, empty string provided")
|
||||
} else if !strings.HasPrefix(s, "[") {
|
||||
return result, fmt.Errorf("cannot unmarshal embedding, invalid json provided")
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(s), &result); err != nil {
|
||||
log.Errorf("faces: %s", err)
|
||||
}
|
||||
err = json.Unmarshal([]byte(s), &result)
|
||||
|
||||
return result
|
||||
return result, err
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -2,6 +2,7 @@ package face
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/montanaflynn/stats"
|
||||
|
@ -21,7 +22,7 @@ func NewEmbeddings(inference [][]float32) Embeddings {
|
|||
for i, v = range inference {
|
||||
e := NewEmbedding(v)
|
||||
|
||||
if e.NotBlacklisted() {
|
||||
if e.CanMatch() {
|
||||
result[i] = e
|
||||
}
|
||||
}
|
||||
|
@ -75,7 +76,7 @@ func (embeddings Embeddings) Float64() [][]float64 {
|
|||
// Contains tests if another embeddings is contained within a radius.
|
||||
func (embeddings Embeddings) Contains(other Embedding, radius float64) bool {
|
||||
for _, e := range embeddings {
|
||||
if d := e.Distance(other); d < radius {
|
||||
if d := e.Dist(other); d < radius {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -83,12 +84,12 @@ func (embeddings Embeddings) Contains(other Embedding, radius float64) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// Distance returns the minimum distance to an embedding.
|
||||
func (embeddings Embeddings) Distance(other Embedding) (dist float64) {
|
||||
// Dist returns the minimum distance to an embedding.
|
||||
func (embeddings Embeddings) Dist(other Embedding) (dist float64) {
|
||||
dist = -1
|
||||
|
||||
for _, e := range embeddings {
|
||||
if d := e.Distance(other); d < dist || dist < 0 {
|
||||
if d := e.Dist(other); d < dist || dist < 0 {
|
||||
dist = d
|
||||
}
|
||||
}
|
||||
|
@ -153,7 +154,7 @@ func EmbeddingsMidpoint(embeddings Embeddings) (result Embedding, radius float64
|
|||
|
||||
// Radius is the max embedding distance + 0.01 from result.
|
||||
for _, emb := range embeddings {
|
||||
if d := clusters.EuclideanDistance(result, emb); d > radius {
|
||||
if d := clusters.EuclideanDist(result, emb); d > radius {
|
||||
radius = d + 0.01
|
||||
}
|
||||
}
|
||||
|
@ -162,14 +163,14 @@ func EmbeddingsMidpoint(embeddings Embeddings) (result Embedding, radius float64
|
|||
}
|
||||
|
||||
// UnmarshalEmbeddings parses face embedding JSON.
|
||||
func UnmarshalEmbeddings(s string) (result Embeddings) {
|
||||
if !strings.HasPrefix(s, "[[") {
|
||||
return nil
|
||||
func UnmarshalEmbeddings(s string) (result Embeddings, err error) {
|
||||
if s == "" {
|
||||
return result, fmt.Errorf("cannot unmarshal empeddings, empty string provided")
|
||||
} else if !strings.HasPrefix(s, "[[") {
|
||||
return result, fmt.Errorf("cannot unmarshal empeddings, invalid json provided")
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(s), &result); err != nil {
|
||||
log.Errorf("faces: %s", err)
|
||||
}
|
||||
err = json.Unmarshal([]byte(s), &result)
|
||||
|
||||
return result
|
||||
return result, err
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
187
internal/face/embeddings_ignore_test.go
Normal file
187
internal/face/embeddings_ignore_test.go
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -103,7 +103,7 @@ func TestNet(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Distance Matrix
|
||||
// Dist Matrix
|
||||
correct := 0
|
||||
|
||||
for i := 0; i < len(embeddings); i++ {
|
||||
|
@ -112,7 +112,7 @@ func TestNet(t *testing.T) {
|
|||
continue
|
||||
}
|
||||
|
||||
dist := embeddings[i].Distance(embeddings[j])
|
||||
dist := embeddings[i].Dist(embeddings[j])
|
||||
|
||||
t.Logf("Dist for %d %d (faces are %d %d) is %f", i, j, faceIndexToPersonID[i], faceIndexToPersonID[j], dist)
|
||||
if faceIndexToPersonID[i] == faceIndexToPersonID[j] {
|
||||
|
|
|
@ -12,7 +12,7 @@ var ClusterScoreThreshold = 15 // Min score for faces forming
|
|||
var SizeThreshold = 50 // Min face size in pixels.
|
||||
var ClusterSizeThreshold = 80 // Min size for faces forming a cluster in pixels.
|
||||
var ClusterDist = 0.64 // Similarity distance threshold of faces forming a cluster core.
|
||||
var MatchDist = 0.46 // Distance offset threshold for matching new faces with clusters.
|
||||
var MatchDist = 0.46 // Dist offset threshold for matching new faces with clusters.
|
||||
var ClusterCore = 4 // Min number of faces forming a cluster core.
|
||||
var SampleThreshold = 2 * ClusterCore // Threshold for automatic clustering to start.
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ func (w *Faces) Cluster(opt FacesOptions) (added entity.Faces, err error) {
|
|||
var c clusters.HardClusterer
|
||||
|
||||
// See https://dl.photoprism.app/research/ for research on face clustering algorithms.
|
||||
if c, err = clusters.DBSCAN(face.ClusterCore, face.ClusterDist, w.conf.Workers(), clusters.EuclideanDistance); err != nil {
|
||||
if c, err = clusters.DBSCAN(face.ClusterCore, face.ClusterDist, w.conf.Workers(), clusters.EuclideanDist); err != nil {
|
||||
return added, err
|
||||
} else if err = c.Learn(embeddings.Float64()); err != nil {
|
||||
return added, err
|
||||
|
@ -73,7 +73,7 @@ func (w *Faces) Cluster(opt FacesOptions) (added entity.Faces, err error) {
|
|||
for _, cluster := range results {
|
||||
if f := entity.NewFace("", entity.SrcAuto, cluster); f == nil {
|
||||
log.Errorf("faces: face should not be nil - bug?")
|
||||
} else if f.Unsuitable() {
|
||||
} else if f.OmitMatch() {
|
||||
log.Infof("faces: ignoring %s, cluster unsuitable for matching", f.ID)
|
||||
} else if err := f.Create(); err == nil {
|
||||
added = append(added, *f)
|
||||
|
|
|
@ -122,7 +122,7 @@ func (w *Faces) MatchFaces(faces entity.Faces, force bool, matchedBefore *time.T
|
|||
// Pointer to the matching face.
|
||||
var f *entity.Face
|
||||
|
||||
// Distance to the matching face.
|
||||
// Dist to the matching face.
|
||||
var d float64
|
||||
|
||||
// Find the closest face match for marker.
|
||||
|
|
|
@ -26,7 +26,7 @@ func (w *Faces) Stats() (err error) {
|
|||
continue
|
||||
}
|
||||
|
||||
d := embeddings[i].Distance(embeddings[j])
|
||||
d := embeddings[i].Dist(embeddings[j])
|
||||
|
||||
if min < 0 || d < min {
|
||||
min = d
|
||||
|
@ -84,7 +84,7 @@ func (w *Faces) Stats() (err error) {
|
|||
continue
|
||||
}
|
||||
|
||||
d := e1.Distance(f2.Embedding())
|
||||
d := e1.Dist(f2.Embedding())
|
||||
|
||||
if min < 0 || d < min {
|
||||
min = d
|
||||
|
|
|
@ -102,7 +102,11 @@ func Embeddings(single, unclustered bool, size, score int) (result face.Embeddin
|
|||
}
|
||||
|
||||
for _, embeddingsJson := range col {
|
||||
if embeddings := face.UnmarshalEmbeddings(embeddingsJson); !embeddings.Empty() {
|
||||
if embeddingsJson == "" {
|
||||
continue
|
||||
} else if embeddings, err := face.UnmarshalEmbeddings(embeddingsJson); err != nil {
|
||||
log.Warnf("faces: %s", err)
|
||||
} else if !embeddings.Empty() {
|
||||
if single {
|
||||
// Single embedding per face detected.
|
||||
result = append(result, embeddings[0])
|
||||
|
|
|
@ -34,8 +34,8 @@ var observation []float64
|
|||
|
||||
// Create a new KMeans++ clusterer with 1000 iterations,
|
||||
// 8 clusters and a distance measurement function of type func([]float64, []float64) float64).
|
||||
// Pass nil to use clusters.EuclideanDistance
|
||||
c, e := clusters.KMeans(1000, 8, clusters.EuclideanDistance)
|
||||
// Pass nil to use clusters.EuclideanDist
|
||||
c, e := clusters.KMeans(1000, 8, clusters.EuclideanDist)
|
||||
if e != nil {
|
||||
panic(e)
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ Algorithms currenly supported are KMeans++, DBSCAN and OPTICS.
|
|||
Algorithms which support online learning can be trained this way using Online() function, which relies on channel communication to coordinate the process:
|
||||
|
||||
```go
|
||||
c, e := clusters.KmeansClusterer(1000, 8, clusters.EuclideanDistance)
|
||||
c, e := clusters.KmeansClusterer(1000, 8, clusters.EuclideanDist)
|
||||
if e != nil {
|
||||
panic(e)
|
||||
}
|
||||
|
@ -104,8 +104,8 @@ The Estimator interface defines an operation of guessing an optimal number of cl
|
|||
var data [][]float64
|
||||
|
||||
// Create a new KMeans++ estimator with 1000 iterations,
|
||||
// a maximum of 8 clusters and default (EuclideanDistance) distance measurement
|
||||
c, e := clusters.KMeansEstimator(1000, 8, clusters.EuclideanDistance)
|
||||
// a maximum of 8 clusters and default (EuclideanDist) distance measurement
|
||||
c, e := clusters.KMeansEstimator(1000, 8, clusters.EuclideanDist)
|
||||
if e != nil {
|
||||
panic(e)
|
||||
}
|
||||
|
|
|
@ -6,9 +6,9 @@ import (
|
|||
"math"
|
||||
)
|
||||
|
||||
// DistanceFunc represents a function for measuring distance
|
||||
// DistFunc represents a function for measuring distance
|
||||
// between n-dimensional vectors.
|
||||
type DistanceFunc func([]float64, []float64) float64
|
||||
type DistFunc func([]float64, []float64) float64
|
||||
|
||||
// Online represents parameters important for online learning in
|
||||
// clustering algorithms.
|
||||
|
@ -73,8 +73,8 @@ type Importer interface {
|
|||
}
|
||||
|
||||
var (
|
||||
// EuclideanDistance is one of the common distance measurement
|
||||
EuclideanDistance = func(a, b []float64) float64 {
|
||||
// EuclideanDist is one of the common distance measurement
|
||||
EuclideanDist = func(a, b []float64) float64 {
|
||||
var (
|
||||
s, t float64
|
||||
)
|
||||
|
@ -87,8 +87,8 @@ var (
|
|||
return math.Sqrt(s)
|
||||
}
|
||||
|
||||
// EuclideanDistanceSquared is one of the common distance measurement
|
||||
EuclideanDistanceSquared = func(a, b []float64) float64 {
|
||||
// EuclideanDistSquared is one of the common distance measurement
|
||||
EuclideanDistSquared = func(a, b []float64) float64 {
|
||||
var (
|
||||
s, t float64
|
||||
)
|
||||
|
|
|
@ -8,7 +8,7 @@ type dbscanClusterer struct {
|
|||
minpts, workers int
|
||||
eps float64
|
||||
|
||||
distance DistanceFunc
|
||||
distance DistFunc
|
||||
|
||||
// slices holding the cluster mapping and sizes. Access is synchronized to avoid read during computation.
|
||||
mu sync.RWMutex
|
||||
|
@ -40,7 +40,7 @@ type dbscanClusterer struct {
|
|||
|
||||
// Implementation of DBSCAN algorithm with concurrent nearest neighbour computation. The number of goroutines acting concurrently
|
||||
// is controlled via workers argument. Passing 0 will result in this number being chosen arbitrarily.
|
||||
func DBSCAN(minpts int, eps float64, workers int, distance DistanceFunc) (HardClusterer, error) {
|
||||
func DBSCAN(minpts int, eps float64, workers int, distance DistFunc) (HardClusterer, error) {
|
||||
if minpts < 1 {
|
||||
return nil, errZeroMinpts
|
||||
}
|
||||
|
@ -53,12 +53,12 @@ func DBSCAN(minpts int, eps float64, workers int, distance DistanceFunc) (HardCl
|
|||
return nil, errZeroEpsilon
|
||||
}
|
||||
|
||||
var d DistanceFunc
|
||||
var d DistFunc
|
||||
{
|
||||
if distance != nil {
|
||||
d = distance
|
||||
} else {
|
||||
d = EuclideanDistance
|
||||
d = EuclideanDist
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ func TestDBSCANCluster(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
c, e := DBSCAN(test.MinPts, test.Eps, 0, EuclideanDistance)
|
||||
c, e := DBSCAN(test.MinPts, test.Eps, 0, EuclideanDist)
|
||||
if e != nil {
|
||||
t.Errorf("Error initializing kmeans clusterer: %s\n", e.Error())
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ type kmeansClusterer struct {
|
|||
alpha float64
|
||||
dimension int
|
||||
|
||||
distance DistanceFunc
|
||||
distance DistFunc
|
||||
|
||||
// slices holding the cluster mapping and sizes. Access is synchronized to avoid read during computation.
|
||||
mu sync.RWMutex
|
||||
|
@ -37,7 +37,7 @@ type kmeansClusterer struct {
|
|||
}
|
||||
|
||||
// Implementation of k-means++ algorithm with online learning
|
||||
func KMeans(iterations, clusters int, distance DistanceFunc) (HardClusterer, error) {
|
||||
func KMeans(iterations, clusters int, distance DistFunc) (HardClusterer, error) {
|
||||
if iterations < 1 {
|
||||
return nil, errZeroIterations
|
||||
}
|
||||
|
@ -46,12 +46,12 @@ func KMeans(iterations, clusters int, distance DistanceFunc) (HardClusterer, err
|
|||
return nil, errOneCluster
|
||||
}
|
||||
|
||||
var d DistanceFunc
|
||||
var d DistFunc
|
||||
{
|
||||
if distance != nil {
|
||||
d = distance
|
||||
} else {
|
||||
d = EuclideanDistance
|
||||
d = EuclideanDist
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ type kmeansEstimator struct {
|
|||
// variables keeping count of changes of points' membership every iteration. User as a stopping condition.
|
||||
changes, oldchanges, counter, threshold int
|
||||
|
||||
distance DistanceFunc
|
||||
distance DistFunc
|
||||
|
||||
a, b []int
|
||||
|
||||
|
@ -28,7 +28,7 @@ type kmeansEstimator struct {
|
|||
// Implementation of cluster number estimator using gap statistic
|
||||
// ("Estimating the number of clusters in a data set via the gap statistic", Tibshirani et al.) with k-means++ as
|
||||
// clustering algorithm
|
||||
func KMeansEstimator(iterations, clusters int, distance DistanceFunc) (Estimator, error) {
|
||||
func KMeansEstimator(iterations, clusters int, distance DistFunc) (Estimator, error) {
|
||||
if iterations < 1 {
|
||||
return nil, errZeroIterations
|
||||
}
|
||||
|
@ -37,12 +37,12 @@ func KMeansEstimator(iterations, clusters int, distance DistanceFunc) (Estimator
|
|||
return nil, errOneCluster
|
||||
}
|
||||
|
||||
var d DistanceFunc
|
||||
var d DistFunc
|
||||
{
|
||||
if distance != nil {
|
||||
d = distance
|
||||
} else {
|
||||
d = EuclideanDistance
|
||||
d = EuclideanDist
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -226,7 +226,7 @@ func (c *kmeansEstimator) wk(data [][]float64, centroids [][]float64, mapping []
|
|||
)
|
||||
|
||||
for i := 0; i < len(mapping); i++ {
|
||||
wk[mapping[i]-1] += EuclideanDistanceSquared(centroids[mapping[i]-1], data[i]) / l
|
||||
wk[mapping[i]-1] += EuclideanDistSquared(centroids[mapping[i]-1], data[i]) / l
|
||||
}
|
||||
|
||||
return floats.Sum(wk)
|
||||
|
|
|
@ -20,7 +20,7 @@ func TestKmeansEstimator(t *testing.T) {
|
|||
t.Errorf("Error importing data: %s\n", e.Error())
|
||||
}
|
||||
|
||||
c, e := KMeansEstimator(1000, C, EuclideanDistance)
|
||||
c, e := KMeansEstimator(1000, C, EuclideanDist)
|
||||
if e != nil {
|
||||
t.Errorf("Error initializing kmeans clusterer: %s\n", e.Error())
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ func TestKmeansClusterNumberMatches(t *testing.T) {
|
|||
t.Errorf("Error importing data: %s\n", e.Error())
|
||||
}
|
||||
|
||||
c, e := KMeans(1000, C, EuclideanDistance)
|
||||
c, e := KMeans(1000, C, EuclideanDist)
|
||||
if e != nil {
|
||||
t.Errorf("Error initializing kmeans clusterer: %s\n", e.Error())
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ type opticsClusterer struct {
|
|||
minpts, workers int
|
||||
eps, xi, x float64
|
||||
|
||||
distance DistanceFunc
|
||||
distance DistFunc
|
||||
|
||||
// slices holding the cluster mapping and sizes. Access is synchronized to avoid read during computation.
|
||||
mu sync.RWMutex
|
||||
|
@ -48,7 +48,7 @@ type opticsClusterer struct {
|
|||
|
||||
// Implementation of OPTICS algorithm with concurrent nearest neighbour computation. The number of goroutines acting concurrently
|
||||
// is controlled via workers argument. Passing 0 will result in this number being chosen arbitrarily.
|
||||
func OPTICS(minpts int, eps, xi float64, workers int, distance DistanceFunc) (HardClusterer, error) {
|
||||
func OPTICS(minpts int, eps, xi float64, workers int, distance DistFunc) (HardClusterer, error) {
|
||||
if minpts < 1 {
|
||||
return nil, errZeroMinpts
|
||||
}
|
||||
|
@ -65,12 +65,12 @@ func OPTICS(minpts int, eps, xi float64, workers int, distance DistanceFunc) (Ha
|
|||
return nil, errZeroXi
|
||||
}
|
||||
|
||||
var d DistanceFunc
|
||||
var d DistFunc
|
||||
{
|
||||
if distance != nil {
|
||||
d = distance
|
||||
} else {
|
||||
d = EuclideanDistance
|
||||
d = EuclideanDist
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -191,7 +191,7 @@ func (c *opticsClusterer) run() {
|
|||
|
||||
c.so = append(c.so, i)
|
||||
|
||||
if d = c.coreDistance(i, l, ns); d != 0 {
|
||||
if d = c.coreDist(i, l, ns); d != 0 {
|
||||
q = newPriorityQueue(l)
|
||||
|
||||
c.update(i, d, l, ns, &q)
|
||||
|
@ -205,7 +205,7 @@ func (c *opticsClusterer) run() {
|
|||
|
||||
c.so = append(c.so, p.v)
|
||||
|
||||
if d = c.coreDistance(p.v, l, nss); d != 0 {
|
||||
if d = c.coreDist(p.v, l, nss); d != 0 {
|
||||
c.update(p.v, d, l, nss, &q)
|
||||
}
|
||||
}
|
||||
|
@ -213,7 +213,7 @@ func (c *opticsClusterer) run() {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *opticsClusterer) coreDistance(p int, l int, r []int) float64 {
|
||||
func (c *opticsClusterer) coreDist(p int, l int, r []int) float64 {
|
||||
if l < c.minpts {
|
||||
return 0
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue