People: Create person based on existing marker labels #22

Work in progress.
This commit is contained in:
Michael Mayer 2021-08-12 17:49:48 +02:00
parent 35f0a1925c
commit e78c4df22e
8 changed files with 67 additions and 22 deletions

View file

@ -759,9 +759,15 @@ export class Photo extends RestModel {
}
file.Markers.forEach((m) => {
if (!valid || !m.Invalid) {
result.push(m);
if (valid && m.Invalid) {
return;
}
if (m.hasOwnProperty("Person") && !!m.Person && !!m.Person.Name) {
m.Label = m.Person.Name;
}
result.push(m);
});
return result;

View file

@ -37,6 +37,7 @@ type Marker struct {
H float32 `gorm:"type:FLOAT;" json:"H" yaml:"H,omitempty"`
CreatedAt time.Time
UpdatedAt time.Time
Person *Person `gorm:"foreignkey:RefUID;association_foreignkey:PersonUID;association_autoupdate:false;association_autocreate:false;association_save_reference:false" json:"Person" yaml:"-"`
}
// UnknownMarker can be used as a default for unknown markers.

File diff suppressed because one or more lines are too long

View file

@ -45,7 +45,12 @@ func (m Markers) FaceCount() int {
// FindMarkers returns all markers for a given file id.
func FindMarkers(fileID uint) (Markers, error) {
m := Markers{}
err := Db().Where(`file_id = ?`, fileID).Order("id").Offset(0).Limit(1000).Find(&m).Error
err := Db().
Where(`file_id = ?`, fileID).
Preload("Person").
Order("id").
Offset(0).Limit(1000).
Find(&m).Error
return m, err
}

View file

@ -57,9 +57,7 @@ type Person struct {
BirthYear int `json:"BirthYear" yaml:"BirthYear,omitempty"`
BirthMonth int `json:"BirthMonth" yaml:"BirthMonth,omitempty"`
BirthDay int `json:"BirthDay" yaml:"BirthDay,omitempty"`
DeathYear int `json:"DeathYear" yaml:"DeathYear,omitempty"`
DeathMonth int `json:"DeathMonth" yaml:"DeathMonth,omitempty"`
DeathDay int `json:"DeathDay" yaml:"DeathDay,omitempty"`
PassedAway *time.Time `json:"PassedAway" yaml:"PassedAway,omitempty"`
CreatedAt time.Time `json:"CreatedAt" yaml:"-"`
UpdatedAt time.Time `json:"UpdatedAt" yaml:"-"`
DeletedAt *time.Time `sql:"index" json:"DeletedAt,omitempty" yaml:"-"`

View file

@ -11,7 +11,7 @@ type PeopleFaces []PersonFace
// PersonFace represents the face of a Person.
type PersonFace struct {
ID string `gorm:"type:VARBINARY(42);primary_key;auto_increment:false;" json:"ID" yaml:"ID"`
PersonUID string `gorm:"type:VARBINARY(42);index;" json:"PersonUID" yaml:"PersonUID"`
PersonUID string `gorm:"type:VARBINARY(42);index;" json:"PersonUID" yaml:"PersonUID,omitempty"`
Embedding string `gorm:"type:LONGTEXT;" json:"Embedding" yaml:"Embedding,omitempty"`
CreatedAt time.Time `json:"CreatedAt" yaml:"CreatedAt,omitempty"`
UpdatedAt time.Time `json:"UpdatedAt" yaml:"UpdatedAt,omitempty"`

View file

@ -9,11 +9,12 @@ const (
SrcAuto = ""
SrcManual = "manual"
SrcEstimate = "estimate"
SrcPeople = "people"
SrcName = "name"
SrcMeta = "meta"
SrcXmp = "xmp"
SrcYaml = "yaml"
SrcPeople = "people"
SrcMarker = "marker"
SrcImage = classify.SrcImage
SrcKeyword = classify.SrcKeyword
SrcLocation = classify.SrcLocation
@ -23,10 +24,11 @@ const (
var SrcPriority = Priorities{
SrcAuto: 1,
SrcEstimate: 2,
SrcPeople: 2,
SrcName: 4,
SrcYaml: 8,
SrcLocation: 8,
SrcPeople: 8,
SrcMarker: 8,
SrcImage: 8,
SrcKeyword: 16,
SrcMeta: 16,

View file

@ -6,6 +6,8 @@ import (
"runtime/debug"
"time"
"github.com/photoprism/photoprism/pkg/txt"
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/mutex"
@ -124,15 +126,15 @@ func (m *People) Start() (err error) {
return err
}
uidMap := make(map[string]string, len(peopleFaces))
faceMap := make(map[string]entity.Embedding, len(peopleFaces))
type Face = struct {
Embedding entity.Embedding
PersonUID string
}
faceMap := make(map[string]Face, len(peopleFaces))
for _, f := range peopleFaces {
faceMap[f.ID] = f.UnmarshalEmbedding()
if f.PersonUID != "" {
uidMap[f.ID] = f.PersonUID
}
faceMap[f.ID] = Face{f.UnmarshalEmbedding(), f.PersonUID}
}
limit := 500
@ -157,20 +159,38 @@ func (m *People) Start() (err error) {
var faceId string
var faceDist float64
for _, e1 := range marker.UnmarshalEmbeddings() {
for id, e2 := range faceMap {
if d := clusters.EuclideanDistance(e1, e2); faceId == "" || d < faceDist {
for _, e := range marker.UnmarshalEmbeddings() {
for id, f := range faceMap {
if d := clusters.EuclideanDistance(e, f.Embedding); faceId == "" || d < faceDist {
faceId = id
faceDist = d
}
}
}
if marker.RefUID != "" && marker.RefUID == uidMap[faceId] {
if faceId == "" {
continue
}
if refUID := uidMap[faceId]; refUID != "" {
if marker.RefUID != "" && marker.RefUID == faceMap[faceId].PersonUID {
continue
}
// Create person from marker label?
if marker.MarkerLabel == "" {
// Do nothing.
} else if person := entity.NewPerson(marker.MarkerLabel, entity.SrcMarker, 1); person == nil {
log.Errorf("people: person should not be nil - bug?")
} else if person = entity.FirstOrCreatePerson(person); person == nil {
log.Errorf("people: failed adding %s", txt.Quote(marker.MarkerLabel))
} else if f, ok := faceMap[faceId]; ok {
faceMap[faceId] = Face{Embedding: f.Embedding, PersonUID: person.PersonUID}
entity.Db().Model(&entity.PersonFace{}).Where("id = ?", faceId).Update("PersonUID", person.PersonUID)
log.Infof("people: added %s", txt.Quote(person.PersonName))
}
// Existing person?
if refUID := faceMap[faceId].PersonUID; refUID != "" {
if err := marker.Updates(entity.Val{"RefUID": refUID, "RefSrc": entity.SrcPeople, "FaceID": ""}); err != nil {
log.Errorf("people: %s while updating person uid", err)
} else {
@ -188,7 +208,7 @@ func (m *People) Start() (err error) {
time.Sleep(50 * time.Millisecond)
}
log.Infof("people: %d faces added, %d recognized, %d markers updated, %d errors", addedFaces, recognized, markersUpdated, updateErrors)
log.Infof("people: %d faces added, %d recognized, %d unknown, %d errors", addedFaces, recognized, markersUpdated, updateErrors)
return nil
}