photoprism/internal/query/markers.go
Michael Mayer 2e85b3cccd People: Split facial recognition into smaller functions #22
Clustering and matching have been improved along the way. This opens
the door for further optimizations while keeping the code readable.
2021-08-22 16:14:34 +02:00

135 lines
3.9 KiB
Go

package query
import (
"fmt"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/pkg/txt"
)
// MarkerByID returns a Marker based on the ID.
func MarkerByID(id uint) (marker entity.Marker, err error) {
if err := UnscopedDb().Where("id = ?", id).
First(&marker).Error; err != nil {
return marker, err
}
return marker, nil
}
// Markers finds a list of file markers filtered by type, embeddings, and sorted by id.
func Markers(limit, offset int, markerType string, embeddings, subjects bool) (result entity.Markers, err error) {
db := Db()
if markerType != "" {
db = db.Where("marker_type = ?", markerType)
}
if embeddings {
db = db.Where("embeddings_json <> ''")
}
if subjects {
db = db.Where("subject_uid <> ''")
}
db = db.Order("id").Limit(limit).Offset(offset)
err = db.Find(&result).Error
return result, err
}
// Embeddings returns existing face embeddings.
func Embeddings(single bool) (result entity.Embeddings, err error) {
var col []string
stmt := Db().
Model(&entity.Marker{}).
Where("marker_type = ?", entity.MarkerFace).
Where("marker_invalid = 0").
Where("embeddings_json <> ''").
Order("id")
if err := stmt.Pluck("embeddings_json", &col).Error; err != nil {
return result, err
}
for _, embeddingsJson := range col {
if embeddings := entity.UnmarshalEmbeddings(embeddingsJson); len(embeddings) > 0 {
if single {
// Single embedding per face detected.
result = append(result, embeddings[0])
} else {
// Return all embedding otherwise.
result = append(result, embeddings...)
}
}
}
return result, nil
}
// AddMarkerSubjects adds and references known marker subjects.
func AddMarkerSubjects() (affected int64, err error) {
var markers entity.Markers
if err := Db().
Where("face_id <> '' AND subject_uid = '' AND subject_src = ?", entity.SrcAuto).
Where("marker_invalid = 0 AND marker_type = ?", entity.MarkerFace).
Where("marker_name <> ''").
Order("marker_name").
Find(&markers).Error; err != nil {
return affected, err
} else if len(markers) == 0 {
return affected, nil
}
for _, m := range markers {
if faceId := m.FaceID; faceId == "" {
// Do nothing.
} else if subj := entity.NewSubject(m.MarkerName, entity.SubjectPerson, entity.SrcMarker); subj == nil {
log.Errorf("faces: subject should not be nil - bug?")
} else if subj = entity.FirstOrCreateSubject(subj); subj == nil {
log.Errorf("faces: failed adding subject %s for marker %d", txt.Quote(m.MarkerName), m.ID)
} else if err := m.Updates(entity.Values{"SubjectUID": subj.SubjectUID, "SubjectSrc": entity.SrcAuto}); err != nil {
return affected, err
} else if err := Db().Model(&entity.Face{}).Where("id = ? AND subject_uid = ''", faceId).Update("SubjectUID", subj.SubjectUID).Error; err != nil {
return affected, err
} else {
affected++
}
}
return affected, err
}
// CleanInvalidMarkerReferences deletes invalid reference IDs from the markers table.
func CleanInvalidMarkerReferences() (err error) {
// Reset subject and face relationships for invalid markers.
err = Db().
Model(&entity.Marker{}).
Where("marker_invalid = 1").
UpdateColumns(entity.Values{"subject_uid": "", "subject_src": "", "face_id": ""}).
Error
if err != nil {
return err
}
// Reset invalid face IDs.
return Db().
Model(&entity.Marker{}).
Where(fmt.Sprintf("face_id <> '' AND face_id NOT IN (SELECT id FROM %s)", entity.Face{}.TableName())).
UpdateColumns(entity.Values{"face_id": ""}).
Error
}
// ResetFaceMarkerMatches removes automatically added subject and face references from the markers table.
func ResetFaceMarkerMatches() (removed int64, err error) {
res := Db().Model(&entity.Marker{}).
Where("subject_src <> ? AND marker_type = ?", entity.SrcManual, entity.MarkerFace).
UpdateColumns(entity.Values{"subject_uid": "", "subject_src": "", "face_id": ""})
return res.RowsAffected, res.Error
}