ImageAnalyzerAI is a high-performance desktop application that leverages local multimodal AI models to generate rich image descriptions and semantic embeddings. Designed for power users and researchers, it supports fast batch processing, semantic search, and database-backed storage — all wrapped in a sleek PyQt5 GUI.
Analyze and search images locally using AI-powered descriptions and semantic embeddings — perfect for organizing memes, emotes, or any large image collection.
- Any OpenAI-compatible Server: Talks to the standard
/v1API, so it works with Ollama, llama.cpp, llama-swap, vLLM, and LM Studio using models like LLaVA, Qwen-VL, Moondream, Nomic, and BGE. - Adjustable Concurrency: Choose how many captioning requests run at once (1–256) to match what your server can batch. Embedding requests are pooled separately.
- Hybrid Search: Fuses semantic (embedding) and full-text (FTS5) ranking, so both "guy looking at another woman" and "distracted_boyfriend" find the same image.
- Database Management: One SQLite file per model pair holding images, descriptions, and embeddings together.
- Dark Mode UI: Built with PyQt5 and styled for low-light environments.
- Clipboard Support: Copy image previews directly from the search results.
- Model Detection: Automatically sorts the server's model list into vision and embedding dropdowns by name.
| Panel | Description |
|---|---|
| Left Panel | Directory selection, server address, model dropdowns, processing controls |
| Right Panel | Search interface with image previews, descriptions, and timestamps |
| Tabs | Switch between database preview and search views |
To run ImageAnalyzerAI, make sure the following are installed and properly configured:
- Python 3.9+ — Recommended for compatibility with PyQt5 and multithreading.
- An OpenAI-compatible server exposing
/v1/models,/v1/chat/completions, and/v1/embeddings, serving at least one vision model and one embedding model. Tested shapes include Ollama, llama.cpp'sllama-server, llama-swap, vLLM, and LM Studio. - Python packages:
PyQt5— For the graphical user interface.NumPy— For handling embedding vectors and similarity calculations.Requests— For communicating with the/v1API.
Install all required packages using:
pip install -r requirements.txt
The app defaults to http://localhost:11434/v1, which is Ollama's port. To point it
anywhere else, set the IMAGE_ANALYZER_BASE_URL environment variable before launching:
# llama.cpp / llama-swap
set IMAGE_ANALYZER_BASE_URL=http://localhost:8080/v1
# vLLM
set IMAGE_ANALYZER_BASE_URL=http://localhost:8000/v1
# LM Studio
set IMAGE_ANALYZER_BASE_URL=http://localhost:1234/v1The /v1 suffix is optional — it is appended automatically if you leave it off.
Because /v1/models reports only model ids, the vision and embedding dropdowns are
filled by matching names against keyword lists (llava, -vl, moondream, embed,
bge, nomic, and so on). Models whose names carry no such hint will not appear in
either dropdown.
A PyInstaller build script is included to generate a compact standalone
.exefor portable distribution without requiring Python on the target system.
-
Start your inference server and ensure that at least one vision model and one embedding model are available.
These models are required for generating image descriptions and semantic embeddings. With Ollama you can pull them usingollama pull <model-name>; with llama.cpp, llama-swap, or vLLM they are whatever your server is configured to serve. -
Launch the application by running
python main.py. -
First Run:
- Select a directory containing images.
- Choose a vision model and an embedding model from the dropdowns. Both list every model your server reports —
/v1/modelsdoes not say what a model can do, so nothing is filtered out and the choice is yours. - Click Process to generate image descriptions and semantic embeddings.
- These results are saved locally in a database and embedding file for future use.
-
Subsequent Runs:
- Previously processed data is automatically loaded.
- You can immediately perform semantic or keyword-based searches without reprocessing.
- Reprocessing is only needed if you change models or add new images to the directory.
A PyInstaller build script is included to generate a compact standalone .exe version of the application.
This allows ImageAnalyzerAI to run as a portable app without requiring Python or external dependencies on the target system.
- The build is optimized for minimal size.
- All required assets and dependencies are bundled.
- Ideal for distributing the app as a single executable.
Search is hybrid — every query runs two independent searches and merges them:
- Meaning: cosine similarity between the query embedding and every stored vector.
- Text: SQLite FTS5 (bm25) over descriptions and filenames. Wrap a phrase in
"quotes"to require it verbatim.
The two are combined with Reciprocal Rank Fusion, which merges by position rather than by score. Cosine similarity and bm25 aren't on comparable scales, so this avoids any per-model tuning — and an image found by either half still surfaces.
There is no similarity threshold. Results are always ranked best-first and capped by Max Results, so a query can't silently come back empty because a cutoff was set slightly too high.
The Match column shows why each row matched: a cosine score (0.82), a full-text hit (text), or both (0.82 + text).
If the embedding server is unreachable, the text half still works on its own.
Each vision/embedding model pair gets one SQLite file in data/, with three tables:
| Table | Holds |
|---|---|
images |
image bytes, name, MD5 hash, byte size — keyed by path, independent of any model |
descriptions |
one caption per image, with the prompt and vision model used |
embeddings |
one float32 vector per image, with its dimension and embedding model |
descriptions and embeddings reference images with ON DELETE CASCADE, so removing an image removes everything derived from it. Because images stands on its own, images can be added without a description — leaving room for a bulk import later.
- Embeddings are batched —
/v1/embeddingsis called with several descriptions per request. If your server rejects array input, or returns fewer vectors than asked for, the app retries the missing ones individually. - Missing embeddings are backfilled — a description whose embedding failed (server offline, run stopped) is picked up on the next run without re-captioning the image.
- Failed images are retried in parallel, up to 3 passes.
- Context size is configured on the server, not in the app. The
/v1API has nonum_ctxequivalent — llama.cpp and vLLM fix context at startup, and Ollama's/v1endpoint ignores it. - Images are re-processed when the prompt changes; otherwise already-described images are skipped.
See the LICENSE file in the repository for usage terms
