Indexer: Improve labels
Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
parent
645d02d782
commit
f92c21aef9
9 changed files with 186 additions and 73 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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").
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
22
internal/util/keywords.go
Normal 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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue