Aperture — Image Classifier Dashboard

Aperture is a modern, high-performance vision inference dashboard. Load an image frame into the viewfinder, and Aperture streams it to a deep learning vision pipeline powered by PyTorch, returning top-ranked predictions, confidence score distributions, and inference latency metrics in real time.


🌟 Project Overview

Aperture bridges modern web interfaces with deep learning computer vision backends. It features an interactive, dark-mode reticle viewfinder for image uploads, real-time prediction readouts, and a persistent classification history database.

Core Features

  • 📷 Interactive Frame Viewfinder: Drag-and-drop or upload JPEG, PNG, and WebP images up to 10MB.
  • Deep Learning Vision Inference: Leverages pre-trained TorchVision models (ResNet18, ResNet50, MobileNetV3) to compute ImageNet category probabilities.
  • 📊 Real-time Readout: Displays ranked classification results with confidence percentage bars and millisecond inference timing.
  • 📜 Persistent Prediction History: Automatically logs classification results into a database (PostgreSQL / SQLite via Async SQLAlchemy) and renders past predictions.
  • 🛡️ Robust Input Validation: Deep binary stream verification prevents corrupt or malformed image files from causing server errors.

💡 What Inspired Me

The inspiration for Aperture came from camera viewfinders and high-tech optical equipment interface design. Machine learning model inference often feels like a "black box" buried inside Jupyter notebooks or hidden behind command-line scripts.

I wanted to build a web application that makes vision AI tangible and visually captivating—where uploading an image feels like capturing a frame through a camera lens, with instant feedback on what the machine vision system "sees".


🚀 How I Built My Project

Aperture is designed as a decoupled full-stack application with clean separation of concerns between backend inference/data persistence and frontend interactive rendering.

       ┌────────────────────────┐
       │   Next.js 16 Frontend  │
       │ (React 19, Tailwind)   │
       └───────────┬────────────┘
                   │ HTTP / REST
                   ▼
       ┌────────────────────────┐
       │    FastAPI Backend     │
       └─────┬────────────┬─────┘
             │            │
             ▼            ▼
   ┌──────────────┐  ┌───────────────────┐
   │ PyTorch      │  │ Async SQLAlchemy  │
   │ Inference    │  │ (PostgreSQL/DB)   │
   └──────────────┘  └───────────────────┘

Stack & Technologies

  • Backend:

    • Framework: FastAPI (Python 3.14)
    • Machine Learning: PyTorch & Torchvision (resnet18, resnet50, mobilenet_v3)
    • Database & ORM: Async SQLAlchemy 2.0, Alembic migrations, AsyncPG / AIOSQLite
    • Validation & Settings: Pydantic v2 & Pydantic-Settings
    • Testing: Pytest & Pytest-Asyncio with in-memory SQLite fixtures
  • Frontend:

    • Framework: Next.js 16 (App Router, Turbopack) & React 19
    • Language: TypeScript
    • Styling: Tailwind CSS with custom view-finder animations and sleek dark aesthetic

📚 What I Learned

Building Aperture provided hands-on experience in full-stack AI application development:

  1. Async Web Architecture with FastAPI & SQLAlchemy: Designing non-blocking async endpoints that perform model inference and asynchronous database writes without blocking the main event loop.
  2. PyTorch Tensor Pipelines in Production: Normalizing image uploads, converting color channels, executing model transforms, and computing softmax probability distributions over ImageNet categories.
  3. Browser Memory Lifecycle Management: Handling client-side image previews efficiently by revoking blob object URLs (URL.revokeObjectURL) to prevent memory leaks during repeated uploads.
  4. Test-Driven Reliability: Structuring isolated test suites with Pytest and async fixtures to ensure API edge-case handling (e.g. corrupt files, empty uploads, oversized files) is robust.

⚡ Challenges Faced & Solutions

1. AsyncPG Event Loop Conflicts in Automated Tests

  • Challenge: Pytest-asyncio creates fresh asyncio event loops per test. Global database engine connections bound to previous event loops resulted in InterfaceError: cannot perform operation: another operation is in progress.
  • Solution: Implemented an isolated in-memory SQLite test fixture (sqlite+aiosqlite:///:memory:) in tests/conftest.py that overrides the FastAPI get_db dependency during test runs. This keeps unit tests isolated, super fast (passing in under 1s), and independent of external database state.

2. Handling Corrupt & Edge-case Image Binary Streams

  • Challenge: Standard file extension checking can be bypassed by spoofed MIME types or truncated binary data, causing unhandled 500 server crashes during inference tensor transforms.
  • Solution: Implemented deep binary verification using Pillow's Image.open() and image.load() inside load_validated_image. Wrapped decoding exceptions (UnidentifiedImageError, OSError, ValueError, SyntaxError) to return clean HTTP 400 responses with descriptive user error messages.

3. Preventing Front-End Blob Object URL Memory Leaks

  • Challenge: Repeatedly dropping new images into the upload area generated temporary blob: preview URLs without releasing old memory references.
  • Solution: Enhanced ImageUploader.tsx state update callbacks to explicitly revoke previous URLs (URL.revokeObjectURL(prevUrl)) prior to creating new preview URLs.

🛠️ Getting Started

Prerequisites

  • Python >= 3.14 (or standard Python 3.10+)
  • Node.js >= 18
  • uv (recommended) or standard pip & npm

1. Backend Setup

cd backend

# Install dependencies (using uv)
uv sync

# Run database migrations (optional if using PostgreSQL)
uv run alembic upgrade head

# Start FastAPI server
uv run uvicorn app.main:app --reload --port 8000

The backend server will start at http://localhost:8000. You can inspect API docs at http://localhost:8000/docs.

2. Running Backend Tests

cd backend
.venv\Scripts\pytest

3. Frontend Setup

cd frontend

# Install dependencies
npm install

# Start Next.js development server
npm run dev

Open http://localhost:3000 in your browser to launch the dashboard.

Built With

Share this project:

Updates