Instance Display Configuration¶
Instance display separates what content to show annotators from what annotations to collect, so any combination of content types (images, videos, audio, text) can sit alongside any annotation schemes (radio buttons, checkboxes, spans, and the rest).
Why Use Instance Display?¶
To show an image and classify it with radio buttons, declare the image under instance_display and the question under annotation_schemes. The image_annotation schema is for drawing on an image, not for displaying one.
With instance_display, you can explicitly configure what content to display:
# OLD (deprecated workaround)
annotation_schemes:
- annotation_type: image_annotation
name: image_display
min_annotations: 0 # Just to show the image
tools: [bbox]
labels: [unused]
- annotation_type: radio
name: category
labels: [A, B, C]
# NEW (recommended)
instance_display:
fields:
- key: image_url
type: image
annotation_schemes:
- annotation_type: radio
name: category
labels: [A, B, C]
Basic Configuration¶
Add an instance_display section to your YAML configuration:
instance_display:
fields:
- key: "image_url" # Field name in your data JSON
type: "image" # Content type
label: "Image to classify" # Optional header
display_options:
max_width: 600
zoomable: true
layout:
direction: "vertical" # vertical or horizontal
gap: "20px"
A display's own options may also be written at the field's top level, which is
how describe_display_type lists them:
- key: "image_url"
type: "image"
max_width: 600
zoomable: true
Only options the display declares are read this way, so an unrelated key is
still ignored. When an option appears in both places, display_options wins.
Supported Display Types¶
| Type | Description | Span Target |
|---|---|---|
text |
Plain text content | Yes |
html |
Sanitized HTML content | No |
image |
Image display with zoom | No |
video |
Video player | No |
audio |
Audio player | No |
audio_dialogue |
Diarized transcript synced to audio: speaker bubbles, per-turn playback, per-turn ratings, spans, cross-turn linking | Yes |
gallery |
Scrollable image gallery with captions | No |
depth_map |
Depth map: near/far window, colormap, overlay on RGB, and the distance in metres under the cursor | No |
dialogue |
Conversation turns | Yes |
conversation_tree |
Conversation tree with collapsible branching nodes | No |
multi_agent_discussion |
Multi-agent discussion with agent legend, colors, addressees, and filtering | Yes |
interactive_chat |
Interactive agent chat with post-interaction trace display | Yes |
pairwise |
Side-by-side comparison | No |
code |
Syntax-highlighted source code | Yes |
spreadsheet |
Tabular data (Excel/CSV) | Yes (row/cell) |
document |
Rich documents (Word, Markdown, HTML) | Yes |
pdf |
PDF documents with page controls | Yes |
agent_trace |
Agent trace as vertical step cards (Thought/Action/Observation) | No |
coding_trace |
Coding agent trace with diffs, terminal blocks, file tree | Yes |
eval_trace |
One trace split into three panes: reasoning | function calls | final answer | Yes |
cot_trace |
Long chain-of-thought trace: step cards with a sticky progress rail for process-reward verification | No |
web_agent_trace |
Web agent trace with screenshots, SVG click/scroll overlays, step navigation | No |
live_agent |
Live AI agent viewer with real-time screenshots and intervention controls | No |
live_coding_agent |
Live coding agent viewer with real-time streaming and intervention controls | No |
For speech and dialogue over audio, see Audio Dialogue and Transcript Format Support. For detailed configuration of document formats (code, spreadsheet, document, pdf), see the Extended Format Support guide. For agent-trace displays (agent_trace, coding_trace, eval_trace, cot_trace, web_agent_trace, live_agent, live_coding_agent), see the Agent Evaluation guides — eval_trace has its own three-pane eval guide.
Display Type Options¶
The sections below cover the options authors reach for most often. They are not
the whole set — pdf alone takes twenty-two. The registry is the complete list,
and it is generated from the renderers, so it cannot fall behind them:
from potato.server_utils.displays.registry import display_registry
for display in display_registry.list_displays():
print(display["name"], sorted(display["optional_fields"]))
An option name Potato does not recognise is now a configuration error rather than a silently ignored key, and the message suggests the nearest real one:
instance_display.fields[0].display_options.speeker_key is not an option for
display type 'multi_agent_discussion', so it would be ignored silently.
Did you mean 'speaker_key'? Accepted options: ...
That check found four dead keys in Potato's own example configs, so it is worth
running potato validate over an existing project after upgrading.
Options every display type takes¶
Three options belong to the resizable container Potato wraps around each field rather than to any one renderer, so they work on every display type:
| Option | Default | Description |
|---|---|---|
resizable |
true |
Wrap the field in a container the annotator can drag to resize. Set false to leave the field at its natural height. |
max_height |
500 |
How tall the field may grow before it scrolls inside itself. |
min_height |
100 |
How short it may be dragged. |
Lengths accept a number or a CSS length: 400, "400px", "50vh" and
"calc(100vh - 200px)" all work. A bare number means pixels.
resizable: false is worth knowing about for a short field — a one-line label
in a drag handle looks like a mistake.
Text Display¶
- key: "text"
type: "text"
label: "Document"
display_options:
collapsible: false # Make content collapsible
collapsed_by_default: false # Start collapsed (needs collapsible)
max_height: 400 # Max height in pixels before scrolling
preserve_whitespace: true # Preserve line breaks and spacing
collapsed_by_default keeps a long case file out of the way until the annotator
asks for it, rather than pushing the questions down the page on every item.
Image Display¶
- key: "image_url"
type: "image"
label: "Image"
display_options:
max_width: 800 # Max width (number or CSS string)
max_height: 600 # Max height
zoomable: true # Enable zoom controls
alt_text: "Description" # Alt text for accessibility
object_fit: "contain" # CSS object-fit property
Video Display¶
- key: "video_url"
type: "video"
label: "Video"
display_options:
max_width: 800
max_height: 450
controls: true # Show video controls
autoplay: false # Auto-play on load
loop: false # Loop playback
muted: false # Start muted
Audio Display¶
- key: "audio_url"
type: "audio"
label: "Audio"
display_options:
controls: true # Show audio controls
autoplay: false
loop: false
show_waveform: false # Show waveform visualization
Audio Dialogue Display¶
For a diarized transcript played against its audio — podcasts, interviews, meetings,
call-center recordings — use audio_dialogue rather than audio or dialogue. Turns
render as colored speaker bubbles; each has a ▶ that plays just that turn, and a sticky
transport bar plays the whole recording while highlighting and auto-scrolling the active
turn. Undiarized turns can be assigned a speaker by the annotator.
- key: "conversation"
type: "audio_dialogue"
label: "Transcript"
span_target: true # enable span highlighting inside turns
display_options:
speakers:
- {id: host, name: Host, color: "#7c3aed", side: left}
- {id: guest, name: Guest, color: "#059669", side: right}
The transcript may be inline, or a path to a sidecar file in any of 21 supported formats — Whisper, WhisperX, cloud ASR, SRT/WebVTT, YouTube captions, TextGrid/EAF:
{"id": "int_001",
"conversation": {"audio": "media/int_001.mp3", "transcript": "media/int_001.srt"}}
See Audio Dialogue for every option, per-turn ratings, cross-turn linking, and speaker assignment.
Dialogue Display¶
For text-only conversations with no audio:
- key: "conversation"
type: "dialogue"
label: "Conversation"
display_options:
alternating_shading: true # Alternate background colors
speaker_extraction: true # Extract "Speaker:" from text
show_turn_numbers: false # Show turn numbers
max_height: 600 # Cap the height and scroll inside it
A long agent trace otherwise pushes the annotation form off the bottom of the
page. max_height scrolls the container rather than clipping it, and takes a
number of pixels or any CSS length.
Data format for dialogue (each line in JSONL file):
{"id": "conv_001", "conversation": ["Speaker A: Hello there!", "Speaker B: Hi, how are you?"]}
Or with structured data:
{"id": "conv_001", "conversation": [{"speaker": "Alice", "text": "Hello there!"}, {"speaker": "Bob", "text": "Hi, how are you?"}]}
Threaded conversations¶
When each turn names the turn it replies to, dialogue renders the reply
structure — nesting depth is derived from reply_to, so nothing has to
precompute it:
- key: "thread"
type: "dialogue"
label: "Discussion"
display_options:
indent_replies: true # indent by reply depth
max_indent_depth: 6 # cap the visual indent
show_reply_lines: true # vertical thread guides
show_timestamps: true # per-turn times
timestamp_format: relative # relative | absolute | epoch
turn_meta_fields: [score] # per-turn metadata chips
meta_key: meta # where that metadata lives
depth_key: depth # explicit depth, when the data has one
reply_to_key: reply_to # where the parent reference lives
{"id": "t1", "thread": [
{"id": "m1", "speaker": "ana", "text": "Anyone tried the new API?"},
{"id": "m2", "speaker": "ben", "text": "Yes, works fine.", "reply_to": "m1"},
{"id": "m3", "speaker": "cy", "text": "Not for me.", "reply_to": "m2"}
]}
The indentation, timestamps, and chips are drawn as CSS pseudo-element content, so they do not affect span offsets. See Dialogue Annotation for the full guide and ConvoKit for importing conversational corpora.
Pairwise Display¶
- key: "comparison"
type: "pairwise"
label: "Compare Options"
display_options:
cell_width: "auto" # Cell width; "auto" splits the row evenly
show_labels: true # Show A/B labels
labels: ["Option A", "Option B"] # Custom labels
vertical_on_mobile: true # Stack vertically on mobile
Layout Options¶
Control how multiple fields are arranged:
instance_display:
layout:
direction: horizontal # horizontal or vertical
gap: 24px # Space between fields
Span Annotation Support¶
Text-based display types (text, dialogue) can be targets for span annotation. Mark a field as a span target:
instance_display:
fields:
- key: "document"
type: "text"
span_target: true # Enable span annotation on this field
annotation_schemes:
- annotation_type: span
name: entities
labels: [PERSON, LOCATION, ORG]
Multiple Span Targets¶
You can have multiple text fields that support span annotation:
instance_display:
fields:
- key: "source_text"
type: "text"
label: "Source Document"
span_target: true
- key: "summary"
type: "text"
label: "Summary"
span_target: true
annotation_schemes:
- annotation_type: span
name: factual_errors
labels: [contradiction, unsupported, fabrication]
When multiple span targets are used, annotations are stored with field association:
{
"factual_errors": {
"source_text": [],
"summary": [
{"start": 45, "end": 67, "label": "unsupported"}
]
}
}
Linking Annotation Schemas to Display Fields¶
For media annotation schemas (image_annotation, video_annotation, audio_annotation), you can link them to display fields using source_field:
instance_display:
fields:
- key: "image_url"
type: "image"
annotation_schemes:
- annotation_type: image_annotation
source_field: "image_url" # Links to display field
tools: [bbox]
labels: [person, car]
Name it when there is more than one image field, or when the field the scheme
should draw on is not the one it would otherwise find. With a single image
field, image_annotation picks it up on its own.
Example: Image Classification¶
A complete example showing image classification:
annotation_task_name: "Image Classification"
data_files:
- data/images.json
item_properties:
id_key: id
text_key: image_url
task_dir: .
output_annotation_dir: annotation_output
instance_display:
fields:
- key: image_url
type: image
label: "Image to Classify"
display_options:
max_width: 600
zoomable: true
- key: context
type: text
label: "Additional Context"
display_options:
collapsible: true
annotation_schemes:
- annotation_type: radio
name: category
description: "What category best describes this image?"
labels:
- nature
- urban
- people
- objects
user_config:
allow_all_users: true
Sample data file (data/images.json) in JSONL format (one JSON object per line):
{"id": "img_001", "image_url": "https://example.com/image1.jpg", "context": "Taken in summer 2023"}
{"id": "img_002", "image_url": "https://example.com/image2.jpg", "context": "Winter landscape"}
Example: Multi-Modal Annotation¶
Video alongside a transcript with span annotation:
annotation_task_name: "Video Analysis"
instance_display:
layout:
direction: horizontal
gap: 24px
fields:
- key: video_url
type: video
label: "Video"
display_options:
max_width: "45%"
- key: transcript
type: text
label: "Transcript"
span_target: true
annotation_schemes:
- annotation_type: radio
name: sentiment
labels: [positive, neutral, negative]
- annotation_type: span
name: highlights
labels:
- key_point
- question
- supporting_evidence
Backwards Compatibility¶
- Existing configs without
instance_displaycontinue to work unchanged - The
text_keyfromitem_propertiesis still used as fallback - Legacy media detection via annotation schemes still works
Migration from Display-Only Patterns¶
If you were using annotation schemas just to display content:
Before (deprecated):
annotation_schemes:
- annotation_type: image_annotation
name: image_display
min_annotations: 0
tools: [bbox]
labels: [unused]
After (recommended):
instance_display:
fields:
- key: image_url
type: image
You will see a deprecation warning in the logs when using the old pattern.
Troubleshooting¶
Missing Field Error¶
If you see an error like:
InstanceDisplayError: Display field 'image_url' not found in instance data.
Available fields: ['id', 'text', 'other_field']
This means your data doesn't have the field specified in instance_display.fields. Check:
1. The key matches exactly with your data field name
2. Your data file includes the required field
3. There are no typos in field names
Image Not Displaying¶
- Check that the URL is accessible (try opening it in a browser)
- Ensure the URL is complete (includes http:// or https://)
- Check browser console for CORS errors
Span Annotation Not Working¶
- Verify the field has
span_target: true - Ensure the field type supports span annotation (
textordialogue) - Check that a
spanannotation scheme is defined
Advanced Features¶
Per-Turn Dialogue Ratings¶
You can add inline rating widgets to individual dialogue turns, allowing annotators to rate specific speakers' turns directly within the conversation view.
instance_display:
fields:
- key: conversation
type: dialogue
label: "Conversation"
display_options:
show_turn_numbers: true
per_turn_ratings:
speakers: ["Agent"] # Only show ratings for these speakers
schema_name: "turn_quality" # Name for the stored annotation data
scheme:
type: likert
size: 5 # Number of rating points (1-5)
labels: ["Poor", "Excellent"] # Min and max labels
How it works:
- Rating circles appear inline below each matching speaker's turn
- Clicking a value selects it (clicking again deselects)
- Ratings fill up to the selected value (1-2-3 all highlight if you click 3)
- All per-turn ratings are stored as a single JSON object: {"0": 4, "2": 5, "4": 3} where keys are turn indices
- The data is saved under the schema_name specified in the config
Data format for per-turn ratings:
{
"turn_quality": "{\"0\": 4, \"2\": 5, \"4\": 3}"
}
Custom Layouts with Instance Display¶
Instance display works alongside custom layout files for maximum flexibility. The display content (images, text, dialogues) renders separately above the annotation forms, which you can fully customize with CSS:
# Display content
instance_display:
layout:
direction: horizontal
gap: 20px
fields:
- key: image_url
type: image
- key: notes
type: text
# Custom annotation layout
task_layout: layouts/custom_task_layout.html
# Annotation schemes (rendered by custom layout)
annotation_schemes:
- annotation_type: radio
name: category
labels: [A, B, C]
See the Layout Customization Guide for details on creating custom layouts.
Example Projects¶
Ready-to-run examples demonstrating advanced instance display usage:
# Content moderation with image + text side by side
python potato/flask_server.py start examples/custom-layouts/content-moderation/config.yaml -p 8000
# Dialogue QA with per-turn ratings
python potato/flask_server.py start examples/custom-layouts/dialogue-qa/config.yaml -p 8000
# Medical review with horizontal image + notes
python potato/flask_server.py start examples/custom-layouts/medical-review/config.yaml -p 8000
See examples/custom-layouts/ for the complete source of each example.