Indexer: Improve labels

Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
Michael Mayer 2019-12-13 03:07:26 +01:00
parent 645d02d782
commit f92c21aef9
9 changed files with 186 additions and 73 deletions

View file

@ -1,4 +1,12 @@
cat:
label: cat
priority: 5
categories:
- animal
- kitty
- mammal
tabby cat:
label: tabby cat
priority: 5
categories:
@ -6,9 +14,7 @@ cat:
- animal
- kitty
- mammal
tabby cat:
see: cat
- stripes
persian cat:
see: cat
@ -90,13 +96,17 @@ cardoon:
window:
see: window shade
bannister:
label: stairs
categories:
- architecture
window shade:
label: house front
label: architecture
categories:
- window
- house
- building
- architecture
daisy:
label: flower
@ -115,11 +125,12 @@ rapeseed:
- buttercup
stole:
threshold: 0.2
label: fashion
threshold: 0.5
priority: -1
quilt:
threshold: 0.2
threshold: 0.5
priority: -1
liner:
@ -224,6 +235,7 @@ hen:
- chicken
ostrich:
threshold: 0.5
categories:
- bird
- animal
@ -389,6 +401,7 @@ common iguana:
- animal
american chameleon:
threshold: 0.4
categories:
- reptile
- animal
@ -429,6 +442,7 @@ green lizard:
- lizard
african chameleon:
threshold: 0.4
label: chameleon
categories:
- reptile
@ -768,9 +782,7 @@ jellyfish:
- fish
sea anemone:
categories:
- animal
- water
label: nature
brain coral:
label: nature
@ -980,10 +992,11 @@ sea lion:
- animal
dog:
label: dog
priority: 5
categories:
- animal
- dog
- puppy
- mammal
chihuahua dog:
@ -1658,48 +1671,62 @@ hippopotamus:
- animal
ox:
label: cattle
priority: 3
categories:
- animal
- mammal
- cow
water buffalo:
categories:
- animal
- mammal
bison:
categories:
- animal
- mammal
ram:
categories:
- animal
- mammal
bighorn:
categories:
- animal
- mammal
ibex:
categories:
- animal
- mammal
hartebeest:
categories:
- animal
- mammal
impala:
categories:
- animal
- mammal
gazelle:
categories:
- animal
- mammal
Arabian camel:
categories:
- animal
- mammal
llama:
categories:
- animal
- mammal
weasel:
categories:
@ -1949,19 +1976,22 @@ acoustic guitar:
see: instrument
aircraft carrier:
threshold: 0.5
categories:
- plain
- aircraft
- marine
- military
airliner:
categories:
- plain
- aircraft
- fly
airship:
threshold: 0.5
categories:
- plain
- aircraft
- fly
ambulance:
categories:
@ -1976,6 +2006,7 @@ assault rifle:
categories:
- weapon
- gun
- military
backpack:
categories:
@ -1984,6 +2015,8 @@ backpack:
bakery:
categories:
- bread
- shop
- store
ballpoint:
categories:
@ -2074,6 +2107,7 @@ boathouse:
categories:
- building
- house
- water
bobsled:
categories:
@ -2089,7 +2123,8 @@ bookcase:
bookshop:
categories:
- book
- shop
- store
breakwater:
categories:
@ -2105,6 +2140,7 @@ butcher shop:
categories:
- butcher
- meat
- store
cab:
categories:
@ -2114,6 +2150,7 @@ cab:
cannon:
categories:
- weapon
- military
canoe:
categories:
@ -2216,12 +2253,12 @@ computer keyboard:
categories:
- computer
- laptop
- notebook
confectionery:
label: shop
categorie:
- store
- commercial
container ship:
label: ship
@ -2423,29 +2460,30 @@ gong:
see: instrument
gown:
label: dress
label: fashion
grand piano:
see: instrument
greenhouse:
categories:
- plants
- gardening
- architecture
- building
grocery store:
categories:
- store
- shopping
- shop
hammer:
categories:
- tool
hand-held computer:
label: laptop
label: computer
categories:
- laptop
- notebook
harmonica:
see: instrument
@ -2485,8 +2523,10 @@ lampshade:
- light
laptop:
label: computer
categories:
- computer
- notebook
- laptop
lawn mower:
categories:
@ -2600,6 +2640,7 @@ monitor:
- computer
mosque:
label: towers
categories:
- building
- religion
@ -2632,9 +2673,10 @@ necklace:
- jewelery
notebook:
label: computer
categories:
- laptop
- computer
- notebook
obelisk:
categories:
@ -2745,6 +2787,7 @@ pill bottle:
- medicine
pillow:
threshold: 0.5
categories:
- bedroom
@ -2778,9 +2821,10 @@ pop bottle:
- bottle
pot:
label: plant
categories:
- kitchen
- cooking
- nature
- weed
power drill:
categories:
@ -2827,11 +2871,13 @@ revolver:
categories:
- weapon
- gun
- military
rifle:
categories:
- weapon
- gun
- military
rocking chair:
categories:
@ -2881,17 +2927,18 @@ screwdriver:
shoe shop:
categories:
- shoe
- shopping
- store
shopping basket:
categories:
- basket
- shopping
- shop
- store
shopping cart:
categories:
- shopping
- shop
- store
shovel:
categories:
@ -3042,7 +3089,8 @@ tow truck:
toyshop:
categories:
- shopping
- shop
- store
- toys
trailer truck:
@ -3461,10 +3509,10 @@ arabian camel:
- animal
tile roof:
label: house
label: architecture
categories:
- building
- architecture
- house
croquet ball:
label: pumpkin
@ -3474,11 +3522,13 @@ croquet ball:
- sphere
ball:
priority: -1
categories:
- ball
- round
- sphere
- sports
- game
baseball:
see: ball
@ -3505,7 +3555,7 @@ volleyball:
see: ball
spider web:
threshold: 0.6
threshold: 0.7
priority: -1
wool:
@ -3547,3 +3597,9 @@ jigsaw puzzle:
priority: -1
categories:
- game
chain mail:
threshold: 0.6
pinwheel:
threshold: 0.5

View file

@ -15,12 +15,12 @@
}
.rainbow {
background: linear-gradient(270deg, #2acea4, #ffe300, #2a6fce, #8c2ace, #8c2ace, #ce2a78);
background-size: 1200% 1200%;
background: repeating-linear-gradient(45deg, #FF9AA2, #FFB7B2, #FFDAC1, #E2F0CB, #B5EAD7, #C7CEEA);
background-size: 200% 200%;
-webkit-animation: rainbow-animation 10s ease infinite;
-moz-animation: rainbow-animation 10s ease infinite;
animation: rainbow-animation 10s ease infinite;
-webkit-animation: rainbow-animation 6s ease infinite;
-moz-animation: rainbow-animation 6s ease infinite;
animation: rainbow-animation 6s ease infinite;
}
@keyframes colorchange

View file

@ -580,7 +580,7 @@ func (c *Config) ClientConfig() ClientConfig {
db.Table("labels").
Select("COUNT(*) AS labels").
Where("deleted_at IS NULL").
Where("label_priority >= -2 && deleted_at IS NULL").
Take(&count)
db.Table("albums").

View file

@ -82,7 +82,7 @@ func (i *Indexer) indexMediaFile(m *MediaFile, o IndexerOptions) IndexResult {
photo.PhotoName = fileBase
if file.FilePrimary {
if fileChanged || o.UpdateLabels || o.UpdateTitle {
if fileChanged || o.UpdateKeywords || o.UpdateLabels || o.UpdateTitle {
// Image classification labels
labels = i.classifyImage(m)
}
@ -117,7 +117,9 @@ func (i *Indexer) indexMediaFile(m *MediaFile, o IndexerOptions) IndexResult {
}
if fileChanged || o.UpdateKeywords || o.UpdateLocation || o.UpdateTitle {
keywords, labels = i.indexLocation(m, &photo, keywords, labels, fileChanged, o)
locKeywords, locLabels := i.indexLocation(m, &photo, labels, fileChanged, o)
keywords = append(keywords, locKeywords...)
labels = append(labels, locLabels...)
}
if (fileChanged || o.UpdateTitle) && photo.PhotoTitle == "" {
@ -204,6 +206,7 @@ func (i *Indexer) indexMediaFile(m *MediaFile, o IndexerOptions) IndexResult {
if file.FilePrimary && (fileChanged || o.UpdateKeywords || o.UpdateTitle) {
keywords = append(keywords, file.FileMainColor)
keywords = append(keywords, labels.Keywords()...)
photo.IndexKeywords(keywords, i.db)
}
@ -289,7 +292,7 @@ func (i *Indexer) addLabels(photoId uint, labels Labels) {
// Add categories
for _, category := range label.Categories {
sn := entity.NewLabel(category, -1).FirstOrCreate(i.db)
sn := entity.NewLabel(category, -3).FirstOrCreate(i.db)
i.db.Model(&lm).Association("LabelCategories").Append(sn)
}
@ -301,7 +304,9 @@ func (i *Indexer) addLabels(photoId uint, labels Labels) {
}
}
func (i *Indexer) indexLocation(mediaFile *MediaFile, photo *entity.Photo, keywords []string, labels Labels, fileChanged bool, o IndexerOptions) ([]string, Labels) {
func (i *Indexer) indexLocation(mediaFile *MediaFile, photo *entity.Photo, labels Labels, fileChanged bool, o IndexerOptions) ([]string, Labels) {
var keywords []string
if location, err := mediaFile.Location(); err == nil {
i.db.FirstOrCreate(location, "id = ?", location.ID)
photo.Location = location
@ -336,16 +341,13 @@ func (i *Indexer) indexLocation(mediaFile *MediaFile, photo *entity.Photo, keywo
labels = append(labels, NewLocationLabel(location.LocType, 0, -1))
}
// Sort by priority and uncertainty
sort.Sort(labels)
if (fileChanged || o.UpdateTitle) && photo.PhotoTitleChanged == false {
if len(labels) > 0 && labels[0].Priority >= -1 && labels[0].Uncertainty <= 60 && labels[0].Name != "" { // TODO: User defined title format
log.Infof("index: using label %s to create photo title (%d%% uncertainty)", labels[0].Name, labels[0].Uncertainty)
if location.LocCity == "" || len(location.LocCity) > 16 || strings.Contains(labels[0].Name, location.LocCity) {
photo.PhotoTitle = fmt.Sprintf("%s / %s / %s", util.Title(labels[0].Name), location.LocCountry, photo.TakenAt.Format("2006"))
if title := labels.Title(location.LocName); title != "" { // TODO: User defined title format
log.Infof("index: using label \"%s\" to create photo title", title)
if location.LocCity == "" || len(location.LocCity) > 16 || strings.Contains(title, location.LocCity) {
photo.PhotoTitle = fmt.Sprintf("%s / %s / %s", util.Title(title), location.LocCountry, photo.TakenAt.Format("2006"))
} else {
photo.PhotoTitle = fmt.Sprintf("%s / %s / %s", util.Title(labels[0].Name), location.LocCity, photo.TakenAt.Format("2006"))
photo.PhotoTitle = fmt.Sprintf("%s / %s / %s", util.Title(title), location.LocCity, photo.TakenAt.Format("2006"))
}
} else if location.LocName != "" && location.LocCity != "" {
if len(location.LocName) > 45 {

View file

@ -1,6 +1,11 @@
package photoprism
import "strings"
import (
"sort"
"strings"
"github.com/photoprism/photoprism/internal/util"
)
type Label struct {
Name string `json:"label"` // Label name
@ -43,3 +48,41 @@ func (l Labels) AppendLabel(label Label) Labels {
return append(l, label)
}
func (l Labels) Keywords() (result []string) {
for _, label := range l {
result = append(result, util.Keywords(label.Name)...)
for _, c := range label.Categories {
result = append(result, util.Keywords(c)...)
}
}
return result
}
func (l Labels) Title(fallback string) string {
if len(fallback) > 25 || util.ContainsNumber(fallback) {
fallback = ""
}
if len(l) == 0 {
return fallback
}
// Sort by priority and uncertainty
sort.Sort(l)
// Get best label (at the top)
label := l[0]
if fallback != "" && label.Priority < 0 {
return fallback
} else if fallback != "" && label.Priority == 0 && label.Uncertainty > 50 {
return fallback
} else if label.Priority >= -1 && label.Uncertainty <= 60 {
return label.Name
}
return fallback
}

View file

@ -111,8 +111,8 @@ func TestTensorFlow_Labels(t *testing.T) {
assert.IsType(t, Labels{}, result)
assert.Equal(t, 2, len(result))
assert.Equal(t, "chihuahua dog", result[0].Name)
assert.Equal(t, "pembroke dog", result[1].Name)
assert.Equal(t, "dog", result[0].Name)
assert.Equal(t, "dog", result[1].Name)
assert.Equal(t, 34, result[0].Uncertainty)
assert.Equal(t, 91, result[1].Uncertainty)
@ -193,8 +193,8 @@ func TestTensorFlow_LabelRule(t *testing.T) {
tensorFlow := NewTensorFlow(conf)
result := tensorFlow.labelRule("cat")
assert.Equal(t, "tabby cat", result.Label)
assert.Equal(t, "kitty", result.Categories[2])
assert.Equal(t, "cat", result.Label)
assert.Equal(t, "animal", result.Categories[0])
assert.Equal(t, 5, result.Priority)
})
t.Run("labels.txt not existing in config path", func(t *testing.T) {

View file

@ -177,8 +177,7 @@ func (s *Repo) Photos(f form.PhotoSearch) (results []PhotoResult, err error) {
if result := s.db.First(&label, "label_slug = ?", slugString); result.Error != nil {
log.Infof("search: label \"%s\" not found, using fuzzy search", f.Query)
q = q.Joins("LEFT JOIN labels ON photos_labels.label_id = labels.id").
Where("labels.label_name LIKE ? OR keywords.keyword LIKE ?", likeString, likeString)
q = q.Where("keywords.keyword LIKE ?", likeString)
} else {
labelIds = append(labelIds, label.ID)

22
internal/util/keywords.go Normal file
View file

@ -0,0 +1,22 @@
package util
import (
"regexp"
"strings"
)
var KeywordsRegexp = regexp.MustCompile("[\\p{L}\\d]{3,}")
func Keywords(s string) (results []string) {
all := KeywordsRegexp.FindAllString(s, -1)
for _, w := range all {
w = strings.ToLower(w)
if _, ok := Stopwords[w]; ok == false {
results = append(results, w)
}
}
return results
}

View file

@ -6,6 +6,12 @@ import (
"unicode"
)
var ContainsNumberRegexp = regexp.MustCompile("\\d+")
func ContainsNumber(s string) bool {
return ContainsNumberRegexp.MatchString(s)
}
// isSeparator reports whether the rune could mark a word boundary.
func isSeparator(r rune) bool {
// ASCII alphanumerics and underscore are not separators
@ -50,18 +56,3 @@ func Title(s string) string {
},
s)
}
func Keywords(s string) (results []string) {
r := regexp.MustCompile("[\\p{L}\\d]{3,}")
all := r.FindAllString(s, -1)
for _, w := range all {
w = strings.ToLower(w)
if _, ok := Stopwords[w]; ok == false {
results = append(results, w)
}
}
return results
}