photoprism/internal/ai/classify
Michael Mayer 57e4de738e Vision: Fix data race in image classifier during parallel indexing #5694
Indexing workers share one classify.Model whose Run() built tensors into a single shared ImageTensorBuilder buffer with no lock, so concurrent classification corrupted images and produced garbage labels. Pool a per-call builder instead, restoring the pre-#5164 concurrency safety.

Add a concurrent regression test and consolidate the model benchmark.
2026-06-25 15:01:23 +00:00
..
classify.go Links: Use canonical trailing-slash form for website URLs 2026-06-24 17:20:30 +02:00
const.go Metadata: Set labels based on matching keywords in the caption #4603 2025-01-17 06:24:29 +01:00
gen.go CI: Apply Go more linter recommendations to "ai/classify" package #5330 2025-11-22 11:30:58 +01:00
label.go CI: Apply Go linter recommendations to "ai/classify" package #5330 2025-11-22 11:19:30 +01:00
label_rule.go Backend: Rename /internal/tensorflow to /internal/ai 2024-07-02 08:08:14 +02:00
label_rule_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
label_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
labels.go CI: Apply Go linter recommendations to "ai/classify" package #5330 2025-11-22 11:19:30 +01:00
labels_test.go Security: Add gosec fixes with shared URL and fs validation helpers 2026-03-03 16:38:41 +01:00
model.go Vision: Fix data race in image classifier during parallel indexing #5694 2026-06-25 15:01:23 +00:00
model_concurrency_test.go Vision: Fix data race in image classifier during parallel indexing #5694 2026-06-25 15:01:23 +00:00
model_external_test.go Assets: Rename example fixtures directory to "samples" 2026-03-06 18:47:33 +01:00
model_test.go Vision: Fix data race in image classifier during parallel indexing #5694 2026-06-25 15:01:23 +00:00
README.md Clean-up: Drop imaging and pigo library integrations #5353 #5508 #668 2026-04-01 13:47:42 +02:00
rules.go CI: Apply Go more linter recommendations to "ai/classify" package #5330 2025-11-22 11:30:58 +01:00
rules.yml AI: Improve the generation, sorting, and filtering of labels #5232 2025-10-02 13:08:52 +02:00
rules_test.go Backend: Rename /internal/tensorflow to /internal/ai 2024-07-02 08:08:14 +02:00

PhotoPrism — Classification Package

Last Updated: April 1, 2026

Overview

internal/ai/classify wraps PhotoPrisms TensorFlow-based image classification (labels). It loads SavedModel classifiers (Nasnet by default), prepares inputs, runs inference, and maps output probabilities to label rules.

How It Works

  • Model Loading — The classifier loads a SavedModel under assets/models/<name> and resolves model tags and input/output ops (see vision.yml overrides for custom models).
  • Input Preparation — Input images are decoded through PhotoPrisms bounded image helpers and resized/cropped to the models expected input resolution.
  • Inference — The model outputs probabilities; Rules apply thresholds and priority to produce final labels.

Memory & Performance

TensorFlow tensors allocate C memory and are freed by Go GC finalizers. To keep RSS bounded during long runs, PhotoPrism periodically triggers garbage collection to return freed tensor memory to the OS. Tune with:

  • PHOTOPRISM_TF_GC_EVERY (default 200, 0 disables).
    Lower values reduce peak RSS but increase GC overhead and can slow indexing.

Go 1.26 JPEG Decoder Impact

After the base image and toolchain upgrade on February 20, 2026, we observed measurable drift in TensorFlow label uncertainty values caused by changes in Go's image/jpeg implementation:

  • Direct Evidence — The ChameleonLimeJpg fixture shifted from uncertainty 7 with Go 1.25.4 to 8 with Go 1.26.0 for the same model and inputs.
  • Pipeline Relevance — Classification input decoding now goes through pkg/fs direct dispatch helpers, while in-memory resize/pad work uses PhotoPrism's stdlib/x-image thumbnail helpers. JPEG and PNG continue to use direct Go decoders, while TIFF goes through an explicit header/IFD validation path before tiff.Decode.
  • Fixture Scan Result — 55/55 JPEG fixtures in assets/samples decoded successfully on both versions (no compatibility failures), but all produced different decoded pixel hashes between Go 1.25.4 and 1.26.0.
  • Output Stability — In sampled tests, top labels remained stable (chameleon, cat, etc.), while confidence and uncertainty values moved slightly.

Operational notes:

  • Prefer tolerance-based assertions (assert.InDelta) for JPEG-derived uncertainty/confidence tests instead of exact integer equality.
  • Avoid bit-for-bit JPEG expectations in tests unless the codec/toolchain is pinned and intentionally version-locked.
  • Classification no longer relies on generic Go image decoder registration for TIFF input handling.

Troubleshooting Tips

  • Labels are empty: Verify the model labels file and that Rules thresholds are not too strict.
  • Model load failures: Ensure saved_model.pb and variables/ exist under the configured model path.
  • Unexpected outputs: Check TensorFlow.Input/Output settings in vision.yml for custom models.