Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions sam3/agent/agent_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from .client_llm import send_generate_request
from .client_sam3 import call_sam_service
from .helpers.filename_utils import sanitize_filename
from .viz import visualize


Expand Down Expand Up @@ -319,12 +320,14 @@ def agent_inference(
print(f"🔍 Checking mask {i+1}/{num_masks}...")
image_w_mask_i, image_w_zoomed_in_mask_i = visualize(current_outputs, i)

# Sanitize the text prompt to create safe filenames
sanitized_prompt = sanitize_filename(LATEST_SAM3_TEXT_PROMPT)
image_w_zoomed_in_mask_i_path = os.path.join(
sam_output_dir, rf"{LATEST_SAM3_TEXT_PROMPT}.png".replace("/", "_")
).replace(".png", f"_zoom_in_mask_{i + 1}.png")
sam_output_dir, f"{sanitized_prompt}_zoom_in_mask_{i + 1}.png"
)
image_w_mask_i_path = os.path.join(
sam_output_dir, rf"{LATEST_SAM3_TEXT_PROMPT}.png".replace("/", "_")
).replace(".png", f"_selected_mask_{i + 1}.png")
sam_output_dir, f"{sanitized_prompt}_selected_mask_{i + 1}.png"
)
image_w_zoomed_in_mask_i.save(image_w_zoomed_in_mask_i_path)
image_w_mask_i.save(image_w_mask_i_path)

Expand Down Expand Up @@ -391,13 +394,11 @@ def agent_inference(
}

image_w_check_masks = visualize(updated_outputs)
# Sanitize the text prompt to create a safe filename
sanitized_prompt = sanitize_filename(LATEST_SAM3_TEXT_PROMPT)
image_w_check_masks_path = os.path.join(
sam_output_dir, rf"{LATEST_SAM3_TEXT_PROMPT}.png"
).replace(
".png",
f"_selected_masks_{'-'.join(map(str, [i+1 for i in masks_to_keep]))}.png".replace(
"/", "_"
),
sam_output_dir,
f"{sanitized_prompt}_selected_masks_{'-'.join(map(str, [i+1 for i in masks_to_keep]))}.png"
)
image_w_check_masks.save(image_w_check_masks_path)
# save the updated json outputs and append to message history
Expand Down
6 changes: 3 additions & 3 deletions sam3/agent/client_sam3.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from sam3.model.box_ops import box_xyxy_to_xywh
from sam3.train.masks_ops import rle_encode

from .helpers.filename_utils import sanitize_filename
from .helpers.mask_overlap_removal import remove_overlapping_masks
from .viz import visualize

Expand Down Expand Up @@ -59,9 +60,8 @@ def call_sam_service(
"""
print(f"📞 Loading image '{image_path}' and sending with prompt '{text_prompt}'...")

text_prompt_for_save_path = (
text_prompt.replace("/", "_") if "/" in text_prompt else text_prompt
)
# Sanitize the text prompt to create a safe filename
text_prompt_for_save_path = sanitize_filename(text_prompt)

os.makedirs(
os.path.join(output_folder_path, image_path.replace("/", "-")), exist_ok=True
Expand Down
38 changes: 38 additions & 0 deletions sam3/agent/helpers/filename_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Copyright (c) Meta Platforms, Inc. and affiliates. All Rights Reserved

import hashlib
import re


def sanitize_filename(text_prompt: str, max_length: int = 200) -> str:
"""
Sanitize a text prompt to be used as a filename.

Replaces invalid filesystem characters, truncates to max_length, and appends
a hash suffix to ensure uniqueness.

Args:
text_prompt: The text prompt to sanitize
max_length: Maximum length for the filename (default: 200)
Leaves room for extensions and path components

Returns:
A sanitized filename-safe string
"""
# Generate hash suffix for uniqueness (8 chars + 1 underscore = 9 chars)
prompt_hash = hashlib.md5(text_prompt.encode('utf-8')).hexdigest()[:8]
hash_suffix = f"_{prompt_hash}"

# Sanitize: replace invalid chars, collapse whitespace/underscores, trim
sanitized = re.sub(r'[<>:"/\\|?*\x00-\x1f\s]+', '_', text_prompt).strip('_.')

# Use default if empty after sanitization
sanitized = sanitized or "prompt"

# Truncate to fit max_length with hash suffix
available_length = max_length - len(hash_suffix)
if len(sanitized) > available_length:
sanitized = sanitized[:available_length]

return f"{sanitized}{hash_suffix}"

12 changes: 10 additions & 2 deletions sam3/agent/inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import os

from sam3.agent.agent_core import agent_inference
from sam3.agent.helpers.filename_utils import sanitize_filename


def run_single_image_inference(
Expand All @@ -27,8 +28,15 @@ def run_single_image_inference(

# Generate output file names
image_basename = os.path.splitext(os.path.basename(image_path))[0]
prompt_for_filename = text_prompt.replace("/", "_").replace(" ", "_")

# Sanitize the text prompt to create a safe filename
prompt_for_filename = sanitize_filename(text_prompt, max_length=150)

# Build base filename, truncating image_basename if needed to keep total length reasonable
# Leave room for separators and suffixes like "_pred.json"
max_base_length = 200
suffix_length = len(f"_{prompt_for_filename}_agent_{llm_name}")
if len(image_basename) + suffix_length > max_base_length:
image_basename = image_basename[:max(0, max_base_length - suffix_length)]
base_filename = f"{image_basename}_{prompt_for_filename}_agent_{llm_name}"
output_json_path = os.path.join(output_dir, f"{base_filename}_pred.json")
output_image_path = os.path.join(output_dir, f"{base_filename}_pred.png")
Expand Down