Generated banner for Tao Generate Referring Expressions
Generated logo for Tao Generate Referring Expressions

Tao Generate Referring Expressions

NVIDIAOfficialCreated by NVIDIA Corporation

  • Skill
  • Workflow
  • Data and AI
  • Apache-2.0
  • Medium risk

From the author

Four-step image referring-expression pipeline: turns images plus KITTI bounding-box labels into region descriptions, scene captions, grounded referring expressions, and (optionally) verified expressions via VLM distillation. Use when the user wants to generate referring-expression annotations from images with KITTI labels, build region descriptions, produce grouped grounding phrases tied to bboxes, run a double-check verification pass on grounding expressions, auto-label traffic / scene images for referring datasets, or run the image_referring_expression pipeline. Triggers include 'referring expression', 'region description', 'KITTI labels', 'spatial relationship annotation', 'auto-label image referring expression', 'image_referring_expression'.
— the description by NVIDIA Corporation

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

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/nvidia/tao-generate-referring-expressions/download
  2. Unzip 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-setup skill 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:

  1. Images: Ask for data.image_dir, the directory containing .jpg, .jpeg, or .png images.

  2. KITTI labels: Ask for data.kitti_label_dir, the directory containing one .txt label 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.

  3. Resume from existing annotations: If the user already has a unified annotations.jsonl from a previous run, set data.input_annotations_jsonl to that file instead of seeding from data.image_dir and data.kitti_label_dir.

  4. API access: Ask the user which VLM endpoint they want to use. Present these five options and act on the choice:

    1. Gemini — set vlm.backend: "gemini"; require GOOGLE_API_KEY (env var or vlm.gemini.api_key).

    2. NIM (e.g. https://inference-api.nvidia.com/v1) — set vlm.backend: "openai"; collect base_url, model_name, and api_key.

    3. TAO inference microservice (self-hosted, OpenAI-compatible). Confirm whether the server is already running:

      • Running — collect base_url, model_name, and (optionally) api_key; set vlm.backend: "openai".

      • Not running — guide the user through the skills/applications/tao-run-inference-service skill, which stands up a local TAO inference microservice with an OpenAI-compatible API. Before promising a specific model, check skills/applications/tao-run-inference-service/references/service.yaml for valid_network_arch_config_basenames. Once the server is up, collect base_url, model_name, and (optionally) api_key; set vlm.backend: "openai".

    4. vLLM (self-hosted, OpenAI-compatible). Confirm whether the server is already running:

      • Running — collect base_url, model_name, and (optionally) api_key; set vlm.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; set vlm.backend: "openai".

    5. Custom (any other OpenAI-compatible endpoint) — set vlm.backend: "openai"; collect base_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.

  5. 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 context

    • No verification: ["0", "1", "2"]

    • Custom subset: any supported subset of steps

  6. Output format: Choose one of:

    • jsonl: unified schema only

    • legacy: byte-compatible .txt.stepN files only

    • both: 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_KEY

Generate 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.

  1. Run on 5-10 images with all four steps.

  2. Inspect step_0_region_expr/annotations.jsonl — are object types, colors, and discriminating phrases accurate?

  3. Inspect step_2_grounding_expr/annotations.jsonl — are objects grouped sensibly, and do bbox coordinates match the described groups?

  4. Inspect step_3_double_check/annotations.jsonl — were mismatched bboxes removed or tightened? Are any new errors introduced (rare)?

  5. If quality is insufficient, switch the VLM to a stronger model (e.g. gemini-2.5-pro or a larger Qwen3-VL endpoint), raise media_resolution / max_output_tokens, then re-run with workflow.force_reprocess=true.

  6. Scale to the full dataset once satisfied.

Configuration

Key configuration fields (full reference in references/configuration.md):

FieldDefaultDescription
workflow.steps["0","1","2","3"]Which steps to execute (0=region_expr, 1=image_caption, 2=grounding_expr, 3=double_check)
workflow.max_workers4Parallel threads per step (watch API rate limits)
workflow.force_reprocessfalseIgnore 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_dirrequiredDirectory of input images (.jpg / .jpeg / .png)
data.kitti_label_dirrequired (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:

  1. Image directory + KITTI labels (default). Set data.image_dir and data.kitti_label_dir. The orchestrator walks the image directory, reads the matching <stem>.txt KITTI file, parses bboxes (fields 0 + 4-7), reads each image's width/height via PIL, and writes a seed_annotations.jsonl to results_dir/.

  2. Pre-seeded annotations JSONL (resume / pre-computed regions). Set data.input_annotations_jsonl to 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 (unless input_annotations_jsonl was supplied).

  • step_0_region_expr/annotations.jsonl — adds regions[] (each with bbox/bbox_2d, type, color, description).

  • step_1_image_caption/annotations.jsonl — adds caption (string).

  • step_2_grounding_expr/annotations.jsonl — adds expressions[] (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_format is "legacy" or "both", each step also writes byte-compatible step_<N>_*/labels/<stem>.txt.stepN files 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

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 from

Key Configuration Decisions

DecisionConfig fieldGuidance
Which steps to runworkflow.stepsStart 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 captioninclude / 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 providervlm.backend"gemini" for Google Gemini models, "openai" for any OpenAI-compatible endpoint (NIM, vLLM, etc.)
Parallelismworkflow.max_workersHigher = 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 restartworkflow.force_reprocessfalse reuses each step's existing annotations.jsonl (and labels/ legacy files). Set true to regenerate everything
Resume from prior rundata.input_annotations_jsonlPoint at an existing unified annotations.jsonl to skip the KITTI seeding pass
Output formatworkflow.output_format"jsonl" for the unified schema only; "legacy" for byte-compatible 2d-data-engine .txt.stepN files only; "both" (recommended) emits both
Image resolutionvlm.gemini.media_resolutionUse MEDIA_RESOLUTION_HIGH for accurate bbox-to-object matching in Steps 0/2/3. Lower resolutions are cheaper but degrade localization
Output truncationvlm.gemini.max_output_tokens / vlm.openai.max_tokensStep 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_here

Or 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) or gemini-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: 8192

For self-hosted models, the pipeline accepts any endpoint that speaks the OpenAI chat-completions API. Two common ways to provision one:

  1. skills/applications/tao-run-inference-service skill — workflow for standing up a TAO inference microservice locally. Should support Cosmos, Qwen, and Gemma. Check that skill's references/service.yaml valid_network_arch_config_basenames for the current model list.

  2. 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: 300

All Parameters

ParameterDefaultDescription
workflow.steps["0","1","2","3"]Pipeline steps to execute (0=region_expr, 1=image_caption, 2=grounding_expr, 3=double_check)
workflow.max_workers4Thread pool size for parallel API calls within each step
workflow.force_reprocessfalseIgnore 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.temperature0.3VLM sampling temperature
vlm.gemini.max_output_tokens8192Maximum tokens in Gemini response
vlm.gemini.timeout120Request 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.temperature0.7OpenAI-compatible sampling temperature
vlm.openai.max_tokens4096Maximum tokens in the OpenAI-compatible response
vlm.openai.timeout60Request 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.step3

Each 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

ErrorCauseFix
GOOGLE_API_KEY not setGemini API key missingexport GOOGLE_API_KEY=your_key or set image_referring_expression.vlm.gemini.api_key in the YAML
429 / rate limit errorsToo 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 recordsdata.image_dir is empty / not a directory, and no input_annotations_jsonl was suppliedConfirm data.image_dir exists and contains .jpg / .jpeg / .png files
Step 0 produces empty regions[] for every imageKITTI 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 2KITTI 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 emittedRaise 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 goodVLM cannot localize at the requested resolutionRaise media_resolution to MEDIA_RESOLUTION_HIGH; consider a stronger model
Step 3 introduces new errorsVerification model is too aggressiveDisable Step 3 (drop "3" from workflow.steps) or switch to a stronger model
Re-runs skip everythingEach step's annotations.jsonl already existsSet image_referring_expression.workflow.force_reprocess=true to regenerate
Legacy .txt.stepN files missingworkflow.output_format is "jsonl"Set workflow.output_format=both (or legacy)
Unknown autolabel_typeYAML missing or wrong autolabel_typeSet 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