Skip to content
All projects
Source code Delivery & commerceOpen source

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
RIZA AI Visual Search
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

  1. 01

    Text search fails for fashion: shoppers cannot type "beige wrap midi dress with puff sleeves", but they can show it

  2. 02

    One outfit photo holds several products, so searching the whole image returns a mix

  3. 03

    CPU-only server shared with the shop itself, so every model had to justify its RAM

  4. 04

    The catalogue changes daily, so the index must update on save without blocking the admin

  5. 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 IndexFlatIP on L2-normalised vectors
  • Per-product de-duplication: fetch top_k × 6 neighbours 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 × 6 neighbours.
  • 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.

Engineering notes

  1. 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.

  2. 02

    Lazy detector

    OWLv2 is the heaviest model, about 2.5 GB in RAM, and only /detect needs it. It loads behind a double-checked lock on first use or warms up in a background thread, so /search is ready immediately.

  3. 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.

  4. 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.

  5. 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.

  6. 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