"""PySIFT HuggingFace Space — static info + interactive matching demo.""" import time import cv2 import numpy as np import gradio as gr HEADER_HTML = """

PySIFT

GPU-Resident Deterministic SIFT for Deep Learning Vision Pipelines

arXiv Paper GitHub Code pip install staysift Kaggle Tutorial Kaggle Competition

A pure-Python, GPU-resident SIFT implementation that matches OpenCV SIFT accuracy while running 26% faster end-to-end with 4x matching speedup. Zero-copy DLPack interop keeps tensors on the GPU across the full pipeline.

Benchmark Metric PySIFT vs OpenCV
HPatches MMA@10 +2.2pp
IMC Phototourism Inliers/pair 303 vs 205 (+47%)
MegaDepth-1500 AUC@10 +5.6pp
ROxford5K mAP +7.5pp

Quick Start

pip install staysift
from pysift import PySIFT
sift = PySIFT()
keypoints, descriptors = sift.detectAndCompute(gray_image)
""" DEMO_NOTE = ( "This demo runs **OpenCV SIFT on CPU** for compatibility. " "For GPU-accelerated matching, install `staysift` locally with a CUDA GPU." ) def match_images(img1, img2, max_keypoints, ratio_thresh): """Detect, match, and visualize SIFT correspondences between two images.""" if img1 is None or img2 is None: return None, "Please upload both images." gray1 = cv2.cvtColor(img1, cv2.COLOR_RGB2GRAY) gray2 = cv2.cvtColor(img2, cv2.COLOR_RGB2GRAY) sift = cv2.SIFT_create(nfeatures=int(max_keypoints)) t0 = time.perf_counter() kp1, d1 = sift.detectAndCompute(gray1, None) kp2, d2 = sift.detectAndCompute(gray2, None) t_detect = time.perf_counter() - t0 if d1 is None or d2 is None or len(kp1) < 2 or len(kp2) < 2: return None, "Too few keypoints detected. Try different images." t0 = time.perf_counter() bf = cv2.BFMatcher(cv2.NORM_L2) raw = bf.knnMatch(d1, d2, k=2) matches = [m for m, n in raw if m.distance < ratio_thresh * n.distance] t_match = time.perf_counter() - t0 matches_sorted = sorted(matches, key=lambda x: x.distance) draw_count = min(len(matches_sorted), 100) # Resize both images to same height for clean side-by-side visualization target_h = min(img1.shape[0], img2.shape[0], 600) scale1 = target_h / img1.shape[0] scale2 = target_h / img2.shape[0] r_img1 = cv2.resize(img1, None, fx=scale1, fy=scale1) r_img2 = cv2.resize(img2, None, fx=scale2, fy=scale2) # Rescale keypoints to match resized images r_kp1 = [cv2.KeyPoint(k.pt[0]*scale1, k.pt[1]*scale1, k.size*scale1, k.angle, k.response, k.octave, k.class_id) for k in kp1] r_kp2 = [cv2.KeyPoint(k.pt[0]*scale2, k.pt[1]*scale2, k.size*scale2, k.angle, k.response, k.octave, k.class_id) for k in kp2] # Draw with bright green, thicker lines vis = cv2.drawMatches( r_img1, r_kp1, r_img2, r_kp2, matches_sorted[:draw_count], None, matchColor=(0, 255, 0), singlePointColor=None, flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS, ) # Redraw lines thicker for visibility w1 = r_img1.shape[1] for m in matches_sorted[:draw_count]: pt1 = (int(r_kp1[m.queryIdx].pt[0]), int(r_kp1[m.queryIdx].pt[1])) pt2 = (int(r_kp2[m.trainIdx].pt[0]) + w1, int(r_kp2[m.trainIdx].pt[1])) cv2.line(vis, pt1, pt2, (0, 255, 0), 2, cv2.LINE_AA) cv2.circle(vis, pt1, 4, (0, 255, 0), -1, cv2.LINE_AA) cv2.circle(vis, pt2, 4, (0, 255, 0), -1, cv2.LINE_AA) total = t_detect + t_match stats = ( f"**Keypoints:** {len(kp1)} + {len(kp2)} = {len(kp1)+len(kp2)} \n" f"**Matches:** {len(matches)} (showing top {draw_count}) \n" f"**Detection:** {t_detect*1000:.0f}ms | **Matching:** {t_match*1000:.0f}ms | " f"**Total:** {total*1000:.0f}ms \n" f"*On GPU with PySIFT, expect ~3-4x faster detection and ~4x faster matching.*" ) return vis, stats with gr.Blocks(title="PySIFT: GPU-Resident Deterministic SIFT", theme=gr.themes.Soft()) as demo: gr.HTML(HEADER_HTML) gr.Markdown("---") gr.Markdown("## Try It: Interactive SIFT Matching") gr.Markdown(DEMO_NOTE) with gr.Row(): img1 = gr.Image(label="Image A", type="numpy") img2 = gr.Image(label="Image B", type="numpy") with gr.Row(): max_kp = gr.Slider(500, 8000, value=4000, step=500, label="Max Keypoints") ratio = gr.Slider(0.5, 0.95, value=0.75, step=0.05, label="Ratio Test Threshold") match_btn = gr.Button("Match", variant="primary", size="lg") output_img = gr.Image(label="Matches", type="numpy") output_stats = gr.Markdown(label="Stats") match_btn.click( fn=match_images, inputs=[img1, img2, max_kp, ratio], outputs=[output_img, output_stats], ) gr.Markdown("---") gr.Markdown( "*Built by [Sivakumar K S](https://github.com/SivaIITM) at IIT Madras. " "Read the [paper](https://arxiv.org/abs/2605.17869) for full details.*" ) if __name__ == "__main__": demo.launch()