CI: Apply Go more linter recommendations to "ai/face" package #5330

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer 2025-11-22 11:38:45 +01:00
parent 59c8754ca3
commit b954de52e9
7 changed files with 36 additions and 19 deletions

View file

@ -3,7 +3,7 @@ package face
import (
_ "embed"
"fmt"
_ "image/jpeg"
_ "image/jpeg" // register JPEG decoder for pigo image decoding
"os"
"path/filepath"
"runtime/debug"
@ -146,7 +146,7 @@ func (d *pigoDetector) Detect(fileName string) (faces []pigo.Detection, params p
d.angles = append([]float64(nil), DetectionAngles...)
}
file, err := os.Open(fileName)
file, err := os.Open(fileName) //nolint:gosec // fileName comes from caller; reading local images is expected
if err != nil {
return faces, params, err
@ -169,11 +169,12 @@ func (d *pigoDetector) Detect(fileName string) (faces []pigo.Detection, params p
var maxSize int
if cols < 20 || rows < 20 || cols < d.minSize || rows < d.minSize {
switch {
case cols < 20 || rows < 20 || cols < d.minSize || rows < d.minSize:
return faces, params, fmt.Errorf("image size %dx%d is too small", cols, rows)
} else if cols < rows {
case cols < rows:
maxSize = cols - 4
} else {
default:
maxSize = rows - 4
}
@ -370,8 +371,8 @@ func (d *pigoDetector) Faces(det []pigo.Detection, params pigo.CascadeParams, fi
// Create face.
f := Face{
Rows: params.ImageParams.Rows,
Cols: params.ImageParams.Cols,
Rows: params.Rows,
Cols: params.Cols,
Score: int(face.Q),
Area: faceCoord,
Eyes: eyesCoords,

View file

@ -10,6 +10,7 @@ import (
// Embedding represents a face embedding.
type Embedding []float64
// NullEmbedding is a zero-value placeholder embedding used when no data is available.
var NullEmbedding = make(Embedding, 512)
// NewEmbedding creates a new embedding from an inference result.

View file

@ -4,12 +4,17 @@ import (
"math/rand/v2"
)
// Kind identifies the type of embedding.
type Kind int
const (
// RegularFace represents a standard face embedding.
RegularFace Kind = iota + 1
// ChildrenFace represents a child face embedding.
ChildrenFace
// BackgroundFace represents non-face/background embeddings.
BackgroundFace
// AmbiguousFace represents embeddings that should be treated as uncertain.
AmbiguousFace
)
@ -20,7 +25,7 @@ func RandomDist() float64 {
// RandomFloat64 adds a random distance offset to a float64.
func RandomFloat64(f, d float64) float64 {
return f + (rand.Float64()-0.5)*d
return f + (rand.Float64()-0.5)*d //nolint:gosec // pseudo-random is sufficient for test fixtures
}
// RandomEmbeddings returns random embeddings for testing.
@ -76,7 +81,7 @@ func RandomChildrenEmbedding() (result Embedding) {
}
d := 0.1 / 512.0
n := rand.IntN(len(Children))
n := rand.IntN(len(Children)) //nolint:gosec // deterministic seeding not required for synthetic embeddings
e := Children[n].Embedding
for i := range result {
@ -97,7 +102,7 @@ func RandomBackgroundEmbedding() (result Embedding) {
}
d := 0.1 / 512.0
n := rand.IntN(len(Background))
n := rand.IntN(len(Background)) //nolint:gosec // deterministic seeding not required for synthetic embeddings
e := Background[n].Embedding
for i := range result {

View file

@ -7,12 +7,17 @@ import (
"sync"
)
// EngineName identifies a face detection engine implementation.
type EngineName = string
const (
// EngineAuto selects the default engine based on availability.
EngineAuto EngineName = "auto"
// EnginePigo enables the built-in Pigo cascade detector.
EnginePigo EngineName = "pigo"
// EngineONNX enables the ONNX runtime-powered SCRFD detector.
EngineONNX EngineName = "onnx"
// EngineNone disables face detection.
EngineNone EngineName = "none"
)

View file

@ -4,7 +4,7 @@ import (
"errors"
"fmt"
"image"
_ "image/jpeg"
_ "image/jpeg" // register JPEG decoder for ONNX engine input
"math"
"os"
"path/filepath"
@ -17,7 +17,7 @@ import (
onnxruntime "github.com/yalue/onnxruntime_go"
)
// ONNXOptions configures how the ONNX runtime-backed detector is initialised.
// ONNXOptions configures how the ONNX runtime-backed detector is initialized.
type ONNXOptions struct {
ModelPath string
LibraryPath string
@ -27,6 +27,7 @@ type ONNXOptions struct {
}
const (
// DefaultONNXModelFilename is the bundled ONNX model name used when none is provided.
DefaultONNXModelFilename = "scrfd.onnx"
onnxDefaultScoreThreshold = 0.50
onnxDefaultNMSThreshold = 0.40
@ -307,7 +308,7 @@ func (o *onnxEngine) Close() error {
// Detect identifies faces in the provided image using the ONNX runtime session.
func (o *onnxEngine) Detect(fileName string, findLandmarks bool, minSize int) (Faces, error) {
file, err := os.Open(fileName)
file, err := os.Open(fileName) //nolint:gosec // fileName provided by caller; reading local images is required for detection
if err != nil {
return Faces{}, err
}
@ -437,9 +438,9 @@ func (o *onnxEngine) buildBlob(img image.Image) ([]float32, float32, error) {
var r, g, b float32
if x < newWidth && y < newHeight {
cr, cg, cb, _ := resized.At(x, y).RGBA()
r = float32(uint8(cr >> 8))
g = float32(uint8(cg >> 8))
b = float32(uint8(cb >> 8))
r = float32((cr >> 8) & 0xff)
g = float32((cg >> 8) & 0xff)
b = float32((cb >> 8) & 0xff)
}
blob[idx] = (r - onnxInputMean) / onnxInputStd

View file

@ -86,7 +86,7 @@ func (m *Model) Detect(fileName string, minSize int, cacheCrop bool, expected in
return faces, nil
}
// Init initialises tensorflow models if not disabled
// Init initializes tensorflow models if not disabled.
func (m *Model) Init() (err error) {
if m.disabled {
return nil
@ -139,6 +139,10 @@ func (m *Model) Run(img image.Image) Embeddings {
// TODO: pre-whiten image as in facenet
trainPhaseBoolTensor, err := tf.NewTensor(false)
if err != nil {
log.Errorf("faces: failed to create phase_train tensor: %s", err)
return nil
}
output, err := m.model.Session.Run(
map[tf.Output]*tf.Tensor{

View file

@ -123,11 +123,11 @@ func TestNet(t *testing.T) {
t.Logf("Dist for %d %d (faces are %d %d) is %f", i, j, faceIndexToPersonID[i], faceIndexToPersonID[j], dist)
if faceIndexToPersonID[i] == faceIndexToPersonID[j] {
if dist < 1.21 {
correct += 1
correct++
}
} else {
if dist >= 1.21 {
correct += 1
correct++
}
}
}