selects 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- selects-0.1.0/.github/workflows/ci.yml +41 -0
- selects-0.1.0/.github/workflows/release.yml +89 -0
- selects-0.1.0/.gitignore +63 -0
- selects-0.1.0/LICENSE +21 -0
- selects-0.1.0/PKG-INFO +255 -0
- selects-0.1.0/README.md +217 -0
- selects-0.1.0/design/app.js +118 -0
- selects-0.1.0/design/clusters.html +277 -0
- selects-0.1.0/design/index.html +249 -0
- selects-0.1.0/design/stories.html +192 -0
- selects-0.1.0/design/styles.css +981 -0
- selects-0.1.0/docs/superpowers/plans/2026-05-23-travelcull-m1-indexer-and-ui-shell.md +3542 -0
- selects-0.1.0/docs/superpowers/specs/2026-05-23-travelcull-design.md +164 -0
- selects-0.1.0/docs/superpowers/specs/2026-05-24-aesthetic-curation-design.md +158 -0
- selects-0.1.0/examples/ladakh/README.md +34 -0
- selects-0.1.0/examples/ladakh/keywords.json +8 -0
- selects-0.1.0/examples/ladakh/landmarks.json +302 -0
- selects-0.1.0/examples/ladakh/tag_prompts.json +23 -0
- selects-0.1.0/frontend/index.html +14 -0
- selects-0.1.0/frontend/package-lock.json +1797 -0
- selects-0.1.0/frontend/package.json +25 -0
- selects-0.1.0/frontend/src/App.tsx +97 -0
- selects-0.1.0/frontend/src/api/client.ts +172 -0
- selects-0.1.0/frontend/src/api/dedup.ts +65 -0
- selects-0.1.0/frontend/src/api/export.ts +102 -0
- selects-0.1.0/frontend/src/api/faceQuality.ts +36 -0
- selects-0.1.0/frontend/src/api/recap.ts +23 -0
- selects-0.1.0/frontend/src/api/search2.ts +88 -0
- selects-0.1.0/frontend/src/api/taste.ts +39 -0
- selects-0.1.0/frontend/src/api/types.ts +159 -0
- selects-0.1.0/frontend/src/api/videos.ts +102 -0
- selects-0.1.0/frontend/src/api/watch.ts +25 -0
- selects-0.1.0/frontend/src/components/BurstThumb.tsx +32 -0
- selects-0.1.0/frontend/src/components/CompareView.css +182 -0
- selects-0.1.0/frontend/src/components/CompareView.tsx +272 -0
- selects-0.1.0/frontend/src/components/Dedup.css +245 -0
- selects-0.1.0/frontend/src/components/ExportPanel.css +201 -0
- selects-0.1.0/frontend/src/components/ExportPanel.tsx +282 -0
- selects-0.1.0/frontend/src/components/EyesBadge.css +50 -0
- selects-0.1.0/frontend/src/components/EyesBadge.tsx +99 -0
- selects-0.1.0/frontend/src/components/FolderPicker.tsx +148 -0
- selects-0.1.0/frontend/src/components/KbdFooter.tsx +48 -0
- selects-0.1.0/frontend/src/components/ModeViewBar.tsx +96 -0
- selects-0.1.0/frontend/src/components/ModelsCard.tsx +142 -0
- selects-0.1.0/frontend/src/components/Rail.tsx +209 -0
- selects-0.1.0/frontend/src/components/RecapButton.css +14 -0
- selects-0.1.0/frontend/src/components/RecapButton.tsx +41 -0
- selects-0.1.0/frontend/src/components/ScoresCard.tsx +162 -0
- selects-0.1.0/frontend/src/components/StackPhoto.tsx +277 -0
- selects-0.1.0/frontend/src/components/StatusRow.tsx +38 -0
- selects-0.1.0/frontend/src/components/TagBrowser.css +132 -0
- selects-0.1.0/frontend/src/components/TagBrowser.tsx +88 -0
- selects-0.1.0/frontend/src/components/TasteCard.css +61 -0
- selects-0.1.0/frontend/src/components/TasteCard.tsx +103 -0
- selects-0.1.0/frontend/src/components/Topbar.tsx +29 -0
- selects-0.1.0/frontend/src/components/Videos.css +314 -0
- selects-0.1.0/frontend/src/components/WatchCard.css +122 -0
- selects-0.1.0/frontend/src/components/WatchCard.tsx +153 -0
- selects-0.1.0/frontend/src/hooks/useCullKeys.ts +112 -0
- selects-0.1.0/frontend/src/hooks/useLikes.ts +60 -0
- selects-0.1.0/frontend/src/main.tsx +13 -0
- selects-0.1.0/frontend/src/styles.css +2307 -0
- selects-0.1.0/frontend/src/views/BestOf.tsx +334 -0
- selects-0.1.0/frontend/src/views/BurstCull.tsx +974 -0
- selects-0.1.0/frontend/src/views/Calibrate.tsx +791 -0
- selects-0.1.0/frontend/src/views/CalibrateDashboard.tsx +414 -0
- selects-0.1.0/frontend/src/views/ClusterDetail.tsx +338 -0
- selects-0.1.0/frontend/src/views/Clusters.tsx +361 -0
- selects-0.1.0/frontend/src/views/Curated.tsx +391 -0
- selects-0.1.0/frontend/src/views/Dedup.tsx +203 -0
- selects-0.1.0/frontend/src/views/Doctor.tsx +407 -0
- selects-0.1.0/frontend/src/views/Libraries.tsx +244 -0
- selects-0.1.0/frontend/src/views/Map.tsx +144 -0
- selects-0.1.0/frontend/src/views/Onboarding.tsx +361 -0
- selects-0.1.0/frontend/src/views/PersonDetail.tsx +208 -0
- selects-0.1.0/frontend/src/views/Persons.tsx +165 -0
- selects-0.1.0/frontend/src/views/Search.tsx +366 -0
- selects-0.1.0/frontend/src/views/Stories.tsx +1220 -0
- selects-0.1.0/frontend/src/views/Videos.tsx +278 -0
- selects-0.1.0/frontend/tsconfig.json +20 -0
- selects-0.1.0/frontend/vite.config.ts +13 -0
- selects-0.1.0/packaging/build.py +120 -0
- selects-0.1.0/packaging/entry.py +9 -0
- selects-0.1.0/packaging/selects.spec +144 -0
- selects-0.1.0/pyproject.toml +69 -0
- selects-0.1.0/scripts/analyze_quality.py +85 -0
- selects-0.1.0/scripts/bench_vlms.py +245 -0
- selects-0.1.0/scripts/check_clusters_api.py +15 -0
- selects-0.1.0/scripts/check_db_tables.py +16 -0
- selects-0.1.0/scripts/check_tags.py +44 -0
- selects-0.1.0/scripts/compare_scores.py +188 -0
- selects-0.1.0/scripts/compute_ap25.py +95 -0
- selects-0.1.0/scripts/compute_categories.py +126 -0
- selects-0.1.0/scripts/compute_nima.py +87 -0
- selects-0.1.0/scripts/inspect_ladakh.py +39 -0
- selects-0.1.0/scripts/iqa_calibration.py +68 -0
- selects-0.1.0/scripts/personal_performance.py +232 -0
- selects-0.1.0/selects/__init__.py +3 -0
- selects-0.1.0/selects/__main__.py +4 -0
- selects-0.1.0/selects/classical/__init__.py +0 -0
- selects-0.1.0/selects/classical/aesthetic_grade.py +101 -0
- selects-0.1.0/selects/classical/auto_reject.py +29 -0
- selects-0.1.0/selects/classical/blur.py +27 -0
- selects-0.1.0/selects/classical/exposure.py +29 -0
- selects-0.1.0/selects/classical/faces.py +65 -0
- selects-0.1.0/selects/classical/straighten.py +111 -0
- selects-0.1.0/selects/cli.py +176 -0
- selects-0.1.0/selects/config.py +74 -0
- selects-0.1.0/selects/db/__init__.py +127 -0
- selects-0.1.0/selects/db/migrations/env.py +67 -0
- selects-0.1.0/selects/db/migrations/script.py.mako +26 -0
- selects-0.1.0/selects/db/migrations/versions/8ff743c44fc7_baseline_schema.py +297 -0
- selects-0.1.0/selects/db/migrations/versions/a1b2c3d4e5f6_add_source_to_photo_tags_pk.py +105 -0
- selects-0.1.0/selects/db/migrations/versions/c3d4e5f6a7b8_add_face_attribute_columns.py +63 -0
- selects-0.1.0/selects/db/migrations/versions/d4e5f6a7b8c9_add_video_analysis_columns.py +75 -0
- selects-0.1.0/selects/db/models.py +393 -0
- selects-0.1.0/selects/decode/__init__.py +20 -0
- selects-0.1.0/selects/decode/heic.py +16 -0
- selects-0.1.0/selects/decode/jpeg.py +41 -0
- selects-0.1.0/selects/decode/raw.py +27 -0
- selects-0.1.0/selects/decode/video.py +61 -0
- selects-0.1.0/selects/dedup.py +273 -0
- selects-0.1.0/selects/export.py +316 -0
- selects-0.1.0/selects/gpu.py +78 -0
- selects-0.1.0/selects/indexer/__init__.py +0 -0
- selects-0.1.0/selects/indexer/exif.py +147 -0
- selects-0.1.0/selects/indexer/orchestrator.py +113 -0
- selects-0.1.0/selects/indexer/preview.py +39 -0
- selects-0.1.0/selects/indexer/walker.py +84 -0
- selects-0.1.0/selects/ml/__init__.py +0 -0
- selects-0.1.0/selects/ml/caption.py +168 -0
- selects-0.1.0/selects/ml/curation.py +321 -0
- selects-0.1.0/selects/ml/deblur_nafnet.py +275 -0
- selects-0.1.0/selects/ml/embed.py +171 -0
- selects-0.1.0/selects/ml/face_attributes.py +396 -0
- selects-0.1.0/selects/ml/faces.py +126 -0
- selects-0.1.0/selects/ml/locations.py +460 -0
- selects-0.1.0/selects/ml/lowlight.py +173 -0
- selects-0.1.0/selects/ml/model_assets.py +326 -0
- selects-0.1.0/selects/ml/moments.py +214 -0
- selects-0.1.0/selects/ml/nl_story.py +261 -0
- selects-0.1.0/selects/ml/persons.py +149 -0
- selects-0.1.0/selects/ml/ram_tags.py +260 -0
- selects-0.1.0/selects/ml/retouch_csrnet.py +173 -0
- selects-0.1.0/selects/ml/search.py +68 -0
- selects-0.1.0/selects/ml/smart_clusters.py +539 -0
- selects-0.1.0/selects/ml/stories.py +594 -0
- selects-0.1.0/selects/ml/tags.py +150 -0
- selects-0.1.0/selects/ml/taste.py +390 -0
- selects-0.1.0/selects/ml/thematic_clusters.py +270 -0
- selects-0.1.0/selects/ml/trip_data.py +195 -0
- selects-0.1.0/selects/ml/trip_type.py +84 -0
- selects-0.1.0/selects/pipeline.py +128 -0
- selects-0.1.0/selects/recap.py +588 -0
- selects-0.1.0/selects/server/__init__.py +0 -0
- selects-0.1.0/selects/server/app.py +155 -0
- selects-0.1.0/selects/server/dedup_routes.py +91 -0
- selects-0.1.0/selects/server/export_routes.py +252 -0
- selects-0.1.0/selects/server/faces2_routes.py +128 -0
- selects-0.1.0/selects/server/fs_routes.py +49 -0
- selects-0.1.0/selects/server/libraries.py +87 -0
- selects-0.1.0/selects/server/library_manager.py +295 -0
- selects-0.1.0/selects/server/models_routes.py +62 -0
- selects-0.1.0/selects/server/pipeline_runner.py +41 -0
- selects-0.1.0/selects/server/recap_routes.py +52 -0
- selects-0.1.0/selects/server/routes.py +2392 -0
- selects-0.1.0/selects/server/search2_routes.py +212 -0
- selects-0.1.0/selects/server/static/assets/index-8kseOkrj.js +89 -0
- selects-0.1.0/selects/server/static/assets/index-DBCjYqrq.css +1 -0
- selects-0.1.0/selects/server/static/index.html +15 -0
- selects-0.1.0/selects/server/taste_routes.py +44 -0
- selects-0.1.0/selects/server/video_routes.py +199 -0
- selects-0.1.0/selects/server/watch_routes.py +60 -0
- selects-0.1.0/selects/server/ws.py +48 -0
- selects-0.1.0/selects/video.py +392 -0
- selects-0.1.0/selects/watcher.py +314 -0
- selects-0.1.0/tests/__init__.py +0 -0
- selects-0.1.0/tests/conftest.py +52 -0
- selects-0.1.0/tests/fixtures/small.heic +0 -0
- selects-0.1.0/tests/fixtures/small.jpg +0 -0
- selects-0.1.0/tests/fixtures/small.mp4 +0 -0
- selects-0.1.0/tests/test_auto_reject.py +22 -0
- selects-0.1.0/tests/test_classical_blur.py +16 -0
- selects-0.1.0/tests/test_classical_exposure.py +24 -0
- selects-0.1.0/tests/test_classical_faces.py +15 -0
- selects-0.1.0/tests/test_cli.py +33 -0
- selects-0.1.0/tests/test_config.py +57 -0
- selects-0.1.0/tests/test_db.py +100 -0
- selects-0.1.0/tests/test_decode_heic.py +11 -0
- selects-0.1.0/tests/test_decode_jpeg.py +16 -0
- selects-0.1.0/tests/test_decode_raw.py +17 -0
- selects-0.1.0/tests/test_decode_video.py +18 -0
- selects-0.1.0/tests/test_dedup.py +176 -0
- selects-0.1.0/tests/test_exif.py +43 -0
- selects-0.1.0/tests/test_export.py +259 -0
- selects-0.1.0/tests/test_face_attributes.py +403 -0
- selects-0.1.0/tests/test_gpu.py +47 -0
- selects-0.1.0/tests/test_indexer_orchestrator.py +59 -0
- selects-0.1.0/tests/test_migrations.py +200 -0
- selects-0.1.0/tests/test_ml_embed.py +163 -0
- selects-0.1.0/tests/test_ml_moments.py +264 -0
- selects-0.1.0/tests/test_ml_stories.py +313 -0
- selects-0.1.0/tests/test_ml_tags.py +126 -0
- selects-0.1.0/tests/test_model_assets.py +273 -0
- selects-0.1.0/tests/test_pipeline_stage1.py +42 -0
- selects-0.1.0/tests/test_preview.py +37 -0
- selects-0.1.0/tests/test_recap.py +126 -0
- selects-0.1.0/tests/test_search2_routes.py +145 -0
- selects-0.1.0/tests/test_server_libraries.py +122 -0
- selects-0.1.0/tests/test_server_routes.py +64 -0
- selects-0.1.0/tests/test_server_ws.py +20 -0
- selects-0.1.0/tests/test_taste.py +202 -0
- selects-0.1.0/tests/test_trip_data.py +142 -0
- selects-0.1.0/tests/test_video.py +286 -0
- selects-0.1.0/tests/test_walker.py +127 -0
- selects-0.1.0/tests/test_watcher.py +117 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
strategy:
|
|
11
|
+
fail-fast: false
|
|
12
|
+
matrix:
|
|
13
|
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
14
|
+
runs-on: ${{ matrix.os }}
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.11"
|
|
20
|
+
cache: pip
|
|
21
|
+
- name: Install package + test deps (CPU torch)
|
|
22
|
+
run: |
|
|
23
|
+
pip install torch --index-url https://download.pytorch.org/whl/cpu
|
|
24
|
+
pip install -e ".[dev]" transformers huggingface_hub
|
|
25
|
+
- name: Run tests
|
|
26
|
+
run: pytest -q
|
|
27
|
+
|
|
28
|
+
frontend:
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/checkout@v4
|
|
32
|
+
- uses: actions/setup-node@v4
|
|
33
|
+
with:
|
|
34
|
+
node-version: 20
|
|
35
|
+
cache: npm
|
|
36
|
+
cache-dependency-path: frontend/package-lock.json
|
|
37
|
+
- name: Build frontend (strict TS)
|
|
38
|
+
working-directory: frontend
|
|
39
|
+
run: |
|
|
40
|
+
npm ci
|
|
41
|
+
npm run build
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
name: Release builds
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
build:
|
|
9
|
+
strategy:
|
|
10
|
+
fail-fast: false
|
|
11
|
+
matrix:
|
|
12
|
+
include:
|
|
13
|
+
- os: windows-latest
|
|
14
|
+
artifact: selects-windows-x64
|
|
15
|
+
- os: macos-latest
|
|
16
|
+
artifact: selects-macos-arm64
|
|
17
|
+
- os: ubuntu-latest
|
|
18
|
+
artifact: selects-linux-x64
|
|
19
|
+
runs-on: ${{ matrix.os }}
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
- uses: actions/setup-python@v5
|
|
23
|
+
with:
|
|
24
|
+
python-version: "3.11"
|
|
25
|
+
cache: pip
|
|
26
|
+
- uses: actions/setup-node@v4
|
|
27
|
+
with:
|
|
28
|
+
node-version: 20
|
|
29
|
+
- name: Install package (CPU torch) + PyInstaller
|
|
30
|
+
run: |
|
|
31
|
+
pip install torch --index-url https://download.pytorch.org/whl/cpu
|
|
32
|
+
pip install -e . pyinstaller
|
|
33
|
+
- name: Build bundle
|
|
34
|
+
run: python packaging/build.py
|
|
35
|
+
- name: Zip bundle
|
|
36
|
+
shell: bash
|
|
37
|
+
run: |
|
|
38
|
+
cd dist
|
|
39
|
+
if [ "$RUNNER_OS" = "Windows" ]; then
|
|
40
|
+
7z a ../${{ matrix.artifact }}.zip selects
|
|
41
|
+
else
|
|
42
|
+
zip -qr ../${{ matrix.artifact }}.zip selects
|
|
43
|
+
fi
|
|
44
|
+
- uses: actions/upload-artifact@v4
|
|
45
|
+
with:
|
|
46
|
+
name: ${{ matrix.artifact }}
|
|
47
|
+
path: ${{ matrix.artifact }}.zip
|
|
48
|
+
|
|
49
|
+
pypi-publish:
|
|
50
|
+
runs-on: ubuntu-latest
|
|
51
|
+
environment: pypi
|
|
52
|
+
permissions:
|
|
53
|
+
id-token: write # OIDC token for PyPI Trusted Publishing — no API token needed
|
|
54
|
+
steps:
|
|
55
|
+
- uses: actions/checkout@v4
|
|
56
|
+
- uses: actions/setup-python@v5
|
|
57
|
+
with:
|
|
58
|
+
python-version: "3.11"
|
|
59
|
+
- uses: actions/setup-node@v4
|
|
60
|
+
with:
|
|
61
|
+
node-version: 20
|
|
62
|
+
- name: Build frontend into packaged static dir
|
|
63
|
+
run: |
|
|
64
|
+
npm --prefix frontend ci
|
|
65
|
+
npm --prefix frontend run build
|
|
66
|
+
rm -rf selects/server/static
|
|
67
|
+
cp -r frontend/dist selects/server/static
|
|
68
|
+
- name: Build sdist + wheel
|
|
69
|
+
run: |
|
|
70
|
+
pip install build
|
|
71
|
+
python -m build
|
|
72
|
+
- name: Publish to PyPI
|
|
73
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
74
|
+
|
|
75
|
+
release:
|
|
76
|
+
needs: build
|
|
77
|
+
runs-on: ubuntu-latest
|
|
78
|
+
permissions:
|
|
79
|
+
contents: write
|
|
80
|
+
steps:
|
|
81
|
+
- uses: actions/download-artifact@v4
|
|
82
|
+
with:
|
|
83
|
+
merge-multiple: true
|
|
84
|
+
- name: Create GitHub release with artifacts
|
|
85
|
+
uses: softprops/action-gh-release@v2
|
|
86
|
+
with:
|
|
87
|
+
files: "*.zip"
|
|
88
|
+
generate_release_notes: true
|
|
89
|
+
draft: true
|
selects-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
*.egg
|
|
7
|
+
*.egg-info/
|
|
8
|
+
dist/
|
|
9
|
+
build/
|
|
10
|
+
.eggs/
|
|
11
|
+
*.whl
|
|
12
|
+
|
|
13
|
+
# Virtual environments
|
|
14
|
+
.venv/
|
|
15
|
+
venv/
|
|
16
|
+
env/
|
|
17
|
+
|
|
18
|
+
# Testing
|
|
19
|
+
.pytest_cache/
|
|
20
|
+
.coverage
|
|
21
|
+
htmlcov/
|
|
22
|
+
.tox/
|
|
23
|
+
|
|
24
|
+
# IDE
|
|
25
|
+
.vscode/
|
|
26
|
+
.idea/
|
|
27
|
+
*.swp
|
|
28
|
+
*.swo
|
|
29
|
+
*~
|
|
30
|
+
|
|
31
|
+
# Frontend
|
|
32
|
+
frontend/node_modules/
|
|
33
|
+
frontend/dist/
|
|
34
|
+
|
|
35
|
+
# Packaging
|
|
36
|
+
selects/server/static/
|
|
37
|
+
packaging/dist/
|
|
38
|
+
packaging/build/
|
|
39
|
+
|
|
40
|
+
# Tooling state
|
|
41
|
+
.claude/
|
|
42
|
+
.playwright-mcp/
|
|
43
|
+
screenshots/
|
|
44
|
+
*.png
|
|
45
|
+
|
|
46
|
+
# Project state
|
|
47
|
+
.selects/
|
|
48
|
+
|
|
49
|
+
# OS
|
|
50
|
+
.DS_Store
|
|
51
|
+
Thumbs.db
|
|
52
|
+
desktop.ini
|
|
53
|
+
|
|
54
|
+
# Misc
|
|
55
|
+
*.log
|
|
56
|
+
*.tmp
|
|
57
|
+
|
|
58
|
+
# Ad-hoc report artifacts at repo root (not frontend/docs html)
|
|
59
|
+
/iqa_calibration.html
|
|
60
|
+
/personal_performance.html
|
|
61
|
+
/score_comparison.html
|
|
62
|
+
/test_enhanced.jpg
|
|
63
|
+
/*.jpg
|
selects-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 selects contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
selects-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: selects
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Local AI-assisted travel photo and video culling
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Requires-Dist: alembic>=1.13
|
|
9
|
+
Requires-Dist: click>=8.1
|
|
10
|
+
Requires-Dist: fastapi>=0.110
|
|
11
|
+
Requires-Dist: numpy>=1.26
|
|
12
|
+
Requires-Dist: opencv-python>=4.9
|
|
13
|
+
Requires-Dist: pillow-heif>=0.16
|
|
14
|
+
Requires-Dist: pillow>=10.2
|
|
15
|
+
Requires-Dist: pydantic-settings>=2.2
|
|
16
|
+
Requires-Dist: pydantic>=2.6
|
|
17
|
+
Requires-Dist: pyexiv2>=2.11
|
|
18
|
+
Requires-Dist: python-multipart>=0.0.9
|
|
19
|
+
Requires-Dist: rawpy>=0.21
|
|
20
|
+
Requires-Dist: sqlalchemy>=2.0
|
|
21
|
+
Requires-Dist: uvicorn[standard]>=0.27
|
|
22
|
+
Requires-Dist: websockets>=12.0
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: httpx>=0.26; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: ruff>=0.3; extra == 'dev'
|
|
28
|
+
Provides-Extra: ml
|
|
29
|
+
Requires-Dist: hdbscan>=0.8.33; extra == 'ml'
|
|
30
|
+
Requires-Dist: insightface>=0.7; extra == 'ml'
|
|
31
|
+
Requires-Dist: onnxruntime>=1.17; extra == 'ml'
|
|
32
|
+
Requires-Dist: requests>=2.31; extra == 'ml'
|
|
33
|
+
Requires-Dist: scikit-learn>=1.4; extra == 'ml'
|
|
34
|
+
Requires-Dist: torch>=2.2; extra == 'ml'
|
|
35
|
+
Requires-Dist: transformers>=4.40; extra == 'ml'
|
|
36
|
+
Requires-Dist: umap-learn>=0.5.5; extra == 'ml'
|
|
37
|
+
Description-Content-Type: text/markdown
|
|
38
|
+
|
|
39
|
+
# Selects
|
|
40
|
+
|
|
41
|
+
Selects is a local-first, AI-assisted photo culling tool for travel photography. Point it at a
|
|
42
|
+
folder of photos (and videos), and it indexes, scores, clusters, and groups them into stories so
|
|
43
|
+
you can quickly find your keepers — without uploading anything anywhere. All ML inference (image
|
|
44
|
+
embeddings, tagging, face clustering, aesthetic scoring, optional enhancement) runs on your own
|
|
45
|
+
machine; the only network calls are optional reverse-geocoding lookups for place names.
|
|
46
|
+
|
|
47
|
+
## Features
|
|
48
|
+
|
|
49
|
+
- Fast local indexing of JPEG/HEIC/RAW photos and video files, with thumbnail/preview generation
|
|
50
|
+
- Classical signal scoring: blur, exposure, clipped highlights, face detection, auto-reject
|
|
51
|
+
- SigLIP image embeddings for semantic search and similarity
|
|
52
|
+
- Zero-shot and RAM++-based photo tagging
|
|
53
|
+
- ArcFace face embeddings + clustering into "Person" identities, with name labeling
|
|
54
|
+
- GPS/time-based location clustering and "story" building (day-by-day, place-by-place)
|
|
55
|
+
- "Moment" grouping to collapse near-duplicate bursts into a single best pick
|
|
56
|
+
- AP25 + NIMA-based aesthetic curation (per-scope and library-wide percentile gating)
|
|
57
|
+
- Optional local enhancement models: NAFNet (deblur), Zero-DCE++ (low-light), CSRNet (retouch)
|
|
58
|
+
- React/Vite web UI for reviewing clusters, people, stories, and doing burst culling
|
|
59
|
+
- CLI for headless indexing and running individual pipeline stages
|
|
60
|
+
|
|
61
|
+
## Install
|
|
62
|
+
|
|
63
|
+
**Desktop app (recommended).** Download the bundle for your OS from the
|
|
64
|
+
[latest release](https://github.com/bihanikeshav/selects/releases) and run it — no Python
|
|
65
|
+
required. The app downloads its AI models on first launch, with progress.
|
|
66
|
+
|
|
67
|
+
**Via pip** (needs Python 3.11+):
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pip install selects # app + web GUI + CLI
|
|
71
|
+
pip install "selects[ml]" # add the on-device AI (torch, insightface, …)
|
|
72
|
+
selects serve # open the web UI
|
|
73
|
+
selects index /path/to/trip # or run headless from the CLI
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
RAM++ tagging installs separately (no PyPI release):
|
|
77
|
+
`pip install git+https://github.com/xinyu1205/recognize-anything.git`
|
|
78
|
+
|
|
79
|
+
> **GPU acceleration is coming soon.** These builds run on **CPU** — they work on any machine,
|
|
80
|
+
> just slower. GPU-accelerated builds (Apple Silicon and NVIDIA first, AMD to follow) are next up.
|
|
81
|
+
> The pipeline already detects your hardware and will use it once the accelerated packages ship.
|
|
82
|
+
|
|
83
|
+
## Quickstart (from source)
|
|
84
|
+
|
|
85
|
+
Requires Python 3.11+ and Node 18+.
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# Backend
|
|
89
|
+
pip install -e ".[ml]" # add the ML stack (torch, transformers, insightface, etc.)
|
|
90
|
+
selects serve /path/to/photos
|
|
91
|
+
|
|
92
|
+
# Frontend (separate terminal)
|
|
93
|
+
cd frontend
|
|
94
|
+
npm install
|
|
95
|
+
npm run dev
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`selects serve` starts the FastAPI backend, opens the web UI in your browser (pass
|
|
99
|
+
`--no-browser` to skip), and (unless `--no-background` is passed) kicks off indexing in the
|
|
100
|
+
background. The frontend dev server (`npm run dev`) proxies to the backend for a hot-reloading UI
|
|
101
|
+
during development.
|
|
102
|
+
|
|
103
|
+
For a single-process run, build the frontend once (`cd frontend && npm run build`) and the backend
|
|
104
|
+
will serve the compiled UI itself at the same origin — no separate `npm run dev` needed. You can
|
|
105
|
+
also run `selects serve` with **no folder argument**: it opens the active library from your
|
|
106
|
+
registry, or, if none exists, starts on the onboarding page so you can add your first library from
|
|
107
|
+
the browser.
|
|
108
|
+
|
|
109
|
+
If you only need the classical (non-ML) stages — indexing, previews, blur/exposure/face
|
|
110
|
+
auto-reject — you can skip the `ml` extra: `pip install -e .`.
|
|
111
|
+
|
|
112
|
+
You can also drive the pipeline directly from the CLI:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
selects index /path/to/photos # run all default stages
|
|
116
|
+
selects index /path/to/photos --pass embed # run a single named stage
|
|
117
|
+
selects doctor # report CUDA/GPU capabilities
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## How the pipeline works
|
|
121
|
+
|
|
122
|
+
Each stage reads/writes to a per-folder SQLite database at `<folder>/.selects/index.db` and can
|
|
123
|
+
be re-run independently via `selects index <folder> --pass <stage>`:
|
|
124
|
+
|
|
125
|
+
1. **index** — walk the folder, hash files, decode previews/thumbnails, read EXIF/GPS
|
|
126
|
+
2. **classical** — blur, exposure, clipped-highlight, and face-count scoring; auto-reject gate
|
|
127
|
+
3. **embed** — SigLIP-SO400M image embeddings + CLIP-IQA aesthetic score
|
|
128
|
+
4. **tag** — zero-shot tagging via SigLIP text-prompt similarity
|
|
129
|
+
5. **ram_tag** — RAM++ open-vocabulary tagging (more descriptive labels than zero-shot)
|
|
130
|
+
6. **smart_tag** — HDBSCAN clustering over embeddings + VLM-generated cluster names
|
|
131
|
+
7. **thematic / date** — rule-driven location and day clustering from GPS/time
|
|
132
|
+
8. **face_embed** — ArcFace face embeddings for detected faces
|
|
133
|
+
9. **moment** — collapse near-duplicate/burst photos into a single representative pick
|
|
134
|
+
10. **story** — build day/place "stories" combining moments, tags, and locations
|
|
135
|
+
|
|
136
|
+
Aesthetic curation (surfacing the "best of" a trip) combines AP25 and NIMA scores with configurable
|
|
137
|
+
per-scope and library-wide percentile thresholds — see `ap_weight`, `nima_weight`,
|
|
138
|
+
`aesthetic_per_scope_pct`, and `aesthetic_library_pct` below.
|
|
139
|
+
|
|
140
|
+
## Configuration
|
|
141
|
+
|
|
142
|
+
Configuration is per-folder, via `pydantic-settings`. Every field can be overridden with an
|
|
143
|
+
`SELECTS_`-prefixed environment variable (or a `.env` file in the working directory), e.g.
|
|
144
|
+
`SELECTS_WEB_PORT=9000`. Fields (see `selects/config.py`):
|
|
145
|
+
|
|
146
|
+
| Field | Default | Notes |
|
|
147
|
+
|---|---|---|
|
|
148
|
+
| `web_port` | `8765` | Port for the web UI/API |
|
|
149
|
+
| `web_host` | `127.0.0.1` | Bind host |
|
|
150
|
+
| `burst_window_seconds` | `3` | Time window used to group burst shots |
|
|
151
|
+
| `burst_similarity_threshold` | `0.92` | Similarity cutoff for burst grouping |
|
|
152
|
+
| `ap_weight` | `0.6` | Weight of AP25 in the combined aesthetic score |
|
|
153
|
+
| `nima_weight` | `0.4` | Weight of NIMA in the combined aesthetic score |
|
|
154
|
+
| `aesthetic_per_scope_pct` | `75.0` | Photo must be in the top `(100 - pct)`% within its scope |
|
|
155
|
+
| `aesthetic_library_pct` | `50.0` | Photo must also be in the top `(100 - pct)`% library-wide |
|
|
156
|
+
| `speed_mode` | `full` | `fast` skips some ML stages for a quick preview pass |
|
|
157
|
+
|
|
158
|
+
Derived, non-configurable paths under `<folder>/.selects/`: `index.db`, `thumbs/`, `previews/`.
|
|
159
|
+
|
|
160
|
+
### Per-trip customization
|
|
161
|
+
|
|
162
|
+
The location and tagging stages ship with travel-generic defaults, but you can tune them per
|
|
163
|
+
library by dropping optional JSON files into `<folder>/.selects/`:
|
|
164
|
+
|
|
165
|
+
| File | Purpose |
|
|
166
|
+
|---|---|
|
|
167
|
+
| `landmarks.json` | Named GPS landmarks (`{"name", "lat", "lon", "radius_m"}`) used as a fast-path override for reverse geocoding. Without it, geocoding relies entirely on Nominatim. |
|
|
168
|
+
| `keywords.json` | Theme buckets (`{label: [keyword, ...]}`) for pattern/thematic stories. |
|
|
169
|
+
| `tag_prompts.json` | Zero-shot SigLIP tag taxonomy (`{tag: [prompt, ...]}`). |
|
|
170
|
+
|
|
171
|
+
Each file is optional; a missing or malformed file falls back to the built-in defaults. See
|
|
172
|
+
[`examples/ladakh/`](examples/ladakh/) for a complete worked example (the original Ladakh dataset)
|
|
173
|
+
and copy any file into your own `.selects/` to customize.
|
|
174
|
+
|
|
175
|
+
## Hardware notes
|
|
176
|
+
|
|
177
|
+
The ML stages (embedding, tagging, face recognition, clustering, enhancement) are much faster with
|
|
178
|
+
a CUDA GPU — `selects doctor` reports what's available (CUDA, NVDEC via torchcodec,
|
|
179
|
+
nvImageCodec, `cv2.cuda`). Everything falls back to CPU, but expect the ML stages to be
|
|
180
|
+
significantly slower, especially the enhancement models (NAFNet/Zero-DCE++/CSRNet) and the VLM
|
|
181
|
+
cluster-naming pass. Classical scoring (blur/exposure/face-detect) and indexing run fine on CPU.
|
|
182
|
+
|
|
183
|
+
## Project layout
|
|
184
|
+
|
|
185
|
+
- `selects/` — Python package: CLI, config, pipeline orchestration, DB models
|
|
186
|
+
- `classical/` — non-ML signal scoring (blur, exposure, face detect, auto-reject, straighten)
|
|
187
|
+
- `decode/` — image/video/RAW decoding
|
|
188
|
+
- `indexer/` — folder walking, EXIF reading, preview generation, orchestration
|
|
189
|
+
- `ml/` — embedding, tagging, faces, clustering, stories, moments, enhancement models
|
|
190
|
+
- `server/` — FastAPI app, routes, websocket progress bus
|
|
191
|
+
- `frontend/` — React + Vite + TypeScript web UI
|
|
192
|
+
- `tests/` — pytest suite
|
|
193
|
+
- `scripts/` — standalone analysis/benchmarking scripts (not part of the package)
|
|
194
|
+
- `docs/` — design notes and specs
|
|
195
|
+
- `design/` — visual design references
|
|
196
|
+
|
|
197
|
+
## Development
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
pip install -e ".[dev]"
|
|
201
|
+
pytest
|
|
202
|
+
ruff check .
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Database schema is managed with Alembic. Migrations ship inside the package at
|
|
206
|
+
`selects/db/migrations/` (there is no `alembic.ini`), and `init_db()` brings
|
|
207
|
+
each library's SQLite DB up to head automatically on open — fresh DBs are created
|
|
208
|
+
and stamped, pre-existing DBs are stamped at the baseline and upgraded. After
|
|
209
|
+
changing `selects/db/models.py`, create a new revision with autogenerate by
|
|
210
|
+
building an in-code `Config` pointing `script_location` at
|
|
211
|
+
`selects/db/migrations` and `sqlalchemy.url` at a throwaway SQLite file, then
|
|
212
|
+
calling `alembic.command.revision(cfg, autogenerate=True, message="...")`. Always
|
|
213
|
+
review the generated script — SQLite ALTERs must go through `render_as_batch`
|
|
214
|
+
(already enabled in `env.py`).
|
|
215
|
+
|
|
216
|
+
## Packaging a desktop build
|
|
217
|
+
|
|
218
|
+
Produce a self-contained bundle (no Python install required on the target machine) with
|
|
219
|
+
PyInstaller:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
pip install "pyinstaller>=6.6"
|
|
223
|
+
python packaging/build.py
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
The script builds the frontend (`npm run build`), copies `frontend/dist` into
|
|
227
|
+
`selects/server/static/` so the packaged app serves the UI same-origin, then runs PyInstaller in
|
|
228
|
+
**onedir** mode using `packaging/selects.spec`. The result lands in `dist/selects/` — launch
|
|
229
|
+
`dist/selects/selects.exe` (or `./selects` on macOS/Linux).
|
|
230
|
+
|
|
231
|
+
By default the ML stack (torch, transformers, insightface, …) is **excluded** to keep the bundle
|
|
232
|
+
small and the build fast — the base app still indexes, previews, and runs the classical auto-reject
|
|
233
|
+
stages. To bundle the ML deps, use `python packaging/build.py --ml` (or set
|
|
234
|
+
`SELECTS_BUNDLE_ML=1`); any ML package that isn't installed is skipped, so the build always
|
|
235
|
+
degrades gracefully.
|
|
236
|
+
|
|
237
|
+
CPU-torch tip: the default CUDA torch wheels are multiple GB. For a much smaller ML bundle, install
|
|
238
|
+
the CPU build first:
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
pip install torch --index-url https://download.pytorch.org/whl/cpu
|
|
242
|
+
python packaging/build.py --ml
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Known limitations
|
|
246
|
+
|
|
247
|
+
- Aesthetic and burst-detection thresholds were tuned against a single trip's photo set and may
|
|
248
|
+
need adjustment for very different shooting styles or camera gear
|
|
249
|
+
- List endpoints use simple offset/limit pagination, not cursor-based
|
|
250
|
+
- RAM++ tagging depends on a model with no PyPI release (installed via git URL); expect a slower,
|
|
251
|
+
less reproducible install than the rest of the `ml` extra
|
|
252
|
+
|
|
253
|
+
## License
|
|
254
|
+
|
|
255
|
+
MIT — see [LICENSE](LICENSE).
|
selects-0.1.0/README.md
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# Selects
|
|
2
|
+
|
|
3
|
+
Selects is a local-first, AI-assisted photo culling tool for travel photography. Point it at a
|
|
4
|
+
folder of photos (and videos), and it indexes, scores, clusters, and groups them into stories so
|
|
5
|
+
you can quickly find your keepers — without uploading anything anywhere. All ML inference (image
|
|
6
|
+
embeddings, tagging, face clustering, aesthetic scoring, optional enhancement) runs on your own
|
|
7
|
+
machine; the only network calls are optional reverse-geocoding lookups for place names.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- Fast local indexing of JPEG/HEIC/RAW photos and video files, with thumbnail/preview generation
|
|
12
|
+
- Classical signal scoring: blur, exposure, clipped highlights, face detection, auto-reject
|
|
13
|
+
- SigLIP image embeddings for semantic search and similarity
|
|
14
|
+
- Zero-shot and RAM++-based photo tagging
|
|
15
|
+
- ArcFace face embeddings + clustering into "Person" identities, with name labeling
|
|
16
|
+
- GPS/time-based location clustering and "story" building (day-by-day, place-by-place)
|
|
17
|
+
- "Moment" grouping to collapse near-duplicate bursts into a single best pick
|
|
18
|
+
- AP25 + NIMA-based aesthetic curation (per-scope and library-wide percentile gating)
|
|
19
|
+
- Optional local enhancement models: NAFNet (deblur), Zero-DCE++ (low-light), CSRNet (retouch)
|
|
20
|
+
- React/Vite web UI for reviewing clusters, people, stories, and doing burst culling
|
|
21
|
+
- CLI for headless indexing and running individual pipeline stages
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
**Desktop app (recommended).** Download the bundle for your OS from the
|
|
26
|
+
[latest release](https://github.com/bihanikeshav/selects/releases) and run it — no Python
|
|
27
|
+
required. The app downloads its AI models on first launch, with progress.
|
|
28
|
+
|
|
29
|
+
**Via pip** (needs Python 3.11+):
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install selects # app + web GUI + CLI
|
|
33
|
+
pip install "selects[ml]" # add the on-device AI (torch, insightface, …)
|
|
34
|
+
selects serve # open the web UI
|
|
35
|
+
selects index /path/to/trip # or run headless from the CLI
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
RAM++ tagging installs separately (no PyPI release):
|
|
39
|
+
`pip install git+https://github.com/xinyu1205/recognize-anything.git`
|
|
40
|
+
|
|
41
|
+
> **GPU acceleration is coming soon.** These builds run on **CPU** — they work on any machine,
|
|
42
|
+
> just slower. GPU-accelerated builds (Apple Silicon and NVIDIA first, AMD to follow) are next up.
|
|
43
|
+
> The pipeline already detects your hardware and will use it once the accelerated packages ship.
|
|
44
|
+
|
|
45
|
+
## Quickstart (from source)
|
|
46
|
+
|
|
47
|
+
Requires Python 3.11+ and Node 18+.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# Backend
|
|
51
|
+
pip install -e ".[ml]" # add the ML stack (torch, transformers, insightface, etc.)
|
|
52
|
+
selects serve /path/to/photos
|
|
53
|
+
|
|
54
|
+
# Frontend (separate terminal)
|
|
55
|
+
cd frontend
|
|
56
|
+
npm install
|
|
57
|
+
npm run dev
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`selects serve` starts the FastAPI backend, opens the web UI in your browser (pass
|
|
61
|
+
`--no-browser` to skip), and (unless `--no-background` is passed) kicks off indexing in the
|
|
62
|
+
background. The frontend dev server (`npm run dev`) proxies to the backend for a hot-reloading UI
|
|
63
|
+
during development.
|
|
64
|
+
|
|
65
|
+
For a single-process run, build the frontend once (`cd frontend && npm run build`) and the backend
|
|
66
|
+
will serve the compiled UI itself at the same origin — no separate `npm run dev` needed. You can
|
|
67
|
+
also run `selects serve` with **no folder argument**: it opens the active library from your
|
|
68
|
+
registry, or, if none exists, starts on the onboarding page so you can add your first library from
|
|
69
|
+
the browser.
|
|
70
|
+
|
|
71
|
+
If you only need the classical (non-ML) stages — indexing, previews, blur/exposure/face
|
|
72
|
+
auto-reject — you can skip the `ml` extra: `pip install -e .`.
|
|
73
|
+
|
|
74
|
+
You can also drive the pipeline directly from the CLI:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
selects index /path/to/photos # run all default stages
|
|
78
|
+
selects index /path/to/photos --pass embed # run a single named stage
|
|
79
|
+
selects doctor # report CUDA/GPU capabilities
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## How the pipeline works
|
|
83
|
+
|
|
84
|
+
Each stage reads/writes to a per-folder SQLite database at `<folder>/.selects/index.db` and can
|
|
85
|
+
be re-run independently via `selects index <folder> --pass <stage>`:
|
|
86
|
+
|
|
87
|
+
1. **index** — walk the folder, hash files, decode previews/thumbnails, read EXIF/GPS
|
|
88
|
+
2. **classical** — blur, exposure, clipped-highlight, and face-count scoring; auto-reject gate
|
|
89
|
+
3. **embed** — SigLIP-SO400M image embeddings + CLIP-IQA aesthetic score
|
|
90
|
+
4. **tag** — zero-shot tagging via SigLIP text-prompt similarity
|
|
91
|
+
5. **ram_tag** — RAM++ open-vocabulary tagging (more descriptive labels than zero-shot)
|
|
92
|
+
6. **smart_tag** — HDBSCAN clustering over embeddings + VLM-generated cluster names
|
|
93
|
+
7. **thematic / date** — rule-driven location and day clustering from GPS/time
|
|
94
|
+
8. **face_embed** — ArcFace face embeddings for detected faces
|
|
95
|
+
9. **moment** — collapse near-duplicate/burst photos into a single representative pick
|
|
96
|
+
10. **story** — build day/place "stories" combining moments, tags, and locations
|
|
97
|
+
|
|
98
|
+
Aesthetic curation (surfacing the "best of" a trip) combines AP25 and NIMA scores with configurable
|
|
99
|
+
per-scope and library-wide percentile thresholds — see `ap_weight`, `nima_weight`,
|
|
100
|
+
`aesthetic_per_scope_pct`, and `aesthetic_library_pct` below.
|
|
101
|
+
|
|
102
|
+
## Configuration
|
|
103
|
+
|
|
104
|
+
Configuration is per-folder, via `pydantic-settings`. Every field can be overridden with an
|
|
105
|
+
`SELECTS_`-prefixed environment variable (or a `.env` file in the working directory), e.g.
|
|
106
|
+
`SELECTS_WEB_PORT=9000`. Fields (see `selects/config.py`):
|
|
107
|
+
|
|
108
|
+
| Field | Default | Notes |
|
|
109
|
+
|---|---|---|
|
|
110
|
+
| `web_port` | `8765` | Port for the web UI/API |
|
|
111
|
+
| `web_host` | `127.0.0.1` | Bind host |
|
|
112
|
+
| `burst_window_seconds` | `3` | Time window used to group burst shots |
|
|
113
|
+
| `burst_similarity_threshold` | `0.92` | Similarity cutoff for burst grouping |
|
|
114
|
+
| `ap_weight` | `0.6` | Weight of AP25 in the combined aesthetic score |
|
|
115
|
+
| `nima_weight` | `0.4` | Weight of NIMA in the combined aesthetic score |
|
|
116
|
+
| `aesthetic_per_scope_pct` | `75.0` | Photo must be in the top `(100 - pct)`% within its scope |
|
|
117
|
+
| `aesthetic_library_pct` | `50.0` | Photo must also be in the top `(100 - pct)`% library-wide |
|
|
118
|
+
| `speed_mode` | `full` | `fast` skips some ML stages for a quick preview pass |
|
|
119
|
+
|
|
120
|
+
Derived, non-configurable paths under `<folder>/.selects/`: `index.db`, `thumbs/`, `previews/`.
|
|
121
|
+
|
|
122
|
+
### Per-trip customization
|
|
123
|
+
|
|
124
|
+
The location and tagging stages ship with travel-generic defaults, but you can tune them per
|
|
125
|
+
library by dropping optional JSON files into `<folder>/.selects/`:
|
|
126
|
+
|
|
127
|
+
| File | Purpose |
|
|
128
|
+
|---|---|
|
|
129
|
+
| `landmarks.json` | Named GPS landmarks (`{"name", "lat", "lon", "radius_m"}`) used as a fast-path override for reverse geocoding. Without it, geocoding relies entirely on Nominatim. |
|
|
130
|
+
| `keywords.json` | Theme buckets (`{label: [keyword, ...]}`) for pattern/thematic stories. |
|
|
131
|
+
| `tag_prompts.json` | Zero-shot SigLIP tag taxonomy (`{tag: [prompt, ...]}`). |
|
|
132
|
+
|
|
133
|
+
Each file is optional; a missing or malformed file falls back to the built-in defaults. See
|
|
134
|
+
[`examples/ladakh/`](examples/ladakh/) for a complete worked example (the original Ladakh dataset)
|
|
135
|
+
and copy any file into your own `.selects/` to customize.
|
|
136
|
+
|
|
137
|
+
## Hardware notes
|
|
138
|
+
|
|
139
|
+
The ML stages (embedding, tagging, face recognition, clustering, enhancement) are much faster with
|
|
140
|
+
a CUDA GPU — `selects doctor` reports what's available (CUDA, NVDEC via torchcodec,
|
|
141
|
+
nvImageCodec, `cv2.cuda`). Everything falls back to CPU, but expect the ML stages to be
|
|
142
|
+
significantly slower, especially the enhancement models (NAFNet/Zero-DCE++/CSRNet) and the VLM
|
|
143
|
+
cluster-naming pass. Classical scoring (blur/exposure/face-detect) and indexing run fine on CPU.
|
|
144
|
+
|
|
145
|
+
## Project layout
|
|
146
|
+
|
|
147
|
+
- `selects/` — Python package: CLI, config, pipeline orchestration, DB models
|
|
148
|
+
- `classical/` — non-ML signal scoring (blur, exposure, face detect, auto-reject, straighten)
|
|
149
|
+
- `decode/` — image/video/RAW decoding
|
|
150
|
+
- `indexer/` — folder walking, EXIF reading, preview generation, orchestration
|
|
151
|
+
- `ml/` — embedding, tagging, faces, clustering, stories, moments, enhancement models
|
|
152
|
+
- `server/` — FastAPI app, routes, websocket progress bus
|
|
153
|
+
- `frontend/` — React + Vite + TypeScript web UI
|
|
154
|
+
- `tests/` — pytest suite
|
|
155
|
+
- `scripts/` — standalone analysis/benchmarking scripts (not part of the package)
|
|
156
|
+
- `docs/` — design notes and specs
|
|
157
|
+
- `design/` — visual design references
|
|
158
|
+
|
|
159
|
+
## Development
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
pip install -e ".[dev]"
|
|
163
|
+
pytest
|
|
164
|
+
ruff check .
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Database schema is managed with Alembic. Migrations ship inside the package at
|
|
168
|
+
`selects/db/migrations/` (there is no `alembic.ini`), and `init_db()` brings
|
|
169
|
+
each library's SQLite DB up to head automatically on open — fresh DBs are created
|
|
170
|
+
and stamped, pre-existing DBs are stamped at the baseline and upgraded. After
|
|
171
|
+
changing `selects/db/models.py`, create a new revision with autogenerate by
|
|
172
|
+
building an in-code `Config` pointing `script_location` at
|
|
173
|
+
`selects/db/migrations` and `sqlalchemy.url` at a throwaway SQLite file, then
|
|
174
|
+
calling `alembic.command.revision(cfg, autogenerate=True, message="...")`. Always
|
|
175
|
+
review the generated script — SQLite ALTERs must go through `render_as_batch`
|
|
176
|
+
(already enabled in `env.py`).
|
|
177
|
+
|
|
178
|
+
## Packaging a desktop build
|
|
179
|
+
|
|
180
|
+
Produce a self-contained bundle (no Python install required on the target machine) with
|
|
181
|
+
PyInstaller:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
pip install "pyinstaller>=6.6"
|
|
185
|
+
python packaging/build.py
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
The script builds the frontend (`npm run build`), copies `frontend/dist` into
|
|
189
|
+
`selects/server/static/` so the packaged app serves the UI same-origin, then runs PyInstaller in
|
|
190
|
+
**onedir** mode using `packaging/selects.spec`. The result lands in `dist/selects/` — launch
|
|
191
|
+
`dist/selects/selects.exe` (or `./selects` on macOS/Linux).
|
|
192
|
+
|
|
193
|
+
By default the ML stack (torch, transformers, insightface, …) is **excluded** to keep the bundle
|
|
194
|
+
small and the build fast — the base app still indexes, previews, and runs the classical auto-reject
|
|
195
|
+
stages. To bundle the ML deps, use `python packaging/build.py --ml` (or set
|
|
196
|
+
`SELECTS_BUNDLE_ML=1`); any ML package that isn't installed is skipped, so the build always
|
|
197
|
+
degrades gracefully.
|
|
198
|
+
|
|
199
|
+
CPU-torch tip: the default CUDA torch wheels are multiple GB. For a much smaller ML bundle, install
|
|
200
|
+
the CPU build first:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
pip install torch --index-url https://download.pytorch.org/whl/cpu
|
|
204
|
+
python packaging/build.py --ml
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Known limitations
|
|
208
|
+
|
|
209
|
+
- Aesthetic and burst-detection thresholds were tuned against a single trip's photo set and may
|
|
210
|
+
need adjustment for very different shooting styles or camera gear
|
|
211
|
+
- List endpoints use simple offset/limit pagination, not cursor-based
|
|
212
|
+
- RAM++ tagging depends on a model with no PyPI release (installed via git URL); expect a slower,
|
|
213
|
+
less reproducible install than the rest of the `ml` extra
|
|
214
|
+
|
|
215
|
+
## License
|
|
216
|
+
|
|
217
|
+
MIT — see [LICENSE](LICENSE).
|