photoprism/internal/entity/markers.go

160 lines
3.1 KiB
Go

package entity
import (
"fmt"
"github.com/photoprism/photoprism/internal/classify"
"github.com/photoprism/photoprism/internal/face"
"github.com/photoprism/photoprism/pkg/txt"
)
// Markers represents a list of markers.
type Markers []Marker
// Save stores the markers in the database.
func (m Markers) Save(file *File) (count int, err error) {
if file == nil {
return 0, fmt.Errorf("file required for saving markers")
}
for i := range m {
if m[i].UpdateFile(file) {
continue
}
if created, err := CreateMarkerIfNotExists(&m[i]); err != nil {
log.Errorf("markers: %s (save)", err)
} else {
m[i] = *created
}
}
return file.UpdatePhotoFaceCount()
}
// Unsaved tests if any marker hasn't been saved yet.
func (m Markers) Unsaved() bool {
for i := range m {
if m[i].Unsaved() {
return true
}
}
return false
}
// Contains returns true if a marker at the same position already exists.
func (m Markers) Contains(other Marker) bool {
for i := range m {
if m[i].OverlapPercent(other) > face.OverlapThreshold {
return true
}
}
return false
}
// DetectedFaceCount returns the number of automatically detected face markers.
func (m Markers) DetectedFaceCount() (count int) {
for i := range m {
if m[i].DetectedFace() {
count++
}
}
return count
}
// ValidFaceCount returns the number of valid face markers.
func (m Markers) ValidFaceCount() (count int) {
for i := range m {
if m[i].ValidFace() {
count++
}
}
return count
}
// SubjectNames returns known subject names.
func (m Markers) SubjectNames() (names []string) {
for i := range m {
if m[i].MarkerInvalid || m[i].MarkerType != MarkerFace {
continue
} else if n := m[i].SubjectName(); n != "" {
names = append(names, n)
}
}
return txt.UniqueNames(names)
}
// Labels returns matching labels.
func (m Markers) Labels() (result classify.Labels) {
faceCount := 0
labelSrc := SrcImage
labelUncertainty := 100
for i := range m {
if m[i].ValidFace() {
faceCount++
if u := m[i].Uncertainty(); u < labelUncertainty {
labelUncertainty = u
}
if m[i].MarkerSrc != "" {
labelSrc = m[i].MarkerSrc
}
}
}
if faceCount < 1 {
return classify.Labels{}
}
var rule classify.LabelRule
if faceCount == 1 {
rule = classify.Rules["portrait"]
} else {
rule = classify.Rules["people"]
}
return classify.Labels{classify.Label{
Name: rule.Label,
Source: labelSrc,
Uncertainty: labelUncertainty,
Priority: rule.Priority,
Categories: rule.Categories,
}}
}
// Append adds a marker.
func (m *Markers) Append(marker Marker) {
*m = append(*m, marker)
}
// AppendWithEmbedding adds a marker with face embedding.
func (m *Markers) AppendWithEmbedding(marker Marker) {
if !marker.Embeddings().One() {
// Ignore markers that don't have exactly one embedding.
return
}
m.Append(marker)
}
// FindMarkers returns up to 1000 markers for a given file uid.
func FindMarkers(fileUID string) (Markers, error) {
m := Markers{}
err := Db().
Where("file_uid = ?", fileUID).
Order("x").
Offset(0).Limit(1000).
Find(&m).Error
return m, err
}