Tao Generate Referring Expressions
NVIDIAOfficialCreated by NVIDIA Corporation
- Skill
- Workflow
- Data and AI
- Apache-2.0
- Medium risk
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
The tao-generate-referring-expressions skill provides an automated pipeline for transforming images and KITTI-format bounding box labels into rich, grounded referring expressions. By leveraging a Vision-Language Model (VLM) as a teacher, the pipeline performs four distinct steps: generating region-specific descriptions, creating holistic scene captions, producing grouped grounding phrases tied to bounding boxes, and optionally verifying the accuracy of these expressions. This tool is designed to assist in auto-labeling traffic or scene images for training datasets, offering a structured approach to data annotation that supports both Gemini and OpenAI-compatible VLM endpoints.
What it does
- Generates discriminative region descriptions for objects defined by KITTI bounding boxes.
- Produces holistic, location-agnostic scene captions for input images.
- Groups objects into semantic phrases with associated bounding box lists.
- Performs an optional verification pass to tighten or remove inaccurate bounding boxes.
- Supports multiple VLM backends including Gemini and self-hosted OpenAI-compatible services.
- Outputs unified annotations in JSONL format for downstream machine learning tasks.
When to use it
- When you need to auto-label traffic or scene images for referring expression datasets.
- When you have existing KITTI-format bounding box labels and want to add descriptive text annotations.
- When you need to verify and refine existing grounding expressions for higher accuracy.
- When you want to build a dataset of grounded referring expressions tied to specific image regions.
Advantages
- Automates complex annotation tasks using powerful VLM capabilities.
- Provides a modular workflow where steps can be enabled or disabled based on requirements.
- Includes a built-in verification step to improve the quality of generated labels.
- Flexible integration with various VLM endpoints including local self-hosted servers.
- Supports both modern JSONL output and legacy formats for compatibility with existing tools.
Usage examples
- “Run the referring expression pipeline on my traffic images.”
- The agent will guide you through setting up the image and label directories, selecting a VLM endpoint, and executing the four-step annotation process.
- “How can I verify my existing grounding expressions?”
- The agent will configure the pipeline to run the Step 3 double-check verification pass on your existing annotations to identify and correct mismatches.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/nvidia/tao-generate-referring-expressions/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip tao-generate-referring-expressions.zip -d .agents/skills
Documentation by NVIDIA Corporation
Image Referring Expression Pipeline
Standalone install? If this session was not initialized by the TAO skill bank plugin, run the
tao-setupskill first (host preflight, credentials, cross-skill discovery).
Generate referring-expression and grounding annotations from images with KITTI-format bounding box labels. A single VLM (Gemini or any OpenAI-compatible endpoint) runs four steps: per-object region descriptions, holistic image captions, grouped grounding expressions tied to bboxes, and an optional double-check verification pass.
Purpose
Transform (image, KITTI labels) pairs into a unified annotations.jsonl containing rich, grounded referring expressions. The VLM acts as a "teacher" annotator: Steps 0-1 see the image; Step 2 groups Step 0 outputs into grouping phrases with bbox lists; Step 3 (optional) re-examines those bboxes against the image and corrects mismatches.
Pipeline Architecture
Step 0: Region expression ──┐
├──▶ Step 2: Grounding expression ──▶ [Step 3: Double check]
Step 1: Image caption ──────┘ (optional)Step 0 (region_expr) — VLM emits one short discriminative phrase per KITTI bbox (
bbox_2d,type,color,description).Step 1 (image_caption) — VLM emits a holistic, location-agnostic scene caption.
Step 2 (grounding_expr) — VLM groups Step 0 objects into grouping phrases and returns one bbox list per group, optionally using Step 1's caption as extra context.
Step 3 (double_check) — VLM re-checks each Step 2 bbox against the image; bad matches are removed, slightly-off boxes get tightened.
Steps 0 and 1 run in parallel within a single thread pool (they only depend on the seed records). Each step writes its own step_<N>_*/annotations.jsonl and skips already-processed images on re-run unless workflow.force_reprocess: true.
Instructions
Initial setup
When a user wants to run this pipeline, walk through these steps:
Images: Ask for
data.image_dir, the directory containing.jpg,.jpeg, or.pngimages.KITTI labels: Ask for
data.kitti_label_dir, the directory containing one.txtlabel file per image. Each label line must use KITTI format:<type> <truncated> <occluded> <alpha> <bbox_left> <bbox_top> <bbox_right> <bbox_bottom> .... Lines with fewer than 8 fields are silently skipped. Set this even for Step 1-only runs because Steps 0 and 2 require it.Resume from existing annotations: If the user already has a unified
annotations.jsonlfrom a previous run, setdata.input_annotations_jsonlto that file instead of seeding fromdata.image_diranddata.kitti_label_dir.API access: Ask the user which VLM endpoint they want to use. Present these five options and act on the choice:
Gemini — set
vlm.backend: "gemini"; requireGOOGLE_API_KEY(env var orvlm.gemini.api_key).NIM (e.g.
https://inference-api.nvidia.com/v1) — setvlm.backend: "openai"; collectbase_url,model_name, andapi_key.TAO inference microservice (self-hosted, OpenAI-compatible). Confirm whether the server is already running:
Running — collect
base_url,model_name, and (optionally)api_key; setvlm.backend: "openai".Not running — guide the user through the
skills/applications/tao-run-inference-serviceskill, which stands up a local TAO inference microservice with an OpenAI-compatible API. Before promising a specific model, checkskills/applications/tao-run-inference-service/references/service.yamlforvalid_network_arch_config_basenames. Once the server is up, collectbase_url,model_name, and (optionally)api_key; setvlm.backend: "openai".
vLLM (self-hosted, OpenAI-compatible). Confirm whether the server is already running:
Running — collect
base_url,model_name, and (optionally)api_key; setvlm.backend: "openai".Not running — follow references/vllm_server.md to install and launch a vLLM server, then collect
base_url,model_name, and (optionally)api_key; setvlm.backend: "openai".
Custom (any other OpenAI-compatible endpoint) — set
vlm.backend: "openai"; collectbase_url,model_name, and (optionally)api_key.
If the user has no endpoint and does not want to set one up, stop and help resolve API access first.
Workflow steps: Choose one of:
Full pipeline:
["0", "1", "2", "3"]No caption generation:
["0", "2", "3"], where Step 2 falls back to image-only contextNo verification:
["0", "1", "2"]Custom subset: any supported subset of steps
Output format: Choose one of:
jsonl: unified schema onlylegacy: byte-compatible.txt.stepNfiles onlyboth: writes both formats and is the default for downstream tooling
Running the pipeline
The pipeline runs inside the TAO Toolkit container via the auto_label CLI:
auto_label generate -e /path/to/spec.yaml \
results_dir=/results \
image_referring_expression.data.image_dir=/data/images \
image_referring_expression.data.kitti_label_dir=/data/labels \
image_referring_expression.vlm.gemini.api_key=$GOOGLE_API_KEYGenerate a default spec: auto_label default_specs results_dir=/results module_name=auto_label, then set autolabel_type: "image_referring_expression". All fields support Hydra dot-notation overrides on the command line.
See references/configuration.md for the full YAML structure, all parameters, model/endpoint setup, and error patterns.
Recommended pilot workflow
Run on 5-10 images with all four steps.
Inspect
step_0_region_expr/annotations.jsonl— are object types, colors, and discriminating phrases accurate?Inspect
step_2_grounding_expr/annotations.jsonl— are objects grouped sensibly, and do bbox coordinates match the described groups?Inspect
step_3_double_check/annotations.jsonl— were mismatched bboxes removed or tightened? Are any new errors introduced (rare)?If quality is insufficient, switch the VLM to a stronger model (e.g.
gemini-2.5-proor a larger Qwen3-VL endpoint), raisemedia_resolution/max_output_tokens, then re-run withworkflow.force_reprocess=true.Scale to the full dataset once satisfied.
Configuration
Key configuration fields (full reference in references/configuration.md):
| Field | Default | Description |
|---|---|---|
workflow.steps | ["0","1","2","3"] | Which steps to execute (0=region_expr, 1=image_caption, 2=grounding_expr, 3=double_check) |
workflow.max_workers | 4 | Parallel threads per step (watch API rate limits) |
workflow.force_reprocess | false | Ignore cached per-step outputs and reprocess from scratch |
workflow.output_format | "jsonl" (set to "both" in the default spec) | "jsonl", "legacy", or "both" |
vlm.backend | "gemini" | "gemini" or "openai" (OpenAI-compatible endpoint) |
data.image_dir | required | Directory of input images (.jpg / .jpeg / .png) |
data.kitti_label_dir | required (unless resuming) | Directory of KITTI-format .txt label files |
data.input_annotations_jsonl | "" | Optional pre-seeded annotations.jsonl (skips KITTI seeding) |
Inputs
Two ways to seed the pipeline:
Image directory + KITTI labels (default). Set
data.image_diranddata.kitti_label_dir. The orchestrator walks the image directory, reads the matching<stem>.txtKITTI file, parses bboxes (fields 0 + 4-7), reads each image'swidth/heightvia PIL, and writes aseed_annotations.jsonltoresults_dir/.Pre-seeded annotations JSONL (resume / pre-computed regions). Set
data.input_annotations_jsonlto a file with one{"image_id", "image_path", "width", "height", "kitti_bboxes": [...]}object per line.
Outputs
All outputs go to results_dir/:
seed_annotations.jsonl— initial per-image records (unlessinput_annotations_jsonlwas supplied).step_0_region_expr/annotations.jsonl— addsregions[](each withbbox/bbox_2d,type,color,description).step_1_image_caption/annotations.jsonl— addscaption(string).step_2_grounding_expr/annotations.jsonl— addsexpressions[](each{text, instances: [{bbox: [x1,y1,x2,y2]}]}).step_3_double_check/annotations.jsonl— same shape as Step 2, with bboxes removed/updated.results_dir/annotations.jsonl— copy of the last completed step's output.When
workflow.output_formatis"legacy"or"both", each step also writes byte-compatiblestep_<N>_*/labels/<stem>.txt.stepNfiles for the original 2d-data-engine tooling.
Prerequisites
Container:
nvcr.io/nvidia/tao/tao-toolkit:7.3.0-pyt<!-- versions-key: images.tao_toolkit.pyt -->API access: At least one VLM endpoint (Gemini API key or OpenAI-compatible endpoint capable of image input)
PIL / Pillow: Required to read image dimensions during seeding (already present in the TAO container)
Files
- ATTRIBUTION.mdAdded by AutoGineer972 B
- BENCHMARK.md7.3 KiB
- LICENSELicence file added by AutoGineer11.2 KiB
- SKILL.md10.2 KiB
- config/skillspector-baseline.yaml834 B
- evals/evals.json849 B
- references/configuration.md12.7 KiB
- references/skill_info.yaml1.5 KiB
- references/vllm_server.md6.8 KiB
- skill-card.md4.8 KiB
- skill.oms.sig5.4 KiB
references/configuration.md
Image Referring Expression — Full Configuration Reference
Use this reference only when the parent SKILL.md points here for the current task. If this file conflicts with current SKILL.md, skill_info.yaml, schemas, or platform/model skills, the current authoritative source wins.
Contents
Complete YAML Structure
Key Configuration Decisions
Model / Endpoint Configuration
Gemini (default)
OpenAI-compatible endpoints
All Parameters
Input KITTI Label Schema
Resume Schema (
data.input_annotations_jsonl)Output Layout
Error Patterns
Complete YAML Structure
Generate a default experiment spec with auto_label default_specs results_dir=/results module_name=auto_label, then set autolabel_type: "image_referring_expression".
results_dir: ??? # Required — output directory
autolabel_type: "image_referring_expression"
image_referring_expression:
# --- VLM (vision-language model, used for all four steps) ---
vlm:
backend: "gemini" # "gemini" or "openai"
gemini:
api_key: "" # Or set GOOGLE_API_KEY env var
model: "gemini-3.1-flash-lite-preview"
media_resolution: "MEDIA_RESOLUTION_HIGH" # LOW / MEDIUM / HIGH
temperature: 0.3
max_output_tokens: 8192
timeout: 120
openai: # For OpenAI-compatible endpoints (NIM, vLLM, etc.)
api_key: ""
base_url: "" # e.g. "https://inference-api.nvidia.com/v1" — no /chat/completions
model_name: "" # e.g. "Qwen/Qwen3-VL-235B-A22B-Instruct"
temperature: 0.7
max_tokens: 4096
timeout: 60
# --- Workflow ---
workflow:
steps: ["0", "1", "2", "3"] # 0=region_expr, 1=image_caption, 2=grounding_expr, 3=double_check
max_workers: 4 # Parallel threads per step
force_reprocess: false # Ignore cached step outputs
output_format: "both" # "jsonl", "legacy", or "both"
# --- Input data ---
data:
image_dir: ??? # Directory of input images (.jpg / .jpeg / .png)
kitti_label_dir: ??? # Directory of KITTI-format .txt label files
input_annotations_jsonl: "" # Optional: pre-seeded annotations.jsonl to resume fromKey Configuration Decisions
| Decision | Config field | Guidance |
|---|---|---|
| Which steps to run | workflow.steps | Start with all (["0","1","2","3"]). Drop "1" to skip the holistic caption (Step 2 falls back to image-only context). Drop "3" for fast iteration without verification. Run ["0"] first when tuning region-description prompts |
| Caption vs no caption | include / exclude "1" | When Step 1 is included, Step 2 receives the holistic caption as extra context. When omitted, Step 2 still runs using the image and Step 0 region descriptions alone |
| VLM provider | vlm.backend | "gemini" for Google Gemini models, "openai" for any OpenAI-compatible endpoint (NIM, vLLM, etc.) |
| Parallelism | workflow.max_workers | Higher = faster but watch API rate limits. Start with 4, drop to 1-2 if you hit 429s. Steps 0 and 1 also run in parallel with each other when both are enabled — this can double the API load on a single endpoint |
| Resume vs restart | workflow.force_reprocess | false reuses each step's existing annotations.jsonl (and labels/ legacy files). Set true to regenerate everything |
| Resume from prior run | data.input_annotations_jsonl | Point at an existing unified annotations.jsonl to skip the KITTI seeding pass |
| Output format | workflow.output_format | "jsonl" for the unified schema only; "legacy" for byte-compatible 2d-data-engine .txt.stepN files only; "both" (recommended) emits both |
| Image resolution | vlm.gemini.media_resolution | Use MEDIA_RESOLUTION_HIGH for accurate bbox-to-object matching in Steps 0/2/3. Lower resolutions are cheaper but degrade localization |
| Output truncation | vlm.gemini.max_output_tokens / vlm.openai.max_tokens | Step 0 (one entry per object) and Step 2 (one line per group) can be long; raise this if you see parse failures |
Model / Endpoint Configuration
Gemini (default)
Set the API key via environment variable or config:
export GOOGLE_API_KEY=your_key_hereOr in the YAML: image_referring_expression.vlm.gemini.api_key: "your_key".
Recommended model assignments:
For all steps:
gemini-2.5-flash(fast, good enough for most images) orgemini-2.5-pro(better at small / cluttered objects and at the Step 3 verification pass).
Temperature guidance:
Region & grounding (Steps 0, 2, 3): 0.2-0.3 for stable, factual output.
Caption (Step 1): 0.3-0.5 for slightly more natural phrasing.
OpenAI-compatible endpoints
For NVIDIA Inference API, vLLM-served Qwen3-VL, NIM endpoints, etc.:
image_referring_expression:
vlm:
backend: "openai"
openai:
base_url: "https://inference-api.nvidia.com/v1" # no /chat/completions
model_name: "gcp/google/gemini-3-flash-preview"
api_key: "your_key"
temperature: 0.3
max_tokens: 8192For self-hosted models, the pipeline accepts any endpoint that speaks the OpenAI chat-completions API. Two common ways to provision one:
skills/applications/tao-run-inference-serviceskill — workflow for standing up a TAO inference microservice locally. Should support Cosmos, Qwen, and Gemma. Check that skill'sreferences/service.yamlvalid_network_arch_config_basenamesfor the current model list.Bring-your-own deployment — vLLM, NIM, or any other OpenAI-compatible server.
image_referring_expression:
vlm:
backend: "openai"
openai:
base_url: "http://localhost:8000/v1"
model_name: "Qwen/Qwen3-VL-8B-Instruct" # must match vLLM --served-model-name
api_key: "EMPTY" # vLLM ignores it but the SDK requires non-null
temperature: 0.3
max_tokens: 4096
timeout: 300All Parameters
| Parameter | Default | Description |
|---|---|---|
workflow.steps | ["0","1","2","3"] | Pipeline steps to execute (0=region_expr, 1=image_caption, 2=grounding_expr, 3=double_check) |
workflow.max_workers | 4 | Thread pool size for parallel API calls within each step |
workflow.force_reprocess | false | Ignore cached step outputs and reprocess from scratch |
workflow.output_format | "jsonl" (set to "both" in the default spec) | Output format: "jsonl", "legacy", or "both" |
vlm.backend | "gemini" | VLM backend: "gemini" or "openai" |
vlm.gemini.api_key | "" | Gemini API key (or set GOOGLE_API_KEY env var) |
vlm.gemini.model | "gemini-3.1-flash-lite-preview" | Gemini model name |
vlm.gemini.media_resolution | "MEDIA_RESOLUTION_HIGH" | Image resolution sent to Gemini (LOW/MEDIUM/HIGH) |
vlm.gemini.temperature | 0.3 | VLM sampling temperature |
vlm.gemini.max_output_tokens | 8192 | Maximum tokens in Gemini response |
vlm.gemini.timeout | 120 | Request timeout in seconds |
vlm.openai.api_key | "" | API key for OpenAI-compatible endpoint |
vlm.openai.base_url | "" | Base URL of the OpenAI-compatible endpoint (no /chat/completions suffix) |
vlm.openai.model_name | "" | Model name to send in the OpenAI request |
vlm.openai.temperature | 0.7 | OpenAI-compatible sampling temperature |
vlm.openai.max_tokens | 4096 | Maximum tokens in the OpenAI-compatible response |
vlm.openai.timeout | 60 | Request timeout in seconds |
data.image_dir | (required) | Directory of input images (.jpg / .jpeg / .png) |
data.kitti_label_dir | (required unless resuming) | Directory of KITTI-format .txt label files (one per image, matched by stem) |
data.input_annotations_jsonl | "" | Optional unified annotations.jsonl to seed the pipeline; bypasses KITTI seeding |
Input KITTI Label Schema
One line per object, space-separated, with at least 8 fields. Lines with fewer than 8 fields are silently skipped.
<type> <truncated> <occluded> <alpha> <bbox_left> <bbox_top> <bbox_right> <bbox_bottom> [<height> <width> <length> <x> <y> <z> <rotation_y> <score>]<type>(field 0) — string class name (car,pedestrian,truck, ...). Used by Step 0 prompts and Step 2 grouping.<bbox_left> <bbox_top> <bbox_right> <bbox_bottom>(fields 4-7) — pixel-space[x1, y1, x2, y2].All remaining fields are accepted but ignored.
Bboxes are normalized to a 0-1000 coordinate scale before being sent to the VLM in Step 0 and Step 2, then converted back to pixel coordinates in the output records.
Resume Schema (data.input_annotations_jsonl)
When supplied, each line must have at least:
{
"image_id": "stem-of-image-filename",
"image_path": "/abs/or/relative/path/to/image.jpg",
"width": 1920,
"height": 1080,
"kitti_bboxes": [[x1, y1, x2, y2, "type"], ...],
"source": "image_referring_expression",
"pipeline_steps": []
}This is exactly the format produced by the default seeding pass (results_dir/seed_annotations.jsonl).
Output Layout
results_dir/
├── seed_annotations.jsonl # initial per-image records (skipped if resuming)
├── annotations.jsonl # copy of the last completed step's output
├── step_0_region_expr/
│ ├── annotations.jsonl # adds regions[] to each record
│ └── labels/<stem>.txt.step0 # legacy 2d-data-engine format (when output_format != "jsonl")
├── step_1_image_caption/
│ ├── annotations.jsonl # adds caption to each record
│ └── labels/<stem>.txt.step1
├── step_2_grounding_expr/
│ ├── annotations.jsonl # adds expressions[] to each record
│ └── labels/<stem>.txt.step2
└── step_3_double_check/
├── annotations.jsonl # expressions[] with bboxes removed/updated
└── labels/<stem>.txt.step3Each output record carries:
image_id,image_path,width,height— preserved from the seed.kitti_bboxes— original parsed KITTI rows ([x1, y1, x2, y2, type]).regions[](after Step 0) —{bbox: [x1,y1,x2,y2], bbox_2d: [..], type, color, description}per object.caption(after Step 1) — holistic, location-agnostic scene caption.expressions[](after Step 2 and updated by Step 3) —{text, instances: [{bbox: [x1,y1,x2,y2]}, ...]}.pipeline_steps[]— list of step names that have processed this record.source— set to"image_referring_expression".
Error Patterns
| Error | Cause | Fix |
|---|---|---|
GOOGLE_API_KEY not set | Gemini API key missing | export GOOGLE_API_KEY=your_key or set image_referring_expression.vlm.gemini.api_key in the YAML |
| 429 / rate limit errors | Too many parallel API calls (made worse when Steps 0+1 run in parallel against the same endpoint) | Reduce workflow.max_workers, or use different endpoints for Steps 0 and 1 |
image_referring_expression: no input records | data.image_dir is empty / not a directory, and no input_annotations_jsonl was supplied | Confirm data.image_dir exists and contains .jpg / .jpeg / .png files |
Step 0 produces empty regions[] for every image | KITTI label files missing or malformed (each line needs at least 8 space-separated fields) | Verify data.kitti_label_dir and that each <stem>.txt matches the image stem; check the first few label lines |
failed to build query: 'type' warning in Step 2 | KITTI line missing the type field (field 0) | Fix the offending label file; lines with fewer than 8 fields are silently skipped |
| Truncated / unparseable VLM output (Step 0 or Step 2) | Response cut off before the end of the array / before all group lines were emitted | Raise vlm.gemini.max_output_tokens / vlm.openai.max_tokens; lower temperature; for very large images split into smaller batches |
| Step 2 grouping looks wrong even though Step 0 was good | VLM cannot localize at the requested resolution | Raise media_resolution to MEDIA_RESOLUTION_HIGH; consider a stronger model |
| Step 3 introduces new errors | Verification model is too aggressive | Disable Step 3 (drop "3" from workflow.steps) or switch to a stronger model |
| Re-runs skip everything | Each step's annotations.jsonl already exists | Set image_referring_expression.workflow.force_reprocess=true to regenerate |
Legacy .txt.stepN files missing | workflow.output_format is "jsonl" | Set workflow.output_format=both (or legacy) |
Unknown autolabel_type | YAML missing or wrong autolabel_type | Set autolabel_type: "image_referring_expression" at the top of the spec |
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Declares allowed tools, Needs an API key
The author says: Requires docker + nvidia-container-toolkit + at least one VLM endpoint (Gemini API key or OpenAI-compatible).
Security
What our scanner noticed· Medium risk
- Large base64-encoded blobs
- Instructions that use the network
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- NVIDIA Corporation
- Publisher
- NVIDIA (Official)
- Source
- NVIDIA/skills · skills/tao-generate-referring-expressions · commit fce3cca
- Licence
- Apache License 2.0 (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE-APACHE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by NVIDIA Corporation or NVIDIA.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download tao-generate-referring-expressions.zip