diff --git a/runner/app/pipelines/segment_anything_2.py b/runner/app/pipelines/segment_anything_2.py index 64c4080d..bbae27bb 100644 --- a/runner/app/pipelines/segment_anything_2.py +++ b/runner/app/pipelines/segment_anything_2.py @@ -1,7 +1,12 @@ import logging +import os +import shutil +import tempfile from typing import List, Optional, Tuple -import PIL +from PIL import Image +from fastapi import UploadFile +import torch from app.pipelines.base import Pipeline from app.pipelines.utils import get_torch_device, get_model_dir from app.routes.util import InferenceError @@ -9,6 +14,8 @@ from sam2.sam2_image_predictor import SAM2ImagePredictor ImageFile.LOAD_TRUNCATED_IMAGES = True +from sam2.sam2_video_predictor import SAM2VideoPredictor +import subprocess logger = logging.getLogger(__name__) @@ -19,21 +26,81 @@ def __init__(self, model_id: str): kwargs = {"cache_dir": get_model_dir()} torch_device = get_torch_device() + if torch_device.type == "cuda": + torch.autocast("cuda", dtype=torch.bfloat16).__enter__() + # turn on tfloat32 for Ampere GPUs (https://pytorch.org/docs/stable/notes/cuda.html#tensorfloat-32-tf32-on-ampere-devices) + if torch.cuda.get_device_properties(0).major >= 8: + torch.backends.cuda.matmul.allow_tf32 = True + torch.backends.cudnn.allow_tf32 = True + elif torch_device.type == "mps": + print( + "\nSupport for MPS devices is preliminary. SAM 2 is trained with CUDA and might " + "give numerically different outputs and sometimes degraded performance on MPS. " + "See e.g. https://github.com/pytorch/pytorch/issues/84936 for a discussion." + ) self.tm = SAM2ImagePredictor.from_pretrained( model_id=model_id, device=torch_device, **kwargs, ) + + self.tm_vid = SAM2VideoPredictor.from_pretrained( + model_id=model_id, + device=torch_device, + ) def __call__( - self, image: PIL.Image, **kwargs - ) -> Tuple[List[PIL.Image], List[Optional[bool]]]: - try: - self.tm.set_image(image) - prediction = self.tm.predict(**kwargs) - except Exception as e: - raise InferenceError(original_exception=e) + self, media_file: UploadFile, media_type: str, **kwargs + ) -> Tuple[List[UploadFile], str, List[Optional[bool]]]: + if media_type == "image": + try: + image = Image.open(media_file.file).convert("RGB") + self.tm.set_image(image) + prediction = self.tm.predict(**kwargs) + except Exception as e: + raise InferenceError(original_exception=e) + elif media_type == "video": + try: + temp_dir = tempfile.mkdtemp() + # TODO: Fix the file type dependency, try passing to ffmpeg without saving to file + video_path = f"{temp_dir}/input.mp4" + with open(video_path, "wb") as video_file: + video_file.write(media_file.file.read()) + + # Run ffmpeg command to extract frames from video + frame_dir = tempfile.mkdtemp() + output_pattern = f"{frame_dir}/%05d.jpg" + ffmpeg_command = f"ffmpeg -i {video_path} -q:v 2 -start_number 0 {output_pattern}" + subprocess.run(ffmpeg_command, shell=True, check=True) + shutil.rmtree(temp_dir) + + # Limit to the first 500 frames to avoid running out of memory + frame_files = sorted( + [f for f in os.listdir(frame_dir) if f.endswith('.jpg')] + ) + for frame_file in frame_files[:-500]: + os.remove(os.path.join(frame_dir, frame_file)) + + inference_state = self.tm_vid.init_state(video_path=frame_dir) + shutil.rmtree(frame_dir) + + _, out_obj_ids, out_mask_logits = self.tm_vid.add_new_points_or_box( + inference_state, + frame_idx=kwargs.get('frame_idx', None), + obj_id=1, + points=kwargs.get('points', None), + labels=kwargs.get('labels', None), + ) + + for out_frame_idx, out_obj_ids, out_mask_logits in self.tm_vid.propagate_in_video(inference_state): + return { + out_obj_id: (out_mask_logits[i] > 0.0).cpu().numpy() + for i, out_obj_id in enumerate(out_obj_ids) + } + + except Exception as e: + raise InferenceError(original_exception=e) return prediction diff --git a/runner/app/routes/segment_anything_2.py b/runner/app/routes/segment_anything_2.py index 70436432..218232aa 100644 --- a/runner/app/routes/segment_anything_2.py +++ b/runner/app/routes/segment_anything_2.py @@ -45,7 +45,7 @@ include_in_schema=False, ) async def segment_anything_2( - image: Annotated[ + media_file: Annotated[ UploadFile, File(description="Image to segment.", media_type="image/*") ], model_id: Annotated[ @@ -112,6 +112,10 @@ async def segment_anything_2( ) ), ] = True, + frame_idx : Annotated[ + int, + Form(description="Frame index reference for (required video file input only)") + ] = -1, pipeline: Pipeline = Depends(get_pipeline), token: HTTPAuthorizationCredentials = Depends(HTTPBearer(auto_error=False)), ): @@ -144,18 +148,51 @@ async def segment_anything_2( content=http_error(str(e)), ) + supported_video_types = ["video/mp4"] + supported_image_types = ["image/jpeg", "image/png", "image/jpg"] + try: - image = Image.open(image.file).convert("RGB") - masks, scores, low_res_mask_logits = pipeline( - image, - point_coords=point_coords, - point_labels=point_labels, - box=box, - mask_input=mask_input, - multimask_output=multimask_output, - return_logits=return_logits, - normalize_coords=normalize_coords, - ) + if media_file.content_type in supported_image_types: + masks, scores, low_res_mask_logits = pipeline( + media_file, + media_type="image", + point_coords=point_coords, + point_labels=point_labels, + box=box, + mask_input=mask_input, + multimask_output=multimask_output, + return_logits=return_logits, + normalize_coords=normalize_coords, + ) + + # Return masks sorted by descending score as string. + sorted_ind = np.argsort(scores)[::-1] + return { + "masks": str(masks[sorted_ind].tolist()), + "scores": str(scores[sorted_ind].tolist()), + "logits": str(low_res_mask_logits[sorted_ind].tolist()), + } + + elif media_file.content_type in supported_video_types: + low_res_mask_logits = pipeline( + media_file, + media_type="video", + frame_idx=frame_idx, + points=point_coords, + labels=point_labels, + ) + + sadf = low_res_mask_logits + + + return { + "masks": str(""), + "logits": str(np.array(low_res_mask_logits)), + "scores": str(""), + } + else: + raise InferenceError(f"Unsupported media type: {media_file.content_type}") + except Exception as e: logger.error(f"Segment Anything 2 error: {e}") logger.exception(e) @@ -170,10 +207,10 @@ async def segment_anything_2( content=http_error("Segment Anything 2 error"), ) - # Return masks sorted by descending score as string. - sorted_ind = np.argsort(scores)[::-1] - return { - "masks": str(masks[sorted_ind].tolist()), - "scores": str(scores[sorted_ind].tolist()), - "logits": str(low_res_mask_logits[sorted_ind].tolist()), - } + # # Return masks sorted by descending score as string. + # sorted_ind = np.argsort(scores)[::-1] + # return { + # "masks": str(masks[sorted_ind].tolist()), + # "scores": str(scores[sorted_ind].tolist()), + # "logits": str(low_res_mask_logits[sorted_ind].tolist()), + # } diff --git a/runner/gateway.openapi.yaml b/runner/gateway.openapi.yaml index 38208652..26b8d707 100644 --- a/runner/gateway.openapi.yaml +++ b/runner/gateway.openapi.yaml @@ -438,10 +438,10 @@ components: title: Body_image_to_video_image_to_video_post Body_segment_anything_2_segment_anything_2_post: properties: - image: + media_file: type: string format: binary - title: Image + title: Media File description: Image to segment. model_id: type: string @@ -486,9 +486,14 @@ components: description: If true, the point coordinates will be normalized to the range [0,1], with point_coords expected to be with respect to image dimensions. default: true + frame_idx: + type: integer + title: Frame Idx + description: Frame index reference for (required video file input only) + default: -1 type: object required: - - image + - media_file - model_id title: Body_segment_anything_2_segment_anything_2_post Body_upscale_upscale_post: diff --git a/runner/openapi.yaml b/runner/openapi.yaml index afe61426..26d20daf 100644 --- a/runner/openapi.yaml +++ b/runner/openapi.yaml @@ -446,10 +446,10 @@ components: title: Body_image_to_video_image_to_video_post Body_segment_anything_2_segment_anything_2_post: properties: - image: + media_file: type: string format: binary - title: Image + title: Media File description: Image to segment. model_id: type: string @@ -494,9 +494,14 @@ components: description: If true, the point coordinates will be normalized to the range [0,1], with point_coords expected to be with respect to image dimensions. default: true + frame_idx: + type: integer + title: Frame Idx + description: Frame index reference for (required video file input only) + default: -1 type: object required: - - image + - media_file title: Body_segment_anything_2_segment_anything_2_post Body_upscale_upscale_post: properties: diff --git a/worker/runner.gen.go b/worker/runner.gen.go index 0b5450f9..faa4b5e5 100644 --- a/worker/runner.gen.go +++ b/worker/runner.gen.go @@ -115,12 +115,15 @@ type BodySegmentAnything2SegmentAnything2Post struct { // Box A length 4 array given as a box prompt to the model, in XYXY format. Box *string `json:"box,omitempty"` - // Image Image to segment. - Image openapi_types.File `json:"image"` + // FrameIdx Frame index reference for (required video file input only) + FrameIdx *int `json:"frame_idx,omitempty"` // MaskInput A low-resolution mask input to the model, typically from a previous prediction iteration, with the form 1xHxW (H=W=256 for SAM). MaskInput *string `json:"mask_input,omitempty"` + // MediaFile Image to segment. + MediaFile openapi_types.File `json:"media_file"` + // ModelId Hugging Face model ID used for image generation. ModelId *string `json:"model_id,omitempty"` @@ -1779,57 +1782,58 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file