mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-12 00:34:13 +01:00
Faces: Remove PHOTOPRISM_FACE_ENGINE_RUN config option #5167
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
@@ -116,6 +116,8 @@ Additional safeguards were introduced in October 2025 so stubborn clusters are o
|
||||
| `FACE_SCORE` | `9.0` (with dynamic offsets) | Base quality threshold before scale adjustments. |
|
||||
| `FACE_OVERLAP` | `42` | Maximum allowed IoU when deduplicating markers. |
|
||||
|
||||
Run scheduling is configured through the face model entry in `vision.yml`. Adjust the model’s `Run` value (for example `on-schedule`, `manual`, or `never`) to control when detection and embedding jobs execute—no separate `FACE_ENGINE_RUN` flag is required.
|
||||
|
||||
> Additional merge tuning: set `PHOTOPRISM_FACE_MERGE_MAX_RETRY` to control how often manual clusters are retried (default 1, `0` = unlimited). See the optimiser notes above.
|
||||
|
||||
### Benchmark Reference
|
||||
|
||||
@@ -164,6 +164,21 @@ func (c *ConfigValues) ShouldRun(t ModelType, when RunType) bool {
|
||||
return m.ShouldRun(when)
|
||||
}
|
||||
|
||||
// RunType returns the normalized run type for the first enabled model matching
|
||||
// the provided type. Disabled or missing models fall back to RunNever so
|
||||
// callers can treat the result as authoritative scheduling information.
|
||||
func (c *ConfigValues) RunType(t ModelType) RunType {
|
||||
m := c.Model(t)
|
||||
|
||||
if m == nil {
|
||||
return RunNever
|
||||
} else if m.Disabled {
|
||||
return RunNever
|
||||
}
|
||||
|
||||
return m.RunType()
|
||||
}
|
||||
|
||||
// IsDefault checks whether the specified type is the built-in default model.
|
||||
func (c *ConfigValues) IsDefault(t ModelType) bool {
|
||||
m := c.Model(t)
|
||||
|
||||
@@ -18,6 +18,18 @@ const (
|
||||
RunOnIndex RunType = "on-index" // Run manually and on-index.
|
||||
)
|
||||
|
||||
// ReportRunType returns a human-readable string for the run type, preserving the
|
||||
// explicit value when set or "auto" when delegation is in effect.
|
||||
func ReportRunType(when RunType) string {
|
||||
when = ParseRunType(when)
|
||||
|
||||
if when == RunAuto {
|
||||
return "auto"
|
||||
}
|
||||
|
||||
return when
|
||||
}
|
||||
|
||||
// RunTypes maps configuration strings to standard RunType model settings.
|
||||
var RunTypes = map[string]RunType{
|
||||
RunAuto: RunAuto,
|
||||
|
||||
@@ -123,6 +123,10 @@ func (c *Config) FaceEngine() string {
|
||||
return c.options.FaceEngine
|
||||
}
|
||||
|
||||
if vision.Config == nil {
|
||||
return face.EngineNone
|
||||
}
|
||||
|
||||
desired := face.ParseEngine(c.options.FaceEngine)
|
||||
modelPath := c.FaceEngineModelPath()
|
||||
|
||||
@@ -152,6 +156,10 @@ func (c *Config) FaceEngineRunType() vision.RunType {
|
||||
return vision.RunNever
|
||||
}
|
||||
|
||||
if vision.Config == nil {
|
||||
return vision.RunNever
|
||||
}
|
||||
|
||||
if c.DisableFaces() || c.FaceEngine() == face.EngineNone {
|
||||
return vision.RunNever
|
||||
}
|
||||
|
||||
@@ -145,54 +145,62 @@ func TestConfig_FaceEngine(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConfig_FaceEngineRunType(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
t.Run("AutoDefaults", func(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
c.options.FaceEngineThreads = 1
|
||||
assert.Equal(t, "auto", vision.ReportRunType(c.FaceEngineRunType()))
|
||||
|
||||
c.options.FaceEngineThreads = 1
|
||||
assert.Equal(t, "auto", vision.ReportRunType(c.FaceEngineRunType()))
|
||||
c.options.DisableFaces = true
|
||||
assert.Equal(t, "never", vision.ReportRunType(c.FaceEngineRunType()))
|
||||
c.options.DisableFaces = false
|
||||
|
||||
c.options.DisableFaces = true
|
||||
assert.Equal(t, "never", vision.ReportRunType(c.FaceEngineRunType()))
|
||||
c.options.DisableFaces = false
|
||||
|
||||
c.options.FaceEngineThreads = 4
|
||||
assert.Equal(t, "auto", vision.ReportRunType(c.FaceEngineRunType()))
|
||||
}
|
||||
|
||||
func TestConfig_FaceEngineRunType_DisabledFaceModel(t *testing.T) {
|
||||
origVision := vision.Config
|
||||
t.Cleanup(func() {
|
||||
vision.Config = origVision
|
||||
c.options.FaceEngineThreads = 4
|
||||
assert.Equal(t, "auto", vision.ReportRunType(c.FaceEngineRunType()))
|
||||
})
|
||||
t.Run("DisabledFaceModel", func(t *testing.T) {
|
||||
origVision := vision.Config
|
||||
t.Cleanup(func() { vision.Config = origVision })
|
||||
|
||||
c := NewConfig(CliTestContext())
|
||||
vision.Config = &vision.ConfigValues{Models: vision.Models{{Type: vision.ModelTypeFace, Disabled: true}}}
|
||||
assert.Equal(t, vision.RunNever, c.FaceEngineRunType())
|
||||
}
|
||||
|
||||
func TestConfig_FaceEngineRunType_NoFaceModel(t *testing.T) {
|
||||
origVision := vision.Config
|
||||
t.Cleanup(func() {
|
||||
vision.Config = origVision
|
||||
vision.Config = &vision.ConfigValues{Models: vision.Models{{Type: vision.ModelTypeFace, Disabled: true}}}
|
||||
c := NewConfig(CliTestContext())
|
||||
assert.Equal(t, vision.RunNever, c.FaceEngineRunType())
|
||||
})
|
||||
t.Run("NoFaceModel", func(t *testing.T) {
|
||||
origVision := vision.Config
|
||||
t.Cleanup(func() { vision.Config = origVision })
|
||||
|
||||
c := NewConfig(CliTestContext())
|
||||
vision.Config = &vision.ConfigValues{Models: vision.Models{}}
|
||||
assert.Equal(t, vision.RunNever, c.FaceEngineRunType())
|
||||
}
|
||||
|
||||
func TestConfig_FaceEngineRunType_DelegatesToVisionModel(t *testing.T) {
|
||||
origVision := vision.Config
|
||||
t.Cleanup(func() {
|
||||
vision.Config = origVision
|
||||
vision.Config = &vision.ConfigValues{Models: vision.Models{}}
|
||||
c := NewConfig(CliTestContext())
|
||||
assert.Equal(t, vision.RunNever, c.FaceEngineRunType())
|
||||
})
|
||||
t.Run("DelegatesToVisionModel", func(t *testing.T) {
|
||||
origVision := vision.Config
|
||||
t.Cleanup(func() { vision.Config = origVision })
|
||||
|
||||
c := NewConfig(CliTestContext())
|
||||
vision.Config = &vision.ConfigValues{Models: vision.Models{{Type: vision.ModelTypeFace}}}
|
||||
m := vision.Config.Model(vision.ModelTypeFace)
|
||||
require.NotNil(t, m)
|
||||
m.Run = string(vision.RunOnSchedule)
|
||||
require.Equal(t, vision.RunOnSchedule, vision.Config.RunType(vision.ModelTypeFace))
|
||||
assert.Equal(t, vision.RunOnSchedule, c.FaceEngineRunType())
|
||||
vision.Config = &vision.ConfigValues{Models: vision.Models{{Type: vision.ModelTypeFace}}}
|
||||
c := NewConfig(CliTestContext())
|
||||
m := vision.Config.Model(vision.ModelTypeFace)
|
||||
require.NotNil(t, m)
|
||||
m.Run = vision.RunOnSchedule
|
||||
require.Equal(t, vision.RunOnSchedule, vision.Config.RunType(vision.ModelTypeFace))
|
||||
assert.Equal(t, vision.RunOnSchedule, c.FaceEngineRunType())
|
||||
})
|
||||
t.Run("VisionModelShouldRunFace", func(t *testing.T) {
|
||||
origVision := vision.Config
|
||||
t.Cleanup(func() { vision.Config = origVision })
|
||||
|
||||
vision.Config = &vision.ConfigValues{Models: vision.Models{{Type: vision.ModelTypeFace}}}
|
||||
c := NewConfig(CliTestContext())
|
||||
|
||||
m := vision.Config.Model(vision.ModelTypeFace)
|
||||
require.NotNil(t, m)
|
||||
m.Run = vision.RunOnSchedule
|
||||
|
||||
assert.True(t, c.VisionModelShouldRun(vision.ModelTypeFace, vision.RunOnSchedule))
|
||||
|
||||
m.Disabled = true
|
||||
assert.False(t, c.VisionModelShouldRun(vision.ModelTypeFace, vision.RunOnSchedule))
|
||||
})
|
||||
}
|
||||
|
||||
func TestConfig_FaceEngineThreads(t *testing.T) {
|
||||
|
||||
@@ -51,6 +51,10 @@ func (c *Config) VisionModelShouldRun(t vision.ModelType, when vision.RunType) b
|
||||
return false
|
||||
}
|
||||
|
||||
if t == vision.ModelTypeFace && c.DisableFaces() {
|
||||
return false
|
||||
}
|
||||
|
||||
if t == vision.ModelTypeLabels && c.DisableClassification() {
|
||||
return false
|
||||
}
|
||||
@@ -63,6 +67,10 @@ func (c *Config) VisionModelShouldRun(t vision.ModelType, when vision.RunType) b
|
||||
return false
|
||||
}
|
||||
|
||||
if t == vision.ModelTypeFace {
|
||||
return c.FaceEngineShouldRun(when)
|
||||
}
|
||||
|
||||
return vision.Config.ShouldRun(t, when)
|
||||
}
|
||||
|
||||
|
||||
@@ -1173,12 +1173,6 @@ var Flags = CliFlags{
|
||||
Value: face.EngineAuto,
|
||||
EnvVars: EnvVars("FACE_ENGINE"),
|
||||
}}, {
|
||||
Flag: &cli.StringFlag{
|
||||
Name: "face-engine-run",
|
||||
Usage: "face detection run `MODE` (auto, never, manual, newly-indexed, on-demand, on-index, on-schedule, always)",
|
||||
Value: "auto",
|
||||
EnvVars: EnvVars("FACE_ENGINE_RUN"),
|
||||
}}, {
|
||||
Flag: &cli.IntFlag{
|
||||
Name: "face-engine-threads",
|
||||
Usage: "face detection thread `COUNT` (0 uses half the available CPU cores)",
|
||||
|
||||
@@ -231,7 +231,6 @@ type Options struct {
|
||||
VisionFilter string `yaml:"VisionFilter" json:"VisionFilter" flag:"vision-filter"`
|
||||
DetectNSFW bool `yaml:"DetectNSFW" json:"DetectNSFW" flag:"detect-nsfw"`
|
||||
FaceEngine string `yaml:"FaceEngine" json:"-" flag:"face-engine"`
|
||||
FaceEngineRun string `yaml:"FaceEngineRun" json:"-" flag:"face-engine-run"`
|
||||
FaceEngineRetry bool `yaml:"-" json:"-" flag:"-"`
|
||||
FaceEngineThreads int `yaml:"FaceEngineThreads" json:"-" flag:"face-engine-threads"`
|
||||
FaceSize int `yaml:"-" json:"-" flag:"face-size"`
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/ai/vision"
|
||||
)
|
||||
|
||||
// Report returns global config values as a table for reporting.
|
||||
@@ -284,7 +286,7 @@ func (c *Config) Report() (rows [][]string, cols []string) {
|
||||
|
||||
// Facial Recognition.
|
||||
{"face-engine", c.FaceEngine()},
|
||||
{"face-engine-run", c.FaceEngineRunType()},
|
||||
{"face-engine-run", vision.ReportRunType(c.FaceEngineRunType())},
|
||||
{"face-engine-threads", fmt.Sprintf("%d", c.FaceEngineThreads())},
|
||||
{"face-size", fmt.Sprintf("%d", c.FaceSize())},
|
||||
{"face-score", fmt.Sprintf("%f", c.FaceScore())},
|
||||
|
||||
@@ -48,8 +48,7 @@ func NewIndexOptions(path string, rescan, convert, stack, facesOnly, skipArchive
|
||||
facesRunType = vision.RunOnIndex
|
||||
}
|
||||
|
||||
result.DetectFaces = c.FaceEngineShouldRun(facesRunType)
|
||||
|
||||
result.DetectFaces = c.VisionModelShouldRun(vision.ModelTypeFace, facesRunType)
|
||||
result.DetectNsfw = !facesOnly && c.VisionModelShouldRun(vision.ModelTypeNsfw, vision.RunOnIndex)
|
||||
result.GenerateLabels = !facesOnly && c.VisionModelShouldRun(vision.ModelTypeLabels, vision.RunOnIndex)
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ func (w *Meta) Start(delay, interval time.Duration, force bool) (err error) {
|
||||
labelsModelShouldRun := w.conf.VisionModelShouldRun(vision.ModelTypeLabels, vision.RunNewlyIndexed)
|
||||
captionModelShouldRun := w.conf.VisionModelShouldRun(vision.ModelTypeCaption, vision.RunNewlyIndexed)
|
||||
nsfwModelShouldRun := w.conf.VisionModelShouldRun(vision.ModelTypeNsfw, vision.RunNewlyIndexed)
|
||||
detectFaces := w.conf.FaceEngineShouldRun(vision.RunNewlyIndexed)
|
||||
detectFaces := w.conf.VisionModelShouldRun(vision.ModelTypeFace, vision.RunNewlyIndexed)
|
||||
|
||||
if nsfwModelShouldRun {
|
||||
log.Debugf("index: cannot run %s model on %s", vision.ModelTypeNsfw, vision.RunNewlyIndexed)
|
||||
|
||||
@@ -57,6 +57,10 @@ func (w *Vision) StartScheduled() {
|
||||
|
||||
// scheduledModels returns the model types that should run for scheduled jobs.
|
||||
func (w *Vision) scheduledModels() []string {
|
||||
if w.conf == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
models := make([]string, 0, 4)
|
||||
|
||||
if w.conf.VisionModelShouldRun(vision.ModelTypeLabels, vision.RunOnSchedule) {
|
||||
@@ -103,10 +107,6 @@ func (w *Vision) Start(filter string, count int, models []string, customSrc stri
|
||||
defer mutex.VisionWorker.Stop()
|
||||
|
||||
models = vision.FilterModels(models, runType, func(mt vision.ModelType, when vision.RunType) bool {
|
||||
if mt == vision.ModelTypeFace {
|
||||
return w.conf.FaceEngineShouldRun(when)
|
||||
}
|
||||
|
||||
return w.conf.VisionModelShouldRun(mt, when)
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user