| name | sam3-api |
| description | Use when working with SAM3 (Segment Anything Model 3) - provides API patterns for text prompts, bounding boxes, point prompts, video tracking, batch inference, and model building |
SAM3 API Guidance
Use this skill when working with SAM3 (Segment Anything Model 3) from Facebook Research.
Reference examples in references/:
sam3_image_predictor_example.md - Basic text/box prompts
sam3_image_batched_inference.md - DataPoint batch API
sam3_image_interactive.md - Interactive refinement widget
sam3_for_sam1_task_example.md - SAM1-style point prompts
sam3_video_predictor_example.md - Video tracking with text/point prompts
sam3_for_sam2_video_task_example.md - SAM2-style video tracking API
sam3_agent.md - Using SAM3 with LLMs for complex queries
Overview
SAM3 is a unified foundation model for promptable segmentation across images and videos. It supports:
- Text prompts: Open-vocabulary segmentation ("person", "red car", etc.)
- Geometric prompts: Bounding boxes with positive/negative labels
- Point prompts: Click-based segmentation (SAM1-style via
predict_inst)
- Video tracking: Track objects across video frames
Repository: https://github.com/facebookresearch/sam3
Installation
conda create -n sam3 python=3.12
conda activate sam3
pip install torch==2.7.0 torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126
git clone https://github.com/facebookresearch/sam3.git
cd sam3
pip install -e .
huggingface-cli login
Model Building
Image Model
from sam3.model_builder import build_sam3_image_model
from sam3.model.sam3_image_processor import Sam3Processor
model = build_sam3_image_model()
model = build_sam3_image_model(enable_inst_interactivity=True)
processor = Sam3Processor(model, confidence_threshold=0.5)
Video Model
from sam3.model_builder import build_sam3_video_predictor, build_sam3_video_model
predictor = build_sam3_video_predictor(gpus_to_use=range(torch.cuda.device_count()))
sam3_model = build_sam3_video_model()
tracker = sam3_model.tracker
tracker.backbone = sam3_model.detector.backbone
GPU Configuration
import torch
torch.backends.cuda.matmul.allow_tf32 = True
torch.backends.cudnn.allow_tf32 = True
torch.autocast("cuda", dtype=torch.bfloat16).__enter__()
torch.inference_mode().__enter__()
Image Segmentation API
Setting an Image
from PIL import Image
image = Image.open("path/to/image.jpg")
inference_state = processor.set_image(image)
width = inference_state["original_width"]
height = inference_state["original_height"]
Batch Image Processing
For efficient multi-image inference:
from PIL import Image
images = [Image.open(f"image_{i}.jpg") for i in range(3)]
inference_state = processor.set_image_batch(images)
widths = inference_state["original_widths"]
heights = inference_state["original_heights"]
inference_state = processor.set_text_prompt(state=inference_state, prompt="person")
for i in range(len(images)):
masks_i = inference_state["masks"][i]
boxes_i = inference_state["boxes"][i]
scores_i = inference_state["scores"][i]
Key difference: Single image uses original_width/original_height, batch uses original_widths/original_heights (plural).
Text Prompts
Text prompts find ALL instances of a concept in the image:
processor.reset_all_prompts(inference_state)
inference_state = processor.set_text_prompt(state=inference_state, prompt="shoe")
masks = inference_state["masks"]
boxes = inference_state["boxes"]
scores = inference_state["scores"]
Geometric Prompts (Bounding Boxes)
Boxes use normalized cxcywh format (center_x, center_y, width, height):
from sam3.model.box_ops import box_xywh_to_cxcywh
from sam3.visualization_utils import normalize_bbox
box_xywh = torch.tensor([480.0, 290.0, 110.0, 360.0]).view(-1, 4)
box_cxcywh = box_xywh_to_cxcywh(box_xywh)
norm_box = normalize_bbox(box_cxcywh, width, height).flatten().tolist()
processor.reset_all_prompts(inference_state)
inference_state = processor.add_geometric_prompt(
state=inference_state,
box=norm_box,
label=True
)
Multi-Box Prompting (Positive + Negative)
boxes_xywh = [[480.0, 290.0, 110.0, 360.0], [370.0, 280.0, 115.0, 375.0]]
boxes_cxcywh = box_xywh_to_cxcywh(torch.tensor(boxes_xywh).view(-1, 4))
norm_boxes = normalize_bbox(boxes_cxcywh, width, height).tolist()
box_labels = [True, False]
processor.reset_all_prompts(inference_state)
for box, label in zip(norm_boxes, box_labels):
inference_state = processor.add_geometric_prompt(
state=inference_state, box=box, label=label
)
Coordinate Conversion Helper
def convert_xyxy_to_normalized_cxcywh(x0, y0, x1, y1, img_width, img_height):
"""Convert pixel xyxy coordinates to normalized cxcywh format."""
center_x = (x0 + x1) / 2.0 / img_width
center_y = (y0 + y1) / 2.0 / img_height
width = (x1 - x0) / img_width
height = (y1 - y0) / img_height
return [center_x, center_y, width, height]
Confidence Threshold
inference_state = processor.set_confidence_threshold(0.3, inference_state)
SAM1-Style Point Prompts (predict_inst)
For interactive point-based segmentation, use predict_inst:
import numpy as np
model = build_sam3_image_model(enable_inst_interactivity=True)
processor = Sam3Processor(model)
inference_state = processor.set_image(image)
input_point = np.array([[520, 375]])
input_label = np.array([1])
masks, scores, logits = model.predict_inst(
inference_state,
point_coords=input_point,
point_labels=input_label,
multimask_output=True,
)
sorted_ind = np.argsort(scores)[::-1]
masks = masks[sorted_ind]
scores = scores[sorted_ind]
logits = logits[sorted_ind]
Iterative Refinement with Logits
mask_input = logits[np.argmax(scores), :, :]
masks, scores, logits = model.predict_inst(
inference_state,
point_coords=np.array([[500, 375], [1125, 625]]),
point_labels=np.array([1, 1]),
mask_input=mask_input[None, :, :],
multimask_output=False,
)
Box Prompts with predict_inst
input_box = np.array([425, 600, 700, 875])
masks, scores, _ = model.predict_inst(
inference_state,
point_coords=None,
point_labels=None,
box=input_box[None, :],
multimask_output=False,
)
Combined Points and Boxes
input_box = np.array([425, 600, 700, 875])
input_point = np.array([[575, 750]])
input_label = np.array([0])
masks, scores, logits = model.predict_inst(
inference_state,
point_coords=input_point,
point_labels=input_label,
box=input_box,
multimask_output=False,
)
Batched predict_inst
input_boxes = np.array([
[75, 275, 1725, 850],
[425, 600, 700, 875],
[1375, 550, 1650, 800],
])
masks, scores, _ = model.predict_inst(
inference_state,
point_coords=None,
point_labels=None,
box=input_boxes,
multimask_output=False,
)
predict_inst Tips
- Multimask output:
multimask_output=True returns 3 candidate masks for ambiguous prompts (single point). Use False for specific prompts (multiple points, box, or refined) to get a single mask
- Logit refinement: Pass previous
logits to mask_input for iterative refinement
Batched Inference API (DataPoint)
The batched API provides lower-level control for efficient multi-image/multi-query processing. It uses two core data structures:
Understanding DataPoint
A DataPoint is the fundamental unit for batched inference. It bundles an image with one or more queries:
Datapoint(
images=[...],
find_queries=[...],
)
Why use DataPoints?
- Batching: Process multiple images in one forward pass
- Multiple queries per image: Run several text/box queries on one image efficiently
- Shared computation: Image features computed once, reused across all queries
Understanding FindQueryLoaded
A FindQueryLoaded represents a single segmentation query. Each query can be:
- A text prompt (e.g., "cat", "person in red shirt")
- A visual prompt (bounding boxes with positive/negative labels)
- A combination (text hint + visual exemplars)
FindQueryLoaded(
query_text="cat",
image_id=0,
input_bbox=tensor,
input_bbox_label=tensor,
inference_metadata=...,
object_ids_output=[],
is_exhaustive=True,
query_processing_order=0,
)
InferenceMetadata tracks original dimensions for proper mask/box rescaling:
InferenceMetadata(
coco_image_id=1,
original_image_id=1,
original_category_id=1,
original_size=[width, height],
object_id=0,
frame_index=0,
)
Creating DataPoints
from sam3.train.data.sam3_image_dataset import InferenceMetadata, FindQueryLoaded, Image as SAMImage, Datapoint
from sam3.train.data.collator import collate_fn_api as collate
from sam3.model.utils.misc import copy_data_to_device
def create_datapoint_with_text(pil_image, text_query):
"""Create a datapoint for text prompt."""
w, h = pil_image.size
datapoint = Datapoint(find_queries=[], images=[])
datapoint.images = [SAMImage(data=pil_image, objects=[], size=[h, w])]
datapoint.find_queries.append(
FindQueryLoaded(
query_text=text_query,
image_id=0,
object_ids_output=[],
is_exhaustive=True,
query_processing_order=0,
inference_metadata=InferenceMetadata(
coco_image_id=1,
original_image_id=1,
original_category_id=1,
original_size=[w, h],
object_id=0,
frame_index=0,
)
)
)
return datapoint
def create_datapoint_with_boxes(pil_image, boxes_xyxy, labels, text_prompt="visual"):
"""Create a datapoint for visual prompt (boxes in XYXY format)."""
w, h = pil_image.size
datapoint = Datapoint(find_queries=[], images=[])
datapoint.images = [SAMImage(data=pil_image, objects=[], size=[h, w])]
datapoint.find_queries.append(
FindQueryLoaded(
query_text=text_prompt,
image_id=0,
object_ids_output=[],
is_exhaustive=True,
query_processing_order=0,
input_bbox=torch.tensor(boxes_xyxy, dtype=torch.float).view(-1, 4),
input_bbox_label=torch.tensor(labels, dtype=torch.bool).view(-1),
inference_metadata=InferenceMetadata(
coco_image_id=1,
original_image_id=1,
original_category_id=1,
original_size=[w, h],
object_id=0,
frame_index=0,
)
)
)
return datapoint
Transforms
Transforms preprocess DataPoints before model inference. Import from sam3.train.transforms.basic_for_api.
Essential inference transforms:
| Transform | Purpose |
|---|
ComposeAPI | Chains transforms sequentially |
RandomResizeAPI | Resizes image to model input size |
ToTensorAPI | Converts PIL image → PyTorch tensor |
NormalizeAPI | Normalizes channels + converts coordinates |
Important: NormalizeAPI automatically converts input_bbox from absolute XYXY pixels to normalized CxCyWH format. This is why you provide boxes in XYXY pixel format when creating DataPoints—the transform handles conversion.
from sam3.train.transforms.basic_for_api import ComposeAPI, RandomResizeAPI, ToTensorAPI, NormalizeAPI
transform = ComposeAPI(
transforms=[
RandomResizeAPI(sizes=1008, max_size=1008, square=True, consistent_transform=False),
ToTensorAPI(),
NormalizeAPI(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]),
]
)
Transform parameters:
RandomResizeAPI(sizes, max_size, square, consistent_transform): Resize to target size
sizes=1008: Target size (or list of sizes for random selection during training)
square=True: Force square output
consistent_transform=False: For video, apply same resize to all frames if True
NormalizeAPI(mean, std): Channel normalization (SAM3 uses [0.5, 0.5, 0.5])
Training/augmentation transforms (also available in basic_for_api):
RandomSizeCropAPI, CenterCropAPI - Cropping with object preservation
RandomHorizontalFlip, RandomAffine - Geometric augmentation
ColorJitter, RandomGrayscale - Color augmentation
RandomResizedCrop, RandomPadAPI, PadToSizeAPI - Size adjustments
RandomMosaicVideoAPI - Video-specific mosaic augmentation
RandomSelectAPI - Probabilistic transform selection
Postprocessing
The postprocessor converts raw model outputs to usable masks/boxes at original image resolution:
from sam3.eval.postprocessors import PostProcessImage
postprocessor = PostProcessImage(
max_dets_per_img=-1,
iou_type="segm",
use_original_sizes_box=True,
use_original_sizes_mask=True,
convert_mask_to_rle=False,
detection_threshold=0.5,
to_cpu=False,
)
datapoint1 = create_datapoint_with_text(img1, "cat")
datapoint1 = transform(datapoint1)
datapoint2 = create_datapoint_with_boxes(img2, [[59, 144, 76, 163]], [True])
datapoint2 = transform(datapoint2)
batch = collate([datapoint1, datapoint2], dict_key="dummy")["dummy"]
batch = copy_data_to_device(batch, torch.device("cuda"), non_blocking=True)
output = model(batch)
results = postprocessor.process_results(output, batch.find_metadatas)
Video Segmentation API
Video Predictor (Multi-GPU)
from sam3.model_builder import build_sam3_video_predictor
predictor = build_sam3_video_predictor(gpus_to_use=range(torch.cuda.device_count()))
response = predictor.handle_request(
request=dict(type="start_session", resource_path="video.mp4")
)
session_id = response["session_id"]
response = predictor.handle_request(
request=dict(
type="add_prompt",
session_id=session_id,
frame_index=0,
text="person",
)
)
outputs_per_frame = {}
for response in predictor.handle_stream_request(
request=dict(type="propagate_in_video", session_id=session_id)
):
outputs_per_frame[response["frame_index"]] = response["outputs"]
predictor.handle_request(
request=dict(type="remove_object", session_id=session_id, obj_id=2)
)
points = [[0.5, 0.5]]
labels = [1]
response = predictor.handle_request(
request=dict(
type="add_prompt",
session_id=session_id,
frame_index=0,
points=points,
point_labels=labels,
obj_id=2,
)
)
boxes = [[0.3, 0.2, 0.4, 0.5]]
box_labels = [1]
response = predictor.handle_request(
request=dict(
type="add_prompt",
session_id=session_id,
frame_index=0,
bounding_boxes=boxes,
bounding_box_labels=box_labels,
)
)
predictor.handle_request(dict(type="reset_session", session_id=session_id))
predictor.handle_request(dict(type="close_session", session_id=session_id))
predictor.shutdown()
SAM2-Style Tracker API
from sam3.model_builder import build_sam3_video_model
sam3_model = build_sam3_video_model()
predictor = sam3_model.tracker
predictor.backbone = sam3_model.detector.backbone
inference_state = predictor.init_state(video_path="video.mp4")
rel_points = [[x / width, y / height] for x, y in points]
points_tensor = torch.tensor(rel_points, dtype=torch.float32)
labels_tensor = torch.tensor([1], dtype=torch.int32)
_, obj_ids, low_res_masks, video_res_masks = predictor.add_new_points(
inference_state=inference_state,
frame_idx=0,
obj_id=1,
points=points_tensor,
labels=labels_tensor,
clear_old_points=False,
)
rel_box = [[xmin/width, ymin/height, xmax/width, ymax/height] for xmin, ymin, xmax, ymax in boxes]
rel_box = np.array(rel_box, dtype=np.float32)
_, obj_ids, low_res_masks, video_res_masks = predictor.add_new_points_or_box(
inference_state=inference_state,
frame_idx=0,
obj_id=1,
box=rel_box,
)
video_segments = {}
for frame_idx, obj_ids, low_res_masks, video_res_masks, obj_scores in predictor.propagate_in_video(
inference_state,
start_frame_idx=0,
max_frame_num_to_track=300,
reverse=False,
propagate_preflight=True
):
video_segments[frame_idx] = {
obj_id: (video_res_masks[i] > 0.0).cpu().numpy()
for i, obj_id in enumerate(obj_ids)
}
predictor.clear_all_points_in_video(inference_state)
Key Coordinate Formats
| Context | Format | Range |
|---|
add_geometric_prompt box | cxcywh | normalized 0-1 |
predict_inst points | xy | pixels |
predict_inst box | xyxy | pixels |
Batched API input_bbox | xyxy | pixels |
| Video Predictor points | xy | normalized 0-1 |
| Video Predictor bounding_boxes | xywh | normalized 0-1 |
| SAM2-Style Tracker points | xy | normalized 0-1 |
| SAM2-Style Tracker box | xyxy | normalized 0-1 |
Video API comparison:
- Video Predictor (
build_sam3_video_predictor): Multi-GPU, session-based handle_request() API, supports text prompts
- SAM2-Style Tracker (
build_sam3_video_model().tracker): Single-GPU, add_new_points_or_box() API , no text prompts, uses tensors
Important Notes
- Reset prompts: Call
processor.reset_all_prompts(state) to clear previous results when reusing the same inference_state for a new query. Not needed after set_image() which returns a fresh state
- Inference triggers:
add_geometric_prompt() and set_text_prompt() automatically run inference
- Video sessions: Each session is tied to one video; close to free GPU memory
- Text + geometric prompts: The
Sam3Processor API (set_text_prompt, add_geometric_prompt) does not support combining text and geometric prompts. Use the batched DataPoint API instead, which allows text hints with box prompts via FindQueryLoaded(query_text="hint", input_bbox=...)
- Negative-only boxes: Require text prompt hint (use batched API with
query_text)
Visualization
from sam3.visualization_utils import (
plot_results,
draw_box_on_image,
normalize_bbox,
show_mask,
show_points,
show_box,
)
plot_results(pil_image, inference_state)
image_with_box = draw_box_on_image(pil_image, box_xywh)