<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Artists of Intelligence with Harpreet Sahota]]></title><description><![CDATA[Hands-on field notes from Harpreet Sahota on AI engineering, computer vision, multimodal models, agent evaluation, and physical AI, with code and benchmarks.]]></description><link>https://hashnode.employabledatascientist.com</link><image><url>https://cdn.hashnode.com/uploads/logos/6ae4b6ada536be2ed7cfa368/5f1907ee-330e-469a-a6fd-ba9b3b452754.png</url><title>Artists of Intelligence with Harpreet Sahota</title><link>https://hashnode.employabledatascientist.com</link></image><generator>RSS for Node</generator><lastBuildDate>Fri, 30 Oct 2026 18:31:20 GMT</lastBuildDate><atom:link href="https://hashnode.employabledatascientist.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Why Vision-Only Imitation Fails at Delicate Contact: Low-Cost Dexterous Teleoperation & Tactile Policies]]></title><description><![CDATA[Why scaling camera tokens and 10 Hz foundation models hits a wall during high-DoF dexterous manipulation, and what sub-$35 shear sensors and handheld…]]></description><link>https://hashnode.employabledatascientist.com/dexterous-teleoperation-tactile-policies</link><guid isPermaLink="true">https://hashnode.employabledatascientist.com/dexterous-teleoperation-tactile-policies</guid><category><![CDATA[robotics]]></category><category><![CDATA[Tactile Sensing]]></category><category><![CDATA[physical AI]]></category><category><![CDATA[Policy Learning]]></category><category><![CDATA[Imitation Learning]]></category><category><![CDATA[hardware]]></category><category><![CDATA[AI]]></category><dc:creator><![CDATA[Artists of Intelligence with Harpreet Sahota]]></dc:creator><pubDate>Wed, 28 Oct 2026 21:19:25 GMT</pubDate><content:encoded><![CDATA[<p>Tabletop manipulation demos look solved until you try to thread a flexible cable, pick up a credit card off a polished desk, or re-orient an Allen wrench in-hand.</p>
<p>The modern imitation learning playbook—Action Chunking with Transformers (ACT), Diffusion Policies, and Vision-Language-Action (VLA) models—has made impressive strides on 6-DoF parallel jaw grippers. But moving from a rigid two-finger clamp to a 16-to-24 DoF dexterous multi-finger hand reveals a fundamental physics barrier: <strong>vision cannot observe contact state once the hand touches the object.</strong></p>
<p>Here is the breakdown of why vision-only foundation models break at the contact patch, and three recent open-source papers that cut the cost of dexterous data collection and tactile sensing.</p>
<hr />
<h2>1. The Physics Gap: 10 Hz Cameras vs. 15 Millisecond Slip Dynamics</h2>
<p>Most modern Vision-Language-Action models run inference at <strong>5 Hz to 15 Hz</strong>. To make that usable on physical hardware, policies rely on action chunking: predicting the next 30 to 50 waypoints and streaming them to low-level motor controllers without querying the camera on every millisecond.</p>
<p>In delicate manipulation, physics operates on a completely different timeline:</p>
<ol>
<li><strong>Contact Occlusion:</strong> The moment a robotic finger makes contact with a small object, the finger itself occludes the line of sight. The camera cannot observe the millimeter-scale boundary between the silicone fingertip and the object surface.</li>
<li><strong>The Slip Timeline:</strong> Micro-slip and shear deformation occur in <strong>10 to 20 milliseconds</strong>. If an object starts to slip out of a robotic grasp at millisecond zero, a 10 Hz visual loop won't receive the frame until millisecond 100, and won't finish transformer inference until millisecond 180. By the time the policy commands a correction, the object has already dropped.</li>
<li><strong>The Missing Shear Gradient:</strong> An RGB camera cannot measure pressure. More importantly, it cannot measure <em>shear</em>—the lateral friction forces parallel to the surface that signal incipient slip before gross translational displacement occurs.</li>
</ol>
<p>Tokenizing video frames does not solve this, because video tokens do not contain the friction coefficient of a surface or the internal stress state of a contact pad.</p>
<hr />
<h2>2. The Competing Research Paradigms</h2>
<p>This constraint divides the physical AI ecosystem into two distinct architectures:</p>
<table>
<thead>
<tr>
<th align="left">Architectural Dimension</th>
<th align="left">Paradigm A: End-to-End VLA Foundation Models</th>
<th align="left">Paradigm B: Decoupled High-Frequency Tactile Policies</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>Representative Systems</strong></td>
<td align="left">OpenVLA, π0, RT-2</td>
<td align="left">DexPort (ACT/Diffusion) + VecTile / VisTa3D tactile models</td>
</tr>
<tr>
<td align="left"><strong>Inference Control Rate</strong></td>
<td align="left">5 Hz – 15 Hz</td>
<td align="left">100 Hz – 340 Hz closed-loop</td>
</tr>
<tr>
<td align="left"><strong>Sensory Modality</strong></td>
<td align="left">Multi-view RGB camera streams</td>
<td align="left">RGB (coarse targeting) + 3-DoF tactile force arrays (two shear axes plus normal force)</td>
</tr>
<tr>
<td align="left"><strong>Data Collection Rig</strong></td>
<td align="left">$30k–$80k dual-arm stationary robot cell</td>
<td align="left">Handheld skill interface (&lt;$500) + sub-$35 tactile array</td>
</tr>
<tr>
<td align="left"><strong>Primary Failure Mode</strong></td>
<td align="left">Visual occlusion at contact patch; catastrophic slip on thin structures</td>
<td align="left">Trajectory coordinate handoff when mapping handheld wrist demos to an arm</td>
</tr>
</tbody></table>
<hr />
<h2>3. Ground-Truth Research Teardown</h2>
<p>Rather than relying on theoretical arguments, three recent papers directly measure and address these failure modes.</p>
<h3>Paper 1: DexPort — Decoupling Handheld Data Collection from Stationary Arms</h3>
<ul>
<li><strong>Citation:</strong> Amber Xie, Jiawei Fu, Xiaofeng Guo, Carmelo Sferrazza, Guanya Shi, Haozhi Qi (UC Berkeley, CMU). <em>DexPort: A Hand-Held Skill Interface for Dexterous Hand Data Collection</em>, <a href="https://arxiv.org/abs/2610.32969">arXiv:2610.32969</a> (2026).</li>
<li><strong>Project Page:</strong> <a href="https://amberxie88.github.io/dexport/">amberxie88.github.io/dexport/</a></li>
</ul>
<p>In standard teleoperation, capturing high-DoF dexterous demonstrations requires mounting the hand to a 7-axis stationary robot arm. Operators are forced to fight the arm's mechanical inertia and reach limits while simultaneously trying to coordinate 16+ finger joints. Operator fatigue introduces noise and jerky motions into the dataset.</p>
<p>DexPort decouples the robot hand from the robot arm during data collection. The operator holds a lightweight, handheld ergonomic rig directly in unstructured real-world environments (kitchens, workshops), driving the hand via a small library of intuitive coordination skills (pinching, twisting, power grasp).</p>
<p><strong>Empirical Results:</strong></p>
<ul>
<li>Evaluated across 2,500+ demonstrations across 4 manipulation tasks.</li>
<li>Visuomotor policies trained on recorded robot commands rather than measured states improved task success from <strong>11% to 64%</strong>.</li>
<li>Augmenting training with diverse in-the-wild demonstrations collected via the portable rig boosted success from <strong>36% to 78%</strong>.</li>
</ul>
<h3>Paper 2: VecTile — Sub-$35 Flexible 3-DoF Shear Sensing Arrays</h3>
<ul>
<li><strong>Citation:</strong> Jian Hou, Xin Zhou, Adam J. Spiers (Imperial College London). <em>VecTile: A Low-Cost Flexible Tactile Array for 3-DoF Force Sensing</em>, <a href="https://arxiv.org/abs/2610.31472">arXiv:2610.31472</a> (2026).</li>
<li><strong>Project Page:</strong> <a href="https://jiannnh.github.io/VecTile/">jiannnh.github.io/VecTile/</a></li>
</ul>
<p>Optical-elastomer tactile sensors (GelSight, DIGIT) use internal cameras to track rubber deformation. While high resolution, they are bulky, fragile, expensive, and introduce image-processing latency into the control loop.</p>
<p>VecTile is a flexible, &lt;6 mm thick tactile array featuring 16 taxels (64 sensing channels) built from commercial flexible PCB (FPC), piezoresistive fabric, and cast silicone nubs.</p>
<ul>
<li><strong>The Physical Mechanism:</strong> Each silicone nub sits over four quadrant electrodes. Normal force (pressing straight in) depresses all four quadrants equally and is read from their sum. Shear force (sideways friction) tilts the nub and is read from the pressure difference between opposite quadrants.</li>
<li><strong>Cost:</strong> The sensor stack costs <strong>$3.57</strong> in materials. Total BOM with readout electronics is <strong>$34.67</strong> ($14.67 using an ATmega328P clone board).</li>
<li><strong>Performance:</strong> Benchmarked against an ATI Mini40 6-axis reference sensor on a UR5e arm at <strong>343 Hz</strong>. Normal force RMSE was <strong>0.39 N</strong>; shear force RMSE was <strong>0.16 N</strong>, with normal and shear forces decoupling cleanly at <strong>86°–90°</strong> in signal space.</li>
</ul>
<h3>Paper 3: VisTa3D — Empirical Benchmark Proof of Thin-Object Vision Failure</h3>
<ul>
<li><strong>Citation:</strong> Shania Guo, Yeongsik Seo, Andrew Fu, Mei Hao, Iris Xia, Jiwon Jenny Lee, Xinyi Mary Xie, Hyoungseob Park, Aaron Dollar, Alex Wong (Yale University). <em>VisTa3D: A Dataset and Benchmark for Thin Object Reconstruction from Vision, Tactile, and 3D Point Clouds</em>, ECCV 2026, <a href="https://arxiv.org/abs/2608.20740">arXiv:2608.20740</a>.</li>
<li><strong>Dataset Repository:</strong> <a href="https://huggingface.co/datasets/shaniaguo/VisTa3D">huggingface.co/datasets/shaniaguo/VisTa3D</a></li>
</ul>
<p>To test the limits of vision and range sensors on thin structures, the authors constructed 387 scenes with 70 thin objects (wires, sheet materials, utensils) across 17 environments. They captured synchronized RGB imagery, range depth maps, IMU data, and tactile response maps against laser-scanned ground truth.</p>
<p><strong>Empirical Finding:</strong> Current state-of-the-art vision and depth point cloud models exhibit severe degradation on thin objects due to minimal pixel footprint. Integrating tactile response maps provided the local geometry and physical deformation data required to recover accurate 3D boundaries.</p>
<hr />
<h2>4. What Engineering Teams Should Build Today</h2>
<ol>
<li><strong>Decouple Semantic Planning from Contact Execution:</strong> Use large vision models (or VLAs) strictly for coarse spatial targeting and semantic object selection (1–5 Hz). Transition control to a compact (50M–100M parameter) policy running closed-loop on proprioceptive and tactile signals at 100+ Hz once the hand is within 2 cm of contact.</li>
<li><strong>Prioritize Triaxial Shear Over Image Resolution:</strong> Measuring normal compression alone tells you how hard you are squeezing, but shear force tells you if the object is about to slip. Flexible, low-cost shear arrays like VecTile sample at 300+ Hz without overloading compute budgets.</li>
<li><strong>Audit Demonstration Rig Friction:</strong> If your teleoperation rig forces operators to fight arm inertia, your demonstration dataset will encode human fatigue artifacts. Portable, decoupled capture rigs produce cleaner demonstration trajectories and higher policy success rates.</li>
</ol>
<hr />
<h2>5. Inspectable Reference Manifest</h2>
<ol>
<li><strong>DexPort:</strong> Xie et al., <em>DexPort: A Hand-Held Skill Interface for Dexterous Hand Data Collection</em>, <a href="https://arxiv.org/abs/2610.32969">arXiv:2610.32969</a> (2026). Code &amp; CAD: <a href="https://amberxie88.github.io/dexport/">amberxie88.github.io/dexport/</a></li>
<li><strong>VecTile:</strong> Hou et al., <em>VecTile: A Low-Cost Flexible Tactile Array for 3-DoF Force Sensing</em>, <a href="https://arxiv.org/abs/2610.31472">arXiv:2610.31472</a> (2026). Schematics &amp; Firmware: <a href="https://jiannnh.github.io/VecTile/">jiannnh.github.io/VecTile/</a></li>
<li><strong>VisTa3D:</strong> Guo et al., <em>VisTa3D: A Dataset and Benchmark for Thin Object Reconstruction from Vision, Tactile, and 3D Point Clouds</em>, ECCV 2026, <a href="https://arxiv.org/abs/2608.20740">arXiv:2608.20740</a>. Dataset: <a href="https://huggingface.co/datasets/shaniaguo/VisTa3D">huggingface.co/datasets/shaniaguo/VisTa3D</a></li>
</ol>
<h2>Frequently asked questions</h2>
<h3>Why does vision-only imitation learning fail at delicate dexterous manipulation?</h3>
<p>Vision cannot observe contact state once the hand touches the object, because the finger occludes the line of sight. Cameras also cannot measure pressure or shear, the lateral friction forces that signal incipient slip, and video tokens do not contain surface friction or contact pad stress.</p>
<h3>How does the 10 Hz control loop of VLA models compare to slip timing?</h3>
<p>Vision-language-action models run at 5 Hz to 15 Hz, while micro-slip occurs in 10 to 20 milliseconds. A slip at millisecond zero reaches a 10 Hz loop at millisecond 100 and finishes transformer inference at millisecond 180, so the object has already dropped.</p>
<h3>What did DexPort show about handheld dexterous data collection?</h3>
<p>DexPort decouples the robot hand from the arm so operators use a handheld rig. Across 2,500+ demonstrations on 4 tasks, training on recorded robot commands improved success from 11% to 64%, and adding in-the-wild demonstrations raised success from 36% to 78%.</p>
<h3>How much does the VecTile tactile sensor cost and how accurate is it?</h3>
<p>VecTile is a flexible 3-DoF tactile array with a total bill of materials of $34.67, or $14.67 with an ATmega328P clone board. Against an ATI Mini40 at 343 Hz, normal force RMSE was 0.39 N and shear force RMSE was 0.16 N.</p>
<h3>How should teams split control between vision models and tactile policies in dexterous manipulation?</h3>
<p>Use large vision models or VLAs only for coarse spatial targeting and object selection at 1 to 5 Hz. Once the hand is within 2 cm of contact, hand control to a compact 50M to 100M parameter policy running closed-loop on proprioceptive and tactile signals at 100+ Hz.</p>
<hr />
<p><em>This post first appeared as <a href="https://employabledatascientist.com/blog/dexterous-teleoperation-tactile-policies/">the original article</a>. I'm Harpreet Sahota, Hacker-in-Residence at <a href="https://employabledatascientist.com">The Employable Data Scientist</a>, where we help AI and developer-tool teams ship prototypes, evaluations, and technical content. I also host the <a href="https://employabledatascientist.com/podcast/">Artists of Intelligence</a> podcast. For new field notes by email, <a href="https://harpreetsahota.substack.com">subscribe on Substack</a>.</em></p>
]]></content:encoded></item><item><title><![CDATA[Finding a Needle in the Desert]]></title><description><![CDATA[A Reddit user lost a handmade RC plane over the desert. See how FiftyOne's plugin system and Brain embeddings collapsed 3,000 aerial images into minutes of…]]></description><link>https://hashnode.employabledatascientist.com/find-rc-plane-fiftyone-plugin</link><guid isPermaLink="true">https://hashnode.employabledatascientist.com/find-rc-plane-fiftyone-plugin</guid><category><![CDATA[delegatedoperators]]></category><category><![CDATA[Computer Vision]]></category><category><![CDATA[Machine Learning]]></category><category><![CDATA[AI]]></category><dc:creator><![CDATA[Artists of Intelligence with Harpreet Sahota]]></dc:creator><pubDate>Wed, 17 Jun 2026 00:00:00 GMT</pubDate><enclosure url="https://cdn.sanity.io/images/h6toihm1/production/d15462689fd464ef88c236aee9ab28cd944b6e52-1920x1080.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>How FiftyOne and its plugin system turned a stranger's 3,000-image search into a few minutes of review</em></p>
<h2><strong>It started with</strong> <a href="https://www.reddit.com/r/computervision/comments/1u76ln1/need_help_searching_3000_aerial_images_to_locate/?solution=4a52bd37ce51e3c14a52bd37ce51e3c1&amp;js_challenge=1&amp;token=7afd7253fec22262ff1c52b1703fe9ec0cb0b316f070e310521779930c721b76&amp;jsc_orig_r&amp;share_id=WSF-g6MLHI5WU0ckJMQ1h&amp;utm_medium=ios_app&amp;utm_name=ioscss&amp;utm_source=share&amp;utm_term=1"><strong>a Reddit post</strong></a></h2>
<p>Part of my job as a developer advocate at Voxel51 is to keep an eye out for real, messy, unglamorous computer vision problems — the kind that never show up in benchmarks but are exactly what practitioners actually face. So when this post crossed my feed, I couldn't resist:</p>
<p><em>Hey guys. I recently lost a turbine RC plane over the desert and ran an aerial mapping grid over the search area. I now have about 3,000 high-res images to comb through and was thinking that maybe someone knew how to cook up a quick cv script that could flag stuff for manual review. I built the plane myself and it would be sick to find it.</em></p>
<p><em>Note: one of the images is renamed "0000" — it's a false positive that has a shadow on the top left that looks EXACTLY like the plane... (I went and checked it out irl).</em></p>
<p>Three thousand near-identical desert tiles. A handmade carbon-fiber airframe somewhere in the frame. And a known decoy — a shadow that fooled the pilot in person. This is a search problem, a data problem, and a tooling problem all at once. It's also a perfect showcase for the workflow I evangelize every day: stop writing one-off scripts, and start treating your dataset as the thing you build, query, and share.</p>
<h2>Encapsulating the Workflow with FiftyOne</h2>
<p>The instinct most people have here is to write a standalone OpenCV script that dumps a CSV file and a contact sheet JPEG. That gets you exactly one answer, once, with no way to iterate. FiftyOne flips the model: the dataset is the source of truth, every computation writes back onto it, and the App becomes your review surface. Tooling becomes reusable, results become inspectable, and the whole thing becomes shareable.</p>
<p>Concretely, three pieces of the platform carried this project: the FiftyOne Brain for embeddings and uniqueness, the Hugging Face integration for distribution, and — the part I'm most excited about — the plugin system for packaging the actual detector.</p>
<h2>From a folder of images to a queryable dataset</h2>
<p><img src="https://image.mux.com/TGTUZIpaK8cFgGz38X02xd78XS0156g02FDlGg4vCur3XE/animated.gif?width=640" alt="" /></p>
<p>Loading the survey is one call, and the FiftyOne Brain turns raw pixels into something you can search and visualize — CLIP embeddings, a similarity index, and a 2D UMAP map you can lasso in the App:</p>
<pre><code>import fiftyone as fo
import fiftyone.zoo as foz
import fiftyone.brain as fob

dataset = fo.Dataset.from_dir(
    dataset_dir="scans",
    dataset_type=fo.types.ImageDirectory,
    name="Ariel_Scans",
    persistent=True,
)
dataset.compute_metadata()

clip_model = foz.load_zoo_model("open-clip-torch")
dataset.compute_embeddings(clip_model, embeddings_field="clip_embeddings")
fob.compute_similarity(dataset, embeddings="clip_embeddings", brain_key="clip_sim")
fob.compute_visualization(
    dataset, embeddings="clip_embeddings", brain_key="clip_viz", method="umap"
)
</code></pre>
<p>And because every artifact — images, metadata, embeddings, indexes — lives on the dataset, I could <a href="https://huggingface.co/datasets/harpreetsahota/ariel_scans">publish the whole thing to the Hugging Face Hub</a> and let anyone pull it back in a single line. No setup, no recompute:</p>
<pre><code>from fiftyone.utils.huggingface import load_from_hub

dataset = load_from_hub("harpreetsahota/ariel_scans", persistent=True)
</code></pre>
<h2>The detector, packaged as a plugin</h2>
<p><img src="https://image.mux.com/ImqmCgpm02pRrvir02iBuqtxu0000MTFB3PsZPw01lllSZR4/animated.gif?width=640" alt="" /></p>
<p>Here's the heart of it. The desert background is almost uniform mid-gray, and the only dark features are scrubby vegetation — so tone alone can't separate the plane from a bush. The detector instead scores each dark blob on shape and surface character: a carbon-fiber airframe is elongated, fills its bounding box, has a smooth low-variance interior, and is solid and convex. Vegetation is none of those.</p>
<p>I could have left that as a script. Instead, I <a href="https://github.com/harpreetsahota204/plane_finder">wrapped it as a FiftyOne plugin</a> — and that decision is the whole point of this post. A plugin exposes the logic as operators that run identically from the Python SDK and from the App's UI, can be delegated to run in the background, and can be installed by anyone with one command:</p>
<pre><code>fiftyone plugins download https://github.com/harpreetsahota204/plane_finder
</code></pre>
<p>The plugin ships two operators:</p>
<ul>
<li><strong>preview_dark_mask —</strong> a calibration helper that writes the thresholded dark mask back as a heatmap overlay, so you can tune the threshold by eye in the App before committing to a full run.</li>
<li><strong>find_plane —</strong> the detector, which writes a plane_candidates detections field (score in confidence, plus per-detection attributes like rect_aspect and interior_std) and a per-image max_plane_candidates_score for ranking.</li>
</ul>
<p>Driving them from the SDK is just get_operator and a call:</p>
<pre><code>import fiftyone.operators as foo

find_plane = foo.get_operator("@harpreetsahota/plane_finder/find_plane")

# Keep only confident candidates, cap boxes per image so the field stays clean
await find_plane(
    dataset,
    label_field="plane_candidates",
    dark_threshold=105,
    min_score=0.5,
    max_per_image=5,
)
</code></pre>
<p>The exact same operator, with the exact same form inputs, is one backtick-keystroke away in the App — every parameter rendered as a labeled, described field, with a checkbox to delegate execution for the full 6,500-image run. I didn't write that UI. The plugin system generated it from the operator's input schema. That parity between code and UI is, to me, the single most underrated thing about building on FiftyOne.</p>
<h2>Turning 3,000 images into ranked shortlists</h2>
<p>Once the scores are on the dataset, review is just querying. Saved views persist on the dataset and show up in the App's Saved Views menu, so the community member opens the data and immediately has a set of curated entry points — each attacking the search from a different angle:</p>
<pre><code>from fiftyone import ViewField as F

# Everything with a candidate, ranked; then the top 100 for a fast first pass
candidates_by_score = dataset.match(
    F("max_plane_candidates_score") &gt; 0
).sort_by("max_plane_candidates_score", reverse=True)
dataset.save_view("candidates_by_score", candidates_by_score, overwrite=True)
dataset.save_view("top_shortlist", candidates_by_score.limit(100), overwrite=True)

# The most airframe-like crops: elongated AND smooth-surfaced
carbon_like = dataset.filter_labels(
    "plane_candidates",
    (F("rect_aspect") &gt; 3.0) &amp; (F("interior_std") &lt; 13.0),
).sort_by("max_plane_candidates_score", reverse=True)
dataset.save_view("carbon_like", carbon_like, overwrite=True)
</code></pre>
<p>Being able to filter on the detector's own per-detection attributes (rect_aspect, interior_std, solidity, bbox_diag) is what makes this expressive. I saved eight views in total:</p>
<ul>
<li><strong>candidates_by_score —</strong> every flagged sample, ranked by its best score.</li>
<li><strong>top_shortlist —</strong> the top 100 candidates — the fastest first pass.</li>
<li><strong>high_confidence —</strong> only the strong detections, weak boxes dropped.</li>
<li><strong>carbon_like —</strong> the most airframe-like: elongated and smooth-surfaced.</li>
<li><strong>best_geometry —</strong> strongest pure shape, independent of surface smoothness.</li>
<li><strong>review_band —</strong> the mid-scoring "maybe" pile, so nothing borderline is missed.</li>
<li><strong>thermal_frames —</strong> the small thermal/IR frames, reviewed apart from the RGB.</li>
<li><strong>unusual_candidates —</strong> candidates ranked by Brain uniqueness — the plane is an anomaly in a sea of identical desert.</li>
</ul>
<h2>Zooming in: patch embeddings</h2>
<p>The sharpest tool is to explode every candidate into its own crop with to_patches, then run the Brain on the crops instead of the whole frames. Each embedding now describes just the blob, so look-alikes cluster tightly: find one true patch and a similarity search pulls the rest, and ranking by uniqueness floats the real airframe above the repetitive bush and shadow patches.</p>
<pre><code>patches = dataset.to_patches("plane_candidates")
dataset.save_view("plane_patches", patches, overwrite=True)

dataset.compute_patch_embeddings(
    clip_model, "plane_candidates", embeddings_field="patch_embeddings"
)
fob.compute_similarity(
    dataset, patches_field="plane_candidates",
    embeddings="patch_embeddings", brain_key="patch_sim",
)
fob.compute_visualization(
    dataset, patches_field="plane_candidates",
    embeddings="patch_embeddings", brain_key="patch_viz", method="umap",
)

fob.compute_uniqueness(
    patches, embeddings="patch_embeddings", uniqueness_field="patch_uniqueness"
)
</code></pre>
<p>I bundled the view-building and patch-embedding step into a single publish_views.py script that recomputes everything and pushes the enriched dataset back to the Hub. One note worth flagging for anyone adapting it: because compute_embeddings spins up a multiprocessing DataLoader, the script body lives inside a main() guarded by if <strong>name</strong> == "<strong>main</strong>" — otherwise the spawned workers re-import and re-run the whole pipeline.</p>
<pre><code>def main():
    dataset = fo.load_dataset("Ariel_Scans")
    # ... build the eight views, compute patch embeddings ...
    dataset.save()
    push_to_hub(dataset, "ariel_scans", exist_ok=True)

if __name__ == "__main__":
    main()
</code></pre>
<h2>Why the plugin system is the real story</h2>
<p>Strip away the desert and the drone, and what this project really demonstrates is what the FiftyOne plugin system unlocks for developers:</p>
<ul>
<li><strong>Write once, run anywhere —</strong> one operator definition runs from the SDK in a notebook, from a script, and as a point-and-click panel in the App — with the UI generated from the input schema.</li>
<li><strong>Scale without rewrites —</strong> the same operator runs inline on a small view or as a delegated background job over thousands of full-res images, just by toggling a flag.</li>
<li><strong>Shareable by design —</strong> fiftyone plugins download  means the community member runs my exact detector without copying code or matching my environment by hand.</li>
<li><strong>Composable with the rest of the platform —</strong> the plugin's output is ordinary FiftyOne labels, so it immediately works with views, the Brain, similarity search, and the Hub.</li>
</ul>
<p>That's the pitch I make to developers all the time, and this is it in miniature: the gap between "I hacked together a CV script" and "I shipped a reusable, UI-enabled, shareable tool" is a thin plugin wrapper around code you've already written.</p>
<h2>Did it find the plane?</h2>
<p>The tooling did its job: it collapsed a 3,000-image haystack into a handful of short, ranked lists a human can review in minutes, kept the notorious "0000" shadow decoy honest, and shipped as a dataset and a plugin anyone can pull down and rerun. Whether the airframe turns up is now a question of eyes on a shortlist rather than endurance — which is exactly where you want a search-and-rescue problem to land.</p>
<p>If you want to try it yourself: pull the dataset from the Hub, install the plugin from GitHub, and start at the top of top_shortlist. And if you build something like this, come share it with the <a href="https://voxel51.com/community">Voxel51 community</a> — the next messy real-world problem is always around the corner.</p>
<p><em>And if the plane does turn up, somewhere a Redditor owes the internet fifty bucks.</em></p>
<hr />
<p><em>This post first appeared as <a href="https://voxel51.com/blog/find-rc-plane-fiftyone-plugin">the original article</a>. I'm Harpreet Sahota, Hacker-in-Residence at <a href="https://employabledatascientist.com">The Employable Data Scientist</a>, where we help AI and developer-tool teams ship prototypes, evaluations, and technical content. I also host the <a href="https://employabledatascientist.com/podcast/">Artists of Intelligence</a> podcast. For new field notes by email, <a href="https://harpreetsahota.substack.com">subscribe on Substack</a>.</em></p>
]]></content:encoded></item><item><title><![CDATA[Building Production-Ready AI Document Understanding Pipelines with GLM-OCR]]></title><description><![CDATA[Build production-ready AI document understanding pipelines. Extract structured data, handle tables & formulas, and streamline OCR workflows.]]></description><link>https://hashnode.employabledatascientist.com/build-ai-document-understanding-pipelines</link><guid isPermaLink="true">https://hashnode.employabledatascientist.com/build-ai-document-understanding-pipelines</guid><category><![CDATA[plugins]]></category><category><![CDATA[Computer Vision]]></category><category><![CDATA[Machine Learning]]></category><category><![CDATA[AI]]></category><dc:creator><![CDATA[Artists of Intelligence with Harpreet Sahota]]></dc:creator><pubDate>Wed, 11 Feb 2026 00:00:00 GMT</pubDate><enclosure url="https://cdn.sanity.io/images/h6toihm1/production/3ec365bc825af201afa94b2862c02b67f8cd33da-1920x1080.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><img src="https://image.mux.com/iJsJka00XFwbv01gzzycctj01PfPcZja00bv201SjLc7005yE/animated.gif?width=640" alt="" /></p>
<p>Document understanding has long been one of the most challenging problems in computer vision. Traditional OCR vision systems excel at character recognition but struggle with complex layouts, tables, formulas, and structured data extraction. Enter GLM-OCR—a lightweight yet powerful multimodal model that's changing how we approach document parsing.</p>
<p>In this post, we'll explore what document understanding is and how to build efficient, production-ready AI document understanding pipelines using GLM-OCR integrated with <a href="https://voxel51.com/fiftyone">FiftyOne</a>, a powerful dataset management and visualization platform. We'll cover everything from basic text extraction to advanced structured data parsing, with practical document understanding integration examples you can run today.</p>
<h2>What is document understanding?</h2>
<p>Document understanding is an AI-powered approach that goes beyond traditional OCR, enabling machines to read, interpret, and extract structured data from documents. By combining vision and language understanding, AI document understanding systems can handle tables, formulas, and layouts, making OCR vision systems smarter and streamlining workflows with seamless document understanding integration.</p>
<h2>How GLM-OCR differs from traditional OCR vision systems</h2>
<p>GLM-OCR represents a fundamental shift in how we approach document understanding. To appreciate its value, we need to understand how it differs from traditional OCR vision systems.</p>
<h3>Traditional OCR vision systems: Character recognition only</h3>
<p>Classic OCR vision systems like Tesseract, PaddleOCR, or commercial solutions follow a character-by-character recognition approach:</p>
<ul>
<li><strong>Image preprocessing:</strong> binarization, noise reduction</li>
<li><strong>Text detection:</strong> finding text regions</li>
<li><strong>Character recognition:</strong> identifying individual characters</li>
<li><strong>Post-processing:</strong> spell checking, language models</li>
</ul>
<p><strong>Limitations:</strong></p>
<ul>
<li>Outputs flat text strings—no understanding of document structure.</li>
<li>Struggles with complex layouts (tables become garbled text).</li>
<li>Cannot handle formulas (mathematical expressions become meaningless text).</li>
<li>Requires extensive post-processing to extract structured data.</li>
<li>No semantic understanding—just character patterns.</li>
</ul>
<h3>GLM-OCR: Document understanding through multimodal AI</h3>
<p>GLM-OCR takes a completely different approach. Instead of treating documents as collections of characters, it uses vision-language models to understand documents semantically:</p>
<ol>
<li><strong>Visual-Language Fusion:</strong> The CogViT encoder captures both pixel-level details AND layout semantics simultaneously.</li>
<li><strong>Contextual Reasoning:</strong> Uses language understanding to interpret relationships between text blocks, tables, and figures.</li>
<li><strong>Structure-Aware Generation:</strong> Generates structured outputs (Markdown, JSON, LaTeX) that preserve document semantics.</li>
<li><strong>Multi-Token Prediction:</strong> Predicts multiple tokens per step and uses context to correct errors—like semantic proofreading.</li>
</ol>
<p><strong>Key differences:</strong></p>
<p><img src="https://cdn.sanity.io/images/h6toihm1/production/e1b1b55abcdfcb843b240dc147bef001f9e1982f-2048x1272.png" alt="" /></p>
<h3>Why GLM-OCR vision systems matters</h3>
<p>Traditional OCR vision systems give you text, but you still need to parse tables manually or with complex regex, reconstruct formulas from text descriptions, build custom parsers for each document type, and handle edge cases and formatting variations.</p>
<p>GLM-OCR gives you semantic structure directly:</p>
<ul>
<li>Tables are already in HTML/Markdown format.</li>
<li>Formulas are ready-to-render LaTeX.</li>
<li>Structured data matches your exact schema.</li>
<li>Document hierarchy is preserved.</li>
</ul>
<p>This fundamentally changes how you build document processing pipelines—from "extract text then parse" to "extract structure directly".</p>
<p><strong>Key Advantages:</strong></p>
<ul>
<li><strong>Lightweight:</strong> At 0.9B parameters, it runs efficiently on consumer hardware.</li>
<li><strong>Multimodal:</strong> Handles PDFs and images with equal proficiency.</li>
<li><strong>Structure-First:</strong> Outputs semantic Markdown, JSON, or LaTeX—not just text.</li>
<li><strong>Open Source:</strong> Apache-2.0 licensed, deployable anywhere.</li>
<li><strong>Context-Aware:</strong> Uses language understanding to improve accuracy.</li>
</ul>
<h2>The FiftyOne document understanding integration</h2>
<p>While GLM-OCR is powerful on its own, integrating document understanding with FiftyOne unlocks several key benefits:</p>
<ol>
<li><strong>Efficient Batching:</strong> Native support for batched inference, dramatically improving throughput.</li>
<li><strong>Dataset Management:</strong> Seamless integration with FiftyOne's dataset workflows.</li>
<li><strong>Visualization:</strong> Built-in tools for exploring OCR results and debugging.</li>
<li><strong>Flexibility:</strong> Easy switching between operation modes without reloading models.</li>
</ol>
<p><strong><a href="https://docs.voxel51.com/plugins/plugins_ecosystem/glm_ocr.html">Get started now →</a></strong></p>
<h2>Getting started with your document understanding integration</h2>
<h3>Installation</h3>
<p>First, install the required dependencies. Since GLM-OCR is relatively new, you'll need the latest transformers from source:</p>
<pre><code>pip install git+https://github.com/huggingface/transformers.git
pip install -U timm fiftyone
</code></pre>
<p>For the best text viewing experience in FiftyOne, install the Caption Viewer plugin: </p>
<pre><code>fiftyone plugins download https://github.com/harpreetsahota204/caption_viewer
</code></pre>
<p>This plugin provides intelligent formatting for OCR outputs with proper line breaks, table rendering, and JSON pretty-printing—essential for reviewing complex document extractions.</p>
<h3>Loading your dataset</h3>
<p>FiftyOne makes it easy to work with document datasets. Let's start with a sample dataset from Hugging Face Hub:</p>
<pre><code>import fiftyone as fo
from fiftyone.utils.huggingface import load_from_hub

