Skip to main content

Model-agnostic feature-map visualization: PCA-to-RGB feature maps from any vision model and any layer.

Project description

FeatLens

Tests PyPI License: MIT

See what any vision model encodes. FeatLens renders PCA-to-RGB feature maps for any vision model — DINO, DINOv2/v3, CLIP, SigLIP, MAE, DeiT, V-JEPA, CNNs, … — loaded from any source (timm, HuggingFace transformers, torch.hub, an external repo, or a model you built yourself), and from any layer, as a clean model × layer grid.

DINO feature maps across layers

Most "DINO PCA" scripts are welded to one model. FeatLens separates representation access (a small adapter layer over the model zoo) from visualization (robust PCA → RGB), so you can point it at a new model in seconds and compare models/layers side by side.

Gallery

All produced by examples/quickstart.py on the three bundled images. Sizes below are the originals; each image is resized to img_size (default 224) before the model.

visualize(...) — DINO ViT-B/16 feature maps across layers 2 / 5 / 8 / 11:

Image (original size) Source Feature maps
astronaut.jpg · 512×512
cat.jpg · 451×300
coffee.jpg · 600×400

grid(...) — model × layer, overlaid on the image (DINO vs DINOv2 across layers 2/5/8/11):

model x layer grid overlay

compare(...) — models at the final layer  |  custom_adapter — a ResNet-50 (CNN escape hatch)

compare models at last layer    resnet50 feature map

Install

pip install -e ".[timm]"          # timm backend (DINO, CLIP, SigLIP, DeiT, ...)
# extras: [hf] transformers · [clip] open_clip · [all]

Install PyTorch for your platform first (https://pytorch.org).

Quick start (Python)

import featlens as ll

# One model, scrub layers (shared PCA basis -> colors comparable across the row)
ll.visualize("dinov2_vitb14", "img.jpg", layers=[2, 5, 8, 11], out="row.png")

# Compare models at the final layer (per-tile basis)
ll.compare(["dino_vitb16", "mae_vitb16", "clip_large_openai"], "img.jpg", layer=-1, out="cmp.png")

# Full model x layer grid, overlaid on the image
ll.grid(["dino_vitb16", "dinov2_vitb14"], "img.jpg", layers=[2, 5, 8, 11], overlay=True, out="grid.png")

Quick start (CLI)

featlens --models dino_vitb16 clip_large_openai --layers 2 5 8 11 \
    --images examples/images/cat.jpg --mode grid --out out/grid.png
featlens --config configs/example.yaml --images examples/images/cat.jpg --out out/grid.png

Image size & resizing

Images are resized to a square img_size × img_size before the model (default 224). img_size must be divisible by the model's patch size (multiples of 16 for patch-16 models, 14 for patch-14). Larger sizes give a finer feature grid at more compute:

ll.visualize("dinov2_vitb14", "img.jpg", layers=[2, 5, 8, 11], img_size=448)   # 32x32 grid

For non-square images, choose how aspect ratio is handled with resize_mode:

resize_mode behavior
squash (default) resize straight to img_size² — may distort
crop resize shortest side to img_size, center-crop — aspect preserved
pad resize longest side to img_size, pad to square — keeps the whole image
ll.grid([...], "wide.jpg", resize_mode="crop")          # Python
featlens --models dino_vitb16 --images wide.jpg --resize-mode pad --img-size 448 --out g.png

(FeatureGrid(interpolation_size=…) is separate — it only upscales the rendered tiles, not the model input.)

Model sources

Source How to pass it Needs
timm friendly name (dinov2_vitb14) or raw id (vit_base_patch16_224) [timm]
HuggingFace hf:facebook/dinov2-base [hf]
torch.hub (V-JEPA) vjepa2_vitl16 network for weights
External repo (VGGT/SPA/…) external_adapter.load(repo_dir, builder, hook_target=…) the cloned repo
Your own model custom_adapter.load(model, feature_fn=…)

Friendly names (see featlens/registry.py) cover DINO, DINOv2/v3, CLIP, SigLIP, MAE, DeiT, Perception Encoder and V-JEPA; any other timm id works directly.

Layers

layers=[2, 5, 8, 11] selects transformer block indices (0-based, negatives allowed, -1 = last). The same convention holds across backends — for HuggingFace models FeatLens maps block i to hidden_states[i+1] (skipping the embedding output) for you.

Bring your own model

Anything that isn't built in works through the escape hatch — give a feature function or a hook target. CNNs work for free (their conv map is already spatial):

import torch.nn as nn, torchvision
from featlens import FeatureExtractor, FeatureGrid
from featlens.adapters import custom_adapter

resnet = torchvision.models.resnet50(weights="DEFAULT")
trunk = nn.Sequential(*list(resnet.children())[:-2])           # -> [B, 2048, h, w]
lm = custom_adapter.load(trunk, patch_size=32, feature_fn=lambda m, x: m(x), name="resnet50")
FeatureGrid([FeatureExtractor(lm)]).render("img.jpg", out_path="resnet50.png")

For a model in its own repo, external_adapter.load(repo_dir, builder, hook_target="blocks") puts the repo on sys.path, builds the model, and hooks its blocks.

How it works

  1. Adapters resolve a spec → a LoadedModel and drive extraction in one of three modes: forward hooks on per-block modules (ViTs/CNNs/V-JEPA), HF output_hidden_states, or a user callable.
  2. tokens_to_grid normalizes whatever a layer emits ([B,N,D] tokens with optional CLS/register prefixes, or [B,D,h,w] maps) into a dense [B,D,h,w] grid.
  3. Robust PCA (median-absolute-deviation outlier filtering) projects features to RGB; FeatureGrid lays out the model × layer tiles with a per-tile or shared-per-model basis.

The extraction core adapts the FrozenBackbone pattern; the PCA is adapted from the SpaRRTa feature-map script.

Releasing

Releases publish to PyPI automatically via .github/workflows/publish.yml (PyPI Trusted Publishing — no API token stored in the repo).

One-time setup on PyPI: add a trusted publisher for the project (Account → Publishing) with owner turhancan97, repository FeatLens, workflow publish.yml, environment pypi. PyPI supports a pending publisher so the very first release can also go through Actions.

Then cut a release by pushing a tag:

# bump the version in pyproject.toml first, then:
git tag v0.1.0 && git push origin v0.1.0

The workflow builds the sdist + wheel, runs twine check, and uploads to PyPI.

License

MIT.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

featlens-0.1.0.tar.gz (25.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

featlens-0.1.0-py3-none-any.whl (27.4 kB view details)

Uploaded Python 3

File details

Details for the file featlens-0.1.0.tar.gz.

File metadata

  • Download URL: featlens-0.1.0.tar.gz
  • Upload date:
  • Size: 25.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for featlens-0.1.0.tar.gz
Algorithm Hash digest
SHA256 31d7b34c59887db9d80aeb129a33b3ffb95d385b6b8e8b0dc0332e3dc7df6a16
MD5 29f57b8eb3621d05409d4d9df249dc15
BLAKE2b-256 42605aa6663d241da5587c02334a5189d9a11778ff2de9adf4d10815538ff836

See more details on using hashes here.

Provenance

The following attestation bundles were made for featlens-0.1.0.tar.gz:

Publisher: publish.yml on turhancan97/FeatLens

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file featlens-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: featlens-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 27.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for featlens-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 fa1bb53fddd4ae9d152de1af66e37746351954f31c5124f3ae898f39d173bc10
MD5 2edb56d7d3463ed92f63bf98bcdb26c4
BLAKE2b-256 cc78bb34e561884fa7d8733f6af317d14bcfc7d9896dd23f3edb8037cb2f5295

See more details on using hashes here.

Provenance

The following attestation bundles were made for featlens-0.1.0-py3-none-any.whl:

Publisher: publish.yml on turhancan97/FeatLens

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page