• bitcoinBitcoin(BTC)$64,312.00-0.50%
  • ethereumEthereum(ETH)$1,902.72-0.20%
  • tetherTether(USDT)$1.000.00%
  • binancecoinBNB(BNB)$591.57-0.80%
  • usd-coinUSDC(USDC)$1.000.00%
  • rippleXRP(XRP)$1.04-3.00%
  • solanaSolana(SOL)$72.65-1.80%
  • tronTRON(TRX)$0.327079-0.30%
  • Figure HelocFigure Heloc(FIGR_HELOC)$1.00-2.20%
  • HyperliquidHyperliquid(HYPE)$56.24-1.90%
  • dogecoinDogecoin(DOGE)$0.068893-1.60%
  • USDSUSDS(USDS)$1.000.00%
  • RainRain(RAIN)$0.012573-0.10%
  • leo-tokenLEO Token(LEO)$9.760.00%
  • zcashZcash(ZEC)$494.92-4.30%
  • cardanoCardano(ADA)$0.2022897.50%
  • moneroMonero(XMR)$369.241.90%
  • whitebitWhiteBIT Coin(WBT)$55.71-0.50%
  • chainlinkChainlink(LINK)$8.180.50%
  • stellarStellar(XLM)$0.161651-3.00%
  • daiDai(DAI)$1.000.00%
  • bitcoin-cashBitcoin Cash(BCH)$212.81-1.20%
  • USD1USD1(USD1)$1.000.00%
  • Ethena USDeEthena USDe(USDE)$1.000.00%
  • the-open-networkGram (prev. Toncoin)(GRAM)$1.37-2.10%
  • CantonCanton(CC)$0.091042-10.80%
  • litecoinLitecoin(LTC)$45.420.70%
  • Global DollarGlobal Dollar(USDG)$1.00-0.10%
  • Circle USYCCircle USYC(USYC)$1.130.00%
  • hedera-hashgraphHedera(HBAR)$0.068402-1.60%
  • avalanche-2Avalanche(AVAX)$6.45-3.20%
  • paypal-usdPayPal USD(PYUSD)$1.000.00%
  • shiba-inuShiba Inu(SHIB)$0.000005-4.00%
  • suiSui(SUI)$0.67-2.30%
  • BlackRock USD Institutional Digital Liquidity FundBlackRock USD Institutional Digital Liquidity Fund(BUIDL)$1.000.00%
  • tether-goldTether Gold(XAUT)$4,221.25-0.20%
  • crypto-com-chainCronos(CRO)$0.053336-1.40%
  • uniswapUniswap(UNI)$4.01-1.10%
  • nearNEAR Protocol(NEAR)$1.66-2.20%
  • Ondo US Dollar YieldOndo US Dollar Yield(USDY)$1.140.20%
  • pax-goldPAX Gold(PAXG)$4,233.65-0.20%
  • BittensorBittensor(TAO)$192.21-2.10%
  • okbOKB(OKB)$85.36-0.60%
  • OndoOndo(ONDO)$0.358734-3.10%
  • World Liberty FinancialWorld Liberty Financial(WLFI)$0.052622-1.50%
  • HTX DAOHTX DAO(HTX)$0.0000020.00%
  • AsterAster(ASTER)$0.60-1.00%
  • Ripple USDRipple USD(RLUSD)$1.000.00%
  • usddUSDD(USDD)$1.000.10%
  • MemeCoreMemeCore(M)$1.14-7.40%
TradePoint.io
  • Main
  • AI & Technology
  • Stock Charts
  • Market & News
  • Business
  • Finance Tips
  • Trade Tube
  • Blog
  • Shop
No Result
View All Result
TradePoint.io
No Result
View All Result

Pixel-Native RAG: A Practical Guide to Visual Document Indexing

August 4, 2026
in AI & Technology
Reading Time: 6 mins read
A A
Pixel-Native RAG: A Practical Guide to Visual Document Indexing
ShareShareShareShareShare

YOU MAY ALSO LIKE

OpenAI’s Ring-Shaped Smart Speaker Will Reportedly Cost Between $300 And $400

Cloudflare Introduces Kitesurf: An Agent-First Web Browser That Runs Entirely in V8 Isolates on Cloudflare Workers

@dataclass
class Tile:
   tile_id: str
   doc_id: str
   source: str
   kind: str
   page: int
   seq: int
   y0: int
   y1: int
   path: str
   ocr_text: str = ""
   title: str = ""
def _doc_id_from_source(src: str) -> str:
   tail = src.rstrip("/").split("/")[-1] or src
   tail = re.sub(r"\.(html?|pdf|png|jpg)$", "", tail, flags=re.I)
   return re.sub(r"[^A-Za-z0-9_.\-()]+", "_", tail)[:80] or hashlib.md5(src.encode()).hexdigest()[:10]
def _ahash(img, size: int = 8) -> int:
   """64-bit average hash — cheap near-duplicate detection for repeated headers."""
   import numpy as np
   g = img.convert("L").resize((size, size))
   a = np.asarray(g, dtype="float32")
   bits = (a > a.mean()).flatten()
   out = 0
   for b in bits:
       out = (out << 1) | int(b)
   return out
def _hamming(a: int, b: int) -> int:
   return bin(a ^ b).count("1")
def _is_informative(img, cfg: Config) -> bool:
   """Reject blank / solid-colour tiles before they ever reach the GPU."""
   import numpy as np
   a = np.asarray(img.convert("L"), dtype="float32")
   return float(a.std()) >= cfg.blank_std_threshold
def _save_tile(img, out_dir: Path, name: str) -> str:
   out_dir.mkdir(parents=True, exist_ok=True)
   p = out_dir / f"{name}.png"
   img.convert("RGB").save(p, format="PNG", optimize=True)
   return str(p)
def slice_image_to_tiles(img, cfg: Config, *, doc_id: str, source: str, kind: str,
                        page: int, out_dir: Path, start_seq: int = 0,
                        seen_hashes: Optional[List[int]] = None,
                        title: str = "") -> List[Tile]:
   """Vertical sliding window with overlap. Used for PDFs and text fallback."""
   from PIL import Image
   seen_hashes = seen_hashes if seen_hashes is not None else []
   W, H = img.size
   if W != cfg.tile_width:
       new_h = max(1, int(H * cfg.tile_width / W))
       img = img.resize((cfg.tile_width, new_h))
       W, H = img.size
   step = max(1, cfg.tile_height - cfg.tile_overlap)
   tiles: List[Tile] = []
   y, seq = 0, start_seq
   while y < H and (seq - start_seq) < cfg.max_tiles_per_doc:
       h = min(cfg.tile_height, H - y)
       if h < cfg.min_tile_height and seq > start_seq:
           break
       crop = img.crop((0, y, W, y + h))
       if _is_informative(crop, cfg):
           hsh = _ahash(crop)
           if all(_hamming(hsh, s) > cfg.dedup_hamming for s in seen_hashes):
               seen_hashes.append(hsh)
               tid = f"{doc_id}__p{page}__t{seq}"
               tiles.append(Tile(
                   tile_id=tid, doc_id=doc_id, source=source, kind=kind, page=page,
                   seq=seq, y0=y, y1=y + h, title=title,
                   path=_save_tile(crop, out_dir, tid),
               ))
               seq += 1
       y += step
   return tiles
_JS_AUTOSCROLL = """
async () => {
 await new Promise((resolve) => {
   let y = 0;
   const timer = setInterval(() => {
     window.scrollBy(0, 800);
     y += 800;
     if (y >= document.body.scrollHeight || y > 40000) {
       clearInterval(timer);
       window.scrollTo(0, 0);
       setTimeout(resolve, 250);
     }
   }, 40);
 });
}
"""
_JS_FLATTEN = """
() => {
 document.querySelectorAll('*').forEach((el) => {
   const s = getComputedStyle(el);
   if (s.position === 'fixed' || s.position === 'sticky') el.style.position = 'absolute';
 });
 document.querySelectorAll('[role="dialog"], .cookie, #cookie-banner, .cc-banner')
   .forEach((el) => el.remove());
}
"""
_CSS_CLEANUP = """
* { animation: none !important; transition: none !important;
   scroll-behavior: auto !important; }
html { -webkit-font-smoothing: antialiased; }
video, iframe[src*="youtube"] { visibility: hidden !important; }
"""
_UA = ("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) "
      "Chrome/124.0 Safari/537.36 PixelRAG-Tutorial/1.0")
async def _render_urls_async(urls: List[str], cfg: Config, out_dir: Path) -> List[Tile]:
   from playwright.async_api import async_playwright
   from PIL import Image
   all_tiles: List[Tile] = []
   async with async_playwright() as pw:
       browser = await pw.chromium.launch(headless=True, args=cfg.headless_args)
       ctx = await browser.new_context(
           viewport={"width": cfg.tile_width, "height": cfg.tile_height},
           device_scale_factor=cfg.device_scale,
           user_agent=_UA,
           java_script_enabled=True,
       )
       for url in urls:
           doc_id = _doc_id_from_source(url)
           page = await ctx.new_page()
           try:
               await page.goto(url, wait_until="domcontentloaded", timeout=cfg.nav_timeout_ms)
               try:
                   await page.wait_for_load_state("networkidle", timeout=12000)
               except Exception:
                   pass
               await page.evaluate(_JS_AUTOSCROLL)
               await page.add_style_tag(content=_CSS_CLEANUP)
               await page.evaluate(_JS_FLATTEN)
               title = (await page.title()) or doc_id
               height = await page.evaluate(
                   "() => Math.max(document.body.scrollHeight, "
                   "document.documentElement.scrollHeight)")
               height = int(min(height, cfg.max_page_height))
               step = max(1, cfg.tile_height - cfg.tile_overlap)
               seen: List[int] = []
               y, seq = 0, 0
               while y < height and seq < cfg.max_tiles_per_doc:
                   h = min(cfg.tile_height, height - y)
                   if h < cfg.min_tile_height and seq > 0:
                       break
                   buf = await page.screenshot(
                       full_page=True, type="png",
                       clip={"x": 0, "y": y, "width": cfg.tile_width, "height": h})
                   img = Image.open(io.BytesIO(buf)).convert("RGB")
                   if img.size[0] != cfg.tile_width:
                       img = img.resize((cfg.tile_width,
                                         max(1, int(img.size[1] * cfg.tile_width / img.size[0]))))
                   if _is_informative(img, cfg):
                       hsh = _ahash(img)
                       if all(_hamming(hsh, s) > cfg.dedup_hamming for s in seen):
                           seen.append(hsh)
                           tid = f"{doc_id}__p0__t{seq}"
                           all_tiles.append(Tile(
                               tile_id=tid, doc_id=doc_id, source=url, kind="web",
                               page=0, seq=seq, y0=y, y1=y + h, title=title,
                               path=_save_tile(img, out_dir, tid)))
                           seq += 1
                   y += step
               log.info("  rendered %-34s -> %2d tiles (page %dpx)", doc_id, seq, height)
           except Exception as exc:
               log.warning("  FAILED %s (%s)", url, type(exc).__name__)
           finally:
               await page.close()
       await ctx.close()
       await browser.close()
   return all_tiles
def render_urls(urls: List[str], cfg: Config, out_dir: Path) -> List[Tile]:
   """Screenshot every URL into tiles; degrade to the text renderer on failure."""
   try:
       tiles = run_async(_render_urls_async(urls, cfg, out_dir))
       if tiles:
           return tiles
       log.warning("Browser produced no tiles — using text-render fallback.")
   except Exception as exc:
       log.warning("Playwright unavailable (%s: %s) — using text-render fallback.",
                   type(exc).__name__, str(exc)[:160])
   return [t for u in urls for t in render_url_as_text(u, cfg, out_dir)]
def _strip_html(html: str) -> str:
   html = re.sub(r"(?is)<(script|style|nav|footer|header|noscript).*?", " ", html)
   html = re.sub(r"(?s)", " ", html)
   html = re.sub(r"(?i)", "\n", html)
   text = re.sub(r"(?s)<[^>]+>", " ", html)
   for a, b in [(" ", " "), ("&", "&"), ("<", "<"), (">", ">"), (""", '"')]:
       text = text.replace(a, b)
   text = re.sub(r"\[\d+\]", "", text)
   text = re.sub(r"[ \t]+", " ", text)
   return re.sub(r"\n{2,}", "\n", text).strip()
def _mono_font(size: int = 20):
   from PIL import ImageFont
   for cand in ("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
                "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf"):
       if os.path.exists(cand):
           return ImageFont.truetype(cand, size)
   try:
       import matplotlib.font_manager as fm
       return ImageFont.truetype(fm.findfont("DejaVu Sans"), size)
   except Exception:
       return ImageFont.load_default()
def text_to_image(text: str, cfg: Config, title: str = "") -> Any:
   """Render plain text onto a tall white canvas — a browser-free stand-in."""
   from PIL import Image, ImageDraw
   font, tfont = _mono_font(20), _mono_font(30)
   pad, lh, wrap = 40, 30, max(20, (cfg.tile_width - 80) // 11)
   lines: List[str] = []
   for para in text.split("\n"):
       para = para.strip()
       if not para:
           continue
       while len(para) > wrap:
           cut = para.rfind(" ", 0, wrap)
           cut = cut if cut > 0 else wrap
           lines.append(para[:cut])
           para = para[cut:].lstrip()
       lines.append(para)
   lines = lines[:900]
   height = pad * 2 + 60 + lh * len(lines)
   img = Image.new("RGB", (cfg.tile_width, max(cfg.tile_height, height)), "white")
   d = ImageDraw.Draw(img)
   d.text((pad, pad), title[:60], font=tfont, fill=(15, 15, 15))
   for i, ln in enumerate(lines):
       d.text((pad, pad + 60 + i * lh), ln, font=font, fill=(35, 35, 35))
   return img
def render_url_as_text(url: str, cfg: Config, out_dir: Path) -> List[Tile]:
   import requests
   doc_id = _doc_id_from_source(url)
   try:
       r = requests.get(url, timeout=30, headers={"User-Agent": _UA})
       r.raise_for_status()
       body = _strip_html(r.text)
       m = re.search(r"(?is)(.*?)", r.text)
       title = m.group(1).strip() if m else doc_id
   except Exception as exc:
       log.warning("  fetch failed for %s (%s)", url, type(exc).__name__)
       return []
   img = text_to_image(body, cfg, title=title)
   log.info("  text-rendered %-30s -> canvas %dpx", doc_id, img.size[1])
   return slice_image_to_tiles(img, cfg, doc_id=doc_id, source=url, kind="text",
                               page=0, out_dir=out_dir, title=title)
def render_pdf(pdf_path: str, cfg: Config, out_dir: Path, dpi: int = 150) -> List[Tile]:
   import fitz
   from PIL import Image
   doc_id = _doc_id_from_source(pdf_path)
   tiles: List[Tile] = []
   with fitz.open(pdf_path) as doc:
       title = (doc.metadata or {}).get("title") or doc_id
       n_pages = doc.page_count
       for pno in range(n_pages):
           pix = doc[pno].get_pixmap(dpi=dpi)
           img = Image.frombytes("RGB", (pix.width, pix.height), pix.samples)
           tiles += slice_image_to_tiles(img, cfg, doc_id=doc_id, source=pdf_path,
                                         kind="pdf", page=pno, out_dir=out_dir,
                                         title=title)
   log.info("  rendered %-34s -> %2d tiles (%d pages)", doc_id, len(tiles), n_pages)
   return tiles
def make_synthetic_pdf(path: Path) -> str:
   """A tiny PDF so the tutorial always exercises the PDF path, offline or not."""
   import fitz
   body = [
       ("PixelRAG Internal Note", 22),
       ("", 12),
       ("Why pixel-native retrieval?", 16),
       ("Parsers are per-site glue code. A renderer is one code path for every", 11),
       ("document type: HTML, PDF, scanned fax, spreadsheet export, dashboard.", 11),
       ("", 11),
       ("Tiling policy", 16),
       ("Tiles are 1024x1024 with 128px of vertical overlap. Overlap keeps a", 11),
       ("sentence or table row from being split across two embeddings, which is", 11),
       ("the single biggest source of recall loss in naive screenshot pipelines.", 11),
       ("", 11),
       ("Serving", 16),
       ("FAISS inner-product over L2-normalised vectors equals cosine similarity.", 11),
       ("Tile scores are max-pooled per document so one strong tile can surface", 11),
       ("a long page, mirroring late-interaction retrieval behaviour.", 11),
       ("", 11),
       ("The mitochondria reference is a joke; the overlap advice is not.", 11),
   ]
   doc = fitz.open()
   page = doc.new_page()
   y = 72
   for line, size in body:
       page.insert_text((72, y), line, fontsize=size, fontname="helv")
       y += size + 8
   doc.save(str(path))
   doc.close()
   return str(path)

Credit: Source link

ShareTweetSendSharePin

Related Posts

OpenAI’s Ring-Shaped Smart Speaker Will Reportedly Cost Between 0 And 0
AI & Technology

OpenAI’s Ring-Shaped Smart Speaker Will Reportedly Cost Between $300 And $400

August 6, 2026
Cloudflare Introduces Kitesurf: An Agent-First Web Browser That Runs Entirely in V8 Isolates on Cloudflare Workers
AI & Technology

Cloudflare Introduces Kitesurf: An Agent-First Web Browser That Runs Entirely in V8 Isolates on Cloudflare Workers

August 6, 2026
Suno Is Adding Audio Watermarks So AI-Generated Songs Are More Easily Identifiable
AI & Technology

Suno Is Adding Audio Watermarks So AI-Generated Songs Are More Easily Identifiable

August 6, 2026
OpenAI Gives Free ChatGPT Users Unlimited Text Chats on GPT-5.6 Luna – Unite.AI
AI & Technology

OpenAI Gives Free ChatGPT Users Unlimited Text Chats on GPT-5.6 Luna – Unite.AI

August 6, 2026
Next Post
Texas Governor Orders Audit Of New Data Centers After Realizing Over 400 Gigawatts Of Power Is A Lot

Texas Governor Orders Audit Of New Data Centers After Realizing Over 400 Gigawatts Of Power Is A Lot

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Search

No Result
View All Result
Fireworks light up the sky behind Mount Rushmore for America’s 250th anniversary

Fireworks light up the sky behind Mount Rushmore for America’s 250th anniversary

August 6, 2026
Tech Bottom In or Still Coming? Paul Meeks Gives His Rapid Fire Answers

Tech Bottom In or Still Coming? Paul Meeks Gives His Rapid Fire Answers

August 5, 2026
Why Did $META Crash 10% After Reporting Earnings?

Why Did $META Crash 10% After Reporting Earnings?

July 31, 2026

About

Learn more

Our Services

Legal

Privacy Policy

Terms of Use

Bloggers

Learn more

Article Links

Contact

Advertise

Ask us anything

©2020- TradePoint.io - All rights reserved!

Tradepoint.io, being just a publishing and technology platform, is not a registered broker-dealer or investment adviser. So we do not provide investment advice. Rather, brokerage services are provided to clients of Tradepoint.io by independent SEC-registered broker-dealers and members of FINRA/SIPC. Every form of investing carries some risk and past performance is not a guarantee of future results. “Tradepoint.io“, “Instant Investing” and “My Trading Tools” are registered trademarks of Apperbuild, LLC.

This website is operated by Apperbuild, LLC. We have no link to any brokerage firm and we do not provide investment advice. Every information and resource we provide is solely for the education of our readers. © 2020 Apperbuild, LLC. All rights reserved.

No Result
View All Result
  • Main
  • AI & Technology
  • Stock Charts
  • Market & News
  • Business
  • Finance Tips
  • Trade Tube
  • Blog
  • Shop

© 2023 - TradePoint.io - All Rights Reserved!