Skip to content
Back to skills

Ai Science Vision Rag

ASecurity

Module T5-02: Vision RAG with NumPy

  • 4 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added May 27, 2026
data-aipythonexpresstesting

Works with

  • cli

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro shows the line behind each finding and how to fix it

Scanned May 27, 2026

npx -y skills add Pavel-Kravchenko/Bioinformatics --skill ai-science-vision-rag --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Ai Science Vision Rag?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Ai Science Vision Rag
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/pavel-kravchenko-ai-science-vision-rag/badge)](https://www.skillsdirectory.com/skills/pavel-kravchenko-ai-science-vision-rag)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: ai-science-vision-rag
description: "Module T5-02: Vision RAG with NumPy"
tool_type: python
primary_tool: NumPy
---

# Vision RAG

**Attribution:** Patterns inspired by Unsloth AI and Manuel Faysse Vision RAG tutorials.

## VLM Architecture

A VLM combines:
1. **Visual encoder** — ViT processes image patches
2. **Projection layer** — maps visual tokens to LLM embedding space
3. **LLM decoder** — generates text conditioned on visual + text tokens

**Key VLMs (2024–2025):**
| Model | Size | Strengths |
|---|---|---|
| Qwen2-VL | 2B, 7B, 72B | Multi-image, long context, document understanding |
| LLaVA-1.6 | 7B, 13B, 34B | Strong OCR, open weights |
| InternVL2 | 2B–76B | High benchmark scores |
| PaliGemma | 3B | Compact, versatile |

## Vision RAG vs Text RAG

**Standard text RAG:** chunk → embed → vector store → retrieve top-k → LLM

**Vision RAG (ColPali-style):**
1. Render each PDF page as an image
2. Embed pages with a multimodal model → page embedding matrix
3. At query time: embed query text → compute MaxSim against page matrices
4. Retrieve top-k pages → feed as images to VLM
5. VLM generates answer from visual context

**Advantage:** No OCR — layout, tables, and figures are preserved natively.

## ColPali: Late-Interaction Retrieval

**Single-vector (CLIP-style):** compresses page into one vector → loses spatial detail.

**Late interaction (ColPali/ColBERT-style):**
```python
query → Q matrix (n_tokens × dim)
page  → P matrix (n_patches × dim)
score = Σ_i max_j(Q[i] · P[j])    ← MaxSim
```
Each query token independently finds its best matching page patch → preserves spatial detail.

- **14×14 patches**: 224×224 image ÷ 16×16 px patches = 196 patches. Qwen2-VL uses dynamic resolution (patch count scales with image size).
- **MaxSim is directional**: query→page, not bidirectional.
- **ColPali training**: PaliGemma-2 backbone fine-tuned contrastively on (query, relevant page) pairs from ViDoRe benchmark.

```python
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image, ImageDraw
import warnings
warnings.filterwarnings("ignore")

plt.rcParams.update({"figure.dpi": 110})

def create_synthetic_page(text_content, page_num, width=595, height=842):
    """Create a synthetic PDF page as a PIL Image."""
    img = Image.new("RGB", (width, height), color="white")
    draw = ImageDraw.Draw(img)

    # Title
    draw.rectangle([40, 40, width-40, 90], fill="#f0f0f0")
    draw.text((50, 55), f"Document Page {page_num}", fill="black")

    # Content
    y = 110
    for line in text_content.split("\n")[:20]:
        draw.text((50, y), line[:80], fill="black")
        y += 25
        if y > height - 60:
            break

    # Simulated figure box
    draw.rectangle([50, height//2, width-50, height//2+200], outline="#888", width=2)
    draw.text((width//2-60, height//2+90), f"[Figure {page_num}]", fill="#888")

    return img

# Create synthetic document pages
pages_content = [
    "Introduction to RNA-seq\n\nRNA sequencing is a transcriptomic technique\nthat quantifies gene expression genome-wide.\n\nThe standard pipeline involves:\n1. Library preparation\n2. Sequencing (Illumina)\n3. Quality control (FastQC)\n4. Alignment (STAR, HISAT2)\n5. Quantification (featureCounts)\n6. Differential expression (DESeq2)",
    "Methods: Differential Expression\n\nWe used DESeq2 for differential expression.\nNormalization: median of ratios method.\nStatistical model: negative binomial GLM.\n\nFDR threshold: 0.05 (Benjamini-Hochberg)\nLog2 fold-change threshold: 1.0\n\nTotal genes analyzed: 25,000\nDE genes identified: 1,247",
    "Results: Top DE Genes\n\nUpregulated genes (n=743):\n- MYC: log2FC = 3.2, padj = 1e-45\n- VEGFA: log2FC = 2.8, padj = 2e-38\n- HIF1A: log2FC = 2.1, padj = 5e-29\n\nDownregulated genes (n=504):\n- TP53: log2FC = -2.4, padj = 3e-41\n- RB1: log2FC = -1.9, padj = 7e-35",
    "Discussion\n\nOur findings suggest activation of hypoxic\nresponse pathways as evidenced by HIF1A\nand VEGFA upregulation.\n\nThe downregulation of TP53 and RB1\nis consistent with tumor suppressor\nloss in this cancer subtype.\n\nFuture work: single-cell validation.",
    "Methods: GWAS Analysis\n\nGenotype data from 10,000 samples.\nQuality control: MAF > 0.01, HWE p > 1e-6.\nPopulation stratification corrected by\nincluding top 10 principal components.\n\nAssociation test: logistic regression.\nGenome-wide significance: p < 5e-8.\nClumping: r2 < 0.1, window 250kb.",
]

pages = [create_synthetic_page(content, i+1) for i, content in enumerate(pages_content)]
print(f"Created {len(pages)} synthetic document pages")
```

## MaxSim Implementation

```python
DIM = 128
N_Q_TOKENS = 32
N_PATCHES = 196  # 14×14 patches for 224×224 image

def simulate_page_embedding(page_idx, n_patches=N_PATCHES, dim=DIM, seed=None):
    r = np.random.default_rng(seed or page_idx)
    emb = r.normal(0, 1, (n_patches, dim))
    emb /= np.linalg.norm(emb, axis=1, keepdims=True)
    return emb

def simulate_query_embedding(query_text, n_tokens=N_Q_TOKENS, dim=DIM):
    seed = sum(ord(c) for c in query_text) % 10000
    r = np.random.default_rng(seed)
    emb = r.normal(0, 1, (n_tokens, dim))
    emb /= np.linalg.norm(emb, axis=1, keepdims=True)
    return emb

def maxsim_score(query_emb, page_emb):
    """ColBERT MaxSim: for each query token, find max similarity to any patch."""
    # query_emb: (n_q, dim), page_emb: (n_p, dim)
    scores = query_emb @ page_emb.T  # (n_q, n_p)
    return scores.max(axis=1).sum()  # sum of per-token maxima

# Simulate relevance: page 2 is relevant to differential expression query
page_embeddings = [simulate_page_embedding(i) for i in range(5)]

# Boost page 2 (DESeq2) to be relevant to DE query
relevant_features = simulate_query_embedding("differential expression DESeq2").mean(0)
page_embeddings[1][:10] += relevant_features[:DIM] * 3
page_embeddings[1][:10] /= np.linalg.norm(page_embeddings[1][:10], axis=1, keepdims=True)

# Retrieval
query = "What normalization method was used for differential expression?"
query_emb = simulate_query_embedding(query)
scores = [maxsim_score(query_emb, pe) for pe in page_embeddings]
ranked = sorted(range(5), key=lambda i: -scores[i])

print(f"Query: '{query}'")
for rank, page_idx in enumerate(ranked):
    print(f"  Rank {rank+1}: Page {page_idx+1} (score = {scores[page_idx]:.3f})")
print(f"Recall@1: {int(ranked[0] == 1)}")
```

## Full RAG Pipeline

```python
def simple_rag_pipeline(query, pages, page_embeddings, top_k=2, verbose=True):
    """
    1. Embed query
    2. Retrieve top-k pages by MaxSim
    3. (Simulate) VLM generation from retrieved pages
    """
    query_emb = simulate_query_embedding(query)
    scores = [maxsim_score(query_emb, pe) for pe in page_embeddings]
    top_idx = sorted(range(len(scores)), key=lambda i: -scores[i])[:top_k]

    if verbose:
        print(f"Query: '{query}'")
        print(f"Retrieved pages: {[i+1 for i in top_idx]}")
        print(f"Scores: {[f'{scores[i]:.2f}' for i in top_idx]}")

    # In practice: feed pages[top_idx] as images to Qwen2-VL with the query
    page_contents_local = [pages_content[i] for i in top_idx]
    context_summary = " | ".join([c[:80] for c in page_contents_local])

    return {
        "retrieved_pages": [i+1 for i in top_idx],
        "scores": [scores[i] for i in top_idx],
        "simulated_context": context_summary,
    }

for q in ["What normalization method was used?", "Which genes were upregulated?", "What was the GWAS sample size?"]:
    result = simple_rag_pipeline(q, pages, page_embeddings, top_k=2)
    print()
```

## Qwen2-VL Inference Pattern (GPU required)

```python
#  Install (Colab)
# pip install -q transformers accelerate qwen-vl-utils

from transformers import Qwen2VLForConditionalGeneration, AutoProcessor
from qwen_vl_utils import process_vision_info
import torch

model = Qwen2VLForConditionalGeneration.from_pretrained(
    "Qwen/Qwen2-VL-7B-Instruct",
    torch_dtype=torch.bfloat16,
    device_map="auto",
    attn_implementation="flash_attention_2",
)
processor = AutoProcessor.from_pretrained(
    "Qwen/Qwen2-VL-7B-Instruct",
    min_pixels=256*28*28,
    max_pixels=1280*28*28,
)

def answer_from_pages(pages, question):
    """Answer a question using retrieved document pages."""
    content = [{"type": "image", "image": page} for page in pages]
    content.append({"type": "text", "text": question})

    messages = [{"role": "user", "content": content}]
    text = processor.apply_chat_template(messages, tokenize=False,
                                          add_generation_prompt=True)
    image_inputs, video_inputs = process_vision_info(messages)
    inputs = processor(text=[text], images=image_inputs, padding=True,
                       return_tensors="pt").to(model.device)

    with torch.no_grad():
        ids = model.generate(**inputs, max_new_tokens=512, temperature=0.1, do_sample=False)

    # Decode only the new tokens
    answer = processor.decode(ids[0][inputs.input_ids.shape[1]:], skip_special_tokens=True)
    return answer
```

## Pitfalls

- **Coordinate systems**: BED uses 0-based half-open; VCF/GFF use 1-based inclusive — mixing them causes off-by-one errors
- **Batch effects**: Always check for batch confounding before interpreting biological signal
- **Multiple testing**: Apply FDR correction (Benjamini-Hochberg) when testing thousands of features simultaneously

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…