Facet uses a category-based scoring system where photos are automatically classified and scored with specialized weights.
- Category Detection - Photo analyzed for content (faces, tags, EXIF data)
- Filter Evaluation - Categories evaluated in priority order until one matches
- Weight Application - Category-specific weights applied to metrics
- Modifier Application - Bonuses, penalties, and behavior flags applied
- Final Score - Weighted sum clamped to 0-10 range
Facet includes 17 built-in categories evaluated in priority order:
| Priority | Category | Detection Method |
|---|---|---|
| 5 | art |
Tags: painting, statue, drawing, cartoon, anime |
| 10 | astro |
Shutter > 10s AND luminance < 0.15 |
| 15 | concert |
Tags: concert |
| 25 | street |
Tags: street AND has face |
| 30 | silhouette |
Has face AND is_silhouette |
| 35 | group_portrait |
Face ratio > 5% AND multiple faces |
| 45 | portrait |
Face ratio > 25%, not silhouette/group/mono |
| 50 | human_others |
Has face AND face ratio < 5% |
| 55 | macro |
Tags: macro, insect, butterfly, flower |
| 60 | aerial |
Tags: aerial |
| 65 | wildlife |
Tags: animal |
| 70 | food |
Tags: food |
| 75 | architecture |
Tags: architecture |
| 80 | long_exposure |
Shutter 1-10 seconds |
| 85 | night |
Luminance < 0.15 |
| 88 | monochrome |
Saturation < 5% |
| 100 | others |
Tags: landscape, mountain, beach, etc. (fallback) |
Each category in scoring_config.json has these components:
{
"name": "portrait",
"priority": 45,
"filters": {
"face_ratio_min": 0.25,
"has_face": true,
"is_silhouette": false,
"is_group_portrait": false,
"is_monochrome": false
},
"weights": {
"aesthetic_percent": 61,
"face_quality_percent": 3,
"composition_percent": 7,
"dynamic_range_percent": 6,
"isolation_percent": 12,
"leading_lines_percent": 4,
"power_point_percent": 6,
"saturation_percent": 1
},
"modifiers": {
"bonus": 0.5,
"_apply_blink_penalty": true
},
"tags": {}
}| Filter | Field | Description |
|---|---|---|
face_ratio_min / face_ratio_max |
face_ratio |
Face area as fraction (0.0-1.0) |
face_count_min / face_count_max |
face_count |
Number of faces |
iso_min / iso_max |
ISO |
Camera ISO |
shutter_speed_min / shutter_speed_max |
shutter_speed |
Exposure time (seconds) |
luminance_min / luminance_max |
mean_luminance |
Brightness (0.0-1.0) |
focal_length_min / focal_length_max |
focal_length |
Focal length (mm) |
f_stop_min / f_stop_max |
f_stop |
Aperture f-number |
| Filter | Description |
|---|---|
has_face |
At least one face detected |
is_monochrome |
Saturation < 5% |
is_silhouette |
Backlit with heavy shadows/highlights |
is_group_portrait |
face_count >= min_faces_for_group (configurable, default: 4) |
| Filter | Description |
|---|---|
required_tags |
List of tags photo must have |
excluded_tags |
List of tags photo must NOT have |
tag_match_mode |
"any" (default) or "all" |
All weights use _percent suffix and must sum to 100 per category.
| Key | Metric | Source | Best For |
|---|---|---|---|
aesthetic_percent |
Visual appeal | TOPIQ or CLIP+MLP | All |
face_quality_percent |
Face clarity | InsightFace | Portraits |
eye_sharpness_percent |
Eye sharpness | InsightFace landmarks | Portraits |
tech_sharpness_percent |
Overall sharpness | Laplacian variance | Landscapes |
composition_percent |
Composition | SAMP-Net or rule-based | All |
exposure_percent |
Exposure balance | Histogram analysis | All |
color_percent |
Color harmony | HSV analysis | Color photos |
contrast_percent |
Tonal contrast | Histogram spread | B&W |
dynamic_range_percent |
Tonal range | Histogram analysis | HDR, landscapes |
isolation_percent |
Subject separation | Face vs background | Portraits, wildlife |
leading_lines_percent |
Leading lines | Edge detection | Architecture |
power_point_percent |
Rule-of-thirds | Subject placement | All |
saturation_percent |
Color saturation | HSV analysis | Vibrant photos |
noise_percent |
Noise level | Noise estimation | Low-light |
face_sharpness_percent |
Face region sharpness | Face analysis | Portraits |
aesthetic_iaa_percent |
Artistic aesthetic merit | TOPIQ IAA (AVA-trained) | Art, creative |
face_quality_iqa_percent |
Face quality (IQA) | TOPIQ NR-Face | Portraits |
liqe_percent |
LIQE quality score | LIQE | Diagnostics |
subject_sharpness_percent |
Subject region sharpness | BiRefNet + Laplacian | Portraits, wildlife |
subject_prominence_percent |
Subject area ratio | BiRefNet | Macro, wildlife |
subject_placement_percent |
Subject rule-of-thirds | BiRefNet | All |
bg_separation_percent |
Background separation | BiRefNet | Portraits, macro |
Adjust scoring behavior per category:
| Modifier | Type | Description |
|---|---|---|
bonus |
float | Added to final score (e.g., 0.5) |
noise_tolerance_multiplier |
float | Scale noise penalty (0.5 = half) |
iso_tolerance_multiplier |
float | Scale ISO penalty |
min_saturation_bonus |
float | Bonus for high saturation |
contrast_bonus |
float | Bonus for high contrast |
_skip_clipping_penalty |
bool | Skip exposure clipping penalty |
_skip_oversaturation_penalty |
bool | Skip oversaturation penalty |
_clipping_multiplier |
float | Scale clipping penalty |
_apply_blink_penalty |
bool | Apply blink detection penalty |
Four scoring dimensions derived from AI subject segmentation (BiRefNet). These measure how well the main subject stands out in the frame:
| Weight Key | Metric | Description |
|---|---|---|
subject_sharpness_percent |
Subject sharpness | Compares focus quality between the subject region and the background. High values mean the subject is sharp while the background is soft. |
subject_prominence_percent |
Subject prominence | Ratio of subject area to total frame area. High for macro and tightly-framed subjects, low for wide scenes. |
subject_placement_percent |
Subject placement | Rule-of-thirds scoring for the subject's center of mass. Rewards subjects placed at power points rather than dead center. |
bg_separation_percent |
Background separation | Edge gradient difference at the subject boundary. Measures bokeh quality — how cleanly the subject separates from the background. |
Use subject_sharpness_percent and bg_separation_percent for portrait and wildlife categories where subject isolation matters. Use subject_prominence_percent for macro photography where the subject fills the frame.
Three additional quality models that complement the primary aesthetic score:
| Weight Key | Model | Description |
|---|---|---|
aesthetic_iaa_percent |
TOPIQ IAA | Trained on the AVA dataset to measure artistic merit — composition creativity, visual impact, emotional resonance. Differs from the primary aesthetic score which focuses on technical quality. Best for art, creative, and editorial categories. |
face_quality_iqa_percent |
TOPIQ NR-Face | Purpose-built face quality assessment. More accurate than generic quality models for evaluating face regions specifically. Best for portrait categories where face clarity is critical. |
liqe_percent |
LIQE | Outputs both a quality score and a distortion diagnosis (e.g., motion blur, overexposure, noise). Useful for understanding why a photo scores low, not just that it scores low. |
These models run automatically as part of the default scoring pipeline and share VRAM with the primary TOPIQ model. Add their weight keys to any category where the specialized assessment is valuable.
Tags trigger tag-based categories and are matched using CLIP similarity:
{
"tags": {
"landscape": ["landscape", "scenic view", "nature scene"],
"mountain": ["mountain", "alpine", "peaks"],
"beach": ["beach", "ocean", "seaside", "coastal"]
}
}Each key is the canonical tag name, and the array contains synonyms for CLIP matching.
The viewer's "Top Picks" filter uses a custom weighted score:
"top_picks_weights": {
"aggregate_percent": 30,
"aesthetic_percent": 28,
"composition_percent": 18,
"face_quality_percent": 24
}Score computation:
- With face (face_ratio ≥ 20%): All four metrics contribute
- Without face:
face_quality_percentredistributed toaestheticandcomposition
Default weights are optimized for TOPIQ (0.93 SRCC), the aesthetic model for all profiles.
| Profile | Aesthetic Model | Embeddings | Tagger | Recommendations |
|---|---|---|---|---|
24gb |
TOPIQ (0.93 SRCC) | SigLIP 2 NaFlex SO400M | Qwen3.5-4B | Best accuracy, default weights |
16gb |
TOPIQ (0.93 SRCC) | SigLIP 2 NaFlex SO400M | Qwen3.5-2B | Default weights |
8gb |
CLIP+MLP (0.76 SRCC) | CLIP ViT-L-14 | CLIP similarity | Default weights work well |
legacy |
CLIP+MLP on CPU | CLIP ViT-L-14 | CLIP similarity | Default weights, slower |
All profiles additionally run supplementary PyIQA models (TOPIQ IAA, TOPIQ NR-Face, LIQE) and optionally BiRefNet_dynamic for subject saliency.
Run --compute-recommendations after switching profiles to analyze score distributions.
- Open
/stats→ Categories tab → Weights sub-tab - Unlock edition mode
- Select a category from the editor dropdown
- Adjust sliders — the live Score Distribution Preview shows estimated impact
- Click Save then Recompute Scores to apply
The viewer runs --recompute-category under the hood, updating only photos in that category.
python facet.py --compute-recommendationsShows:
- Score distributions per category
- Weight correlation analysis
- Suggested adjustments
Edit scoring_config.json category weights. Ensure they sum to 100.
python facet.py --recompute-average # All categories
python facet.py --recompute-category portrait # Single category (faster)Uses stored embeddings - no GPU needed.
python facet.py --compute-recommendationsCompare distributions before/after.
Train weights by comparing photo pairs:
- Set a non-empty
edition_passwordin config:"viewer": { "edition_password": "your-password" } - Start viewer:
python viewer.py - Click "Compare" button
- Side-by-side photos
- Keyboard: A (left wins), B (right wins), T (tie), S (skip)
- Progress bar shows comparisons toward 50 minimum
# Check comparison stats
python facet.py --comparison-stats
# Optimize weights from comparisons
python facet.py --optimize-weights
# Apply to all photos
python facet.py --recompute-average- Open Weight Preview panel during comparison
- Adjust sliders to see real-time score changes
- Click "Suggest Weights" for optimized values
- Manually update config
{
"name": "underwater",
"priority": 62,
"filters": {
"required_tags": ["underwater"],
"tag_match_mode": "any"
},
"weights": {
"aesthetic_percent": 40,
"color_percent": 25,
"composition_percent": 20,
"exposure_percent": 15
},
"modifiers": {
"noise_tolerance_multiplier": 0.3,
"bonus": 0.5
},
"tags": {
"underwater": ["underwater", "scuba", "diving", "ocean"],
"fish": ["fish", "coral", "reef"]
}
}Add to the categories array in scoring_config.json, then run --recompute-average (or --recompute-category underwater for just the new category).
# Edit scoring_config.json:
# Find "concert" category, adjust:
# "noise_tolerance_multiplier": 0.05
# "exposure_percent": 5
python facet.py --recompute-category concertOr use the viewer's weight editor at /stats → Categories → Weights for live preview and one-click recompute.
# Edit: "vram_profile": "8gb"
python facet.py --compute-recommendations # Analyze
# Reduce aesthetic_percent in categories if needed
python facet.py --recompute-average- Add category definition (see above)
- Run
python facet.py --validate-categories - Run
python facet.py --recompute-average