MCP tools¶
Every tool the annotools MCP server exposes, in the order an annotation session usually needs them.
Coordinates are normalized 0.0-1.0 relative to the uncropped source; shared parameter groups, metadata
keys, and error rules are in the overview. The parameter tables come from the server's tool
schemas; the text below each table is the tool's specification.
preview_image¶
Downscale an image (optionally zoomed to a normalized crop box) to fit a token budget.
Returns the image followed by one JSON metadata object (original_size, crop, output_size, scale, format). Coordinates are normalized 0-1 relative to the uncropped source.
| Parameter | Type | Default | Required | Description |
|---|---|---|---|---|
source |
str |
— | yes | Local path or fsspec URL (file://, s3://, gs://, http(s)://, memory://) |
crop |
[float, float, float, float] | null |
null |
no | Normalized [x_min, y_min, x_max, y_max] of the source to zoom into (0-1, min < max) |
target_pixels |
int | null |
null |
no | Cap on output area in pixels |
max_width |
int |
384 |
no | Maximum output width in pixels |
max_height |
int |
384 |
no | Maximum output height in pixels |
allow_upscale |
bool |
false |
no | Enlarge small images/regions up to the limits |
output_format |
"jpeg" | "png" | "webp" |
"jpeg" |
no | Encoding: jpeg (quality 90), png, or webp |
save_to |
str | null |
null |
no | Also write the encoded image to this path or fsspec URL |
Goal¶
Return an image at a size an MLLM can read within a bounded token budget, optionally zoomed into a region, so an agent never has to load a full-resolution asset to look at it.
Interface¶
Tool: preview_image (MCP) / annotools.image.preview.preview + encode (library)
Parameters: exactly PreviewOptions from .agents/knowledge/spec/mcp-overview.md.
Returns: [Image, metadata] with the base metadata keys.
Behavior¶
- Load
sourcethrough fsspec; apply EXIF orientation; recordoriginal_size. - If
cropis given, validate it and crop to the pixel box (rounded outward so the region is never smaller than requested). - Compute the output size: start from the cropped size; scale down to fit
max_width×max_heightand, if given,target_pixels; scale up only whenallow_upscaleis true and the cropped image is smaller than the limits (the same fit rule, applied upward). Aspect ratio is preserved; sizes are rounded to integers ≥ 1. - Resize with Lanczos (down) / bicubic (up); encode as
output_format(JPEG q90, alpha flattened on white); optionally write tosave_to. - Error:
cropoutside[0, 1]or non-increasing →ValueError("crop: …"). - Error:
max_width,max_height,target_pixels< 1 →ValueError. - Error: missing source →
FileNotFoundError; other read failures →OSError; undecodable content →ValueError; all name the URI. - EXIF orientation is applied on load (a rotated-by-tag portrait reports its display size).
Out of scope¶
Grids, overlays, video frames (own specs).
preview_image_grid¶
Preview an image with a semi-transparent grid (default 10x10 cells, 50% white) to anchor positions.
Returns the image and one JSON metadata object including grid step sizes in normalized coordinates
of the cropped view. Coordinates are normalized 0-1 relative to the uncropped source.
| Parameter | Type | Default | Required | Description |
|---|---|---|---|---|
source |
str |
— | yes | Local path or fsspec URL (file://, s3://, gs://, http(s)://, memory://) |
columns |
int |
10 |
no | Number of grid cells along this axis |
rows |
int |
10 |
no | Number of grid cells along this axis |
mode |
"ratio" | "fixed" |
"ratio" |
no | |
column_width |
int | null |
null |
no | Cell size in output pixels (mode='fixed') |
row_width |
int | null |
null |
no | Cell size in output pixels (mode='fixed') |
color |
"white" | "black" | "invert" |
"white" |
no | |
opacity |
float |
0.5 |
no | Line opacity, 0 (invisible) to 1 (solid) |
line_width |
int |
1 |
no | Grid line width in output pixels |
crop |
[float, float, float, float] | null |
null |
no | Normalized [x_min, y_min, x_max, y_max] of the source to zoom into (0-1, min < max) |
target_pixels |
int | null |
null |
no | Cap on output area in pixels |
max_width |
int |
384 |
no | Maximum output width in pixels |
max_height |
int |
384 |
no | Maximum output height in pixels |
allow_upscale |
bool |
false |
no | Enlarge small images/regions up to the limits |
output_format |
"jpeg" | "png" | "webp" |
"jpeg" |
no | Encoding: jpeg (quality 90), png, or webp |
save_to |
str | null |
null |
no | Also write the encoded image to this path or fsspec URL |
Goal¶
Overlay a semi-transparent grid on a preview so an MLLM can anchor positions to cells; 10×10 by default (9 lines each way), which evidence suggests improves localization without occluding content.
Interface¶
Tool: preview_image_grid (MCP) / annotools.image.grid.draw_grid (library)
Parameters: PreviewOptions plus GridOptions (flattened: columns, rows, mode, column_width,
row_width, color, opacity, line_width) from .agents/knowledge/spec/mcp-overview.md.
Returns: [Image, metadata] with the base keys plus
grid: {"columns": int, "rows": int, "step_x": float, "step_y": float, "cell_width": float,
"cell_height": float} where step_* is the cell size in normalized coordinates of the cropped view
(so 1 / columns in ratio mode) and cell_* is the same cell size in output pixels (output_width /
columns, or the configured widths in fixed mode). When a later step re-fits the image (the
preview_image_segmentation legend), cell_* is rescaled with it and refers to the returned pixels.
Behavior¶
- Render the preview exactly as
preview_image. - Compute line positions in output pixels: ratio mode →
i × width / columnsfori = 1..columns−1(same for rows); fixed mode → multiples ofcolumn_width/row_widthinside the image; the resulting cell counts are reported ascolumns/rows. - Draw each line
line_widthpx wide, centred on the position, blended withopacity:whiteandblackblend toward that color;invertblends toward the per-pixel inverse of the image. - Encode as usual.
- Error:
columns/rows< 1,opacityoutside[0, 1],line_width< 1 →ValueError. - Error:
mode="fixed"without both widths, or a width < 1 →ValueError("column_width/row_width …").
Out of scope¶
Row/column index labels; overlays (own specs).
preview_image_bboxes¶
Preview an image with bounding boxes (normalized xyxy, optional labels), optionally over a grid.
Use it to check candidate detections before writing them to the database. Returns the image and one
JSON metadata object (base keys, grid when used, objects count).
| Parameter | Type | Default | Required | Description |
|---|---|---|---|---|
source |
str |
— | yes | Local path or fsspec URL (file://, s3://, gs://, http(s)://, memory://) |
objects |
array[object{bbox, label, color}] |
— | yes | A bounding box in normalized coordinates of the uncropped source. |
grid |
object{columns, rows, mode, column_width, row_width, color, opacity, line_width} | null |
null |
no | Grid parameters shared by every tool that accepts grid. |
line_width |
int |
2 |
no | Outline width in output pixels |
crop |
[float, float, float, float] | null |
null |
no | Normalized [x_min, y_min, x_max, y_max] of the source to zoom into (0-1, min < max) |
target_pixels |
int | null |
null |
no | Cap on output area in pixels |
max_width |
int |
384 |
no | Maximum output width in pixels |
max_height |
int |
384 |
no | Maximum output height in pixels |
allow_upscale |
bool |
false |
no | Enlarge small images/regions up to the limits |
output_format |
"jpeg" | "png" | "webp" |
"jpeg" |
no | Encoding: jpeg (quality 90), png, or webp |
save_to |
str | null |
null |
no | Also write the encoded image to this path or fsspec URL |
Goal¶
Let an agent see its candidate bounding boxes on the preview (optionally over the grid) before writing them to the database, so it can correct positions in a short verify-and-adjust loop.
Interface¶
Tool: preview_image_bboxes (MCP) / annotools.image.overlay.draw_bboxes (library)
Parameters: PreviewOptions, grid: GridOptions | null (nested object; null = no grid),
objects: list[BBoxObject] (≥ 1), line_width: int = 2 (≥ 1).
BBoxObject: bbox: [x_min, y_min, x_max, y_max] normalized to the uncropped source,
label: str | null, color: str = "blue" (CSS/PIL name or #RRGGBB).
Returns: [Image, metadata] with the base keys, grid when a grid was drawn, and objects: int.
Behavior¶
- Render the preview (and the grid, if given) exactly as
preview_image_grid. - Map each box from source-normalized coordinates into output pixels through the crop: parts outside
the crop are clipped; a box entirely outside is skipped (still counted in
objects). - Draw the rectangle outline
line_widthpx wide incolor. With a label, draw a filled tag incolorwith the label text (PIL default font) just above the box's top-left corner, moved inside the image when it would leave the frame; text is white or black depending on the color's luminance. - Encode as usual.
- Error: empty
objects, invalid box (range/order), unknown color,line_width < 1→ValueErrornaming the object index (objects[2].bbox: …).
Out of scope¶
Keypoints, polygons, segmentation, filled boxes.
preview_image_keypoints¶
Preview an image with keypoints (normalized xy, optional labels) drawn as dots, optionally over a grid.
Returns the image and one JSON metadata object (base keys, grid when used, objects count).
| Parameter | Type | Default | Required | Description |
|---|---|---|---|---|
source |
str |
— | yes | Local path or fsspec URL (file://, s3://, gs://, http(s)://, memory://) |
objects |
array[object{point, label, color}] |
— | yes | A single point in normalized coordinates of the uncropped source. |
grid |
object{columns, rows, mode, column_width, row_width, color, opacity, line_width} | null |
null |
no | Grid parameters shared by every tool that accepts grid. |
point_diameter |
int |
3 |
no | Vertex/point dot diameter in output pixels |
crop |
[float, float, float, float] | null |
null |
no | Normalized [x_min, y_min, x_max, y_max] of the source to zoom into (0-1, min < max) |
target_pixels |
int | null |
null |
no | Cap on output area in pixels |
max_width |
int |
384 |
no | Maximum output width in pixels |
max_height |
int |
384 |
no | Maximum output height in pixels |
allow_upscale |
bool |
false |
no | Enlarge small images/regions up to the limits |
output_format |
"jpeg" | "png" | "webp" |
"jpeg" |
no | Encoding: jpeg (quality 90), png, or webp |
save_to |
str | null |
null |
no | Also write the encoded image to this path or fsspec URL |
Goal¶
Show keypoint annotations as dots (with optional labels) on the preview, optionally over the grid, so an agent can verify point placement before storing it.
Interface¶
Tool: preview_image_keypoints (MCP) / annotools.image.overlay.draw_keypoints (library)
Parameters: PreviewOptions, grid: GridOptions | null, objects: list[KeypointObject] (≥ 1),
point_diameter: int = 3 (≥ 1, output pixels).
KeypointObject: point: [x, y] normalized to the uncropped source, label: str | null,
color: str = "blue".
Returns: [Image, metadata] with the base keys, grid when drawn, and objects: int.
Behavior¶
- Render the preview (and grid) as
preview_image_grid. - Map each point through the crop; points outside the output image are skipped (still counted).
- Draw a filled circle of
point_diameterpx centred on the mapped point. With a label, draw the same filled tag as bounding boxes to the right of the dot, vertically centred on it (moved inside the frame when needed). - Error: empty
objects, point outside[0, 1], unknown color,point_diameter < 1→ValueErrornaming the object index.
Out of scope¶
Skeleton edges between keypoints; visibility flags.
preview_image_polygons¶
Preview an image with polygons (flat normalized [x1, y1, x2, y2, ...]) with vertex dots and indices.
Works for COCO-style outlines and DOTA-style rotated boxes (4 corners). Returns the image and one JSON
metadata object (base keys, grid when used, objects count).
| Parameter | Type | Default | Required | Description |
|---|---|---|---|---|
source |
str |
— | yes | Local path or fsspec URL (file://, s3://, gs://, http(s)://, memory://) |
objects |
array[object{points, label, color}] |
— | yes | A closed polygon as a flat list of normalized coordinates of the uncropped source. |
grid |
object{columns, rows, mode, column_width, row_width, color, opacity, line_width} | null |
null |
no | Grid parameters shared by every tool that accepts grid. |
line_width |
int |
2 |
no | Outline width in output pixels |
point_diameter |
int |
3 |
no | Vertex/point dot diameter in output pixels |
show_point_index |
bool |
true |
no | |
crop |
[float, float, float, float] | null |
null |
no | Normalized [x_min, y_min, x_max, y_max] of the source to zoom into (0-1, min < max) |
target_pixels |
int | null |
null |
no | Cap on output area in pixels |
max_width |
int |
384 |
no | Maximum output width in pixels |
max_height |
int |
384 |
no | Maximum output height in pixels |
allow_upscale |
bool |
false |
no | Enlarge small images/regions up to the limits |
output_format |
"jpeg" | "png" | "webp" |
"jpeg" |
no | Encoding: jpeg (quality 90), png, or webp |
save_to |
str | null |
null |
no | Also write the encoded image to this path or fsspec URL |
Goal¶
Preview polygons — COCO-style segmentation outlines or DOTA-style rotated boxes given as 4 corners — with vertex dots and 1-based vertex indices, so an agent can verify both shape and vertex order.
Interface¶
Tool: preview_image_polygons (MCP) / annotools.image.overlay.draw_polygons (library)
Parameters: PreviewOptions, grid: GridOptions | null, objects: list[PolygonObject] (≥ 1),
line_width: int = 2, point_diameter: int = 3, show_point_index: bool = true.
PolygonObject: points: [x1, y1, x2, y2, …] (flat, even count, ≥ 3 points, normalized to the
uncropped source), label: str | null, color: str = "blue".
Returns: [Image, metadata] with the base keys, grid when drawn, and objects: int.
Behavior¶
- Render the preview (and grid) as
preview_image_grid. - Map every vertex through the crop; draw the closed outline
line_widthpx wide (PIL clips segments outside the frame). A polygon with every vertex outside the frame is skipped (still counted). - Draw a dot of
point_diameterat each vertex; whenshow_point_indexis true, draw the 1-based index as a tag next to the vertex, offset away from the polygon centroid. - With a label, draw the label tag at the first vertex.
- Error: empty
objects, odd number of values or fewer than 3 points, a value outside[0, 1], unknown color,line_width/point_diameter < 1→ValueErrornaming the object index.
Out of scope¶
Filled polygons; segmentation masks (own spec); rotated-box conversion (rotated_bbox_to_polygon, P2).
preview_image_segmentation¶
Preview an image with an ID mask (instance/panoptic/semantic) blended on top, labelled or with a legend.
Regions are coloured with color_from_text(str(id)). Returns the image and one JSON metadata object
(base keys, grid when used, ids, and legend in legend mode).
| Parameter | Type | Default | Required | Description |
|---|---|---|---|---|
source |
str |
— | yes | Local path or fsspec URL (file://, s3://, gs://, http(s)://, memory://) |
mask_source |
str |
— | yes | Single-channel ID mask (uint8/uint16 PNG/TIFF); 0 = background |
annotation |
"label" | "legend" |
"label" |
no | label: ID at each region; legend: strip below |
id_names |
object{string: str} | null |
null |
no | Optional display names per ID |
alpha |
float |
0.5 |
no | Blend strength of region colours |
line_width |
int |
2 |
no | Region outline width in output pixels; 0 disables |
grid |
object{columns, rows, mode, column_width, row_width, color, opacity, line_width} | null |
null |
no | Grid parameters shared by every tool that accepts grid. |
crop |
[float, float, float, float] | null |
null |
no | Normalized [x_min, y_min, x_max, y_max] of the source to zoom into (0-1, min < max) |
target_pixels |
int | null |
null |
no | Cap on output area in pixels |
max_width |
int |
384 |
no | Maximum output width in pixels |
max_height |
int |
384 |
no | Maximum output height in pixels |
allow_upscale |
bool |
false |
no | Enlarge small images/regions up to the limits |
output_format |
"jpeg" | "png" | "webp" |
"jpeg" |
no | Encoding: jpeg (quality 90), png, or webp |
save_to |
str | null |
null |
no | Also write the encoded image to this path or fsspec URL |
Goal¶
Show an instance / panoptic / semantic mask on top of the preview (optionally over the grid) so an agent can check region extents and which ID is which, at preview size.
Interface¶
Tool: preview_image_segmentation (MCP) / annotools.image.segmentation.load_mask + overlay_mask
Parameters: PreviewOptions, grid: GridOptions | null, plus
| Parameter | Type | Default | Constraints |
|---|---|---|---|
mask_source |
str | — | single-channel image (uint8/uint16 PNG or TIFF); pixel value = ID, 0 = background |
annotation |
"label" | "legend" |
"label" |
label: ID text at each region; legend: strip below the image |
id_names |
{id: name} | null |
null | optional display names (JSON object keys are strings) |
alpha |
float | 0.5 | [0, 1], blend strength of the region colour |
line_width |
int | 2 | ≥ 0; region outline width, 0 disables outlines |
Returns: [Image, metadata] with the base keys, grid when drawn, ids (number of non-zero IDs
present), and in legend mode legend: [{"id", "name", "color"}] plus image_size: [w, h] — the
image area at the top of the composite. In legend mode output_size (and output_width/output_height)
is the whole composite and grid.cell_*, if a grid was drawn, is rescaled to it; the
inverse mapping of mcp-overview.md applies to image_size (and scale refers to it).
Behavior¶
- Load the mask; it must decode to mode
L,P(palette indices),I, orI;16(any byte order) with one channel — anything else raisesValueError("mask_source: …"). - Resize the mask to the source size (nearest neighbour) if it differs, apply exactly the pixel crop the image received (carried on the preview result, not re-derived from the normalized box), and resize to the output size (nearest). RGBA sources are flattened onto white first.
- Colour every non-zero ID with
color_from_text(str(id))and blend it over the image atalpha. - If
line_width > 0, draw region boundaries (pixels whose ID differs from a 4-neighbour) in the region colour at full opacity,line_widthpx. label: draw the ID (or itsid_namesentry) as a tag at the region centroid, clamped inside the frame.legend: append a strip below the image listingswatch id namerows (wrapping into columns), then re-fit the combined image insidemax_width×max_heightandtarget_pixels(plain resize of the composite); reportimage_sizeand rescalescaleaccordingly. The limits apply to the composite and default to the settings, not to the preview's own size, so a caller that previewed at 768 px and passes no limits gets a 384 px composite back.- Grid (if any) is drawn before the mask colours.
- Error:
alphaoutside[0, 1],line_width < 0, unknownannotation→ValueError.
Out of scope¶
RGB-encoded panoptic masks; polygonization; per-ID custom colours.
preview_video¶
Sample a video at fps (default 1 frame/s, at most max_frames) and preview each frame at a bounded size.
Returns the frames in time order followed by one JSON metadata object (timestamps, duration,
thinning, first-frame size/crop/scale). save_to is a directory for the frame files.
| Parameter | Type | Default | Required | Description |
|---|---|---|---|---|
source |
str |
— | yes | Local path or fsspec URL (file://, s3://, gs://, http(s)://, memory://) |
fps |
float |
1.0 |
no | Target sampling rate in frames per second |
start |
float | null |
null |
no | Start time in seconds |
end |
float | null |
null |
no | End time in seconds (exclusive) |
max_frames |
int |
32 |
no | Hard cap on returned frames; extra frames are thinned evenly |
crop |
[float, float, float, float] | null |
null |
no | Normalized [x_min, y_min, x_max, y_max] of the source to zoom into (0-1, min < max) |
target_pixels |
int | null |
null |
no | Cap on output area in pixels |
max_width |
int |
384 |
no | Maximum output width in pixels |
max_height |
int |
384 |
no | Maximum output height in pixels |
allow_upscale |
bool |
false |
no | Enlarge small images/regions up to the limits |
output_format |
"jpeg" | "png" | "webp" |
"jpeg" |
no | Encoding: jpeg (quality 90), png, or webp |
save_to |
str | null |
null |
no | Directory (path or fsspec URL) to write the frames into as frame_ |
Goal¶
Show a video as a bounded sequence of small frames — sampled at a low frame rate, capped in count, and each rendered through the image preview pipeline — instead of loading the whole video into a model.
Interface¶
Tools: preview_video, preview_video_grid (MCP) / annotools.video.sample_frames (library)
Parameters: PreviewOptions (applied to every frame) plus
| Parameter | Type | Default | Constraints |
|---|---|---|---|
fps |
float | 1.0 | > 0; target sampling rate in frames per second |
start |
float | null | null | seconds, ≥ 0 |
end |
float | null | null | seconds, > start |
max_frames |
int | 32 | ≥ 1; hard cap on returned frames |
preview_video_grid adds the flat GridOptions parameters of preview_image_grid.
Returns: N image blocks (in time order) followed by one JSON text block with the base keys of the
first frame plus frames: N, timestamps: [seconds…], duration: seconds, requested_fps,
thinned: bool, and grid for the grid variant.
Behavior¶
- Open the source with PyAV (
annotools[media]) through a streamed fsspec handle (local and remote alike); a missing dependency raisesImportErrornaming the extra; undecodable content raisesValueErrornaming the source. - Decode the first video stream from
start(keyframe seek, then skip) toend(or the end of the stream); select the first decoded frame whose time reaches each targetstart + k / fps. - If more than
max_framesframes were selected, keepmax_framesof them evenly spaced by index (always including the first) and setthinnedto true. - Render each frame with the image preview (crop, limits, upscale rule) and, for the grid variant,
the grid; encode with
output_format.save_tois treated as a directory: frames are written asframe_<index>_<timestamp>.<ext>inside it. - Error:
fps ≤ 0,start < 0,end ≤ start(start defaults to 0),max_frames < 1→ValueError; no video stream, or no frame inside[start, end)→ValueErrornaming the source; unreadable source → aspreview_image.
Out of scope¶
Audio; scene detection; overlays on frames (use the image tools on saved frames).
preview_video_grid¶
Like preview_video, with a semi-transparent grid (default 10x10) drawn on every frame to anchor positions.
| Parameter | Type | Default | Required | Description |
|---|---|---|---|---|
source |
str |
— | yes | Local path or fsspec URL (file://, s3://, gs://, http(s)://, memory://) |
fps |
float |
1.0 |
no | Target sampling rate in frames per second |
start |
float | null |
null |
no | Start time in seconds |
end |
float | null |
null |
no | End time in seconds (exclusive) |
max_frames |
int |
32 |
no | Hard cap on returned frames; extra frames are thinned evenly |
columns |
int |
10 |
no | Number of grid cells along this axis |
rows |
int |
10 |
no | Number of grid cells along this axis |
mode |
"ratio" | "fixed" |
"ratio" |
no | |
column_width |
int | null |
null |
no | Cell size in output pixels (mode='fixed') |
row_width |
int | null |
null |
no | Cell size in output pixels (mode='fixed') |
color |
"white" | "black" | "invert" |
"white" |
no | |
opacity |
float |
0.5 |
no | Line opacity, 0 (invisible) to 1 (solid) |
line_width |
int |
1 |
no | Grid line width in output pixels |
crop |
[float, float, float, float] | null |
null |
no | Normalized [x_min, y_min, x_max, y_max] of the source to zoom into (0-1, min < max) |
target_pixels |
int | null |
null |
no | Cap on output area in pixels |
max_width |
int |
384 |
no | Maximum output width in pixels |
max_height |
int |
384 |
no | Maximum output height in pixels |
allow_upscale |
bool |
false |
no | Enlarge small images/regions up to the limits |
output_format |
"jpeg" | "png" | "webp" |
"jpeg" |
no | Encoding: jpeg (quality 90), png, or webp |
save_to |
str | null |
null |
no | Directory (path or fsspec URL) to write the frames into as frame_ |
Specification: shared with preview_video (.agents/knowledge/spec/preview-video.md).
clip_audio¶
Cut a segment from an audio (or video) file, optionally resampled, and return it as 16-bit WAV.
Returns the audio followed by one JSON metadata object (source_duration, start, end, duration, sample_rate, channels).
| Parameter | Type | Default | Required | Description |
|---|---|---|---|---|
source |
str |
— | yes | Local path or fsspec URL (file://, s3://, gs://, http(s)://, memory://) |
start |
float | null |
null |
no | Start time in seconds |
end |
float | null |
null |
no | End time in seconds (exclusive) |
sample_rate |
int | null |
null |
no | Resample to this rate; omit to keep the source rate |
save_to |
str | null |
null |
no | Also write the WAV file to this path or fsspec URL |
Goal¶
Hand a model only the audio segment it needs, at the sample rate the caller chooses, without shipping the whole file.
Interface¶
Tool: clip_audio (MCP) / annotools.audio.clip_audio (library)
| Parameter | Type | Default | Constraints |
|---|---|---|---|
source |
str | — | local path or fsspec URL of an audio (or video) file |
start |
float | null | null | seconds, ≥ 0 |
end |
float | null | null | seconds, > start |
sample_rate |
int | null | null | ≥ 1; resample when given, else keep the source rate |
save_to |
str | null | null | also write the WAV to this path/URL |
Returns: one audio/wav block followed by one JSON text block: source_duration, start, end,
duration, sample_rate, channels, format: "wav", saved_to when used.
Behavior¶
- Open the source with PyAV (
annotools[media]; missing →ImportErrornaming the extra) through a streamed fsspec handle; a source without an audio stream, or undecodable content, raisesValueErrornaming it. - Seek to
start(keyframe-safe), decode the first audio stream, drop samples beforestart, stop atend(or the end of the stream). - Resample to
sample_ratewhen given (channel layout kept), then encode 16-bit PCM WAV in memory. - Error:
start < 0,end ≤ start(start defaults to 0),sample_rate < 1→ValueError;startbeyond the source duration →ValueError.
Out of scope¶
Compressed output formats; loudness normalization; channel mixing.
color_from_text¶
Return a stable, saturated color for any text: same text, same color; different text, unrelated color.
| Parameter | Type | Default | Required | Description |
|---|---|---|---|---|
text |
str |
— | yes | Any text, e.g. a label or an instance id |
Goal¶
Give agents (and the segmentation tool) a stable colour for any label or ID without maintaining a palette: the same text always maps to the same colour, and slightly different text maps to an unrelated one.
Interface¶
Tool: color_from_text (MCP) / annotools.color.color_from_text (library)
| Parameter | Type | Default | Constraints |
|---|---|---|---|
text |
str | — | any string, including empty |
Returns (structured): {"hex": "#rrggbb", "rgb": [r, g, b]} with hex lowercase.
Behavior¶
digest = sha256(text.encode("utf-8")).- Hue =
digest[0:2]as a big-endian integer / 65536 (a 0–1 hue ascolorsysexpects; × 360 for degrees); saturation = 0.75; lightness = 0.5. Two bytes give 65 536 hues, which already exceeds what 8-bit RGB can distinguish on one HSL ring, so the "first 3 bytes" sketch in issue #24 was reduced to 2. - Convert HSL → RGB (0–255, rounded) and format
hex.
No error conditions.
Out of scope¶
Palette optimisation for contrast between arbitrary label sets.
rotated_bbox_to_polygon¶
Convert rotated boxes (cx, cy, w, h, theta; theta clockwise) into DOTA-style 8-number corner polygons.
Corners are not clipped: a box touching the border can yield coordinates outside 0-1, which preview_image_polygons rejects — clamp or shrink such boxes before previewing them. Pass the source aspect ratio so rotation on non-square images does not shear.
| Parameter | Type | Default | Required | Description |
|---|---|---|---|---|
boxes |
array[object{cx, cy, w, h, theta}] |
— | yes | Rotated boxes (cx, cy, w, h, theta), normalized |
angle_unit |
"degrees" | "radians" |
"degrees" |
no | Unit of every theta |
aspect_ratio |
float |
1.0 |
no | Source width / height; 1.0 assumes a square image |
Goal¶
Turn rotated detections (cx, cy, w, h, theta) into the DOTA-style 8-number corner lists that
preview_image_polygons renders and that SQLite exports store, and let agents verify that a 4-point
polygon is actually a rectangle.
Interface¶
Tool: rotated_bbox_to_polygon (MCP) / annotools.geometry.rotated_box_to_corners and
annotools.geometry.is_rectangle (library)
| Parameter | Type | Default | Constraints |
|---|---|---|---|
boxes |
list of {cx, cy, w, h, theta} |
— | ≥ 1; cx, cy in [0, 1]; w, h > 0 (normalized) |
angle_unit |
"degrees" | "radians" |
"degrees" |
applies to every theta |
aspect_ratio |
float | 1.0 | source width / height; > 0 |
Returns (structured): {"polygons": [[x1, y1, x2, y2, x3, y3, x4, y4], ...]} — one entry per box,
corners in clockwise image order starting at the top-left corner of the unrotated box, normalized to
the same frame as the input.
Behavior¶
thetais the clockwise rotation (image coordinates, y down) of the box about its centre;angle_unit="radians"converts first. This matches DOTA/mmrotatele90after their own conversion to degrees; the spec does not normalize the angle range.- Rotation happens in an isotropic frame: x is scaled by
aspect_ratiobefore rotating and divided afterwards, so a box on a non-square image rotates without shearing. With the default 1.0 the result is exact only for square sources. - Corners may fall outside [0, 1]; they are returned unclipped.
preview_image_polygonsrejects such coordinates, so callers clamp or shrink border boxes before previewing (aclipoption is a possible follow-up). is_rectangle(points, *, angle_tol_deg=2.0, length_tol=0.02)(tolerances keyword-only): true when the polygon has 4 points, adjacent edges are perpendicular withinangle_tol_deg, and opposite edges have equal length withinlength_tol(relative). Angles are measured in the space the points are given in: normalizing scales x and y differently on a non-square image, which shears a rotated rectangle until it fails the test, so run it on the model's pixel answer. Axis-aligned rectangles are unaffected.- Error: empty
boxes,w/h≤ 0,cx/cyoutside [0, 1],aspect_ratio≤ 0 →ValueErrornamingboxes[i].<field>or the parameter.
Out of scope¶
Polygon → rotated box conversion; angle-range normalization.
normalize_coordinates¶
Convert a model's coordinates into the storage convention (normalized 0-1, x-first, uncropped source).
Ask each model in its native convention, then pass its answer here with the frame it used: for pixel answers (Claude, Qwen2.5-VL) base_width/base_height are the preview's output_width/output_height; for 0-1000 answers (Gemini, Qwen3-VL) or 0-999 (GPT) use 1000 or 999. Pass the preview's crop so a zoomed view maps back into the full image. Results are clamped to [0, 1].
| Parameter | Type | Default | Required | Description |
|---|---|---|---|---|
coordinates |
array[array[float]] |
— | yes | Entries of flat x, y, x, y, ... values (point, box, polygon); y, x per pair when axis_order='yx' |
base_width |
float |
— | yes | Width of the frame the coordinates refer to: the preview output_width, or 1000 for 0-1000 outputs |
base_height |
float |
— | yes | Height of that frame: output_height, or 1000 |
crop |
[float, float, float, float] | null |
null |
no | Normalized [x_min, y_min, x_max, y_max] of the source to zoom into (0-1, min < max) |
axis_order |
"xy" | "yx" |
"xy" |
no | Pair order on the base-frame side only (yx for Gemini's [ymin, xmin, ymax, xmax]) |
Goal¶
Models answer localization questions in their own frame — Claude and Qwen2.5-VL in pixels of the image
they were shown, Gemini and Qwen3-VL in a 0–1000 space (Gemini y-first), GPT in a 0–999 space — and
perform worse when asked to normalize themselves. The storage convention is normalized 0–1, x-first,
relative to the uncropped source. These two tools do the conversion in code so an agent can ask every
model natively and store one format, including when the model looked at a crop.
Interface¶
Tools: normalize_coordinates, denormalize_coordinates (MCP) /
annotools.geometry.normalize_coordinates, annotools.geometry.denormalize_coordinates (library).
| Parameter | Type | Default | Constraints |
|---|---|---|---|
coordinates |
list[list[float]] |
— | ≥ 1 entries; each entry a flat even-length list x, y, x, y, … (point, box, polygon) |
base_width |
float | — | > 0; the frame width the entries use: preview output_width, or 1000 / 999 |
base_height |
float | — | > 0; likewise output_height, or 1000 / 999 |
crop |
[x_min, y_min, x_max, y_max] | null |
null | the applied crop reported by the preview the model saw |
axis_order |
"xy" | "yx" |
"xy" |
pair order in the base frame; yx for Gemini's [ymin, xmin, ymax, xmax] |
Returns: {"coordinates": list[list[float]]} with the same shape as the input.
normalize_coordinates: base frame (of the cropped view) → normalized source coordinates, alwaysx, yorder, each value clamped to [0, 1].denormalize_coordinates: normalized source coordinates (x, yorder, each in [0, 1]) → base frame of the cropped view, writteny, xper pair whenaxis_order="yx"; not clamped or rounded.
Behavior¶
- Validate
base_width/base_height> 0 andcrop(same rules asPreviewOptions.crop). - For each entry, split into pairs (swap when
axis_order="yx"on the base side). - normalize:
x_norm = crop.x_min + x / base_width × (crop.x_max − crop.x_min), likewisey; clamp. denormalize:x_base = (x − crop.x_min) / (crop.x_max − crop.x_min) × base_width, likewisey. - Error
coordinates[i]: expected an even number of values, got nfor an odd-length entry. - Error
coordinates: base_width and base_height must be > 0for a non-positive base. - Error
coordinates[i]: (x, y) is outside [0, 1]when denormalizing a value outside the range. - Error
crop: …from the shared crop validation.
Out of scope¶
Choosing the convention for a model (the mllm-multimodal-input skill); rotated boxes (use
rotated_bbox_to_polygon first, then convert the 8-number entry); rounding to integer pixels.
denormalize_coordinates¶
Convert stored normalized coordinates into a model's frame (pixels of a preview or a 0-1000 space).
The inverse of normalize_coordinates: the input is normalized 0-1 relative to the uncropped source, the output is in the base_width x base_height frame of the given crop (y-first pairs when axis_order='yx'). Values are not clamped, so a point outside the crop maps outside the frame.
| Parameter | Type | Default | Required | Description |
|---|---|---|---|---|
coordinates |
array[array[float]] |
— | yes | Entries of flat x, y, x, y, ... values in [0, 1] (always x-first) |
base_width |
float |
— | yes | Width of the frame the coordinates refer to: the preview output_width, or 1000 for 0-1000 outputs |
base_height |
float |
— | yes | Height of that frame: output_height, or 1000 |
crop |
[float, float, float, float] | null |
null |
no | Normalized [x_min, y_min, x_max, y_max] of the source to zoom into (0-1, min < max) |
axis_order |
"xy" | "yx" |
"xy" |
no | Pair order on the base-frame side only (yx for Gemini's [ymin, xmin, ymax, xmax]) |
Specification: shared with normalize_coordinates (.agents/knowledge/spec/coordinates.md).