2021-06-02 17:25:04 +02:00
|
|
|
package query
|
|
|
|
|
|
|
|
import (
|
2021-08-19 21:12:38 +02:00
|
|
|
"fmt"
|
2021-08-24 20:15:36 +02:00
|
|
|
"time"
|
2021-08-19 21:12:38 +02:00
|
|
|
|
2021-06-02 17:25:04 +02:00
|
|
|
"github.com/photoprism/photoprism/internal/entity"
|
|
|
|
)
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
2021-08-12 04:54:20 +02:00
|
|
|
|
|
|
|
// Markers finds a list of file markers filtered by type, embeddings, and sorted by id.
|
2021-08-24 20:15:36 +02:00
|
|
|
func Markers(limit, offset int, markerType string, embeddings, subjects bool, matchedBefore time.Time) (result entity.Markers, err error) {
|
2021-08-14 20:48:38 +02:00
|
|
|
db := Db()
|
2021-08-12 04:54:20 +02:00
|
|
|
|
|
|
|
if markerType != "" {
|
2021-08-14 20:48:38 +02:00
|
|
|
db = db.Where("marker_type = ?", markerType)
|
2021-08-12 04:54:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if embeddings {
|
2021-08-15 20:57:26 +02:00
|
|
|
db = db.Where("embeddings_json <> ''")
|
2021-08-12 04:54:20 +02:00
|
|
|
}
|
|
|
|
|
2021-08-19 21:12:38 +02:00
|
|
|
if subjects {
|
|
|
|
db = db.Where("subject_uid <> ''")
|
2021-08-12 04:54:20 +02:00
|
|
|
}
|
|
|
|
|
2021-08-24 20:15:36 +02:00
|
|
|
if !matchedBefore.IsZero() {
|
|
|
|
db = db.Where("matched_at IS NULL OR matched_at < ?", matchedBefore)
|
|
|
|
}
|
|
|
|
|
|
|
|
db = db.Order("matched_at, id").Limit(limit).Offset(offset)
|
2021-08-14 20:48:38 +02:00
|
|
|
|
|
|
|
err = db.Find(&result).Error
|
2021-08-12 04:54:20 +02:00
|
|
|
|
|
|
|
return result, err
|
|
|
|
}
|
|
|
|
|
2021-08-13 20:04:59 +02:00
|
|
|
// Embeddings returns existing face embeddings.
|
2021-08-24 20:15:36 +02:00
|
|
|
func Embeddings(single, unclustered bool, score int) (result entity.Embeddings, err error) {
|
2021-08-12 04:54:20 +02:00
|
|
|
var col []string
|
|
|
|
|
|
|
|
stmt := Db().
|
|
|
|
Model(&entity.Marker{}).
|
|
|
|
Where("marker_type = ?", entity.MarkerFace).
|
2021-08-19 21:12:38 +02:00
|
|
|
Where("marker_invalid = 0").
|
2021-08-15 20:57:26 +02:00
|
|
|
Where("embeddings_json <> ''").
|
2021-08-12 04:54:20 +02:00
|
|
|
Order("id")
|
|
|
|
|
2021-08-24 20:15:36 +02:00
|
|
|
if score > 0 {
|
|
|
|
stmt = stmt.Where("score >= ?", score)
|
|
|
|
}
|
|
|
|
|
2021-08-23 16:22:01 +02:00
|
|
|
if unclustered {
|
|
|
|
stmt = stmt.Where("face_id = ''")
|
|
|
|
}
|
|
|
|
|
2021-08-15 20:57:26 +02:00
|
|
|
if err := stmt.Pluck("embeddings_json", &col).Error; err != nil {
|
2021-08-12 04:54:20 +02:00
|
|
|
return result, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, embeddingsJson := range col {
|
|
|
|
if embeddings := entity.UnmarshalEmbeddings(embeddingsJson); len(embeddings) > 0 {
|
2021-08-13 20:04:59 +02:00
|
|
|
if single {
|
|
|
|
// Single embedding per face detected.
|
|
|
|
result = append(result, embeddings[0])
|
|
|
|
} else {
|
|
|
|
// Return all embedding otherwise.
|
|
|
|
result = append(result, embeddings...)
|
|
|
|
}
|
2021-08-12 04:54:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
2021-08-14 20:48:38 +02:00
|
|
|
|
2021-08-23 16:22:01 +02:00
|
|
|
// RemoveInvalidMarkerReferences deletes invalid reference IDs from the markers table.
|
|
|
|
func RemoveInvalidMarkerReferences() (removed int64, err error) {
|
|
|
|
// Remove subject and face relationships for invalid markers.
|
|
|
|
if res := Db().
|
2021-08-19 21:12:38 +02:00
|
|
|
Model(&entity.Marker{}).
|
2021-08-23 16:22:01 +02:00
|
|
|
Where("marker_invalid = 1 AND (subject_uid <> '' OR face_id <> '')").
|
|
|
|
UpdateColumns(entity.Values{"subject_uid": "", "face_id": ""}); res.Error != nil {
|
|
|
|
return removed, res.Error
|
|
|
|
} else {
|
|
|
|
removed += res.RowsAffected
|
2021-08-19 21:12:38 +02:00
|
|
|
}
|
|
|
|
|
2021-08-23 16:22:01 +02:00
|
|
|
// Remove invalid face IDs.
|
|
|
|
if res := Db().
|
2021-08-19 21:12:38 +02:00
|
|
|
Model(&entity.Marker{}).
|
2021-08-23 16:22:01 +02:00
|
|
|
Where("marker_type = ?", entity.MarkerFace).
|
2021-08-22 16:14:34 +02:00
|
|
|
Where(fmt.Sprintf("face_id <> '' AND face_id NOT IN (SELECT id FROM %s)", entity.Face{}.TableName())).
|
2021-08-23 16:22:01 +02:00
|
|
|
UpdateColumns(entity.Values{"face_id": ""}); res.Error != nil {
|
|
|
|
return removed, res.Error
|
|
|
|
} else {
|
|
|
|
removed += res.RowsAffected
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove invalid subject UIDs.
|
|
|
|
if res := Db().
|
|
|
|
Model(&entity.Marker{}).
|
|
|
|
Where(fmt.Sprintf("subject_uid <> '' AND subject_uid NOT IN (SELECT subject_uid FROM %s)", entity.Subject{}.TableName())).
|
|
|
|
UpdateColumns(entity.Values{"subject_uid": ""}); res.Error != nil {
|
|
|
|
return removed, res.Error
|
|
|
|
} else {
|
|
|
|
removed += res.RowsAffected
|
|
|
|
}
|
|
|
|
|
|
|
|
return removed, nil
|
2021-08-14 20:48:38 +02:00
|
|
|
}
|
2021-08-15 14:14:27 +02:00
|
|
|
|
2021-08-22 16:14:34 +02:00
|
|
|
// 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).
|
2021-08-24 20:15:36 +02:00
|
|
|
UpdateColumns(entity.Values{"subject_uid": "", "subject_src": "", "face_id": "", "matched_at": nil})
|
2021-08-15 14:14:27 +02:00
|
|
|
|
2021-08-22 16:14:34 +02:00
|
|
|
return res.RowsAffected, res.Error
|
2021-08-15 14:14:27 +02:00
|
|
|
}
|
2021-08-24 20:15:36 +02:00
|
|
|
|
|
|
|
// CountUnmatchedFaceMarkers counts the number of unmatched face markers in the index.
|
|
|
|
func CountUnmatchedFaceMarkers() (n int, matchedBefore time.Time) {
|
|
|
|
var f entity.Face
|
|
|
|
|
|
|
|
if err := Db().Where("face_src <> ?", entity.SrcDefault).
|
|
|
|
Order("updated_at DESC").Limit(1).Take(&f).Error; err != nil || f.UpdatedAt.IsZero() {
|
|
|
|
return 0, matchedBefore
|
|
|
|
}
|
|
|
|
|
|
|
|
matchedBefore = time.Now().UTC().Round(time.Second).Add(-2 * time.Hour)
|
|
|
|
|
|
|
|
if f.UpdatedAt.Before(matchedBefore) {
|
|
|
|
matchedBefore = f.UpdatedAt.Add(time.Second)
|
|
|
|
}
|
|
|
|
|
|
|
|
q := Db().Model(&entity.Markers{}).
|
|
|
|
Where("marker_type = ?", entity.MarkerFace).
|
|
|
|
Where("face_id = '' AND subject_src = '' AND marker_invalid = 0 AND embeddings_json <> ''").
|
|
|
|
Where("matched_at IS NULL OR matched_at < ?", matchedBefore)
|
|
|
|
|
|
|
|
if err := q.Count(&n).Error; err != nil {
|
|
|
|
log.Errorf("faces: %s (count unmatched markers)", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return n, matchedBefore
|
|
|
|
}
|
|
|
|
|
|
|
|
// CountMarkers counts the number of face markers in the index.
|
|
|
|
func CountMarkers(markerType string) (n int) {
|
|
|
|
q := Db().Model(&entity.Markers{})
|
|
|
|
|
|
|
|
if markerType != "" {
|
|
|
|
q = q.Where("marker_type = ?", markerType)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := q.Count(&n).Error; err != nil {
|
|
|
|
log.Errorf("faces: %s (count markers)", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return n
|
|
|
|
}
|