Facet Photo Analysis and Culling Engine

repository·master·Indexed 41 days ago

https://github.com/ncoevoet/facet

A local-first, AI-powered photo analysis engine that uses computer vision and face recognition to score, rank, and cull photo libraries. It analyzes images across 9 dimensions—including aesthetic quality and technical sharpness—and provides a web gallery for browsing, semantic search, and organization. Supports JPEG, HEIF/HEIC, and various RAW formats. Built with Python 3.10+, FastAPI 0.115+, and Angular 21.

Tokens
115.9K
Snippets
284
Records
460
Agent score
55%

What's inside Facet

  1. Overview of Facet photo analysis engine

    master
    Facet is a multi-dimensional photo analysis engine designed to score, rank, and cull local photo libraries. It provides a gallery interface for browsing analyzed photos. The engine supports various workflows including scoring, culling, browsing, organizing, and understanding photo content.
  2. Overview of Facet

    master

    Facet is a local photo-analysis and culling engine designed to run entirely on your machine without requiring cloud services, accounts, or API keys. It analyzes images across 9 dimensions (such as aesthetic quality and face sharpness) to provide scoring, browsing, culling, and organization capabilities via a web gallery.

    Key Technical Specifications:

    • Python Version: 3.10+
    • Frontend: Angular 21
    • Backend: FastAPI 0.115+
    • Supported Platforms: Windows, macOS, Linux, and Docker
    • Hardware Acceleration: GPU is auto-detected and optional. It can run CPU-only or utilize up to 24 GB of VRAM.
  3. Define scoring categories

    master

    Categories are defined in an array within scoring_config.json. Each category determines how photos are evaluated and prioritized.

    Category Structure

    Each category object contains:

    • name: A unique identifier for the category.
    • priority: An integer where lower values indicate higher priority (evaluated first).
    • filters: Conditions used to match photos to this category.
    • weights: Scoring metric weights. These must sum to 100.
      • Note: Every category's weights block can include five metric keys: symmetry_percent, balance_percent, edge_entropy_percent, fractal_percent, and color_harmony_percent. These are populated by running --recompute-form.
    • modifiers: Behavior adjustments for the category.
    • tags: CLIP vocabulary used for tag-based matching.

    Important: Because arrays are replaced wholesale, you must copy the entire default categories array from config/scoring_config.default.json if you wish to modify or add a category.

  4. Understand processing modes: auto, multi-pass, and single-pass

    master

    Facet supports three processing modes to balance performance and hardware constraints:

    • auto: Automatically selects between multi-pass or single-pass based on available VRAM.
    • multi-pass: Designed for limited VRAM. It loads images in RAM chunks (defined by ram_chunk_size), then sequentially loads a model, processes the chunk, and unloads the model. This allows large models (like tagger/composition VLMs) to run on hardware that couldn't hold all models simultaneously.
    • single-pass: Loads all models into VRAM at once. This is the fastest mode but requires high VRAM capacity.
  5. Metadata interoperability with Lightroom, darktable, and others

    master

    Facet is designed to complement existing photo workflows (Lightroom, darktable, digiKam, etc.) rather than replace them. It achieves this through XMP metadata interoperability:

    • Writing Metadata: Facet writes ratings, labels, keywords, captions, and named face regions to sidecar .xmp files (leaving originals intact by default). It can also embed these into JPEG/HEIC/TIFF/PNG/DNG files using the gallery's "Write metadata to file" action or the CLI flags --export-sidecars --embed-originals.
    • Reading Metadata: Facet can re-read external changes made in other editors using the --import-sidecars flag.
  6. Webhook security and delivery behavior

    master

    Facet implements several security measures and delivery guarantees for outgoing webhooks:

    Security

    • URL Validation: Only http and https protocols are allowed. Hostnames resolving to private, loopback, or link-local addresses (including the cloud metadata IP 169.254.169.254) are rejected.
    • DNS Rebinding Protection: The request connects to the resolved IP while maintaining the original hostname in the Host header to prevent redirection to private addresses.

    Delivery

    • Best-Effort: Delivery is not retried on 5xx errors, timeouts, or transport errors. For at-least-once delivery guarantees, implement queuing and retries on your receiving server.
    • Timeout: The outbound request timeout is 10 seconds.
  7. Understand and optimize scoring metrics

    master

    Fine-tune how your photos are evaluated using these advanced features:

    • AI Critique: Get detailed breakdowns of scores and natural language evaluations using a VLM [GPU] [16gb/24gb].
    • Weight Adjustment: Use the category weight editor with real-time score previews. Use A/B photo comparisons to help the system learn your preferences.
    • Scoring Contexts: Control which category scores a photo (independent of weights). You can reorder global category priority, apply named contexts (e.g., "Portrait Session", "Wildlife") per album, or define photo-specific category overrides.
    • 'My Taste' Sorting: Sort the gallery by a score learned from your personal classifier. A confidence badge indicates the learned coverage and precision.
    • Learning from Labels: Your selection decisions, star ratings, favorites, and rejections are used to optimize weights. Use the following flags to enable this:
      • --sync-label-comparisons
      • --mine-insights
    • Snapshots: Save, restore, and compare different weight configurations.
    • AI Captions [GPU] [16gb/24gb]: Generates editable text descriptions, available in 5 languages.
  8. Understand the Facet workload separation

    master

    Facet splits its operations into two distinct workloads to optimize hardware usage:

    1. Scoring (facet.py): Requires high resources (GPU with 6-24GB VRAM or CPU with 8GB+ RAM) to analyze and score photos.
    2. Viewer (viewer.py): A low-resource component that serves the web gallery. This is the only component that must run on your remote server or NAS.

    Recommended Workflow: Perform scoring on a powerful workstation, then sync the resulting database to your viewer server.

  9. Handle normalization convergence with per-category settings

    master

    When per_category is enabled in the normalization config, a single --recompute-average run may not converge if your changes also affect photo categories (e.g., reordering priorities or changing scoring contexts).

    Why this happens: compute_percentiles_per_category groups photos by their currently stored photos.category column. If a config change causes photos to move to new categories, the first recompute uses the old category groupings.

    Required Workflow:

    1. Apply configuration changes that affect categories.
    2. Run --recompute-average once.
    3. Run --recompute-average a second time to ensure percentiles are computed against the updated category column.
  10. Configure the event-driven plugin system

    master

    The plugin system allows you to react to scoring events via Python modules, webhooks, or built-in actions. Enable it using the enabled key in the plugins block.

    Configuration Keys:

    • enabled: Master switch (Default: false).
    • high_score_threshold: Minimum aggregate score to trigger on_high_score events (Default: 8.0).
    • webhooks: A list of webhook endpoints to receive JSON POST payloads.
    • actions: A map of named built-in actions triggered by events.

    Supported Events and Payloads:

    • on_score_complete: Triggered after each photo is scored. Payload includes path, filename, aggregate, aesthetic, comp_score, category, and tags.
    • on_new_photo: Triggered when a photo enters the database. Uses the same payload as on_score_complete.
    • on_high_score: Triggered when aggregate score $\ge$ high_score_threshold. Uses the same payload as on_score_complete.
    • on_burst_detected: Triggered when a burst group is identified. Payload includes burst_group_id, photo_count, best_path, and paths.
    {
      "plugins": {
        "enabled": true,
        "high_score_threshold": 8.0,
        "webhooks": [
          {
            "url": "https://example.com/hook",
            "events": ["on_score_complete", "on_high_score"],
            "min_score": 8.0
          }
        ],
        "actions": {
          "copy_high_scores": {
            "event": "on_high_score",
            "action": "copy_to_folder",
            "folder": "/path/to/best-photos",
            "min_score": 9.0
          }
        }
      }
    }
  11. How the Facet scoring system works

    master

    Facet classifies photos into categories and then assigns a score based on that category's specific weights. The process follows five steps:

    1. Category Detection: The photo is analyzed for content using faces, tags, and EXIF data.
    2. Filter Evaluation: Categories are evaluated in priority order. The first category whose filters match the photo is selected (this is the scoring context).
    3. Weight Application: Category-specific weights are applied to various metrics.
    4. Modifier Application: Bonuses, penalties, and behavior flags are applied.
    5. Final Score: A weighted sum is calculated and clamped to a range of 0-10.

    Note: Reordering categories or using scoring contexts only changes the order in which categories are tried; it cannot force a category to match if its filters (like EXIF data or required tags) are not met.

  12. How Facet evaluates photos

    master

    Facet evaluates every photo across 9 dimensions: aesthetic quality, composition, face quality, eye sharpness, technical sharpness, color, exposure, subject recognition, and dynamic range.

    Photos are categorized into over 30 content categories (e.g., Portrait, Landscape, Macro, Street) and evaluated using category-specific weights. You can use the Beste Auswahl (Best Selection) filter to sort your library based on a combined score.

    To see a detailed breakdown of a photo's score and its EXIF data, hover your mouse over any photo to reveal a tooltip.