dataset = load_from_hub(
  "Voxel51/scanned_receipts",
  overwrite=True,
  persistent=True,
  name="scanned_receipts",
  max_samples=50
)
</code></pre>
<p>This loads 50 scanned receipt images—perfect for demonstrating GLM-OCR's capabilities.</p>
<h3>Registering and loading GLM-OCR</h3>
<p>The FiftyOne integration makes GLM-OCR available as a zoo model: </p>
<pre><code>import fiftyone.zoo as foz

# Register the model source
foz.register_zoo_model_source(
  "https://github.com/harpreetsahota204/glm_ocr",
  overwrite=True
)

# Load the model
model = foz.load_zoo_model("zai-org/GLM-OCR")
</code></pre>
<p>The model automatically detects and uses the best available device (CUDA, MPS, or CPU), making it easy to get started regardless of your hardware setup.</p>
<h2>Four operation modes for document understanding</h2>
<p>GLM-OCR supports four distinct operation modes, each optimized for different use cases:</p>
<h3>1. Text recognition</h3>
<p>The most straightforward mode—extract plain text while preserving formatting:</p>
<pre><code>model.operation = "text"
dataset.apply_model(
  model,
  label_field="text_extraction",
  batch_size=8,
  num_workers=2
)
</code></pre>
<p><strong>Output:</strong> Clean text with preserved layout and structure. Perfect for general OCR tasks where you need readable text output.</p>
<h3>2. Formula recognition</h3>
<p>Extract mathematical formulas in LaTeX format—essential for scientific and technical documents:</p>
<pre><code>model.operation = "formula"
dataset.apply_model(model, label_field="formulas", batch_size=8)
</code></pre>
<p><strong>Output:</strong> LaTeX-formatted mathematical expressions ready for rendering in scientific documents or notebooks.</p>
<h3>3. Table recognition</h3>
<p>Parse complex table structures into HTML or Markdown:</p>
<pre><code>model.operation = "table"
dataset.apply_model(model, label_field="tables", batch_size=8)
</code></pre>
<p><strong>Output:</strong> Properly structured tables in HTML or Markdown format, preserving cell relationships and hierarchies.</p>
<h3>4. Custom structured extraction</h3>
<p>The most powerful mode—extract structured data using custom JSON schema prompts:</p>
<pre><code>model.operation = "custom"
model.custom_prompt = """请按下列JSON格式输出图中信息:
{
  "company": "",
  "address": {
    "street": "",
    "city": "",
    "state": "",
    "zip_code": ""
  },
  "invoice_number": "",
  "dates": {
    "purchase_date": "",
    "purchase_time": ""
  },
  "items": [
    {"name": "", "quantity": "", "price": ""}
  ],
  "totals": {
    "purchase_amount": "",
    "tax_amount": "",
    "total": ""
  },
  "payment": {
    "payment": "",
    "change": ""
  }
}
"""

