Skip to content

CropCatcher

CropCatcher

retrieve the image source of any visual crop

Python 3.13+ uv Ruff PyPI IIIF: Presentation v2/v3, Image API License: MIT

CropCatcher is a Python package for determining whether two images show the same physical content.

It orchestrates classical OpenCV-based local feature matching methods such as SIFT, AKAZE and ORB, with descriptor matching, RANSAC and homography estimation for geometric verification and localization and exposes with a simple API.

CropCatcher is not a semantic similarity tool (not using visual embeddings): It is designed to identify the exact source image, rank candidates and, when relevant, locate the query inside the matching image.

Typical Cultural Heritage use case: you've found an old, low-resolution or cropped image somewhere like a screenshot, a thumbnail, a scan on a blog or in a PDF and you suspect it comes from a page held in a digital library such as BnF-Gallica. CropCatcher searches across one or more IIIF manifests, tells you which page it's actually contained in and where, and hands back that page's IIIF service. So, you can fetch the original at full resolution and read off its metadata (manuscript, shelfmark, folio) straight from the manifest.

Is the query crop contained in a candidate folio?

A real query crop (left) matched with SIFT against real folios from the same manuscript scrapped from BnF-Gallica: the true source scores clearly highest (green) and is accepted, while other pages sharing the same script, style and parchment (red) score far lower and are rejected. See Benchmarks for how this holds up across methods and distortions.

Install

Requires Python >= 3.13

use uv:

uv add cropcatcher

or pip:

pip install cropcatcher

Install (for development only)

git clone git@github.com:odil-ai/cropcatcher.git && cd IIIF_image_matcher/
uv sync                                         # add --group notebook dev docs for the notebooks, lint tests, mkdocs etc.

Quickstart

from cropcatcher import Matcher

matcher = Matcher(method="sift")  # or any feature extraction method: "akaze", "orb", "star_brief"

results = matcher.search(
    query="my_query_image.jpg",
    images="local_images_folder/",  # a directory, a list of paths, or a single path
)

best = results.best
print(best.source, best.score, best.inliers, best.bbox)

Output: Each result is a MatchResult:

MatchResult(
    source="image_142.jpg",
    score=0.87,
    matches=94,
    inliers=61,
    inlier_ratio=0.65,
    bbox=(412, 580, 1034, 1280),
    homography=...,
)

Against a digital library instead of local files:

results = matcher.search_iiif(
    query="my_query_image.jpg",
    manifests=["https://gallica.bnf.fr/iiif/ark:/12148/btv1b8427253m/manifest.json"],
)

How it works

flowchart TD
    Q["Query image"] --> FD1["Feature detection & description"]
    C["Candidate image"] --> FD2["Feature detection & description"]
    FD1 --> DM["Descriptor matching + Lowe's ratio test"]
    FD2 --> DM
    DM --> RA["RANSAC homography"]
    RA --> SC["Score + localization"]
  • Feature detection & description: extracts local keypoints and descriptors with SIFT, AKAZE, ORB, or optional STAR+BRIEF / SURF.
  • Descriptor matching: pairs similar descriptors using L2 or Hamming distance, then filters ambiguous matches with Lowe's ratio test. mutual_check=True adds a bidirectional consistency check to reduce false positives.
  • RANSAC homography: uses RANSAC to keep geometrically consistent matches and estimate a homography between the query and candidate.
  • Score + localization: combines inlier ratio and count into a confidence score, then projects the query into the candidate to estimate its bounding box.

SURF requires a custom OpenCV build (see above).

Where to go next

If you want to… See
Match against local images, understand MatchResult Matching local images
Search IIIF manifests, tune cost Searching IIIF
Reconcile a whole CSV of folios Batch reconciliation
Know how well it actually works Benchmarks

Citation

If you use CropCatcher in your research, please cite it. Metadata also lives in CITATION.cff at the repository root, which GitHub exposes through its "Cite this repository" button.

@software{terriel_cropcatcher,
  author       = {Terriel, Lucas},
  title        = {{CropCatcher: Locate a Visual Crop Inside Candidate Images or IIIF Manifests}},
  organization = {École nationale des chartes - PSL},
  year         = {2026},
  version      = {0.0.2},
  license      = {MIT}
}