RIZA
Fashion shopping, searchable by photo
Riza | Fashion & Shopping, a Saudi fashion store on Google Play and the App Store, and the photo search behind it: OWLv2 finds each garment, FashionCLIP and FAISS match the closest products.
- Country
Saudi Arabia
- Type
- Source code
- Timeline
- 2026
- Platforms
- Android · iOS · API · WordPress
- product images indexed
- 11,934
- products searchable by photo
- 2,697
- service memory, CPU only
- 6 GB → 2 GB
Overview
RIZA is a Saudi fashion and lifestyle brand running on WordPress and WooCommerce, with its store app, Riza | Fashion & Shopping, live on Google Play and the App Store. Shoppers often arrive with a picture of something they like, a social media screenshot or a street photo, and no idea what it is called in the catalogue.
Mahmoud designed and built the feature that answers "show me products that look like this": garment detection, region-level similarity search, "similar items" on product pages, and REST endpoints shared by the web storefront and the app.
This repository is a clean, store-agnostic rewrite of that production work: a Python FastAPI service and a WordPress/WooCommerce plugin that can drop into any store. It keeps the production algorithms and tuning, with no store data or credentials.
The challenge
- 01
Text search fails for fashion: shoppers cannot type "beige wrap midi dress with puff sleeves", but they can show it
- 02
One outfit photo holds several products, so searching the whole image returns a mix
- 03
CPU-only server shared with the shop itself, so every model had to justify its RAM
- 04
The catalogue changes daily, so the index must update on save without blocking the admin
- 05
A public, anonymous upload endpoint needs hard limits on uploads, rate and response data
Highlights
Search
- Garment detection with OWLv2 across 36 descriptive prompts, each item shown as a tappable chip with its bounding box
- Region search: the selected box is cropped with 10% padding and embedded with FashionCLIP
- Quick search endpoint that skips detection for an instant whole-image search
- "Similar items" for any product, built from the mean of its image embeddings
Ranking quality
- Exact cosine search with FAISS
IndexFlatIPon L2-normalised vectors - Per-product de-duplication: fetch
top_k × 6neighbours and keep the best image per product - Two thresholds: a loose one in the API for recall, a stricter cut-off in the store for precision
- Category boost moves products matching the detected item ("Dress" / "فستان") to the top
Integration
- WordPress plugin with a settings page, batch indexer, auto-indexing on save and trash, shortcodes and theme-overridable templates
- Public REST routes (
/wp-json/vsw/v1/*) shared by the web widget and the mobile app - Catalogue indexer CLI for CSV, JSON or the WooCommerce REST API
Safety
- API-key auth compared in constant time
- Upload size and pixel limits, real MIME sniffing and private uploads deleted after 30 minutes
- Per-visitor rate limiting on anonymous routes
- SSRF-guarded image fetcher: no redirects, public hosts or an explicit allow-list only
Signature feature
Search by photo
- The shopper uploads a photo. WordPress sniffs the MIME type, caps the size and stores it privately for 30 minutes.
- The API detects shoppable items and returns labelled boxes in English and Arabic, shown as chips over the photo.
- The shopper picks an item. The API crops it with 10% padding, embeds it with FashionCLIP and queries FAISS for
top_k × 6neighbours. - Results are de-duplicated to the best image per product, thresholded and category-boosted, then the store returns only published, visible products above its cut-off.
Screens
Engineering notes
- 01
Three models on a CPU box, then two
The first release loaded OWLv2, FashionCLIP and a general-purpose CLIP. Measurements showed FashionCLIP handled beauty, fragrance and toys well enough, so the general model was dropped, saving about 1.8 GB of RAM.
- 02
Lazy detector
OWLv2 is the heaviest model, about 2.5 GB in RAM, and only
/detectneeds it. It loads behind a double-checked lock on first use or warms up in a background thread, so/searchis ready immediately. - 03
One hit per product
Products have several gallery images, so raw neighbours are dominated by a few products. The index oversamples, keeps the best image per product, then applies
top_k. - 04
Category-aware re-ranking
A beige bag can look closer to a beige dress than another dress does. A stable partition, matching category first and score order inside each group, fixed this without retraining, including for Arabic category names.
- 05
Correct boxes on non-square photos
OWLv2 pads inputs to a square, so the rewrite post-processes against the padded size. Boxes line up with the original pixels and the storefront draws them in percentages over a responsive image.
- 06
SQLite as source of truth
Vectors are upserted per product image and the in-memory FAISS index is rebuilt at startup and after deletes. Re-indexing is idempotent and a restart never loses the index.
Results
- 11,934 product images from 2,697 products indexed and searchable in production
- Resident memory cut from about 6 GB to about 2 GB by merging the two CLIP models and lazy-loading the detector, on CPU only
- The same REST endpoints served both the web storefront and the mobile app
- 42 pytest tests covering both the FAISS and NumPy backends
Stack
- Python
- FastAPI
- Pydantic v2
- FashionCLIP
- OWLv2
- FAISS
- SQLite
- WordPress
- WooCommerce
- PHP
- Vanilla JS
- pytest
- Docker