Skip to content

rassulz/image_extractor_agent

Repository files navigation

Image Extractor Agent

Natural-language semantic image search β€” type "a dog running on grass" and instantly get the matching photos from a 3,000-image Flickr8k subset, ranked by how well they visually match your words.

No captions, no tags, no text metadata are used for retrieval β€” the system understands the images themselves. Images are embedded locally with the CLIP image encoder, the query with the CLIP text encoder, and retrieval is cosine similarity in their shared vector space: one numpy matmul over a cached index, so search is instant and works fully offline.

Python PyTorch FastAPI React License

🎬 Video demo

Watch the demo on YouTube

β–Ά Watch the full walkthrough on YouTube β€” semantic search, the 2D embedding Atlas, and the agent pipeline in action.

Features

  • πŸ” Semantic search (Поиск) β€” describe a scene in natural language, get ranked photos with a match %, metadata, and an explanation of why each image matched.
  • πŸ—ΊοΈ Semantic Atlas (Атлас) β€” the entire 512-D CLIP index projected to 2D with PCA, clustered with KMeans, and each cluster auto-labeled with CLIP itself (zero-shot against candidate label prompts). Hover and click to explore how the model organizes the dataset.
  • πŸ€– Agent-style pipeline β€” the query is parsed into visual intent + metadata filters (location / date), filters are applied before ranking, results are explained, and a Markdown/JSON report is saved.
  • ⚑ Offline-first β€” CLIP runs locally (GPU if available); the whole retrieval path makes zero API calls. An OPENAI_API_KEY is optional and only adds generated captions/tags and richer explanations.
  • βœ… Honest retrieval β€” Flickr8k's captions.txt is never used for indexing, retrieval, or ranking. The point of the project is that the system understands images directly.

How it works

                       offline (once)                          per query (instant)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ data/images/   │──►│ CLIP image encoder    β”‚    β”‚ "people near water in Astana"        β”‚
β”‚ 3,000 photos   β”‚   β”‚ (ViT-B/32, batched,   β”‚    β”‚        β”‚                             β”‚
β”‚ + metadata.csv β”‚   β”‚  L2-normalized)       β”‚    β”‚        β–Ό                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚ parse β†’ visual query + filters       β”‚
                                β–Ό                β”‚        β”‚                             β”‚
                     cache/image_embeddings.npy  β”‚        β–Ό                             β”‚
                     cache/embeddings_meta.json  β”‚ metadata filter (before ranking)     β”‚
                                β”‚                β”‚        β”‚                             β”‚
                                β–Ό                β”‚        β–Ό                             β”‚
                     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚ CLIP text encoder β†’ cosine top-k     β”‚
                     β”‚ local vector index    │◄───        β”‚                             β”‚
                     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚        β–Ό                             β”‚
                                                 β”‚ explanations + Markdown/JSON report  β”‚
                                                 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  • Model: open_clip ViT-B-32 (laion2b_s34b_b79k) β€” embeds 3,000 images in ~1–2 minutes on a 4 GB GPU.
  • Index: pure CLIP image embeddings. Captions/metadata never enter the index vectors.
  • Vision enrichment (optional): retrieved images can be captioned/tagged lazily by a vision model β€” used only for explanations and reports, never for retrieval.

Quick start

git clone https://github.com/rassulz/image_extractor_agent.git
cd image_extractor_agent

conda activate practical-ai-engineering    # or your own env: conda create -n image-extractor python=3.11
pip install -r requirements.txt            # torch, open-clip, fastapi, …

# 1. Dataset β€” either unzip the bundled subset:
unzip images.zip -d data/                  # -> data/images/ (3,000 images, ~65 MB)
#    …or rebuild it deterministically from Kaggle:
# python scripts/prepare_flickr8k_subset.py --count 3000 --remove-raw

# 2. Frontend deps
cd frontend && npm install && cd ..

# 3. One command: build the CLIP index (once) + start backend + frontend
./run_demo.sh                              # CONDA_ENV=<name> ./run_demo.sh to override the env
# β†’ open http://localhost:5173

OPENAI_API_KEY in .env is optional (see .env.example): it only enables generated captions/tags and LLM explanations. The backend does pure-CLIP search by default, so a demo costs zero API tokens.

Manual run (without the script)

python scripts/build_index.py            # build/refresh the CLIP index
uvicorn backend.main:app --port 8000     # backend  β†’ http://localhost:8000
cd frontend && npm run dev               # frontend β†’ http://localhost:5173

Notebook

The course-required notebook demo is image_extractor_agent_day5.ipynb β€” the same pipeline end-to-end (dataset β†’ embeddings β†’ search β†’ filters β†’ agent β†’ report), regenerable with python scripts/make_notebook.py.

API

Endpoint Method What it does
/api/search POST Natural-language query β†’ parsed intent, filters, top-k ranked images with scores and explanations
/api/atlas?k=6 GET 2D PCA projection of the whole index + KMeans clusters auto-labeled with CLIP
/api/samples?n=8 GET Random sample images for the landing gallery
/api/health GET Index status, model name, image count
/images/… GET Static dataset images

Project layout

image_extractor_agent/
β”œβ”€β”€ image_extractor_agent_day5.ipynb   # notebook demo (required deliverable)
β”œβ”€β”€ run_demo.sh                        # build index + start backend + frontend
β”œβ”€β”€ requirements.txt / .env.example
β”œβ”€β”€ scripts/    prepare_flickr8k_subset.py Β· build_index.py Β· make_notebook.py
β”œβ”€β”€ src/        dataset_loader Β· embedding_index Β· search_tools Β· vision_extractor Β· report_writer Β· agent
β”œβ”€β”€ backend/    main.py                # FastAPI
β”œβ”€β”€ frontend/   src/App.jsx Β· Atlas.jsx  # React + Vite + Tailwind
β”œβ”€β”€ report/     Image_Extractor_Agent_Report.pdf
β”œβ”€β”€ data/       images/ Β· metadata.csv   # local only (gitignored)
β”œβ”€β”€ cache/      image_embeddings.npy Β· embeddings_meta.json Β· generated_image_descriptions.json
└── outputs/    search_results.json Β· search_report.md

Design notes & limitations

  • CLIP cosine scores are low in absolute terms (~0.2–0.32 for good cross-modal matches). They are meaningful for ranking, not thresholding β€” the UI maps them to a friendly match %.
  • Synthetic metadata: Flickr8k has no real capture metadata, so location is unknown (none is invented) and date is generated from a seeded RNG β€” purely to demonstrate filter-before-ranking metadata filtering.
  • Stale-cache protection: the index is validated against metadata.csv (ids + order + model name) on load; a mismatched cache is rejected and rebuilt instead of silently returning wrong images.
  • Deterministic dataset: the 3,000-image subset is selected with sorted filenames + a fixed seed, so anyone can reproduce the exact same index.

License

Apache 2.0

About

Natural language semantic image search with CLIP + FastAPI + React demo over a 3,000 image Flickr8k subset, fully offline retrieval

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors