Skip to content

vnmoorthy/photofinder

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

4 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ”Ž PhotoFinder

Search your entire photo library by meaning β€” not filenames, not tags.

Type "people laughing at the beach" and get the right photos back in milliseconds. Embeddings run on serverless GPUs via Runpod Flash β€” no Docker, scale-to-zero.

Live Demo Runpod Flash

Python CLIP FastAPI License

🌐 Live Demo Β· ⚑ Quickstart Β· πŸ— How it works


The 30-second pitch

Your phone has thousands of photos and the only way to find one is to scroll. Filenames are IMG_4821.jpg. There are no tags. PhotoFinder fixes that: it understands what's in each photo, so you can search the way you actually think β€” "my dog on the beach", "that whiteboard from the meeting", "sunset over mountains" β€” and it just finds them. It also spots near-duplicates so you can reclaim storage.

It's powered by OpenAI's CLIP, which embeds images and text into the same vector space β€” so a text query can be compared directly against every photo. The heavy lifting runs on Runpod Flash: GPU-backed serverless endpoints defined in pure Python, no Dockerfile, scaling from zero.

What we built β€” answered plainly

Question Answer
What does the workload do? Embeds images and text with CLIP into one shared vector space, then ranks photos against a query by cosine similarity.
Where does Flash remove friction? No Dockerfile, no registry, no infra. A @Endpoint-decorated Python function is the GPU service. flash deploy β†’ live HTTPS endpoint. Workers scale from zero and bill per-second.
What input does it accept? A folder of images (indexing) and a natural-language query string (search).
What output does it produce? Ranked photos with similarity scores, plus grouped near-duplicates.
What becomes easier in a real product? Photo apps, e-commerce catalogs, DAMs, and content moderation all need "find by meaning" + dedupe. This is that core, in ~150 lines, with the GPU cost only when a job actually runs.

⚑ Quickstart

Prereqs: Python 3.10–3.13, uv, and a Runpod account with a verified email.

git clone https://github.com/vnmoorthy/photofinder.git
cd photofinder

uv venv
source .venv/bin/activate
uv pip install -r requirements.txt

uv run flash login          # one-time: authorize in browser
flash deploy                # builds + deploys the GPU/CPU endpoints (first build ~5 min)

python fetch_samples.py     # OR point step below at your own photo folder
python index.py sample_images

python -m uvicorn app:app --port 8000   # open http://localhost:8000

Best demo: python index.py ~/Pictures β€” "search my photos" is the moment that lands.

πŸ— How it works

                 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ Runpod Flash (serverless GPU) ───────────────────────┐
                 β”‚                                                                               β”‚
  index.py  ─────┼──▢  clip_embed( images )   ← BATCH: fans out N parallel jobs, scales from 0   β”‚
  (your photos)  β”‚           β”‚                                                                   β”‚
                 β”‚           β–Ό                                                                    β”‚
                 β”‚     embeddings.npy  +  paths.json   (local vector index)                       β”‚
                 β”‚                                                                                β”‚
  app.py    ─────┼──▢  clip_embed( "beach at sunset" )  ← LIVE: real-time query embedding         β”‚
  (FastAPI) ◀────┼─────────  cosine rank over the index  ──────────  ranked photos + scores       β”‚
                 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

One model, two roles, defined in endpoints.py:

  • Index time (async batch): index.py splits the library into chunks and fires them at the GPU endpoint in parallel β€” clip_embed runs on whatever GPU Flash assigns and reports its name back as proof.
  • Query time (real-time endpoint): every search embeds the query text on the same endpoint, then a sub-millisecond cosine search over the local index returns the best matches.
  • Optional CPU stage: fetch_and_prep (a CPU @Endpoint) downloads + resizes images from URLs, showing Flash as a true multi-endpoint pipeline, not just a deploy shortcut.

🧱 Project layout

File Role
endpoints.py Flash endpoints β€” clip_embed (GPU) + fetch_and_prep (CPU)
index.py Async batch indexer β€” fans out embeddings across GPU workers
app.py FastAPI app β€” live /search, /duplicates, serves the UI
static/index.html The product UI
fetch_samples.py Grabs demo photos so you can try it in 30s

πŸ›  Tech

CLIP ViT-B/32 (sentence-transformers) Β· Runpod Flash serverless GPU Β· FastAPI Β· NumPy cosine retrieval Β· vanilla-JS UI.

πŸ“œ License

MIT Β© 2026 vnmoorthy. See LICENSE.

Built for the Runpod Flash Hack Day · powered by Runpod Flash ⚑

About

πŸ”Ž Search your photos by meaning β€” semantic photo search powered by CLIP on Runpod Flash serverless GPUs

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors