A reproducible pipeline to classify digital heritage objects using Iconclass VLM via Ollama.
This project implements an automated, locally-hosted pipeline for classifying artwork images with Iconclass codes using a Vision-Language Model (VLM) running under Ollama. Designed for the Stadt Geschichte Basel digital collections, the pipeline processes metadata, downloads images, classifies them, and writes results back with full provenance tracking.
- 🎨 Automated Iconclass Classification: Uses the Iconclass VLM model via Ollama for accurate classification
- 📦 Batch Processing: Process entire collections from metadata.json files
- 🔄 Smart Image Processing: Automatic download, resize, normalization, and SHA256-based caching
- 🎯 Intelligent Filtering: Automatically filters to process only children (m) objects, excluding parents (abb)
- 🎲 Flexible Sampling: Random, fixed, or full dataset sampling modes with reproducible seeds
- 📝 Multiple Prompts: Three prompt templates (default, instruction, few-shot) for optimal results
- 📊 Dual Output: Compact codes in metadata + detailed JSONL records for auditability
- 🔍 Full Provenance: Timestamped run directories with complete audit trail (requests, responses, manifests)
- 🛡️ Robust: Built-in retry logic, comprehensive error handling, and logging
- 📐 Type-Safe: Pydantic models ensure data validation and consistency
- 🧪 Tested: Unit and integration tests ensure reliability
- Python ≥ 3.11
- Ollama running locally
- The Iconclass VLM model:
ollama pull hf.co/mradermacher/iconclass-vlm-GGUF:Q4_K_MWe use the fast Python package manager uv for reproducible environments.
# Clone the repository
git clone https://github.com/Stadt-Geschichte-Basel/iconclass-classification.git
cd iconclass-classification
# (Optional) Install uv if you don't have it yet
curl -LsSf https://astral.sh/uv/install.sh | sh
# Sync all project dependencies (creates .venv automatically)
uv sync
# Ensure dev tools (ruff, ty, pytest)
uv sync --group dev
# Show CLI help
uv run iconclass-classification --helpUsing Ollama (local):
uv run iconclass-classification classify-ollama \
--source https://forschung.stadtgeschichtebasel.ch/assets/data/metadata.json \
--model hf.co/mradermacher/iconclass-vlm-GGUF:Q4_K_M \
--sampling-mode random \
--sampling-size 10Using OpenRouter (cloud):
export OPENROUTER_API_KEY=your_key_here
uv run iconclass-classification classify-openrouter \
--source https://forschung.stadtgeschichtebasel.ch/assets/data/metadata.json \
--model qwen/qwen3-vl-235b-a22b-instruct \
--sampling-mode random \
--sampling-size 10You can store your API key in a local .env file and load it for the session (recommended on macOS/Linux with zsh):
# Create .env at the project root
echo 'OPENROUTER_API_KEY=your_key_here' > .env
# Load all variables from .env into the environment for this shell
set -a; source .env; set +a
# Run with OpenRouter
uv run iconclass-classification classify-openrouter \
--source https://forschung.stadtgeschichtebasel.ch/assets/data/metadata.json \
--model qwen/qwen3-vl-235b-a22b-instruct \
--sampling-mode random \
--sampling-size 10Notes:
- Keep
.envout of version control. Do not commit secrets. Anexample.envfile is provided as a template. - Alternatively, export the variable once per session using
export OPENROUTER_API_KEY=....
Important: The pipeline automatically filters to only process children objects (m prefix). Parent objects (abb prefix) are excluded from classification.
The pipeline will:
- Fetch metadata from the source URL
- Filter out abb (parent) objects, keep only m (children) objects
- Sample objects based on your configuration
- Download and process images
- Classify each image with the selected backend (Ollama or OpenRouter)
- Write results to
runs/<timestamp>/
For detailed usage instructions and advanced features, see:
- USAGE.md - Complete usage guide for both backends
- training-and-prompting.md - Prompt templates and troubleshooting
Each run creates a timestamped directory with complete provenance:
runs/<UTC-ISO8601>/
raw/metadata.json # Original metadata
data/
src/ # Cached images (deduplicated by SHA256)
<objectid>.jpg # Processed images
classify/
<objectid>_request.json # Classification requests
<objectid>_response.json # Model responses
logs/pipeline.log # Detailed logs
results/
metadata.classified.json # Metadata with Iconclass codes
iconclass_details.jsonl # Detailed classification records
manifest.json # Run metadata & counts
Compact metadata (metadata.classified.json):
{
"objectid": "abb10039",
"title": "Die Löblich und wyt berümpt Stat Basel",
"subject": ["71H7131", "25F2"]
}Detailed record (iconclass_details.jsonl):
{
"objectid": "abb10039",
"subject": {
"iconclass": {
"codes": ["71H7131", "25F2"],
"model": "hf.co/mradermacher/iconclass-vlm-GGUF:Q4_K_M",
"image_sha256": "...",
"timestamp": "2025-11-01T10:00:00Z"
}
}
}The pipeline is modular and consists of these components:
src/iconclass_classification/
models.py # Pydantic data models
image_utils.py # Image download & processing
ollama_client.py # Ollama API integration
pipeline.py # Pipeline orchestration
cli.py # Command-line interface
- Fetch Metadata: Download metadata.json from source URL
- Image Selection: Choose best available image URL (prefer
object_location, fallback toobject_thumb) - Download & Cache: Download images with SHA256-based deduplication
- Process: Resize to max size, convert to RGB, compress as JPEG
- Classify: Send processed images to Ollama Iconclass VLM
- Extract: Parse Iconclass codes from model responses using regex
- Write: Update metadata with codes, save detailed JSONL records
- Log: Save all requests, responses, and run manifest
# Ensure dev dependencies are installed
uv sync --group dev
# Run all tests
uv run pytest test/ -v
# Run only unit tests
uv run pytest test/unit/ -v
# Run only integration tests
uv run pytest test/integration/ -v# Format Python code
uv run ruff format .
# Check Python code
uv run ruff check .
# Auto-fix issues
uv run ruff check --fix .
# (Optional) Type check
uv run ty check
# Format all non-Python files (Prettier)
npm run format
# Check all non-Python files
npm run checkEnsure Ollama is running:
ollama servePull the Iconclass VLM model:
ollama pull hf.co/mradermacher/iconclass-vlm-GGUF:Q4_K_MReduce image size or use sample mode:
uv run iconclass-classification classify-ollama \
--source <URL> \
--model hf.co/mradermacher/iconclass-vlm-GGUF:Q4_K_M \
--max-side 512 \
--sample 10Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
If you use this project in your research, please cite:
@software{iconclass_classification,
title = {Iconclass Classification Pipeline},
author = {Stadt Geschichte Basel},
year = {2025},
url = {https://github.com/Stadt-Geschichte-Basel/iconclass-classification}
}This project builds upon:
- Iconclass classification system
- Ollama for local LLM hosting
- The open-research-data-template for infrastructure
- Pydantic for data validation
- Pillow for image processing
- Stadt Geschichte Basel - Website
See also the list of contributors who participated in this project.