Various improvements to faces UI

This commit is contained in:
viktorstrate
2021-02-19 23:30:43 +01:00
parent bdd2318afc
commit 5368b4bae0
9 changed files with 121 additions and 10 deletions

View File

@@ -48,6 +48,9 @@ models:
model: github.com/photoview/photoview/api/graphql/models.FaceGroup
ImageFace:
model: github.com/photoview/photoview/api/graphql/models.ImageFace
fields:
faceGroup:
resolver: true
FaceRectangle:
model: github.com/photoview/photoview/api/graphql/models.FaceRectangle
SiteInfo:

View File

@@ -39,6 +39,7 @@ type Config struct {
type ResolverRoot interface {
Album() AlbumResolver
ImageFace() ImageFaceResolver
Media() MediaResolver
Mutation() MutationResolver
Query() QueryResolver
@@ -85,6 +86,7 @@ type ComplexityRoot struct {
}
ImageFace struct {
FaceGroup func(childComplexity int) int
ID func(childComplexity int) int
Media func(childComplexity int) int
Rectangle func(childComplexity int) int
@@ -258,6 +260,9 @@ type AlbumResolver interface {
Path(ctx context.Context, obj *models.Album) ([]*models.Album, error)
Shares(ctx context.Context, obj *models.Album) ([]*models.ShareToken, error)
}
type ImageFaceResolver interface {
FaceGroup(ctx context.Context, obj *models.ImageFace) (*models.FaceGroup, error)
}
type MediaResolver interface {
Thumbnail(ctx context.Context, obj *models.Media) (*models.MediaURL, error)
HighRes(ctx context.Context, obj *models.Media) (*models.MediaURL, error)
@@ -486,6 +491,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.FaceRectangle.MinY(childComplexity), true
case "ImageFace.faceGroup":
if e.complexity.ImageFace.FaceGroup == nil {
break
}
return e.complexity.ImageFace.FaceGroup(childComplexity), true
case "ImageFace.id":
if e.complexity.ImageFace.ID == nil {
break
@@ -1857,6 +1869,7 @@ type ImageFace {
id: ID!
media: Media!
rectangle: FaceRectangle
faceGroup: FaceGroup!
}
type FaceRectangle {
@@ -3489,6 +3502,41 @@ func (ec *executionContext) _ImageFace_rectangle(ctx context.Context, field grap
return ec.marshalOFaceRectangle2githubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐFaceRectangle(ctx, field.Selections, res)
}
func (ec *executionContext) _ImageFace_faceGroup(ctx context.Context, field graphql.CollectedField, obj *models.ImageFace) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "ImageFace",
Field: field,
Args: nil,
IsMethod: true,
IsResolver: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.ImageFace().FaceGroup(rctx, obj)
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*models.FaceGroup)
fc.Result = res
return ec.marshalNFaceGroup2ᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐFaceGroup(ctx, field.Selections, res)
}
func (ec *executionContext) _Media_id(ctx context.Context, field graphql.CollectedField, obj *models.Media) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@@ -9188,15 +9236,29 @@ func (ec *executionContext) _ImageFace(ctx context.Context, sel ast.SelectionSet
case "id":
out.Values[i] = ec._ImageFace_id(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
atomic.AddUint32(&invalids, 1)
}
case "media":
out.Values[i] = ec._ImageFace_media(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
atomic.AddUint32(&invalids, 1)
}
case "rectangle":
out.Values[i] = ec._ImageFace_rectangle(ctx, field, obj)
case "faceGroup":
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._ImageFace_faceGroup(ctx, field, obj)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
default:
panic("unknown field " + strconv.Quote(field.Name))
}

View File

@@ -21,7 +21,8 @@ type FaceGroup struct {
type ImageFace struct {
Model
FaceGroupID int `gorm:"not null;index"`
FaceGroupID int `gorm:"not null;index"`
FaceGroup *FaceGroup
MediaID int `gorm:"not null;index"`
Media Media `gorm:"constraint:OnDelete:CASCADE;"`
Descriptor FaceDescriptor `gorm:"not null"`

View File

@@ -3,6 +3,7 @@ package resolvers
import (
"context"
api "github.com/photoview/photoview/api/graphql"
"github.com/photoview/photoview/api/graphql/auth"
"github.com/photoview/photoview/api/graphql/models"
"github.com/photoview/photoview/api/scanner/face_detection"
@@ -10,6 +11,29 @@ import (
"gorm.io/gorm"
)
type imageFaceResolver struct {
*Resolver
}
func (r *Resolver) ImageFace() api.ImageFaceResolver {
return imageFaceResolver{r}
}
func (r imageFaceResolver) FaceGroup(ctx context.Context, obj *models.ImageFace) (*models.FaceGroup, error) {
if obj.FaceGroup != nil {
return obj.FaceGroup, nil
}
var faceGroup models.FaceGroup
if err := r.Database.Model(&obj).Association("FaceGroup").Find(&faceGroup); err != nil {
return nil, err
}
obj.FaceGroup = &faceGroup
return &faceGroup, nil
}
func (r *queryResolver) MyFaceGroups(ctx context.Context, paginate *models.Pagination) ([]*models.FaceGroup, error) {
user := auth.UserFromContext(ctx)
if user == nil {

View File

@@ -338,6 +338,7 @@ type ImageFace {
id: ID!
media: Media!
rectangle: FaceRectangle
faceGroup: FaceGroup!
}
type FaceRectangle {

View File

@@ -104,7 +104,7 @@ func (fd *FaceDetector) DetectFaces(media *models.Media) error {
}
func (fd *FaceDetector) classifyDescriptor(descriptor face.Descriptor) int32 {
return int32(fd.rec.ClassifyThreshold(descriptor, 0.2))
return int32(fd.rec.ClassifyThreshold(descriptor, 0.3))
}
func (fd *FaceDetector) classifyFace(face *face.Face, media *models.Media, imagePath string) error {

View File

@@ -150,6 +150,7 @@ var fileExtensions = map[string]MediaType{
".arw": TypeARW,
".sr2": TypeSR2,
".srf": TypeSRF,
".srw": TypeSRW,
".cr2": TypeCR2,
".crw": TypeCRW,
".erf": TypeERF,
@@ -159,15 +160,22 @@ var fileExtensions = map[string]MediaType{
".mrw": TypeMRW,
".nef": TypeNEF,
".nrw": TypeNRW,
".mdc": TypeMDC,
".mef": TypeMEF,
".orf": TypeORF,
".pef": TypePEF,
".raf": TypeRAF,
".raw": TypeRAW,
".rw2": TypeRW2,
".dcs": TypeDCS,
".drf": TypeDRF,
".gpr": TypeGPR,
".3fr": Type3FR,
".fff": TypeFFF,
".cap": TypeCap,
".iiq": TypeIIQ,
".mos": TypeMOS,
".rwl": TypeRWL,
// Video formats
".mp4": TypeMP4,

View File

@@ -1,19 +1,28 @@
import PropTypes from 'prop-types'
import React from 'react'
import { Link } from 'react-router-dom'
import styled from 'styled-components'
const FaceBoxStyle = styled.div`
const FaceBoxStyle = styled(Link)`
box-shadow: inset 0 0 2px 1px rgba(0, 0, 0, 0.3), 0 0 0 1px rgb(255, 255, 255);
border-radius: 50%;
position: absolute;
top: ${({ minY }) => minY * 100}%;
bottom: ${({ maxY }) => (1 - maxY) * 100}%;
left: ${({ minX }) => minX * 100}%;
right: ${({ maxX }) => (1 - maxX) * 100}%;
top: ${({ $minY }) => $minY * 100}%;
bottom: ${({ $maxY }) => (1 - $maxY) * 100}%;
left: ${({ $minX }) => $minX * 100}%;
right: ${({ $maxX }) => (1 - $maxX) * 100}%;
`
const FaceBox = ({ face /*media*/ }) => {
return <FaceBoxStyle {...face.rectangle}></FaceBoxStyle>
return (
<FaceBoxStyle
to={`/people/${face.faceGroup.id}`}
$minX={face.rectangle.minX}
$maxX={face.rectangle.maxX}
$minY={face.rectangle.minY}
$maxY={face.rectangle.maxY}
></FaceBoxStyle>
)
}
FaceBox.propTypes = {

View File

@@ -62,6 +62,9 @@ const mediaQuery = gql`
minY
maxY
}
faceGroup {
id
}
}
}
}