dataset.apply_model(
  model,
  label_field="structured_extraction",
  batch_size=8,
  num_workers=2
)
</code></pre>
<p><strong>Output:</strong> JSON-formatted structured data matching your exact schema. This is incredibly powerful for building ETL pipelines, data warehouses, or API integrations.</p>
<p><strong><a href="https://docs.voxel51.com/plugins/plugins_ecosystem/glm_ocr.html">Get started now →</a></strong></p>
<h2>Real-world document understanding example</h2>
<p>Let's see GLM-OCR's document understanding in action with a real receipt. After running text extraction, here's what we get:</p>
<pre><code>Sarang Hae Yo
TRENDYMAX (M) SDN. BHD.
Company Reg. No.: (583246-A).
P6, Block C, GM Klang Wholesale City,
Jalan Kasuarina 1,
41200 Klang, Selangor D.E.
Whatsapp : +6012-5060668
www.saranghaeyo.com
GST Reg : 000384098304

TAX INVOICE
CB#: GM3-46792 15/01/2018 1:48:36 PM
Cashier: W001 - erah @ C6
Remarks:

Qty Description Price Disc Total
1 MS LOYALTY PACKAGE
15.00 0.00 15.00 SR
10 IB-RDM IRON BASKET ROUND 27 * 25
28.90 -28.90 260.10 SR
...
</code></pre>
<p>The text extraction preserves the layout beautifully. But with structured extraction, we get machine-readable JSON:</p>
<pre><code>{
  "company": "Sarang Hae Yo",
  "address": {
    "street": "P6, Block C, GM Klang Wholesale City, Jalan Kasuarina 1,",
    "city": "Klang",
    "state": "Selangor D.E.",
    "zip_code": "41200"
  },
  "invoice_number": "GM3-46792",
  "dates": {
    "purchase_date": "2018-01-15",
    "purchase_time": "14:36 PM"
  },
  "items": [
    {
      "name": "MS LOYALTY PACKAGE",
      "quantity": "1",
      "price": "15.00"
    },
    {
      "name": "IB-RDM IRON BASKET ROUND 27 * 25",
      "quantity": "10",
      "price": "28.90"
    }
  ],
  "totals": {
    "purchase_amount": "329.10",
    "tax_amount": "329.10",
    "total": "329.10"
  }
}
</code></pre>
<p>This structured output can be directly ingested into databases, analytics systems, or financial software—no manual parsing required.</p>
<h2>Visualizing results</h2>
<p>One of FiftyOne's greatest strengths is its visualization capabilities. Launch the app to explore your results:</p>
<pre><code>session = fo.launch_app(dataset)
</code></pre>
<h3>Using the caption viewer plugin</h3>
<p>The <a href="https://docs.voxel51.com/plugins/plugins_ecosystem/caption_viewer.html">caption viewer plugin</a> transforms how you review OCR vision system outputs:</p>
<ol>
<li>Click on any sample to open the modal view</li>
<li>Click the + button to add a panel</li>
<li>Select "<strong>Caption Viewer</strong>" from the panel list</li>
<li>In the panel menu (☰), select the field you want to view:<ol>
<li><code>text_extraction</code> for plain OCR text</li>
<li><code>structured_extraction</code> for JSON outputs</li>
<li>Any other text field</li>
</ol>
</li>
</ol>
<p>The plugin automatically:</p>
<ul>
<li>Renders line breaks properly</li>
<li>Converts HTML tables to markdown</li>
<li>Pretty-prints JSON content</li>
<li>Shows character counts</li>
</ul>
<p>This makes it easy to spot issues, verify accuracy, and understand what the model extracted.</p>
<h2>Performance optimization</h2>
<p>GLM-OCR vision system delivers impressive throughput: approximately 1.86 pages/second for PDFs and 0.67 images/second. The FiftyOne integration's batching support significantly improves efficiency.</p>
<h3>Batch size recommendations</h3>
<p>Choose your batch size based on available hardware:</p>
<ul>
<li><strong>CPU:</strong> <code>batch_size=2-4</code> (slower but works anywhere)</li>
<li><strong>GPU (8GB):</strong> <code>batch_size=8-16</code> (good balance)</li>
<li><strong>GPU (16GB+):</strong> <code>batch_size=16-32</code> (maximum throughput)</li>
</ul>
<h3>Runtime configuration</h3>
<p>One of the integration's best features is the ability to change operation modes without reloading the model:</p>
<pre><code># Start with text recognition
model.operation = "text"
dataset.apply_model(model, label_field="text")

# Switch to table recognition
model.operation = "table"
dataset.apply_model(model, label_field="tables")

# Use custom prompt for structured extraction
model.operation = "custom"
model.custom_prompt = "Extract invoice data as JSON..."
dataset.apply_model(model, label_field="invoice_data")
</code></pre>
<p>You can also adjust generation parameters such as increasing max tokens for longer outputs:</p>
<pre><code># Increase max tokens for longer outputs
model.max_new_tokens = 16384

# Adjust batch size for memory constraints
model.batch_size = 4
</code></pre>
<h2>Real-world structured output use cases</h2>
<p>The true power of GLM-OCR vision systems emerges when you need structured data, not just text. Here are practical use cases where structured extraction is essential, along with how to use the output downstream.</p>
<h3>Use case 1: Financial document processing</h3>
<p><strong>Challenge:</strong> Extract structured financial data from invoices, receipts, and statements for accounting automation.</p>
<p><strong>Why structured output is essential:</strong> Financial systems require specific fields (amounts, dates, line items) in exact formats. Flat text requires complex parsing that breaks with format variations.</p>
<p><strong>Implementation:</strong></p>
<pre><code>model.operation = "custom"
model.custom_prompt = """Extract invoice information as JSON:
{
  "vendor": "",
  "invoice_number": "",
  "date": "",
  "due_date": "",
  "line_items": [
    {"description": "", "quantity": "", "unit_price": "", "total": ""}
  ],
  "subtotal": "",
  "tax": "",
  "total": "",
  "payment_terms": ""
}"""

dataset.apply_model(model, label_field="invoice_data")
</code></pre>
<p><strong>Downstream Usage:</strong></p>
<pre><code>import json
from accounting_system import InvoiceProcessor

processor = InvoiceProcessor()

for sample in dataset:
  invoice_json = json.loads(sample.invoice_data)

  # Direct database insertion
  invoice_id = processor.create_invoice(
    vendor=invoice_json["vendor"],
    invoice_number=invoice_json["invoice_number"],
    date=invoice_json["date"],
    total=float(invoice_json["total"])
  )
  
  # Add line items
    for item in invoice_json["line_items"]:
      processor.add_line_item(
        invoice_id=invoice_id,
        description=item["description"],
        quantity=float(item["quantity"]),
        unit_price=float(item["unit_price"])
  )
  
  # Trigger approval workflow if amount exceeds threshold
  if float(invoice_json["total"]) &gt; 10000:
    processor.send_for_approval(invoice_id)
</code></pre>
<p><strong>Benefits:</strong> No manual data entry, automatic categorization, immediate integration with accounting software.</p>
<h3>Use case 2: Medical record digitization</h3>
<p><strong>Challenge:</strong> Extract structured patient information from scanned medical forms, lab reports, and prescriptions.</p>
<p><strong>Why structured output is essential:</strong> <a href="https://voxel51.com/industries/healthcare">Healthcare systems</a> require structured data for EHR integration, insurance claims, and clinical decision support. Unstructured text cannot be reliably parsed.</p>
<p><strong>Implementation:</strong></p>
<pre><code>model.operation = "custom"
model.custom_prompt = """Extract patient information from medical document as JSON:
{
  "patient_name": "",
  "date_of_birth": "",
  "patient_id": "",
  "visit_date": "",
  "vital_signs": {
    "blood_pressure": "",
    "heart_rate": "",
    "temperature": "",
    "weight": ""
  },
  "diagnosis": [],
  "medications": [
    {"name": "", "dosage": "", "frequency": ""}
  ],
  "lab_results": [
    {"test_name": "", "value": "", "unit": "", "reference_range": ""}
  ],
  "physician": ""
}"""

dataset.apply_model(model, label_field="medical_data")
</code></pre>
<p><strong>Downstream usage:</strong></p>
<pre><code>from hl7 import create_patient_message
from ehr_system import EHRClient

ehr = EHRClient()

for sample in dataset:
  medical_json = json.loads(sample.medical_data)

  # Create HL7 message for EHR integration
  hl7_message = create_patient_message(
    patient_id=medical_json["patient_id"],
    name=medical_json["patient_name"],
    dob=medical_json["date_of_birth"]
  )
  
  # Send to EHR system
  ehr.send_message(hl7_message)

  # Extract and validate lab results
  for lab in medical_json["lab_results"]:
    ehr.add_lab_result(
      patient_id=medical_json["patient_id"],
      test_name=lab["test_name"],
      value=float(lab["value"]),
      unit=lab["unit"],
      reference_range=lab["reference_range"]
    )
    
    # Flag abnormal values
    if not ehr.is_normal_range(lab["value"], lab["reference_range"]):
      ehr.alert_physician(medical_json["physician"], lab)

  # Check for drug interactions
  medications = [m["name"] for m in medical_json["medications"]]
  interactions = ehr.check_drug_interactions(medications)
  if interactions:
    ehr.alert_pharmacist(interactions)
</code></pre>
<p><strong>Benefits:</strong> Automated EHR population, real-time alerts for abnormal values, drug interaction checking, reduced manual data entry errors.</p>
<h3>Use case 3: Legal document analysis</h3>
<p><strong>Challenge:</strong> Extract structured information from contracts, NDAs, and legal agreements for compliance monitoring and analysis.</p>
<p><strong>Why structured output is essential:</strong> Legal analysis requires identifying specific clauses, dates, parties, and obligations. These must be structured for automated compliance checking and <a href="https://voxel51.com/industries/security">risk assessment</a>.</p>
<p><strong>Implementation:</strong></p>
<pre><code>model.operation = "custom"
model.custom_prompt = """Extract contract information as JSON:
{
  "parties": [
  {"name": "", "role": ""}
  ],
  "effective_date": "",
  "expiration_date": "",
  "key_clauses": {
    "termination": "",
    "liability": "",
    "confidentiality": "",
    "payment_terms": "",
    "governing_law": ""
  },
  "obligations": [
    {"party": "", "obligation": "", "deadline": ""}
  ],
  "financial_terms": {
    "amount": "",
    "currency": "",
    "payment_schedule": ""
  },
  "renewal_terms": ""
}"""

dataset.apply_model(model, label_field="contract_data")
</code></pre>
<p><strong>Downstream usage:</strong></p>
<pre><code>from contract_analyzer import ContractAnalyzer
from compliance_checker import ComplianceChecker

analyzer = ContractAnalyzer()
compliance = ComplianceChecker()

for sample in dataset:
  contract_json = json.loads(sample.contract_data)
  
  # Store in contract management system
  contract_id = analyzer.store_contract(contract_json)

  # Check compliance against company policies
  violations = compliance.check_policy(
    liability_clause=contract_json["key_clauses"]["liability"],
    termination_clause=contract_json["key_clauses"]["termination"],
    payment_terms=contract_json["key_clauses"]["payment_terms"]
  )
  
  if violations:
    analyzer.flag_for_review(contract_id, violations)

  # Extract obligations for tracking
  for obligation in contract_json["obligations"]:
    analyzer.create_task(
      contract_id=contract_id,
      assigned_to=obligation["party"],
      description=obligation["obligation"],
      deadline=obligation["deadline"]
    )

  # Set expiration reminders
  analyzer.set_expiration_alert(
    contract_id=contract_id,
    expiration_date=contract_json["expiration_date"],
    days_before=90
  )

  # Generate summary for legal team
  summary = analyzer.generate_summary(contract_json)
  analyzer.notify_legal_team(contract_id, summary)
</code></pre>
<p><strong>Benefits:</strong> Automated compliance checking, obligation tracking, expiration monitoring, risk flagging, reduced manual review time.</p>
<h3>Use case 4: Academic paper processing</h3>
<p><strong>Challenge:</strong> Extract structured metadata, formulas, and references from research papers for knowledge base construction.</p>
<p><strong>Why structured output is essential:</strong> Academic databases require structured metadata (authors, affiliations, citations). Formulas must be in LaTeX for rendering. References need to be parseable for citation networks.</p>
<p><strong>Implementation:</strong></p>
<pre><code># First, extract structured metadata
model.operation = "custom"
model.custom_prompt = """Extract paper metadata as JSON:
{
  "title": "",
  "authors": [
    {"name": "", "affiliation": "", "email": ""}
  ],
  "abstract": "",
  "keywords": [],
  "publication_date": "",
  "journal": "",
  "doi": "",
  "references": [
    {"title": "", "authors": "", "year": "", "doi": ""}
  ],
  "sections": [
    {"heading": "", "content": ""}
  ]
}"""

dataset.apply_model(model, label_field="paper_metadata")

# Then extract formulas separately
model.operation = "formula"
dataset.apply_model(model, label_field="formulas")
</code></pre>
<p><strong>Downstream usage:</strong></p>
<pre><code>from knowledge_graph import KnowledgeGraph
from citation_network import CitationNetwork
from latex_renderer import render_formula

kg = KnowledgeGraph()
citation_net = CitationNetwork()

for sample in dataset:
  metadata = json.loads(sample.paper_metadata)

  # Create paper node in knowledge graph
  paper_id = kg.create_paper(
    title=metadata["title"],
    abstract=metadata["abstract"],
    doi=metadata["doi"],
    publication_date=metadata["publication_date"]
  )
  
  # Add authors and affiliations
  for author in metadata["authors"]:
    author_id = kg.get_or_create_author(author["name"])
    kg.link_author_to_paper(author_id, paper_id)

    if author["affiliation"]:
      org_id = kg.get_or_create_organization(author["affiliation"])
      kg.link_author_to_org(author_id, org_id)

  # Build citation network
  for ref in metadata["references"]:
    cited_paper_id = citation_net.get_or_create_paper(
      title=ref["title"],
      authors=ref["authors"],
      year=ref["year"]
    )
    citation_net.add_citation(paper_id, cited_paper_id)

  # Extract and render formulas
  if sample.formulas:
    formulas = sample.formulas.split("\n\n") # Assuming formulas separated by double newline
    for i, formula in enumerate(formulas):
      rendered_image = render_formula(formula)
      kg.add_formula(paper_id, formula, rendered_image, position=i)

  # Extract key concepts from sections
  for section in metadata["sections"]:
    concepts = kg.extract_concepts(section["content"])
    for concept in concepts:
      kg.link_paper_to_concept(paper_id, concept)

  # Generate recommendations based on citation network
  similar_papers = citation_net.find_similar(paper_id, limit=10)
  kg.add_recommendations(paper_id, similar_papers)
</code></pre>
<p><strong>Benefits:</strong> Automated knowledge graph construction, citation network analysis, formula rendering, concept extraction, paper recommendations.</p>
<h3>Use case 5: Supply chain document processing</h3>
<p><strong>Challenge:</strong> Extract structured data from shipping manifests, customs forms, and delivery receipts for logistics automation.</p>
<p><strong>Why structured output is essential:</strong> Logistics systems require structured data for tracking, customs clearance, and inventory management. Each document type has specific fields that must be extracted accurately.</p>
<p><strong>Implementation:</strong></p>
<pre><code>model.operation = "custom"
model.custom_prompt = """Extract shipping manifest information as JSON:
{
  "shipper": {
    "name": "",
    "address": "",
    "contact": ""
  },
  "consignee": {
    "name": "",
    "address": "",
    "contact": ""
  },
  "shipment_id": "",
  "tracking_number": "",
  "ship_date": "",
  "expected_delivery": "",
  "items": [
    {
      "description": "",
      "quantity": "",
      "weight": "",
      "value": "",
      "hs_code": ""
    }
  ],
  "total_weight": "",
  "total_value": "",
  "carrier": "",
  "service_type": "",
  "special_instructions": ""
}"""

dataset.apply_model(model, label_field="manifest_data")
</code></pre>
<p><strong>Downstream usage:</strong></p>
<pre><code>from logistics_system import LogisticsSystem
from customs_api import CustomsAPI
from inventory_system import InventorySystem

logistics = LogisticsSystem()
customs = CustomsAPI()
inventory = InventorySystem()

for sample in dataset:
  manifest = json.loads(sample.manifest_data)

  # Create shipment in logistics system
  shipment_id = logistics.create_shipment(
    tracking_number=manifest["tracking_number"],
    shipper=manifest["shipper"],
    consignee=manifest["consignee"],
    carrier=manifest["carrier"],
    service_type=manifest["service_type"]
  )
  
  # Submit customs declaration
  customs_declaration = customs.create_declaration(
    shipment_id=shipment_id,
    items=manifest["items"],
    total_value=float(manifest["total_value"]),
    total_weight=float(manifest["total_weight"])
  )
  
  # Check for restricted items
  for item in manifest["items"]:
    if customs.is_restricted(item["hs_code"]):
      logistics.flag_for_review(shipment_id, item)

    # Update inventory system
    inventory.update_in_transit(
      item_description=item["description"],
      quantity=int(item["quantity"]),
      expected_delivery=manifest["expected_delivery"]
    )
    
  # Calculate shipping costs
  cost = logistics.calculate_cost(
    weight=float(manifest["total_weight"]),
    service_type=manifest["service_type"],
    destination=manifest["consignee"]["address"]
  )
  logistics.set_cost(shipment_id, cost)
  
  # Set up tracking alerts
  logistics.set_delivery_alerts(
    shipment_id=shipment_id,
    consignee_email=manifest["consignee"]["contact"],
    expected_delivery=manifest["expected_delivery"]
  )
  
  # Generate shipping labels
  label = logistics.generate_label(shipment_id)
  logistics.print_label(shipment_id, label)
</code></pre>
<p><strong>Benefits:</strong> Automated customs processing, real-time tracking, inventory updates, cost calculation, delivery notifications.</p>
<h2>Multi-mode processing for complex document understanding</h2>
<p>To understand some documents, they require multiple extraction modes. Here's how to combine them:</p>
<pre><code># Extract text for full-text search
model.operation = "text"
dataset.apply_model(model, label_field="full_text")

# Extract tables for data analysis
model.operation = "table"
dataset.apply_model(model, label_field="tables")

# Extract formulas for rendering
model.operation = "formula"
dataset.apply_model(model, label_field="formulas")

# Extract structured metadata
model.operation = "custom"
model.custom_prompt = "Extract key information as JSON..."
dataset.apply_model(model, label_field="metadata")
</code></pre>
<p>Now each document has multiple representations, enabling different downstream uses:</p>
<ul>
<li><strong>Full text:</strong> Search and retrieval</li>
<li><strong>Tables:</strong> Data analysis and visualization</li>
<li><strong>Formulas:</strong> Rendering in documentation</li>
<li><strong>Metadata:</strong> Database storage and filtering</li>
</ul>
<h2>Quality assurance workflows</h2>
<p>Use FiftyOne's filtering and evaluation capabilities to build QA workflows:</p>
<pre><code># Filter samples with empty extractions
empty_data = dataset.match(F("structured_extraction") == None)
print(f"Found {len(empty_data)} samples with no structured extraction")

# Review problematic samples
session = fo.launch_app(empty_data)

# Validate JSON structure
import json
invalid_json = []
for sample in dataset:
  if sample.structured_extraction:
    try:
      json.loads(sample.structured_extraction)
    except json.JSONDecodeError:
      invalid_json.append(sample.id)

if invalid_json:
  print(f"Found {len(invalid_json)} samples with invalid JSON")
  review_view = dataset.select(invalid_json)
  session = fo.launch_app(review_view)
</code></pre>
<h2>Architecture deep dive</h2>
<p>Understanding GLM-OCR's vision system architecture helps optimize your usage:</p>
<h3>Three-stage pipeline</h3>
<ol>
<li><strong>Visual ingestion:</strong> CogViT visual encoder captures pixel-level and layout information.</li>
<li><strong>Multimodal reasoning:</strong> GLM-V-based vision-language fusion aligns visual features with language understanding.</li>
<li><strong>Structured generation:</strong> Decoder with Multi-Token Prediction (MTP) generates structured output while correcting errors.</li>
</ol>
<p>The MTP mechanism is particularly clever—it predicts multiple tokens per step and uses context to fix errors on the fly, acting like semantic proofreading rather than naive character recognition.</p>
<h3>Batching implementation</h3>
<p>The FiftyOne integration implements efficient batching through:</p>
<ul>
<li><strong>SupportsGetItem:</strong> Custom GetItem transform loads images as PIL Images.</li>
<li><strong>TorchModelMixin:</strong> Enables DataLoader-based batching.</li>
<li><strong>Left padding:</strong> Correctly handles decoder-only model generation.</li>
<li><strong>Variable-size images:</strong> Handles different image sizes in the same batch.</li>
</ul>
<p>This architecture allows processing multiple documents simultaneously while maintaining accuracy.</p>
<h2>OCR vision system best practices</h2>
<h3>1. Choose the right operation mode</h3>
<ul>
<li>Use <code>text</code> for general OCR and human-readable output</li>
<li>Use <code>formula</code> for scientific documents with mathematical expressions</li>
<li>Use <code>table</code> when you need structured table data</li>
<li>Use <code>custom</code> for domain-specific structured extraction</li>
</ul>
<h3>2. Optimize batch sizes</h3>
<p>Start with smaller batches and increase until you hit memory limits. Monitor GPU/CPU usage to find the sweet spot.</p>
<h3>3. Store raw outputs</h3>
<p>Always store the raw GLM-OCR output (Markdown/JSON) alongside processed data. This allows reprocessing as your downstream logic evolves.</p>
<h3>4. Combine with LLMs</h3>
<p>GLM-OCR excels at structure extraction. Feed its output into LLMs for:</p>
<ul>
<li>Summarization</li>
<li>Question answering</li>
<li>Risk analysis</li>
<li>Report generation</li>
</ul>
<h3>5. Handle edge cases</h3>
<p>For low-quality scans, consider preprocessing:</p>
<ul>
<li>Binarization</li>
<li>De-skewing</li>
<li>Noise reduction</li>
</ul>
<p>This helps the visual encoder and improves structure detection.</p>
<h2>The future of OCR workflows with AI-powered document understanding</h2>
<p>GLM-OCR vision systems combined with FiftyOne provides a powerful, flexible solution for AI-powered document understanding. The integration's batching support, visualization capabilities, and multiple operation modes make it suitable for everything from quick prototypes to production pipelines.</p>
<p><strong>Key takeaways:</strong></p>
<ul>
<li><strong>Fundamentally different:</strong> GLM-OCR understands document semantics, not just characters.</li>
<li><strong>Structure-first output:</strong> Get Markdown, JSON, and LaTeX directly—no parsing needed.</li>
<li><strong>Lightweight yet powerful:</strong> 0.9B parameters with SOTA accuracy (94.62 on OmniDocBench V1.5).</li>
<li><strong>Structured extraction:</strong> Extract domain-specific data via custom prompts.</li>
<li><strong>Open source:</strong> Apache-2.0 licensed, deployable anywhere.</li>
<li><strong>Developer-friendly:</strong> Easy integration with existing workflows via FiftyOne.</li>
</ul>
<p>Whether you're building ETL pipelines, processing receipts, analyzing contracts, extracting data from scientific papers, or digitizing medical records, GLM-OCR with FiftyOne gives you the tools to build robust document understanding systems that output structured data ready for downstream processing.</p>
<h2>GLM-OCR next steps</h2>
<ol>
<li><strong>Try it yourself:</strong> Run the example notebook with your own documents.</li>
<li><strong>Experiment with modes:</strong> Test different operation types on your use case.</li>
<li><strong>Build a pipeline:</strong> Integrate structured extraction into your workflow.</li>
<li><strong>Optimize:</strong> Tune batch sizes and parameters for your hardware.</li>
<li><strong>Extend:</strong> Combine with LLMs for end-to-end document understanding.</li>
</ol>
<p>The future of AI document understanding is here—and it's more accessible than ever.</p>
<p><strong><a href="https://docs.voxel51.com/plugins/plugins_ecosystem/glm_ocr.html">Get started now →</a></strong></p>
<h2>Resources</h2>
<ul>
<li><a href="https://docs.voxel51.com/plugins/plugins_ecosystem/glm_ocr.html">GLM-OCR FiftyOne Implementation</a></li>
<li><a href="https://huggingface.co/zai-org/GLM-OCR">GLM-OCR Model Card</a></li>
<li><a href="https://github.com/harpreetsahota204/glm_ocr/blob/main/glm_ocr_fiftyone_example.ipynb">Example Notebook</a></li>
<li><a href="https://docs.voxel51.com">FiftyOne Documentation</a></li>
<li><a href="https://github.com/harpreetsahota204/caption_viewer">Caption Viewer Plugin</a></li>
</ul>
<hr />
<p><em>This post first appeared as <a href="https://voxel51.com/blog/build-ai-document-understanding-pipelines">the original article</a>. I'm Harpreet Sahota, Hacker-in-Residence at <a href="https://employabledatascientist.com">The Employable Data Scientist</a>, where we help AI and developer-tool teams ship prototypes, evaluations, and technical content. I also host the <a href="https://employabledatascientist.com/podcast/">Artists of Intelligence</a> podcast. For new field notes by email, <a href="https://harpreetsahota.substack.com">subscribe on Substack</a>.</em></p>
]]></content:encoded></item><item><title><![CDATA[LightOnOCR-2: A Compact AI Model Revolutionizing Document OCR]]></title><description><![CDATA[Discover LightOnOCR-2, a compact AI OCR model for document OCR. Extract text, tables, and scientific notation accurately for fast document management…]]></description><link>https://hashnode.employabledatascientist.com/lightoneocr-2-ai-model-revolutionizing-document-ocr</link><guid isPermaLink="true">https://hashnode.employabledatascientist.com/lightoneocr-2-ai-model-revolutionizing-document-ocr</guid><category><![CDATA[integrations]]></category><category><![CDATA[Computer Vision]]></category><category><![CDATA[Python]]></category><category><![CDATA[Machine Learning]]></category><category><![CDATA[AI]]></category><dc:creator><![CDATA[Artists of Intelligence with Harpreet Sahota]]></dc:creator><pubDate>Wed, 11 Feb 2026 00:00:00 GMT</pubDate><enclosure url="https://cdn.sanity.io/images/h6toihm1/production/a51a2bb23a5c96fa666ff4fc0cd40daf089e036e-1920x1080.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<img src="https://image.mux.com/vKJHfXpL8fAD4MimxREZUBPmtCgjAwaSOAacgt7P3fs/animated.gif?width=640" alt="" style="display:block;margin:0 auto" />

<p>In document digitization, traditional OCR pipelines have long been the bottleneck—fragile, multi-stage systems that struggle with complex layouts, multilingual content, and scientific notation. Enter <strong>LightOnOCR-2-1B</strong>, a revolutionary 1-billion-parameter <a href="https://voxel51.com/blog/computer-vision-natural-language-interface">vision-language model</a> that challenges the status quo by delivering state-of-the-art document OCR performance in a remarkably compact package.</p>
<h2>What is document OCR?</h2>
<p>Document OCR is a technology that allows machines to read and extract text from scanned documents, images, and PDFs. Traditional OCR systems often struggle with complex layouts, tables, forms, and multilingual content, making accurate text extraction challenging.</p>
<p>Modern AI OCR models like LightOnOCR-2 go beyond basic character recognition by leveraging vision-language understanding to preserve document structure, layout, and even scientific notation. This makes document OCR ideal for document management system OCR applications, enabling faster, more accurate digitization and streamlined workflows.</p>
<h2>Breaking the document OCR size-performance tradeoff</h2>
<p>What makes LightOnOCR-2 remarkable isn't just its accuracy—it's how efficiently it achieves it. The model achieves the highest score on OlmOCR-Bench, a comprehensive document OCR evaluation benchmark, while being <strong>9x smaller</strong> than prior best-performing models.</p>
<p>This isn't just a marginal improvement; it's a fundamental shift in what's possible with compact vision-language models. The AI OCR model processes documents at an impressive <strong>5.71 pages per second on H100 GPUs</strong>, substantially outperforming larger alternatives. This speed advantage, combined with its smaller footprint, makes LightOnOCR-2 practical for real-world deployment scenarios where computational resources matter.</p>
<h2>End-to-end document OCR architecture: Simplicity meets performance</h2>
<p>Unlike traditional document OCR systems that rely on brittle multi-stage pipelines (text detection → text recognition → layout analysis → post-processing), LightOnOCR-2 uses a single, unified end-to-end architecture. This approach offers several key advantages:</p>
<ul>
<li><strong>Fully differentiable</strong>: The entire pipeline can be optimized end-to-end, eliminating error propagation between stages</li>
<li><strong>Naturally ordered output</strong>: The model produces clean, naturally ordered text without requiring complex post-processing heuristics</li>
<li><strong>Robust to layout complexity</strong>: Excels at handling tables, forms, receipts, scientific notation, and multi-column layouts</li>
</ul>
<p>The AI OCR model is trained at <strong>1540px maximum longest edge resolution</strong>, enabling it to capture fine details in dense mathematical notation and small text—critical for scientific and technical documents.</p>
<h2>Multilingual document OCR excellence with a French focus</h2>
<p>LightOnOCR-2 demonstrates strong multilingual capabilities, with particular emphasis on European languages and French documents. The AI OCR model was trained on a large-scale, high-quality distillation mix that includes extensive coverage of:</p>
<ul>
<li>Scanned documents with varying quality and degradation</li>
<li>French-language content</li>
<li>Scientific PDFs with dense typography and mathematical notation</li>
</ul>
<p>This training approach enables the AI OCR model to handle the nuances of different languages and document types without requiring language-specific preprocessing or post-processing steps.</p>
<h2>Advanced document OCR training techniques</h2>
<p>The development of LightOnOCR-2 incorporates several cutting-edge techniques:</p>
<ul>
<li><strong>RLVR optimization</strong>: The model uses Reinforcement Learning from Visual Representations (RLVR) with IoU-based rewards to refine its performance, particularly for tasks involving bounding box prediction for embedded images.</li>
<li><strong>Checkpoint averaging</strong>: Multiple model checkpoints are averaged to improve robustness and generalization, reducing variance in predictions across different document types.</li>
<li><strong>Task-Arithmetic merging</strong>: The model leverages task-arithmetic merging techniques to combine capabilities from different training stages, resulting in a more capable and robust final model.</li>
</ul>
<h2>Practical AI OCR model usage with FiftyOne</h2>
<p>The AI OCR model integrates seamlessly with <a href="https://voxel51.com/fiftyone">FiftyOne</a>, making it easy to apply OCR to document datasets:</p>
<pre><code class="language-python">import fiftyone as fo
import fiftyone.zoo as foz
# Register and load the model
foz.register_zoo_model_source(
    "https://github.com/harpreetsahota204/LightOnOCR-2",
    overwrite=True
)
model = foz.load_zoo_model("lightonai/LightOnOCR-2-1B")
# Apply to your dataset
dataset = fo.Dataset.from_images_dir("/path/to/documents")
dataset.apply_model(
    model,
    label_field="ocr_text",
    batch_size=8,
    num_workers=2
)
</code></pre>
<p>The integration supports efficient batch processing, allowing you to process large document collections quickly. With configurable parameters for maximum tokens, custom prompts, and batch sizes, the model adapts to various use cases and computational constraints.</p>
<p><a href="https://docs.voxel51.com/plugins/plugins_ecosystem/lightonocr_2.html"><strong>Get started now →</strong></a></p>
<h2>Document OCR use cases</h2>
<p>LightOnOCR-2 excels in several key scenarios:</p>
<ul>
<li><strong>Scientific PDFs</strong>: Handles dense typography and accurately transcribes LaTeX mathematical notation</li>
<li><strong>Scanned documents</strong>: Robust to moderate degradation, noise, and rotation</li>
<li><strong>European languages</strong>: Strong Latin script support, especially for French</li>
<li><strong>Complex layouts</strong>: Multi-column documents, tables, forms, and structured content</li>
</ul>
<h2>Looking forward to a more accessible document OCR future</h2>
<p>LightOnOCR-2 represents a significant step forward in making high-quality document OCR accessible and practical. By combining state-of-the-art performance with compact size and fast inference, it opens up new possibilities for document processing applications. The AI OCR model is released under Apache 2.0 license, making it freely available for both research and commercial use. The team has also released the training dataset and LightOnOCR-bbox-bench evaluation suite, contributing to the broader OCR research community. As document digitization remains a critical need across industries—from legal and healthcare to <a href="https://voxel51.com/research">research</a> and education—models like LightOnOCR-2 show we don't need to choose between accuracy and efficiency. Sometimes, the best solution does more with less.</p>
<p><a href="https://docs.voxel51.com/plugins/plugins_ecosystem/lightonocr_2.html"><strong>Get started now →</strong></a></p>
<h2>Document OCR resources:</h2>
<ul>
<li><a href="https://huggingface.co/lightonai/LightOnOCR-2-1B">Model</a></li>
<li><a href="https://arxiv.org/abs/2601.14251">Paper</a></li>
<li><a href="https://docs.voxel51.com/plugins/plugins_ecosystem/lightonocr_2.html">FiftyOne Integration</a></li>
</ul>
<hr />
<p><em>This post first appeared as</em> <a href="https://voxel51.com/blog/lightoneocr-2-ai-model-revolutionizing-document-ocr"><em>the original article</em></a><em>. I'm Harpreet Sahota, Hacker-in-Residence at</em> <a href="https://employabledatascientist.com"><em>The Employable Data Scientist</em></a><em>, where we help AI and developer-tool teams ship prototypes, evaluations, and technical content. I also host the</em> <a href="https://employabledatascientist.com/podcast/"><em>Artists of Intelligence</em></a> <em>podcast. For new field notes by email,</em> <a href="https://harpreetsahota.substack.com"><em>subscribe on Substack</em></a><em>.</em></p>
]]></content:encoded></item><item><title><![CDATA[C-RADIOv4: A Distilled Vision Foundation Model for FiftyOne]]></title><description><![CDATA[Learn how C-RADIOv4 uses multi-teacher foundation model distillation to combine vision-language, dense perception, and segmentation in a single efficient…]]></description><link>https://hashnode.employabledatascientist.com/c-radiov4-distilled-vision-foundation-model</link><guid isPermaLink="true">https://hashnode.employabledatascientist.com/c-radiov4-distilled-vision-foundation-model</guid><category><![CDATA[plugins]]></category><category><![CDATA[Computer Vision]]></category><category><![CDATA[Python]]></category><category><![CDATA[Machine Learning]]></category><category><![CDATA[AI]]></category><dc:creator><![CDATA[Artists of Intelligence with Harpreet Sahota]]></dc:creator><pubDate>Fri, 06 Feb 2026 00:00:00 GMT</pubDate><enclosure url="https://cdn.sanity.io/images/h6toihm1/production/8bc81138e0fee9f3280915dc3094fc00f66179aa-1920x1080.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><img src="https://image.mux.com/YY1MKWsox24TcZ9TSRJfhH02XkYS2WAmMtj5LOl00pia4/animated.gif?width=640" alt="" /></p>
<h2>Introduction</h2>
<p>The field of computer vision has seen remarkable progress with vision foundation models that excel at specific tasks—CLIP for vision-language alignment, DINOv3 for dense perception, <a href="https://voxel51.com/blog/how-to-use-segment-anything-model-sam3">SAM3 for segmentation</a>. But what if you could distill the best of these foundation models into a single, unified architecture? That's exactly what <strong>C-RADIOv4</strong> achieves.</p>
<p>C-RADIOv4 is the latest release in NVIDIA Labs' agglomerative vision foundation model family. Through innovative multi-teacher distillation, it creates a unified student model that retains and improves upon the distinct capabilities of multiple state-of-the-art teachers: <strong>SigLIP2</strong>, <strong>DINOv3</strong>, and <strong>SAM3</strong>.</p>
<p>In this blog post, we'll explore what makes C-RADIOv4 special, dive into its technical innovations, and show you how to leverage its powerful capabilities in FiftyOne for <a href="https://voxel51.com/blog/introducing-fiftyone-enterprise-visual-ai-workflows">computer vision workflows</a>.</p>
<h2>What are foundation models?</h2>
<p>Foundation models are large, pretrained models that learn broad, reusable representations from massive datasets and can be adapted to many downstream tasks with little or no task-specific training.</p>
<h2>What is C-RADIOv4?</h2>
<p>C-RADIOv4 is an agglomerative foundation model that learns from multiple teacher models simultaneously. Instead of training on labeled data, it distills foundation model knowledge from three cutting-edge vision models:</p>
<ul>
<li><strong>SigLIP2:</strong> The frontier text-image foundation encoder, providing enhanced zero-shot classification capabilities</li>
<li><strong>DINOv3</strong>: A self-supervised learning powerhouse that delivers strong dense perception and semantic segmentation</li>
<li><strong>SAM3</strong>: The latest iteration of Segment Anything Model, enabling powerful segmentation capabilities</li>
</ul>
<p>The result? A single vision language model that combines text-image alignment, dense perception, and segmentation—all while maintaining competitive performance at a fraction of the parameters of its teachers.</p>
<p><strong><a href="https://docs.voxel51.com/plugins/plugins_ecosystem/cradiov4.html?_gl=1*1ipgsx3*_gcl_aw*R0NMLjE3NjU5MjczODMuQ2p3S0NBaUEwZVRKQmhCYUVpd0EtUGEtaFJiOXRFM1FCVy1vTTlDUldZVU5rUkNsTjdEclM3WkVWQzN5TTEtdE5vemlQcGZ0Q0tSVkN4b0NrNThRQXZEX0J3RQ..*_gcl_au*MTAxMDI3ODYzMi4xNzY4OTMyMTU0">Get started with C-RADIOv4 →</a></strong></p>
<h2>C-RADIOv4: Vision foundation model variants</h2>
<p>C-RADIOv4 comes in two variants:</p>
<p><img src="https://cdn.sanity.io/images/h6toihm1/production/df6d9655b502aff93d33a3a5a4b3146aab96dcd1-1024x236.png" alt="" /></p>
<p>Both foundation models achieve impressive results:</p>
<ul>
<li><strong>Zero-shot accuracy</strong>: 83.09% (H) and 82.01% (SO400M) on ImageNet-1K</li>
<li><strong>kNN accuracy</strong>: 86.59% (H) and 85.76% (SO400M) on ImageNet-1K</li>
<li><strong>Semantic segmentation</strong>: 55.20 mIoU (H) and 55.14 mIoU (SO400M) on ADE20k</li>
</ul>
<p>Remarkably, C-RADIOv4-H achieves performance competitive with DINOv3-7B (which has 6.7B parameters) on dense tasks, despite having an order of magnitude fewer parameters.</p>
<h2>C-RADIOv4: Key technical innovations for vision foundation models</h2>
<h3>1. Stochastic resolution training</h3>
<p>Previous RADIO vision foundation models trained at just two resolutions, which could lead to inconsistent behavior at inference time—a phenomenon called "mode switching." C-RADIOv4 solves this by training across a wide range of resolutions:</p>
<ul>
<li><strong>Low-resolution partition</strong>: {128, 192, 224, 256, 384, 432} pixels</li>
<li><strong>High-resolution partition</strong>: {512, 768, 1024, 1152} pixels</li>
</ul>
<p>This enables smooth resolution scaling and substantial quality improvements at low resolutions. The model demonstrates strong robustness even at resolutions higher than those it was trained on, achieving 57.72 mIoU at 1536px on ADE20k.</p>
<h3>2. Shift equivariance</h3>
<p>One challenge in foundation model distillation is that students can learn not just useful features, but also fixed-pattern noise from teachers. C-RADIOv4 addresses this with two forms of shift equivariance:</p>
<ul>
<li><strong>Shift Equivariant Loss</strong>: Randomly shifts crops for both student and teachers independently, preventing the model from learning position-dependent artifacts</li>
<li><strong>Shift Equivariant MESA</strong>: Applies exponential moving average matching with different crops, further reducing fixed-pattern noise</li>
</ul>
<p>These techniques ensure the model learns robust, semantically meaningful features rather than spurious patterns.</p>
<h3>3. ViTDet mode for high-resolution efficiency</h3>
<p>For high-resolution inference, C-RADIOv4 supports "ViTDet mode," which uses windowed attention instead of full global attention. This dramatically reduces inference time:</p>
<ul>
<li>The SO400M variant with ViTDet window size ≤ 12 is faster than SAM3's encoder</li>
<li>ViT-H with window size 8 is nearly as fast as SAM3</li>
<li>Latency scales much more favorably with resolution compared to full attention</li>
</ul>
<p>This makes C-RADIOv4 practical for real-world applications requiring high-resolution processing.</p>
<h3>4. Balanced summary loss</h3>
<p>Different teachers produce summary features with different angular dispersions. Without normalization, teachers with larger dispersion would dominate the loss. C-RADIOv4 uses an angular loss formulation that normalizes for each teacher's cone radius, ensuring balanced learning from all teachers.</p>
<h2>Getting started with C-RADIOv4 in FiftyOne</h2>
<p>FiftyOne provides seamless integration with C-RADIOv4, making it easy to leverage these powerful capabilities in your <a href="https://voxel51.com/blog/unified-model-insights-with-fiftyone-model-evaluation-workflows">computer vision workflows</a>.</p>
<h3>Installation</h3>
<p>First, install FiftyOne and register the C-RADIOv4 model source:</p>
<pre><code class="language-python"># Install FiftyOne
pip install fiftyone

# Register the RADIO model source
import fiftyone.zoo as foz
foz.register_zoo_model_source(
    "https://github.com/harpreetsahota204/CRADIOv4",
)
</code></pre>
<h3>Quick start: Computing embeddings</h3>
<p>The most common use case is extracting image embeddings for similarity search, clustering, and visualization:</p>
<pre><code class="language-python">import fiftyone as fo
import fiftyone.zoo as foz

# Load your dataset
dataset = foz.load_zoo_dataset("quickstart", shuffle=True)

# Load the C-RADIOv4 model
model = foz.load_zoo_model("nv_labs/c-radio_v4-h")

# Compute embeddings with efficient batching
dataset.compute_embeddings(
    model=model,
    embeddings_field="radio_embeddings",
    batch_size=16,      # Adjust based on GPU memory
    num_workers=4       # Parallel data loading
)

# Launch the FiftyOne App to explore
session = fo.launch_app(dataset)
</code></pre>
<h3>Model configuration options</h3>
<p>C-RADIOv4 offers flexible configuration for different use cases:</p>
<pre><code class="language-python"># Global image embeddings (default)
model = foz.load_zoo_model(
    "nv_labs/c-radio_v4-h",
    output_type="summary",      # Global semantic features
    feature_format="NCHW"        # or "NLC"
)

# Spatial attention features
spatial_model = foz.load_zoo_model(
    "nv_labs/c-radio_v4-h", 
    output_type="spatial",       # Patch-level spatial features
    feature_format="NCHW",       # Required for spatial
    apply_smoothing=True,         # Gaussian smoothing for cleaner heatmaps
    smoothing_sigma=1.51         # Smoothing strength
)
</code></pre>
<h2>C-RADIOv4: Practical use cases for vision foundation models</h2>
<h3>1. Embedding visualization with UMAP</h3>
<p>Create beautiful 2D visualizations of your image embeddings to understand dataset structure:</p>
<pre><code class="language-python">import fiftyone.brain as fob

# Compute embeddings first
dataset.compute_embeddings(
    model=model,
    embeddings_field="radio_embeddings"
)

# Create UMAP visualization
results = fob.compute_visualization(
    dataset,
    method="umap",  # Also supports "tsne", "pca"
    brain_key="radio_viz",
    embeddings="radio_embeddings"
)

# Explore in the App
session = fo.launch_app(dataset)
</code></pre>
<p>The visualization reveals clusters of similar images, making it easy to identify patterns and outliers in your dataset.</p>
<h3>2. Similarity search</h3>
<p>Build powerful similarity search capabilities:</p>
<pre><code class="language-python">import fiftyone.brain as fob

# Build similarity index
results = fob.compute_similarity(
    dataset,
    backend="sklearn",
    brain_key="radio_sim", 
    embeddings="radio_embeddings"
)

# Find similar images
sample_id = dataset.first().id
similar_samples = dataset.sort_by_similarity(
    sample_id,
    brain_key="radio_sim",
    k=10  # Top 10 most similar
)

# View results
session = fo.launch_app(similar_samples)
</code></pre>
<p>This is incredibly useful for:</p>
<ul>
<li>Finding duplicate or near-duplicate images</li>
<li>Discovering similar content for data augmentation</li>
<li>Building recommendation systems</li>
<li>Quality control and anomaly detection</li>
</ul>
<h3>3. Spatial attention heatmaps</h3>
<p>Visualize what regions of an image the model focuses on using spatial attention features:</p>
<pre><code class="language-python"># Load spatial model with smoothing
spatial_model = foz.load_zoo_model(
    "nv_labs/c-radio_v4-h",
    output_type="spatial",
    apply_smoothing=True,
    smoothing_sigma=1.51,
    feature_format="NCHW"
)

# Generate attention heatmaps
dataset.apply_model(
    spatial_model, 
    "radio_heatmap",
    batch_size=16,
    num_workers=4
)

# View heatmaps in FiftyOne App
session = fo.launch_app(dataset)
</code></pre>
<p>The heatmaps use PCA visualization (as described in the C-RADIOv4 paper) to show object boundaries and regions of interest. This is particularly useful for:</p>
<ul>
<li>Understanding model behavior</li>
<li>Debugging and interpretability</li>
<li>Identifying important image regions</li>
<li>Quality assessment</li>
</ul>
<h3>4. Dataset curation: Uniqueness and representativeness</h3>
<p>Use embeddings to score how unique or representative each sample is:</p>
<pre><code class="language-python">import fiftyone.brain as fob

# Compute uniqueness scores
fob.compute_uniqueness(
    dataset,
    embeddings="radio_embeddings"
)

# Compute representativeness scores
fob.compute_representativeness(
    dataset,
    representativeness_field="radio_represent",
    method="cluster-center",
    embeddings="radio_embeddings"
)

# Find most representative samples
representative_view = dataset.sort_by("radio_represent", reverse=True)
</code></pre>
<p>These scores help with:</p>
<ul>
<li><strong>Active learning</strong>: Select diverse samples for labeling</li>
<li><strong>Dataset balancing</strong>: Identify underrepresented regions</li>
<li><strong>Data cleaning</strong>: Find outliers and edge cases</li>
<li><strong>Efficient annotation</strong>: Prioritize representative samples</li>
</ul>
<h3>5. Duplicate detection</h3>
<p>Automatically find and remove near-duplicate images:</p>
<pre><code class="language-python">import fiftyone.brain as fob

# Detect duplicates using embeddings
results = fob.compute_uniqueness(
    dataset,
    embeddings="radio_embeddings"
)

# Filter to most unique samples
unique_view = dataset.sort_by("uniqueness", reverse=True)
</code></pre>
<p>This is essential for:</p>
<ul>
<li>Cleaning datasets before training</li>
<li>Reducing storage costs</li>
<li>Improving dataset quality</li>
<li>Preventing data leakage in train/test splits</li>
</ul>
<h2>Advanced workflows for vision foundation models</h2>
<h3>Comprehensive analysis pipeline</h3>
<p>Combine multiple C-RADIOv4 outputs for thorough dataset analysis:</p>
<pre><code class="language-python"># Step 1: Global embeddings for similarity
embedding_model = foz.load_zoo_model("nv_labs/c-radio_v4-h")
dataset.compute_embeddings(embedding_model, "radio_embeddings")

# Step 2: Spatial heatmaps for attention analysis
spatial_model = foz.load_zoo_model(
    "nv_labs/c-radio_v4-h",
    output_type="spatial",
    apply_smoothing=True,
    smoothing_sigma=0.8
)
dataset.apply_model(spatial_model, "radio_heatmap")

# Step 3: Build similarity index
import fiftyone.brain as fob
fob.compute_similarity(dataset, embeddings="radio_embeddings", brain_key="radio_sim")

# Step 4: Compute additional metrics
fob.compute_uniqueness(dataset, embeddings="radio_embeddings")
fob.compute_representativeness(dataset, embeddings="radio_embeddings")

# Step 5: Comprehensive exploration
session = fo.launch_app(dataset)
</code></pre>
<p>This pipeline gives you:</p>
<ul>
<li><strong>Similarity search</strong> for finding related images</li>
<li><strong>Attention visualization</strong> for understanding model focus</li>
<li><strong>Uniqueness scores</strong> for identifying outliers</li>
<li><strong>Representativeness scores</strong> for dataset balancing</li>
<li><strong>Interactive exploration</strong> in the FiftyOne App</li>
</ul>
<h2>Performance optimization for vision foundation models</h2>
<h3>Batch processing</h3>
<p>For optimal performance on large datasets, use batching with parallel data loading:</p>
<pre><code class="language-python">dataset.compute_embeddings(
    model=model,
    embeddings_field="radio_embeddings",
    batch_size=16,      # Adjust based on GPU memory
    num_workers=4       # Parallel data loading workers
)
</code></pre>
<p><strong>Recommended batch sizes by GPU:</strong></p>
<ul>
<li>RTX 3090/4090 (24GB): <code>batch_size=16-32</code></li>
<li>A100 (40GB/80GB): <code>batch_size=32-64</code></li>
<li>Smaller GPUs: <code>batch_size=4-8</code></li>
</ul>
<h3>GPU acceleration</h3>
<p>C-RADIOv4 automatically uses GPU acceleration with mixed precision (bfloat16) on compatible hardware (Ampere+). The vision foundation model automatically handles:</p>
<ul>
<li>Device placement (GPU/CPU)</li>
<li>Mixed precision inference</li>
<li>Efficient memory management</li>
</ul>
<h3>High-resolution processing</h3>
<p>For high-resolution images, consider using the SO400M variant with ViTDet mode enabled (when available through the API) for faster inference:</p>
<pre><code class="language-python"># Use smaller model for faster inference
model = foz.load_zoo_model("nv_labs/c-radio_v4-so400m")
</code></pre>
<h2>Real-world applications for vision foundation models</h2>
<p>C-RADIOv4 has been successfully deployed in various domains:</p>
<ul>
<li><strong>Autonomous Vehicles</strong>: Dense perception for scene understanding</li>
<li><strong>Robotics</strong>: Visual navigation and manipulation</li>
<li><strong>Document Processing</strong>: OCR and document parsing (Nemotron Parse)</li>
<li><strong>Open Vocabulary Segmentation</strong>: RADSeg uses RADIO for state-of-the-art segmentation</li>
<li><strong>Vision-Language Models</strong>: Used as vision encoder in Nemotron Nano V2 VL</li>
</ul>
<p>The vision foundation model's permissive license (NVIDIA Open Model License Agreement) makes it suitable for both research and commercial applications.</p>
<h2>Why C-RADIOv4?</h2>
<h3>Unified capabilities</h3>
<p>Instead of maintaining separate models for different tasks, C-RADIOv4 provides:</p>
<ul>
<li><strong>Zero-shot classification</strong> (from SigLIP2)</li>
<li><strong>Dense perception</strong> (from DINOv3)</li>
<li><strong>Segmentation capabilities</strong> (from SAM3)</li>
</ul>
<p>All in a single, efficient model.</p>
<h3>Efficiency</h3>
<p>With 631M parameters (H variant) or 412M parameters (SO400M variant), C-RADIOv4 achieves performance competitive with models having 10x more parameters, making it practical for deployment.</p>
<h3>Flexibility</h3>
<ul>
<li>Works across a wide range of resolutions (128px to 1152px+)</li>
<li>Supports both global embeddings and spatial features</li>
<li>Can replace SAM3's vision encoder for segmentation tasks</li>
<li>ViTDet mode enables efficient high-resolution processing</li>
</ul>
<h3>Quality</h3>
<p>The vision foundation model demonstrates:</p>
<ul>
<li>Cleaner object boundaries (as shown in PCA visualizations)</li>
<li>Strong resolution scaling properties</li>
<li>Robust performance across diverse benchmarks</li>
<li>Reduced fixed-pattern noise compared to individual teachers</li>
</ul>
<h2>Foundation model distillation and the road ahead for vision models</h2>
<p>C-RADIOv4 represents a significant advancement in agglomerative vision foundation models. By combining the strengths of SigLIP2, DINOv3, and SAM3 through innovative foundation model distillation techniques, it delivers a unified model that's both powerful and efficient.</p>
<p>The integration with <a href="https://voxel51.com/fiftyone">FiftyOne</a> makes it easy to leverage these capabilities for:</p>
<ul>
<li><strong>Dataset exploration</strong> and visualization</li>
<li><strong>Similarity search</strong> and duplicate detection</li>
<li><strong>Attention visualization</strong> and interpretability</li>
<li><strong>Dataset curation</strong> and active learning</li>
<li><strong>Quality control</strong> and anomaly detection</li>
</ul>
<p>Whether you're working on autonomous vehicles, <a href="https://voxel51.com/industries/robotics">robotics</a>, document processing, or any computer vision application, C-RADIOv4 provides a solid foundation for your workflows.</p>
<h2>Resources</h2>
<ul>
<li><strong>GitHub Repository</strong>: <a href="https://docs.voxel51.com/plugins/plugins_ecosystem/cradiov4.html">CRADIOv4 FiftyOne Integration</a></li>
<li><strong>NVIDIA RADIO GitHub</strong>: <a href="https://github.com/NVlabs/RADIO">Original Implementation</a></li>
<li><strong>Hugging Face Models</strong>: <a href="https://huggingface.co/nvidia/C-RADIOv4">C-RADIOv4 Models</a></li>
<li><strong>Technical Report</strong>: <a href="https://arxiv.org/abs/2601.17237">C-RADIOv4 Tech Report</a></li>
<li><strong>FiftyOne Documentation</strong>: <a href="https://docs.voxel51.com/">docs.voxel51.com</a></li>
</ul>
<h2>Citation</h2>
<p>If you use C-RADIOv4 in your research, please cite:</p>
<pre><code>@misc{ranzinger2026cradiov4,
      title={C-RADIOv4 (Tech Report)},
      author={Mike Ranzinger and Greg Heinrich and Collin McCarthy and Jan Kautz and Andrew Tao and Bryan Catanzaro and Pavlo Molchanov},
      year={2026},
      eprint={2601.17237},
      archivePrefix={arXiv},
      primaryClass={cs.CV},
      url={https://arxiv.org/abs/2601.17237},
}
</code></pre>
<p>Ready to get started? <a href="https://voxel51.com/sales">Install FiftyOne</a>, register the C-RADIOv4 model source, and begin exploring your datasets with this powerful unified vision foundation model!</p>
<p><strong><a href="https://docs.voxel51.com/plugins/plugins_ecosystem/cradiov4.html?_gl=1*1ipgsx3*_gcl_aw*R0NMLjE3NjU5MjczODMuQ2p3S0NBaUEwZVRKQmhCYUVpd0EtUGEtaFJiOXRFM1FCVy1vTTlDUldZVU5rUkNsTjdEclM3WkVWQzN5TTEtdE5vemlQcGZ0Q0tSVkN4b0NrNThRQXZEX0J3RQ..*_gcl_au*MTAxMDI3ODYzMi4xNzY4OTMyMTU0">Get started →</a></strong></p>
<hr />
<p><em>This post first appeared as <a href="https://voxel51.com/blog/c-radiov4-distilled-vision-foundation-model">the original article</a>. I'm Harpreet Sahota, Hacker-in-Residence at <a href="https://employabledatascientist.com">The Employable Data Scientist</a>, where we help AI and developer-tool teams ship prototypes, evaluations, and technical content. I also host the <a href="https://employabledatascientist.com/podcast/">Artists of Intelligence</a> podcast. For new field notes by email, <a href="https://harpreetsahota.substack.com">subscribe on Substack</a>.</em></p>
]]></content:encoded></item></channel></rss>