From d30f64b239f0a2aefc5a1a5a5fb94a299322a0b4 Mon Sep 17 00:00:00 2001 From: Peter Schroedl Date: Wed, 20 Nov 2024 15:59:26 -0800 Subject: [PATCH] initial commit --- LICENSE | 201 ++++ __init__.py | 3 + nodes.py | 229 +++++ pyproject.toml | 6 + requirements.txt | 6 + sam2/_C.so | Bin 0 -> 1357648 bytes sam2/__init__.py | 9 + sam2/automatic_mask_generator.py | 434 ++++++++ sam2/build_sam.py | 130 +++ sam2/csrc/connected_components.cu | 289 ++++++ sam2/modeling/__init__.py | 5 + sam2/modeling/backbones/__init__.py | 5 + sam2/modeling/backbones/hieradet.py | 295 ++++++ sam2/modeling/backbones/image_encoder.py | 133 +++ sam2/modeling/backbones/utils.py | 95 ++ sam2/modeling/memory_attention.py | 169 ++++ sam2/modeling/memory_encoder.py | 181 ++++ sam2/modeling/position_encoding.py | 216 ++++ sam2/modeling/sam/__init__.py | 5 + sam2/modeling/sam/mask_decoder.py | 295 ++++++ sam2/modeling/sam/prompt_encoder.py | 182 ++++ sam2/modeling/sam/transformer.py | 327 ++++++ sam2/modeling/sam2_base.py | 829 +++++++++++++++ sam2/modeling/sam2_utils.py | 149 +++ sam2/sam2_camera_predictor.py | 1165 ++++++++++++++++++++++ sam2/sam2_image_predictor.py | 446 +++++++++ sam2/sam2_video_predictor.py | 898 +++++++++++++++++ sam2/utils/__init__.py | 5 + sam2/utils/amg.py | 348 +++++++ sam2/utils/misc.py | 238 +++++ sam2/utils/transforms.py | 99 ++ sam2_configs/__init__.py | 5 + sam2_configs/sam2_hiera_t.yaml | 118 +++ setup.py | 59 ++ 34 files changed, 7574 insertions(+) create mode 100644 LICENSE create mode 100644 __init__.py create mode 100644 nodes.py create mode 100644 pyproject.toml create mode 100644 requirements.txt create mode 100755 sam2/_C.so create mode 100644 sam2/__init__.py create mode 100644 sam2/automatic_mask_generator.py create mode 100644 sam2/build_sam.py create mode 100644 sam2/csrc/connected_components.cu create mode 100644 sam2/modeling/__init__.py create mode 100644 sam2/modeling/backbones/__init__.py create mode 100644 sam2/modeling/backbones/hieradet.py create mode 100644 sam2/modeling/backbones/image_encoder.py create mode 100644 sam2/modeling/backbones/utils.py create mode 100644 sam2/modeling/memory_attention.py create mode 100644 sam2/modeling/memory_encoder.py create mode 100644 sam2/modeling/position_encoding.py create mode 100644 sam2/modeling/sam/__init__.py create mode 100644 sam2/modeling/sam/mask_decoder.py create mode 100644 sam2/modeling/sam/prompt_encoder.py create mode 100644 sam2/modeling/sam/transformer.py create mode 100644 sam2/modeling/sam2_base.py create mode 100644 sam2/modeling/sam2_utils.py create mode 100644 sam2/sam2_camera_predictor.py create mode 100644 sam2/sam2_image_predictor.py create mode 100644 sam2/sam2_video_predictor.py create mode 100644 sam2/utils/__init__.py create mode 100644 sam2/utils/amg.py create mode 100644 sam2/utils/misc.py create mode 100644 sam2/utils/transforms.py create mode 100644 sam2_configs/__init__.py create mode 100644 sam2_configs/sam2_hiera_t.yaml create mode 100644 setup.py diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..2e96bd6 --- /dev/null +++ b/__init__.py @@ -0,0 +1,3 @@ +from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS + +__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"] \ No newline at end of file diff --git a/nodes.py b/nodes.py new file mode 100644 index 0000000..93e0b50 --- /dev/null +++ b/nodes.py @@ -0,0 +1,229 @@ +import torch +from torch.functional import F +import os +import cv2 +import numpy as np + +from .sam2.sam2_camera_predictor import SAM2CameraPredictor +from comfy.utils import load_torch_file + +from omegaconf import OmegaConf +from hydra.utils import instantiate +from hydra import initialize_config_dir, compose +from hydra.core.global_hydra import GlobalHydra + +import comfy.model_management as mm +import folder_paths + +script_directory = os.path.dirname(os.path.abspath(__file__)) + +class DownloadAndLoadSAM2RealtimeModel: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "model": ([ + 'sam2_hiera_tiny.safetensors', + ],), + "segmentor": ( + ['realtime'], + ), + "device": (['cuda', 'cpu', 'mps'], ), + "precision": ([ 'fp16','bf16','fp32'], + { + "default": 'fp16' + }), + + }, + } + + RETURN_TYPES = ("SAM2MODEL",) + RETURN_NAMES = ("sam2_model",) + FUNCTION = "loadmodel" + CATEGORY = "SAM2-Realtime" + + def loadmodel(self, model, segmentor, device, precision): + if precision != 'fp32' and device == 'cpu': + raise ValueError("fp16 and bf16 are not supported on cpu") + + if device == "cuda": + if torch.cuda.get_device_properties(0).major >= 8: + # turn on tfloat32 for Ampere GPUs (https://pytorch.org/docs/stable/notes/cuda.html#tensorfloat-32-tf32-on-ampere-devices) + torch.backends.cuda.matmul.allow_tf32 = True + torch.backends.cudnn.allow_tf32 = True + dtype = {"bf16": torch.bfloat16, "fp16": torch.float16, "fp32": torch.float32}[precision] + device = {"cuda": torch.device("cuda"), "cpu": torch.device("cpu"), "mps": torch.device("mps")}[device] + + download_path = os.path.join(folder_paths.models_dir, "sam2") + if precision != 'fp32' and "2.1" in model: + base_name, extension = model.rsplit('.', 1) + model = f"{base_name}-fp16.{extension}" + model_path = os.path.join(download_path, model) + print("model_path: ", model_path) + + if not os.path.exists(model_path): + print(f"Downloading SAM2 model to: {model_path}") + from huggingface_hub import snapshot_download + snapshot_download(repo_id="Kijai/sam2-safetensors", + allow_patterns=[f"*{model}*"], + local_dir=download_path, + local_dir_use_symlinks=False) + + config_dir = os.path.join(script_directory, "sam2_configs") + + # Code ripped out of sam2.build_sam.build_sam2_camera_predictor to appease Hydra + model_cfg = "sam2_hiera_t.yaml" #TODO: remove hardcoded config and path + with initialize_config_dir(config_dir=config_dir, version_base=None): + cfg = compose(config_name=model_cfg) + + hydra_overrides = [ + "++model._target_=sam2.sam2_camera_predictor.SAM2CameraPredictor", + ] + hydra_overrides_extra = [ + "++model.sam_mask_decoder_extra_args.dynamic_multimask_via_stability=true", + "++model.sam_mask_decoder_extra_args.dynamic_multimask_stability_delta=0.05", + "++model.sam_mask_decoder_extra_args.dynamic_multimask_stability_thresh=0.98", + "++model.binarize_mask_from_pts_for_mem_enc=true", + "++model.fill_hole_area=8", + ] + hydra_overrides.extend(hydra_overrides_extra) + + cfg = compose(config_name=model_cfg, overrides=hydra_overrides) + OmegaConf.resolve(cfg) + + model = instantiate(cfg.model, _recursive_=True) + + sd = load_torch_file(model_path) + model.load_state_dict(sd) + device = "cuda" if torch.cuda.is_available() else "cpu" + model = model.to(device) + model.eval() + + sam2_model = { + 'model': model, + 'dtype': dtype, + 'device': device, + 'segmentor' : segmentor, + 'version': "2.0" + } + + return (sam2_model,) + +class Sam2RealtimeSegmentation: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "images": ("IMAGE",), + "sam2_model": ("SAM2MODEL",), + "keep_model_loaded": ("BOOLEAN", {"default": True}), + }, + "optional": { + "coordinates_positive": ("STRING", {"forceInput": True}), + "coordinates_negative": ("STRING", {"forceInput": True}), + "bboxes": ("BBOX", ), + "individual_objects": ("BOOLEAN", {"default": False}), + "mask": ("MASK", ), + }, + } + + RETURN_NAMES = ("PROCESSED_IMAGES",) + RETURN_TYPES = ("IMAGE",) + FUNCTION = "segment_images" + CATEGORY = "SAM2-Realtime" + + def __init__(self): + self.predictor = None + self.if_init = False + + def segment_images( + self, + images, + sam2_model, + keep_model_loaded, + coordinates_positive=None, + coordinates_negative=None, + bboxes=None, + individual_objects=False, + mask=None, + ): + model = sam2_model["model"] + device = sam2_model["device"] + + device = torch.device("cuda") + model.to(device) + + processed_frames = [] + + # The `model` variable is now ready and equivalent to `predictor` returned by sam2.build_sam.build_sam2_camera_predictor + if self.predictor is None: + self.predictor = model + + def process_frame(frame, frame_idx): + with torch.inference_mode(), torch.autocast("cuda", dtype=torch.bfloat16): + height = 512 + width = 512 + + img_np = frame.numpy() + if img_np.shape[-1] != 3: + img_np = img_np.transpose(1, 2, 0) # CHW to HWC + if img_np.max() <= 1.0: + img_np = (img_np * 255).astype(np.uint8) + + frame = img_np + + if not self.if_init: + self.predictor.load_first_frame(frame) + self.if_init = True + obj_id = 1 + + # Define point prompt (e.g., background selection for demo purposes) + # point = [int(width * 2 / 3), int(height / 2)] + point = [384,384] + points = [point] + labels = [1] + + _, _, out_mask_logits = self.predictor.add_new_prompt(frame_idx, obj_id, points=points, labels=labels) + else: + # Track objects in subsequent frames + out_obj_ids, out_mask_logits = self.predictor.track(frame) + + # Process output mask only if it's non-empty + if out_mask_logits.shape[0] > 0: + # Apply threshold at 0.5 + mask = (out_mask_logits[0, 0] > 0.5).cpu().numpy().astype("uint8") * 255 + else: + mask = np.zeros((frame.shape[0], frame.shape[1]), dtype="uint8") + + # Ensure the mask matches frame dimensions + if mask.shape[:2] != frame.shape[:2]: + mask = cv2.resize(mask, (frame.shape[1], frame.shape[0]), interpolation=cv2.INTER_NEAREST) + + # Convert the mask to a 3-channel image without inversion + mask_colored = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR) + if mask_colored.shape != frame.shape: + mask_colored = cv2.resize(mask_colored, (frame.shape[1], frame.shape[0])) + + # Ensure matching types for blending + frame = frame.astype(np.uint8) + mask_colored = mask_colored.astype(np.uint8) + + # Blend the frame with the mask + overlayed_frame = cv2.addWeighted(frame, 0.7, mask_colored, 0.3, 0) + + image_tensor = torch.from_numpy(overlayed_frame).float() / 255.0 # Normalize to [0, 1] + processed_frames.append(image_tensor) + + for frame_idx, img in enumerate(images): + process_frame(img, frame_idx) + + stacked_frames = torch.stack(processed_frames, dim=0) + return (stacked_frames,) + +NODE_CLASS_MAPPINGS = { + "DownloadAndLoadSAM2RealtimeModel": DownloadAndLoadSAM2RealtimeModel, + "Sam2RealtimeSegmentation": Sam2RealtimeSegmentation +} +NODE_DISPLAY_NAME_MAPPINGS = { + "DownloadAndLoadSAM2RealtimeModel": "(Down)Load SAM2-Realtime Model", + "Sam2RealtimeSegmentation": "Sam2RealtimeSegmentation" +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..f7e8652 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,6 @@ +[build-system] +requires = [ + "setuptools>=61.0", + "torch>=2.3.1", + ] +build-backend = "setuptools.build_meta" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f80e9bf --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +pyyaml +numpy>=1.24.4 +tqdm>=4.66.1 +hydra-core>=1.3.2 +iopath>=0.1.10 +pillow>=9.4.0 \ No newline at end of file diff --git a/sam2/_C.so b/sam2/_C.so new file mode 100755 index 0000000000000000000000000000000000000000..2f48252d1e4fd70dd1100a59d6462d9c7c214812 GIT binary patch literal 1357648 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%0DosKt9@eb|tYYokIeu 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

E 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 None: + """ + Using a SAM 2 model, generates masks for the entire image. + Generates a grid of point prompts over the image, then filters + low quality and duplicate masks. The default settings are chosen + for SAM 2 with a HieraL backbone. + + Arguments: + model (Sam): The SAM 2 model to use for mask prediction. + points_per_side (int or None): The number of points to be sampled + along one side of the image. The total number of points is + points_per_side**2. If None, 'point_grids' must provide explicit + point sampling. + points_per_batch (int): Sets the number of points run simultaneously + by the model. Higher numbers may be faster but use more GPU memory. + pred_iou_thresh (float): A filtering threshold in [0,1], using the + model's predicted mask quality. + stability_score_thresh (float): A filtering threshold in [0,1], using + the stability of the mask under changes to the cutoff used to binarize + the model's mask predictions. + stability_score_offset (float): The amount to shift the cutoff when + calculated the stability score. + mask_threshold (float): Threshold for binarizing the mask logits + box_nms_thresh (float): The box IoU cutoff used by non-maximal + suppression to filter duplicate masks. + crop_n_layers (int): If >0, mask prediction will be run again on + crops of the image. Sets the number of layers to run, where each + layer has 2**i_layer number of image crops. + crop_nms_thresh (float): The box IoU cutoff used by non-maximal + suppression to filter duplicate masks between different crops. + crop_overlap_ratio (float): Sets the degree to which crops overlap. + In the first crop layer, crops will overlap by this fraction of + the image length. Later layers with more crops scale down this overlap. + crop_n_points_downscale_factor (int): The number of points-per-side + sampled in layer n is scaled down by crop_n_points_downscale_factor**n. + point_grids (list(np.ndarray) or None): A list over explicit grids + of points used for sampling, normalized to [0,1]. The nth grid in the + list is used in the nth crop layer. Exclusive with points_per_side. + min_mask_region_area (int): If >0, postprocessing will be applied + to remove disconnected regions and holes in masks with area smaller + than min_mask_region_area. Requires opencv. + output_mode (str): The form masks are returned in. Can be 'binary_mask', + 'uncompressed_rle', or 'coco_rle'. 'coco_rle' requires pycocotools. + For large resolutions, 'binary_mask' may consume large amounts of + memory. + use_m2m (bool): Whether to add a one step refinement using previous mask predictions. + multimask_output (bool): Whether to output multimask at each point of the grid. + """ + + assert (points_per_side is None) != ( + point_grids is None + ), "Exactly one of points_per_side or point_grid must be provided." + if points_per_side is not None: + self.point_grids = build_all_layer_point_grids( + points_per_side, + crop_n_layers, + crop_n_points_downscale_factor, + ) + elif point_grids is not None: + self.point_grids = point_grids + else: + raise ValueError("Can't have both points_per_side and point_grid be None.") + + assert output_mode in [ + "binary_mask", + "uncompressed_rle", + "coco_rle", + ], f"Unknown output_mode {output_mode}." + if output_mode == "coco_rle": + try: + from pycocotools import mask as mask_utils # type: ignore # noqa: F401 + except ImportError as e: + print("Please install pycocotools") + raise e + + self.predictor = SAM2ImagePredictor( + model, + max_hole_area=min_mask_region_area, + max_sprinkle_area=min_mask_region_area, + ) + self.points_per_batch = points_per_batch + self.pred_iou_thresh = pred_iou_thresh + self.stability_score_thresh = stability_score_thresh + self.stability_score_offset = stability_score_offset + self.mask_threshold = mask_threshold + self.box_nms_thresh = box_nms_thresh + self.crop_n_layers = crop_n_layers + self.crop_nms_thresh = crop_nms_thresh + self.crop_overlap_ratio = crop_overlap_ratio + self.crop_n_points_downscale_factor = crop_n_points_downscale_factor + self.min_mask_region_area = min_mask_region_area + self.output_mode = output_mode + self.use_m2m = use_m2m + self.multimask_output = multimask_output + + @torch.no_grad() + def generate(self, image: np.ndarray) -> List[Dict[str, Any]]: + """ + Generates masks for the given image. + + Arguments: + image (np.ndarray): The image to generate masks for, in HWC uint8 format. + + Returns: + list(dict(str, any)): A list over records for masks. Each record is + a dict containing the following keys: + segmentation (dict(str, any) or np.ndarray): The mask. If + output_mode='binary_mask', is an array of shape HW. Otherwise, + is a dictionary containing the RLE. + bbox (list(float)): The box around the mask, in XYWH format. + area (int): The area in pixels of the mask. + predicted_iou (float): The model's own prediction of the mask's + quality. This is filtered by the pred_iou_thresh parameter. + point_coords (list(list(float))): The point coordinates input + to the model to generate this mask. + stability_score (float): A measure of the mask's quality. This + is filtered on using the stability_score_thresh parameter. + crop_box (list(float)): The crop of the image used to generate + the mask, given in XYWH format. + """ + + # Generate masks + mask_data = self._generate_masks(image) + + # Encode masks + if self.output_mode == "coco_rle": + mask_data["segmentations"] = [ + coco_encode_rle(rle) for rle in mask_data["rles"] + ] + elif self.output_mode == "binary_mask": + mask_data["segmentations"] = [rle_to_mask(rle) for rle in mask_data["rles"]] + else: + mask_data["segmentations"] = mask_data["rles"] + + # Write mask records + curr_anns = [] + for idx in range(len(mask_data["segmentations"])): + ann = { + "segmentation": mask_data["segmentations"][idx], + "area": area_from_rle(mask_data["rles"][idx]), + "bbox": box_xyxy_to_xywh(mask_data["boxes"][idx]).tolist(), + "predicted_iou": mask_data["iou_preds"][idx].item(), + "point_coords": [mask_data["points"][idx].tolist()], + "stability_score": mask_data["stability_score"][idx].item(), + "crop_box": box_xyxy_to_xywh(mask_data["crop_boxes"][idx]).tolist(), + } + curr_anns.append(ann) + + return curr_anns + + def _generate_masks(self, image: np.ndarray) -> MaskData: + orig_size = image.shape[:2] + crop_boxes, layer_idxs = generate_crop_boxes( + orig_size, self.crop_n_layers, self.crop_overlap_ratio + ) + + # Iterate over image crops + data = MaskData() + for crop_box, layer_idx in zip(crop_boxes, layer_idxs): + crop_data = self._process_crop(image, crop_box, layer_idx, orig_size) + data.cat(crop_data) + + # Remove duplicate masks between crops + if len(crop_boxes) > 1: + # Prefer masks from smaller crops + scores = 1 / box_area(data["crop_boxes"]) + scores = scores.to(data["boxes"].device) + keep_by_nms = batched_nms( + data["boxes"].float(), + scores, + torch.zeros_like(data["boxes"][:, 0]), # categories + iou_threshold=self.crop_nms_thresh, + ) + data.filter(keep_by_nms) + data.to_numpy() + return data + + def _process_crop( + self, + image: np.ndarray, + crop_box: List[int], + crop_layer_idx: int, + orig_size: Tuple[int, ...], + ) -> MaskData: + # Crop the image and calculate embeddings + x0, y0, x1, y1 = crop_box + cropped_im = image[y0:y1, x0:x1, :] + cropped_im_size = cropped_im.shape[:2] + self.predictor.set_image(cropped_im) + + # Get points for this crop + points_scale = np.array(cropped_im_size)[None, ::-1] + points_for_image = self.point_grids[crop_layer_idx] * points_scale + + # Generate masks for this crop in batches + data = MaskData() + for (points,) in batch_iterator(self.points_per_batch, points_for_image): + batch_data = self._process_batch( + points, cropped_im_size, crop_box, orig_size, normalize=True + ) + data.cat(batch_data) + del batch_data + self.predictor.reset_predictor() + + # Remove duplicates within this crop. + keep_by_nms = batched_nms( + data["boxes"].float(), + data["iou_preds"], + torch.zeros_like(data["boxes"][:, 0]), # categories + iou_threshold=self.box_nms_thresh, + ) + data.filter(keep_by_nms) + + # Return to the original image frame + data["boxes"] = uncrop_boxes_xyxy(data["boxes"], crop_box) + data["points"] = uncrop_points(data["points"], crop_box) + data["crop_boxes"] = torch.tensor([crop_box for _ in range(len(data["rles"]))]) + + return data + + def _process_batch( + self, + points: np.ndarray, + im_size: Tuple[int, ...], + crop_box: List[int], + orig_size: Tuple[int, ...], + normalize=False, + ) -> MaskData: + orig_h, orig_w = orig_size + + # Run model on this batch + points = torch.as_tensor(points, device=self.predictor.device) + in_points = self.predictor._transforms.transform_coords( + points, normalize=normalize, orig_hw=im_size + ) + in_labels = torch.ones( + in_points.shape[0], dtype=torch.int, device=in_points.device + ) + masks, iou_preds, low_res_masks = self.predictor._predict( + in_points[:, None, :], + in_labels[:, None], + multimask_output=self.multimask_output, + return_logits=True, + ) + + # Serialize predictions and store in MaskData + data = MaskData( + masks=masks.flatten(0, 1), + iou_preds=iou_preds.flatten(0, 1), + points=points.repeat_interleave(masks.shape[1], dim=0), + low_res_masks=low_res_masks.flatten(0, 1), + ) + del masks + + if not self.use_m2m: + # Filter by predicted IoU + if self.pred_iou_thresh > 0.0: + keep_mask = data["iou_preds"] > self.pred_iou_thresh + data.filter(keep_mask) + + # Calculate and filter by stability score + data["stability_score"] = calculate_stability_score( + data["masks"], self.mask_threshold, self.stability_score_offset + ) + if self.stability_score_thresh > 0.0: + keep_mask = data["stability_score"] >= self.stability_score_thresh + data.filter(keep_mask) + else: + # One step refinement using previous mask predictions + in_points = self.predictor._transforms.transform_coords( + data["points"], normalize=normalize, orig_hw=im_size + ) + labels = torch.ones( + in_points.shape[0], dtype=torch.int, device=in_points.device + ) + masks, ious = self.refine_with_m2m( + in_points, labels, data["low_res_masks"], self.points_per_batch + ) + data["masks"] = masks.squeeze(1) + data["iou_preds"] = ious.squeeze(1) + + if self.pred_iou_thresh > 0.0: + keep_mask = data["iou_preds"] > self.pred_iou_thresh + data.filter(keep_mask) + + data["stability_score"] = calculate_stability_score( + data["masks"], self.mask_threshold, self.stability_score_offset + ) + if self.stability_score_thresh > 0.0: + keep_mask = data["stability_score"] >= self.stability_score_thresh + data.filter(keep_mask) + + # Threshold masks and calculate boxes + data["masks"] = data["masks"] > self.mask_threshold + data["boxes"] = batched_mask_to_box(data["masks"]) + + # Filter boxes that touch crop boundaries + keep_mask = ~is_box_near_crop_edge( + data["boxes"], crop_box, [0, 0, orig_w, orig_h] + ) + if not torch.all(keep_mask): + data.filter(keep_mask) + + # Compress to RLE + data["masks"] = uncrop_masks(data["masks"], crop_box, orig_h, orig_w) + data["rles"] = mask_to_rle_pytorch(data["masks"]) + del data["masks"] + + return data + + @staticmethod + def postprocess_small_regions( + mask_data: MaskData, min_area: int, nms_thresh: float + ) -> MaskData: + """ + Removes small disconnected regions and holes in masks, then reruns + box NMS to remove any new duplicates. + + Edits mask_data in place. + + Requires open-cv as a dependency. + """ + if len(mask_data["rles"]) == 0: + return mask_data + + # Filter small disconnected regions and holes + new_masks = [] + scores = [] + for rle in mask_data["rles"]: + mask = rle_to_mask(rle) + + mask, changed = remove_small_regions(mask, min_area, mode="holes") + unchanged = not changed + mask, changed = remove_small_regions(mask, min_area, mode="islands") + unchanged = unchanged and not changed + + new_masks.append(torch.as_tensor(mask).unsqueeze(0)) + # Give score=0 to changed masks and score=1 to unchanged masks + # so NMS will prefer ones that didn't need postprocessing + scores.append(float(unchanged)) + + # Recalculate boxes and remove any new duplicates + masks = torch.cat(new_masks, dim=0) + boxes = batched_mask_to_box(masks) + keep_by_nms = batched_nms( + boxes.float(), + torch.as_tensor(scores), + torch.zeros_like(boxes[:, 0]), # categories + iou_threshold=nms_thresh, + ) + + # Only recalculate RLEs for masks that have changed + for i_mask in keep_by_nms: + if scores[i_mask] == 0.0: + mask_torch = masks[i_mask].unsqueeze(0) + mask_data["rles"][i_mask] = mask_to_rle_pytorch(mask_torch)[0] + mask_data["boxes"][i_mask] = boxes[i_mask] # update res directly + mask_data.filter(keep_by_nms) + + return mask_data + + def refine_with_m2m(self, points, point_labels, low_res_masks, points_per_batch): + new_masks = [] + new_iou_preds = [] + + for cur_points, cur_point_labels, low_res_mask in batch_iterator( + points_per_batch, points, point_labels, low_res_masks + ): + best_masks, best_iou_preds, _ = self.predictor._predict( + cur_points[:, None, :], + cur_point_labels[:, None], + mask_input=low_res_mask[:, None, :], + multimask_output=False, + return_logits=True, + ) + new_masks.append(best_masks) + new_iou_preds.append(best_iou_preds) + masks = torch.cat(new_masks, dim=0) + return masks, torch.cat(new_iou_preds, dim=0) diff --git a/sam2/build_sam.py b/sam2/build_sam.py new file mode 100644 index 0000000..477061f --- /dev/null +++ b/sam2/build_sam.py @@ -0,0 +1,130 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +import logging + +import torch +from hydra import compose +from hydra.utils import instantiate +from omegaconf import OmegaConf + + +def build_sam2( + config_file, + ckpt_path=None, + device="cuda", + mode="eval", + hydra_overrides_extra=[], + apply_postprocessing=True, +): + + if apply_postprocessing: + hydra_overrides_extra = hydra_overrides_extra.copy() + hydra_overrides_extra += [ + # dynamically fall back to multi-mask if the single mask is not stable + "++model.sam_mask_decoder_extra_args.dynamic_multimask_via_stability=true", + "++model.sam_mask_decoder_extra_args.dynamic_multimask_stability_delta=0.05", + "++model.sam_mask_decoder_extra_args.dynamic_multimask_stability_thresh=0.98", + ] + # Read config and init model + cfg = compose(config_name=config_file, overrides=hydra_overrides_extra) + OmegaConf.resolve(cfg) + model = instantiate(cfg.model, _recursive_=True) + _load_checkpoint(model, ckpt_path) + model = model.to(device) + if mode == "eval": + model.eval() + return model + + +def build_sam2_video_predictor( + config_file, + ckpt_path=None, + device="cuda", + mode="eval", + hydra_overrides_extra=[], + apply_postprocessing=True, +): + hydra_overrides = [ + "++model._target_=sam2.sam2_video_predictor.SAM2VideoPredictor", + ] + if apply_postprocessing: + hydra_overrides_extra = hydra_overrides_extra.copy() + hydra_overrides_extra += [ + # dynamically fall back to multi-mask if the single mask is not stable + "++model.sam_mask_decoder_extra_args.dynamic_multimask_via_stability=true", + "++model.sam_mask_decoder_extra_args.dynamic_multimask_stability_delta=0.05", + "++model.sam_mask_decoder_extra_args.dynamic_multimask_stability_thresh=0.98", + # the sigmoid mask logits on interacted frames with clicks in the memory encoder so that the encoded masks are exactly as what users see from clicking + "++model.binarize_mask_from_pts_for_mem_enc=true", + # fill small holes in the low-res masks up to `fill_hole_area` (before resizing them to the original video resolution) + "++model.fill_hole_area=8", + ] + hydra_overrides.extend(hydra_overrides_extra) + + # Read config and init model + cfg = compose(config_name=config_file, overrides=hydra_overrides) + OmegaConf.resolve(cfg) + model = instantiate(cfg.model, _recursive_=True) + _load_checkpoint(model, ckpt_path) + model = model.to(device) + if mode == "eval": + model.eval() + return model + + +def build_sam2_camera_predictor( + config_file, + ckpt_path=None, + device="cuda", + mode="eval", + hydra_overrides_extra=[], + apply_postprocessing=True, +): + if GlobalHydra.instance().is_initialized(): + GlobalHydra.instance().clear() + + # Initialize Hydra to load the configuration + config_path = "sam2_configs" + + hydra_overrides = [ + "++model._target_=sam2.sam2_camera_predictor.SAM2CameraPredictor", + ] + if apply_postprocessing: + hydra_overrides_extra = hydra_overrides_extra.copy() + hydra_overrides_extra += [ + # dynamically fall back to multi-mask if the single mask is not stable + "++model.sam_mask_decoder_extra_args.dynamic_multimask_via_stability=true", + "++model.sam_mask_decoder_extra_args.dynamic_multimask_stability_delta=0.05", + "++model.sam_mask_decoder_extra_args.dynamic_multimask_stability_thresh=0.98", + # the sigmoid mask logits on interacted frames with clicks in the memory encoder so that the encoded masks are exactly as what users see from clicking + "++model.binarize_mask_from_pts_for_mem_enc=true", + # fill small holes in the low-res masks up to `fill_hole_area` (before resizing them to the original video resolution) + "++model.fill_hole_area=8", + ] + hydra_overrides.extend(hydra_overrides_extra) + + # Read config and init model + cfg = compose(config_name=config_file, overrides=hydra_overrides) + OmegaConf.resolve(cfg) + model = instantiate(cfg.model, _recursive_=True) + _load_checkpoint(model, ckpt_path) + model = model.to(device) + if mode == "eval": + model.eval() + return model + +def _load_checkpoint(model, ckpt_path): + if ckpt_path is not None: + sd = torch.load(ckpt_path, map_location="cpu")["model"] + missing_keys, unexpected_keys = model.load_state_dict(sd) + if missing_keys: + logging.error(missing_keys) + raise RuntimeError() + if unexpected_keys: + logging.error(unexpected_keys) + raise RuntimeError() + logging.info("Loaded checkpoint sucessfully") diff --git a/sam2/csrc/connected_components.cu b/sam2/csrc/connected_components.cu new file mode 100644 index 0000000..ced21eb --- /dev/null +++ b/sam2/csrc/connected_components.cu @@ -0,0 +1,289 @@ +// Copyright (c) Meta Platforms, Inc. and affiliates. +// All rights reserved. + +// This source code is licensed under the license found in the +// LICENSE file in the root directory of this source tree. + +// adapted from https://github.com/zsef123/Connected_components_PyTorch +// with license found in the LICENSE_cctorch file in the root directory. +#include +#include +#include +#include +#include +#include + +// 2d +#define BLOCK_ROWS 16 +#define BLOCK_COLS 16 + +namespace cc2d { + +template +__device__ __forceinline__ unsigned char hasBit(T bitmap, unsigned char pos) { + return (bitmap >> pos) & 1; +} + +__device__ int32_t find(const int32_t* s_buf, int32_t n) { + while (s_buf[n] != n) + n = s_buf[n]; + return n; +} + +__device__ int32_t find_n_compress(int32_t* s_buf, int32_t n) { + const int32_t id = n; + while (s_buf[n] != n) { + n = s_buf[n]; + s_buf[id] = n; + } + return n; +} + +__device__ void union_(int32_t* s_buf, int32_t a, int32_t b) { + bool done; + do { + a = find(s_buf, a); + b = find(s_buf, b); + + if (a < b) { + int32_t old = atomicMin(s_buf + b, a); + done = (old == b); + b = old; + } else if (b < a) { + int32_t old = atomicMin(s_buf + a, b); + done = (old == a); + a = old; + } else + done = true; + + } while (!done); +} + +__global__ void +init_labeling(int32_t* label, const uint32_t W, const uint32_t H) { + const uint32_t row = (blockIdx.y * blockDim.y + threadIdx.y) * 2; + const uint32_t col = (blockIdx.x * blockDim.x + threadIdx.x) * 2; + const uint32_t idx = row * W + col; + + if (row < H && col < W) + label[idx] = idx; +} + +__global__ void +merge(uint8_t* img, int32_t* label, const uint32_t W, const uint32_t H) { + const uint32_t row = (blockIdx.y * blockDim.y + threadIdx.y) * 2; + const uint32_t col = (blockIdx.x * blockDim.x + threadIdx.x) * 2; + const uint32_t idx = row * W + col; + + if (row >= H || col >= W) + return; + + uint32_t P = 0; + + if (img[idx]) + P |= 0x777; + if (row + 1 < H && img[idx + W]) + P |= 0x777 << 4; + if (col + 1 < W && img[idx + 1]) + P |= 0x777 << 1; + + if (col == 0) + P &= 0xEEEE; + if (col + 1 >= W) + P &= 0x3333; + else if (col + 2 >= W) + P &= 0x7777; + + if (row == 0) + P &= 0xFFF0; + if (row + 1 >= H) + P &= 0xFF; + + if (P > 0) { + // If need check about top-left pixel(if flag the first bit) and hit the + // top-left pixel + if (hasBit(P, 0) && img[idx - W - 1]) { + union_(label, idx, idx - 2 * W - 2); // top left block + } + + if ((hasBit(P, 1) && img[idx - W]) || (hasBit(P, 2) && img[idx - W + 1])) + union_(label, idx, idx - 2 * W); // top bottom block + + if (hasBit(P, 3) && img[idx + 2 - W]) + union_(label, idx, idx - 2 * W + 2); // top right block + + if ((hasBit(P, 4) && img[idx - 1]) || (hasBit(P, 8) && img[idx + W - 1])) + union_(label, idx, idx - 2); // just left block + } +} + +__global__ void compression(int32_t* label, const int32_t W, const int32_t H) { + const uint32_t row = (blockIdx.y * blockDim.y + threadIdx.y) * 2; + const uint32_t col = (blockIdx.x * blockDim.x + threadIdx.x) * 2; + const uint32_t idx = row * W + col; + + if (row < H && col < W) + find_n_compress(label, idx); +} + +__global__ void final_labeling( + const uint8_t* img, + int32_t* label, + const int32_t W, + const int32_t H) { + const uint32_t row = (blockIdx.y * blockDim.y + threadIdx.y) * 2; + const uint32_t col = (blockIdx.x * blockDim.x + threadIdx.x) * 2; + const uint32_t idx = row * W + col; + + if (row >= H || col >= W) + return; + + int32_t y = label[idx] + 1; + + if (img[idx]) + label[idx] = y; + else + label[idx] = 0; + + if (col + 1 < W) { + if (img[idx + 1]) + label[idx + 1] = y; + else + label[idx + 1] = 0; + + if (row + 1 < H) { + if (img[idx + W + 1]) + label[idx + W + 1] = y; + else + label[idx + W + 1] = 0; + } + } + + if (row + 1 < H) { + if (img[idx + W]) + label[idx + W] = y; + else + label[idx + W] = 0; + } +} + +__global__ void init_counting( + const int32_t* label, + int32_t* count_init, + const int32_t W, + const int32_t H) { + const uint32_t row = (blockIdx.y * blockDim.y + threadIdx.y); + const uint32_t col = (blockIdx.x * blockDim.x + threadIdx.x); + const uint32_t idx = row * W + col; + + if (row >= H || col >= W) + return; + + int32_t y = label[idx]; + if (y > 0) { + int32_t count_idx = y - 1; + atomicAdd(count_init + count_idx, 1); + } +} + +__global__ void final_counting( + const int32_t* label, + const int32_t* count_init, + int32_t* count_final, + const int32_t W, + const int32_t H) { + const uint32_t row = (blockIdx.y * blockDim.y + threadIdx.y); + const uint32_t col = (blockIdx.x * blockDim.x + threadIdx.x); + const uint32_t idx = row * W + col; + + if (row >= H || col >= W) + return; + + int32_t y = label[idx]; + if (y > 0) { + int32_t count_idx = y - 1; + count_final[idx] = count_init[count_idx]; + } else { + count_final[idx] = 0; + } +} + +} // namespace cc2d + +std::vector get_connected_componnets( + const torch::Tensor& inputs) { + AT_ASSERTM(inputs.is_cuda(), "inputs must be a CUDA tensor"); + AT_ASSERTM(inputs.ndimension() == 4, "inputs must be [N, 1, H, W] shape"); + AT_ASSERTM( + inputs.scalar_type() == torch::kUInt8, "inputs must be a uint8 type"); + + const uint32_t N = inputs.size(0); + const uint32_t C = inputs.size(1); + const uint32_t H = inputs.size(2); + const uint32_t W = inputs.size(3); + + AT_ASSERTM(C == 1, "inputs must be [N, 1, H, W] shape"); + AT_ASSERTM((H % 2) == 0, "height must be an even number"); + AT_ASSERTM((W % 2) == 0, "width must be an even number"); + + // label must be uint32_t + auto label_options = + torch::TensorOptions().dtype(torch::kInt32).device(inputs.device()); + torch::Tensor labels = torch::zeros({N, C, H, W}, label_options); + torch::Tensor counts_init = torch::zeros({N, C, H, W}, label_options); + torch::Tensor counts_final = torch::zeros({N, C, H, W}, label_options); + + dim3 grid = dim3( + ((W + 1) / 2 + BLOCK_COLS - 1) / BLOCK_COLS, + ((H + 1) / 2 + BLOCK_ROWS - 1) / BLOCK_ROWS); + dim3 block = dim3(BLOCK_COLS, BLOCK_ROWS); + dim3 grid_count = + dim3((W + BLOCK_COLS) / BLOCK_COLS, (H + BLOCK_ROWS) / BLOCK_ROWS); + dim3 block_count = dim3(BLOCK_COLS, BLOCK_ROWS); + cudaStream_t stream = at::cuda::getCurrentCUDAStream(); + + for (int n = 0; n < N; n++) { + uint32_t offset = n * H * W; + + cc2d::init_labeling<<>>( + labels.data_ptr() + offset, W, H); + cc2d::merge<<>>( + inputs.data_ptr() + offset, + labels.data_ptr() + offset, + W, + H); + cc2d::compression<<>>( + labels.data_ptr() + offset, W, H); + cc2d::final_labeling<<>>( + inputs.data_ptr() + offset, + labels.data_ptr() + offset, + W, + H); + + // get the counting of each pixel + cc2d::init_counting<<>>( + labels.data_ptr() + offset, + counts_init.data_ptr() + offset, + W, + H); + cc2d::final_counting<<>>( + labels.data_ptr() + offset, + counts_init.data_ptr() + offset, + counts_final.data_ptr() + offset, + W, + H); + } + + // returned values are [labels, counts] + std::vector outputs; + outputs.push_back(labels); + outputs.push_back(counts_final); + return outputs; +} + +PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) { + m.def( + "get_connected_componnets", + &get_connected_componnets, + "get_connected_componnets"); +} diff --git a/sam2/modeling/__init__.py b/sam2/modeling/__init__.py new file mode 100644 index 0000000..5277f46 --- /dev/null +++ b/sam2/modeling/__init__.py @@ -0,0 +1,5 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. diff --git a/sam2/modeling/backbones/__init__.py b/sam2/modeling/backbones/__init__.py new file mode 100644 index 0000000..5277f46 --- /dev/null +++ b/sam2/modeling/backbones/__init__.py @@ -0,0 +1,5 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. diff --git a/sam2/modeling/backbones/hieradet.py b/sam2/modeling/backbones/hieradet.py new file mode 100644 index 0000000..1ae7d4c --- /dev/null +++ b/sam2/modeling/backbones/hieradet.py @@ -0,0 +1,295 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +from functools import partial +from typing import List, Tuple, Union + +import torch +import torch.nn as nn +import torch.nn.functional as F + +from sam2.modeling.backbones.utils import ( + PatchEmbed, + window_partition, + window_unpartition, +) + +from sam2.modeling.sam2_utils import DropPath, MLP + + +def do_pool(x: torch.Tensor, pool: nn.Module, norm: nn.Module = None) -> torch.Tensor: + if pool is None: + return x + # (B, H, W, C) -> (B, C, H, W) + x = x.permute(0, 3, 1, 2) + x = pool(x) + # (B, C, H', W') -> (B, H', W', C) + x = x.permute(0, 2, 3, 1) + if norm: + x = norm(x) + + return x + + +class MultiScaleAttention(nn.Module): + def __init__( + self, + dim: int, + dim_out: int, + num_heads: int, + q_pool: nn.Module = None, + ): + super().__init__() + + self.dim = dim + self.dim_out = dim_out + + self.num_heads = num_heads + head_dim = dim_out // num_heads + self.scale = head_dim**-0.5 + + self.q_pool = q_pool + self.qkv = nn.Linear(dim, dim_out * 3) + self.proj = nn.Linear(dim_out, dim_out) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + B, H, W, _ = x.shape + # qkv with shape (B, H * W, 3, nHead, C) + qkv = self.qkv(x).reshape(B, H * W, 3, self.num_heads, -1) + # q, k, v with shape (B, H * W, nheads, C) + q, k, v = torch.unbind(qkv, 2) + + # Q pooling (for downsample at stage changes) + if self.q_pool: + q = do_pool(q.reshape(B, H, W, -1), self.q_pool) + H, W = q.shape[1:3] # downsampled shape + q = q.reshape(B, H * W, self.num_heads, -1) + + # Torch's SDPA expects [B, nheads, H*W, C] so we transpose + x = F.scaled_dot_product_attention( + q.transpose(1, 2), + k.transpose(1, 2), + v.transpose(1, 2), + ) + # Transpose back + x = x.transpose(1, 2) + x = x.reshape(B, H, W, -1) + + x = self.proj(x) + + return x + + +class MultiScaleBlock(nn.Module): + def __init__( + self, + dim: int, + dim_out: int, + num_heads: int, + mlp_ratio: float = 4.0, + drop_path: float = 0.0, + norm_layer: Union[nn.Module, str] = "LayerNorm", + q_stride: Tuple[int, int] = None, + act_layer: nn.Module = nn.GELU, + window_size: int = 0, + ): + super().__init__() + + if isinstance(norm_layer, str): + norm_layer = partial(getattr(nn, norm_layer), eps=1e-6) + + self.dim = dim + self.dim_out = dim_out + self.norm1 = norm_layer(dim) + + self.window_size = window_size + + self.pool, self.q_stride = None, q_stride + if self.q_stride: + self.pool = nn.MaxPool2d( + kernel_size=q_stride, stride=q_stride, ceil_mode=False + ) + + self.attn = MultiScaleAttention( + dim, + dim_out, + num_heads=num_heads, + q_pool=self.pool, + ) + self.drop_path = DropPath(drop_path) if drop_path > 0.0 else nn.Identity() + + self.norm2 = norm_layer(dim_out) + self.mlp = MLP( + dim_out, + int(dim_out * mlp_ratio), + dim_out, + num_layers=2, + activation=act_layer, + ) + + if dim != dim_out: + self.proj = nn.Linear(dim, dim_out) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + shortcut = x # B, H, W, C + x = self.norm1(x) + + # Skip connection + if self.dim != self.dim_out: + shortcut = do_pool(self.proj(x), self.pool) + + # Window partition + window_size = self.window_size + if window_size > 0: + H, W = x.shape[1], x.shape[2] + x, pad_hw = window_partition(x, window_size) + + # Window Attention + Q Pooling (if stage change) + x = self.attn(x) + if self.q_stride: + # Shapes have changed due to Q pooling + window_size = self.window_size // self.q_stride[0] + H, W = shortcut.shape[1:3] + + pad_h = (window_size - H % window_size) % window_size + pad_w = (window_size - W % window_size) % window_size + pad_hw = (H + pad_h, W + pad_w) + + # Reverse window partition + if self.window_size > 0: + x = window_unpartition(x, window_size, pad_hw, (H, W)) + + x = shortcut + self.drop_path(x) + # MLP + x = x + self.drop_path(self.mlp(self.norm2(x))) + return x + + +class Hiera(nn.Module): + """ + Reference: https://arxiv.org/abs/2306.00989 + """ + + def __init__( + self, + embed_dim: int = 96, # initial embed dim + num_heads: int = 1, # initial number of heads + drop_path_rate: float = 0.0, # stochastic depth + q_pool: int = 3, # number of q_pool stages + q_stride: Tuple[int, int] = (2, 2), # downsample stride bet. stages + stages: Tuple[int, ...] = (2, 3, 16, 3), # blocks per stage + dim_mul: float = 2.0, # dim_mul factor at stage shift + head_mul: float = 2.0, # head_mul factor at stage shift + window_pos_embed_bkg_spatial_size: Tuple[int, int] = (14, 14), + # window size per stage, when not using global att. + window_spec: Tuple[int, ...] = ( + 8, + 4, + 14, + 7, + ), + # global attn in these blocks + global_att_blocks: Tuple[int, ...] = ( + 12, + 16, + 20, + ), + return_interm_layers=True, # return feats from every stage + ): + super().__init__() + + assert len(stages) == len(window_spec) + self.window_spec = window_spec + + depth = sum(stages) + self.q_stride = q_stride + self.stage_ends = [sum(stages[:i]) - 1 for i in range(1, len(stages) + 1)] + assert 0 <= q_pool <= len(self.stage_ends[:-1]) + self.q_pool_blocks = [x + 1 for x in self.stage_ends[:-1]][:q_pool] + self.return_interm_layers = return_interm_layers + + self.patch_embed = PatchEmbed( + embed_dim=embed_dim, + ) + # Which blocks have global att? + self.global_att_blocks = global_att_blocks + + # Windowed positional embedding (https://arxiv.org/abs/2311.05613) + self.window_pos_embed_bkg_spatial_size = window_pos_embed_bkg_spatial_size + self.pos_embed = nn.Parameter( + torch.zeros(1, embed_dim, *self.window_pos_embed_bkg_spatial_size) + ) + self.pos_embed_window = nn.Parameter( + torch.zeros(1, embed_dim, self.window_spec[0], self.window_spec[0]) + ) + + dpr = [ + x.item() for x in torch.linspace(0, drop_path_rate, depth) + ] # stochastic depth decay rule + + cur_stage = 1 + self.blocks = nn.ModuleList() + + for i in range(depth): + dim_out = embed_dim + # lags by a block, so first block of + # next stage uses an initial window size + # of previous stage and final window size of current stage + window_size = self.window_spec[cur_stage - 1] + + if self.global_att_blocks is not None: + window_size = 0 if i in self.global_att_blocks else window_size + + if i - 1 in self.stage_ends: + dim_out = int(embed_dim * dim_mul) + num_heads = int(num_heads * head_mul) + cur_stage += 1 + + block = MultiScaleBlock( + dim=embed_dim, + dim_out=dim_out, + num_heads=num_heads, + drop_path=dpr[i], + q_stride=self.q_stride if i in self.q_pool_blocks else None, + window_size=window_size, + ) + + embed_dim = dim_out + self.blocks.append(block) + + self.channel_list = ( + [self.blocks[i].dim_out for i in self.stage_ends[::-1]] + if return_interm_layers + else [self.blocks[-1].dim_out] + ) + + def _get_pos_embed(self, hw: Tuple[int, int]) -> torch.Tensor: + h, w = hw + window_embed = self.pos_embed_window + pos_embed = F.interpolate(self.pos_embed, size=(h, w), mode="bicubic") + pos_embed = pos_embed + window_embed.tile( + [x // y for x, y in zip(pos_embed.shape, window_embed.shape)] + ) + pos_embed = pos_embed.permute(0, 2, 3, 1) + return pos_embed + + def forward(self, x: torch.Tensor) -> List[torch.Tensor]: + x = self.patch_embed(x) + # x: (B, H, W, C) + + # Add pos embed + x = x + self._get_pos_embed(x.shape[1:3]) + + outputs = [] + for i, blk in enumerate(self.blocks): + x = blk(x) + if (i == self.stage_ends[-1]) or ( + i in self.stage_ends and self.return_interm_layers + ): + feats = x.permute(0, 3, 1, 2) + outputs.append(feats) + + return outputs diff --git a/sam2/modeling/backbones/image_encoder.py b/sam2/modeling/backbones/image_encoder.py new file mode 100644 index 0000000..5f92baf --- /dev/null +++ b/sam2/modeling/backbones/image_encoder.py @@ -0,0 +1,133 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +from typing import List, Optional + +import torch +import torch.nn as nn +import torch.nn.functional as F + + +class ImageEncoder(nn.Module): + def __init__( + self, + trunk: nn.Module, + neck: nn.Module, + scalp: int = 0, + ): + super().__init__() + self.trunk = trunk + self.neck = neck + self.scalp = scalp + assert ( + self.trunk.channel_list == self.neck.backbone_channel_list + ), f"Channel dims of trunk and neck do not match. Trunk: {self.trunk.channel_list}, neck: {self.neck.backbone_channel_list}" + + def forward(self, sample: torch.Tensor): + # Forward through backbone + features, pos = self.neck(self.trunk(sample)) + if self.scalp > 0: + # Discard the lowest resolution features + features, pos = features[: -self.scalp], pos[: -self.scalp] + + src = features[-1] + output = { + "vision_features": src, + "vision_pos_enc": pos, + "backbone_fpn": features, + } + return output + + +class FpnNeck(nn.Module): + """ + A modified variant of Feature Pyramid Network (FPN) neck + (we remove output conv and also do bicubic interpolation similar to ViT + pos embed interpolation) + """ + + def __init__( + self, + position_encoding: nn.Module, + d_model: int, + backbone_channel_list: List[int], + kernel_size: int = 1, + stride: int = 1, + padding: int = 0, + fpn_interp_model: str = "bilinear", + fuse_type: str = "sum", + fpn_top_down_levels: Optional[List[int]] = None, + ): + """Initialize the neck + :param trunk: the backbone + :param position_encoding: the positional encoding to use + :param d_model: the dimension of the model + :param neck_norm: the normalization to use + """ + super().__init__() + self.position_encoding = position_encoding + self.convs = nn.ModuleList() + self.backbone_channel_list = backbone_channel_list + for dim in backbone_channel_list: + current = nn.Sequential() + current.add_module( + "conv", + nn.Conv2d( + in_channels=dim, + out_channels=d_model, + kernel_size=kernel_size, + stride=stride, + padding=padding, + ), + ) + + self.convs.append(current) + self.fpn_interp_model = fpn_interp_model + assert fuse_type in ["sum", "avg"] + self.fuse_type = fuse_type + + # levels to have top-down features in its outputs + # e.g. if fpn_top_down_levels is [2, 3], then only outputs of level 2 and 3 + # have top-down propagation, while outputs of level 0 and level 1 have only + # lateral features from the same backbone level. + if fpn_top_down_levels is None: + # default is to have top-down features on all levels + fpn_top_down_levels = range(len(self.convs)) + self.fpn_top_down_levels = list(fpn_top_down_levels) + + def forward(self, xs: List[torch.Tensor]): + + out = [None] * len(self.convs) + pos = [None] * len(self.convs) + assert len(xs) == len(self.convs) + # fpn forward pass + # see https://github.com/facebookresearch/detectron2/blob/main/detectron2/modeling/backbone/fpn.py + prev_features = None + # forward in top-down order (from low to high resolution) + n = len(self.convs) - 1 + for i in range(n, -1, -1): + x = xs[i] + lateral_features = self.convs[n - i](x) + if i in self.fpn_top_down_levels and prev_features is not None: + top_down_features = F.interpolate( + prev_features.to(dtype=torch.float32), + scale_factor=2.0, + mode=self.fpn_interp_model, + align_corners=( + None if self.fpn_interp_model == "nearest" else False + ), + antialias=False, + ) + prev_features = lateral_features + top_down_features + if self.fuse_type == "avg": + prev_features /= 2 + else: + prev_features = lateral_features + x_out = prev_features + out[i] = x_out + pos[i] = self.position_encoding(x_out).to(x_out.dtype) + + return out, pos diff --git a/sam2/modeling/backbones/utils.py b/sam2/modeling/backbones/utils.py new file mode 100644 index 0000000..32d55c7 --- /dev/null +++ b/sam2/modeling/backbones/utils.py @@ -0,0 +1,95 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +"""Some utilities for backbones, in particular for windowing""" + +from typing import Tuple + +import torch +import torch.nn as nn +import torch.nn.functional as F + + +def window_partition(x, window_size): + """ + Partition into non-overlapping windows with padding if needed. + Args: + x (tensor): input tokens with [B, H, W, C]. + window_size (int): window size. + Returns: + windows: windows after partition with [B * num_windows, window_size, window_size, C]. + (Hp, Wp): padded height and width before partition + """ + B, H, W, C = x.shape + + pad_h = (window_size - H % window_size) % window_size + pad_w = (window_size - W % window_size) % window_size + if pad_h > 0 or pad_w > 0: + x = F.pad(x, (0, 0, 0, pad_w, 0, pad_h)) + Hp, Wp = H + pad_h, W + pad_w + + x = x.view(B, Hp // window_size, window_size, Wp // window_size, window_size, C) + windows = ( + x.permute(0, 1, 3, 2, 4, 5).contiguous().view(-1, window_size, window_size, C) + ) + return windows, (Hp, Wp) + + +def window_unpartition(windows, window_size, pad_hw, hw): + """ + Window unpartition into original sequences and removing padding. + Args: + x (tensor): input tokens with [B * num_windows, window_size, window_size, C]. + window_size (int): window size. + pad_hw (Tuple): padded height and width (Hp, Wp). + hw (Tuple): original height and width (H, W) before padding. + Returns: + x: unpartitioned sequences with [B, H, W, C]. + """ + Hp, Wp = pad_hw + H, W = hw + B = windows.shape[0] // (Hp * Wp // window_size // window_size) + x = windows.view( + B, Hp // window_size, Wp // window_size, window_size, window_size, -1 + ) + x = x.permute(0, 1, 3, 2, 4, 5).contiguous().view(B, Hp, Wp, -1) + + if Hp > H or Wp > W: + x = x[:, :H, :W, :].contiguous() + return x + + +class PatchEmbed(nn.Module): + """ + Image to Patch Embedding. + """ + + def __init__( + self, + kernel_size: Tuple[int, ...] = (7, 7), + stride: Tuple[int, ...] = (4, 4), + padding: Tuple[int, ...] = (3, 3), + in_chans: int = 3, + embed_dim: int = 768, + ): + """ + Args: + kernel_size (Tuple): kernel size of the projection layer. + stride (Tuple): stride of the projection layer. + padding (Tuple): padding size of the projection layer. + in_chans (int): Number of input image channels. + embed_dim (int): embed_dim (int): Patch embedding dimension. + """ + super().__init__() + self.proj = nn.Conv2d( + in_chans, embed_dim, kernel_size=kernel_size, stride=stride, padding=padding + ) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + x = self.proj(x) + # B C H W -> B H W C + x = x.permute(0, 2, 3, 1) + return x diff --git a/sam2/modeling/memory_attention.py b/sam2/modeling/memory_attention.py new file mode 100644 index 0000000..0b07f9d --- /dev/null +++ b/sam2/modeling/memory_attention.py @@ -0,0 +1,169 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +from typing import Optional + +import torch +from torch import nn, Tensor + +from sam2.modeling.sam.transformer import RoPEAttention + +from sam2.modeling.sam2_utils import get_activation_fn, get_clones + + +class MemoryAttentionLayer(nn.Module): + + def __init__( + self, + activation: str, + cross_attention: nn.Module, + d_model: int, + dim_feedforward: int, + dropout: float, + pos_enc_at_attn: bool, + pos_enc_at_cross_attn_keys: bool, + pos_enc_at_cross_attn_queries: bool, + self_attention: nn.Module, + ): + super().__init__() + self.d_model = d_model + self.dim_feedforward = dim_feedforward + self.dropout_value = dropout + self.self_attn = self_attention + self.cross_attn_image = cross_attention + + # Implementation of Feedforward model + self.linear1 = nn.Linear(d_model, dim_feedforward) + self.dropout = nn.Dropout(dropout) + self.linear2 = nn.Linear(dim_feedforward, d_model) + + self.norm1 = nn.LayerNorm(d_model) + self.norm2 = nn.LayerNorm(d_model) + self.norm3 = nn.LayerNorm(d_model) + self.dropout1 = nn.Dropout(dropout) + self.dropout2 = nn.Dropout(dropout) + self.dropout3 = nn.Dropout(dropout) + + self.activation_str = activation + self.activation = get_activation_fn(activation) + + # Where to add pos enc + self.pos_enc_at_attn = pos_enc_at_attn + self.pos_enc_at_cross_attn_queries = pos_enc_at_cross_attn_queries + self.pos_enc_at_cross_attn_keys = pos_enc_at_cross_attn_keys + + def _forward_sa(self, tgt, query_pos): + # Self-Attention + tgt2 = self.norm1(tgt) + q = k = tgt2 + query_pos if self.pos_enc_at_attn else tgt2 + tgt2 = self.self_attn(q, k, v=tgt2) + tgt = tgt + self.dropout1(tgt2) + return tgt + + def _forward_ca(self, tgt, memory, query_pos, pos, num_k_exclude_rope=0): + kwds = {} + if num_k_exclude_rope > 0: + assert isinstance(self.cross_attn_image, RoPEAttention) + kwds = {"num_k_exclude_rope": num_k_exclude_rope} + + # Cross-Attention + tgt2 = self.norm2(tgt) + tgt2 = self.cross_attn_image( + q=tgt2 + query_pos if self.pos_enc_at_cross_attn_queries else tgt2, + k=memory + pos if self.pos_enc_at_cross_attn_keys else memory, + v=memory, + **kwds, + ) + tgt = tgt + self.dropout2(tgt2) + return tgt + + def forward( + self, + tgt, + memory, + pos: Optional[Tensor] = None, + query_pos: Optional[Tensor] = None, + num_k_exclude_rope: int = 0, + ) -> torch.Tensor: + + # Self-Attn, Cross-Attn + tgt = self._forward_sa(tgt, query_pos) + tgt = self._forward_ca(tgt, memory, query_pos, pos, num_k_exclude_rope) + # MLP + tgt2 = self.norm3(tgt) + tgt2 = self.linear2(self.dropout(self.activation(self.linear1(tgt2)))) + tgt = tgt + self.dropout3(tgt2) + return tgt + + +class MemoryAttention(nn.Module): + def __init__( + self, + d_model: int, + pos_enc_at_input: bool, + layer: nn.Module, + num_layers: int, + batch_first: bool = True, # Do layers expect batch first input? + ): + super().__init__() + self.d_model = d_model + self.layers = get_clones(layer, num_layers) + self.num_layers = num_layers + self.norm = nn.LayerNorm(d_model) + self.pos_enc_at_input = pos_enc_at_input + self.batch_first = batch_first + + def forward( + self, + curr: torch.Tensor, # self-attention inputs + memory: torch.Tensor, # cross-attention inputs + curr_pos: Optional[Tensor] = None, # pos_enc for self-attention inputs + memory_pos: Optional[Tensor] = None, # pos_enc for cross-attention inputs + num_obj_ptr_tokens: int = 0, # number of object pointer *tokens* + ): + if isinstance(curr, list): + assert isinstance(curr_pos, list) + assert len(curr) == len(curr_pos) == 1 + curr, curr_pos = ( + curr[0], + curr_pos[0], + ) + + assert ( + curr.shape[1] == memory.shape[1] + ), "Batch size must be the same for curr and memory" + + output = curr + if self.pos_enc_at_input and curr_pos is not None: + output = output + 0.1 * curr_pos + + if self.batch_first: + # Convert to batch first + output = output.transpose(0, 1) + curr_pos = curr_pos.transpose(0, 1) + memory = memory.transpose(0, 1) + memory_pos = memory_pos.transpose(0, 1) + + for layer in self.layers: + kwds = {} + if isinstance(layer.cross_attn_image, RoPEAttention): + kwds = {"num_k_exclude_rope": num_obj_ptr_tokens} + + output = layer( + tgt=output, + memory=memory, + pos=memory_pos, + query_pos=curr_pos, + **kwds, + ) + normed_output = self.norm(output) + + if self.batch_first: + # Convert back to seq first + normed_output = normed_output.transpose(0, 1) + curr_pos = curr_pos.transpose(0, 1) + + return normed_output diff --git a/sam2/modeling/memory_encoder.py b/sam2/modeling/memory_encoder.py new file mode 100644 index 0000000..f60202d --- /dev/null +++ b/sam2/modeling/memory_encoder.py @@ -0,0 +1,181 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +import math +from typing import Tuple + +import torch +import torch.nn as nn +import torch.nn.functional as F + +from sam2.modeling.sam2_utils import DropPath, get_clones, LayerNorm2d + + +class MaskDownSampler(nn.Module): + """ + Progressively downsample a mask by total_stride, each time by stride. + Note that LayerNorm is applied per *token*, like in ViT. + + With each downsample (by a factor stride**2), channel capacity increases by the same factor. + In the end, we linearly project to embed_dim channels. + """ + + def __init__( + self, + embed_dim=256, + kernel_size=4, + stride=4, + padding=0, + total_stride=16, + activation=nn.GELU, + ): + super().__init__() + num_layers = int(math.log2(total_stride) // math.log2(stride)) + assert stride**num_layers == total_stride + self.encoder = nn.Sequential() + mask_in_chans, mask_out_chans = 1, 1 + for _ in range(num_layers): + mask_out_chans = mask_in_chans * (stride**2) + self.encoder.append( + nn.Conv2d( + mask_in_chans, + mask_out_chans, + kernel_size=kernel_size, + stride=stride, + padding=padding, + ) + ) + self.encoder.append(LayerNorm2d(mask_out_chans)) + self.encoder.append(activation()) + mask_in_chans = mask_out_chans + + self.encoder.append(nn.Conv2d(mask_out_chans, embed_dim, kernel_size=1)) + + def forward(self, x): + return self.encoder(x) + + +# Lightly adapted from ConvNext (https://github.com/facebookresearch/ConvNeXt) +class CXBlock(nn.Module): + r"""ConvNeXt Block. There are two equivalent implementations: + (1) DwConv -> LayerNorm (channels_first) -> 1x1 Conv -> GELU -> 1x1 Conv; all in (N, C, H, W) + (2) DwConv -> Permute to (N, H, W, C); LayerNorm (channels_last) -> Linear -> GELU -> Linear; Permute back + We use (2) as we find it slightly faster in PyTorch + + Args: + dim (int): Number of input channels. + drop_path (float): Stochastic depth rate. Default: 0.0 + layer_scale_init_value (float): Init value for Layer Scale. Default: 1e-6. + """ + + def __init__( + self, + dim, + kernel_size=7, + padding=3, + drop_path=0.0, + layer_scale_init_value=1e-6, + use_dwconv=True, + ): + super().__init__() + self.dwconv = nn.Conv2d( + dim, + dim, + kernel_size=kernel_size, + padding=padding, + groups=dim if use_dwconv else 1, + ) # depthwise conv + self.norm = LayerNorm2d(dim, eps=1e-6) + self.pwconv1 = nn.Linear( + dim, 4 * dim + ) # pointwise/1x1 convs, implemented with linear layers + self.act = nn.GELU() + self.pwconv2 = nn.Linear(4 * dim, dim) + self.gamma = ( + nn.Parameter(layer_scale_init_value * torch.ones((dim)), requires_grad=True) + if layer_scale_init_value > 0 + else None + ) + self.drop_path = DropPath(drop_path) if drop_path > 0.0 else nn.Identity() + + def forward(self, x): + input = x + x = self.dwconv(x) + x = self.norm(x) + x = x.permute(0, 2, 3, 1) # (N, C, H, W) -> (N, H, W, C) + x = self.pwconv1(x) + x = self.act(x) + x = self.pwconv2(x) + if self.gamma is not None: + x = self.gamma * x + x = x.permute(0, 3, 1, 2) # (N, H, W, C) -> (N, C, H, W) + + x = input + self.drop_path(x) + return x + + +class Fuser(nn.Module): + def __init__(self, layer, num_layers, dim=None, input_projection=False): + super().__init__() + self.proj = nn.Identity() + self.layers = get_clones(layer, num_layers) + + if input_projection: + assert dim is not None + self.proj = nn.Conv2d(dim, dim, kernel_size=1) + + def forward(self, x): + # normally x: (N, C, H, W) + x = self.proj(x) + for layer in self.layers: + x = layer(x) + return x + + +class MemoryEncoder(nn.Module): + def __init__( + self, + out_dim, + mask_downsampler, + fuser, + position_encoding, + in_dim=256, # in_dim of pix_feats + ): + super().__init__() + + self.mask_downsampler = mask_downsampler + + self.pix_feat_proj = nn.Conv2d(in_dim, in_dim, kernel_size=1) + self.fuser = fuser + self.position_encoding = position_encoding + self.out_proj = nn.Identity() + if out_dim != in_dim: + self.out_proj = nn.Conv2d(in_dim, out_dim, kernel_size=1) + + def forward( + self, + pix_feat: torch.Tensor, + masks: torch.Tensor, + skip_mask_sigmoid: bool = False, + ) -> Tuple[torch.Tensor, torch.Tensor]: + ## Process masks + # sigmoid, so that less domain shift from gt masks which are bool + if not skip_mask_sigmoid: + masks = F.sigmoid(masks) + masks = self.mask_downsampler(masks) + + ## Fuse pix_feats and downsampled masks + # in case the visual features are on CPU, cast them to CUDA + pix_feat = pix_feat.to(masks.device) + + x = self.pix_feat_proj(pix_feat) + x = x + masks + x = self.fuser(x) + x = self.out_proj(x) + + pos = self.position_encoding(x).to(x.dtype) + + return {"vision_features": x, "vision_pos_enc": [pos]} diff --git a/sam2/modeling/position_encoding.py b/sam2/modeling/position_encoding.py new file mode 100644 index 0000000..f4b57ae --- /dev/null +++ b/sam2/modeling/position_encoding.py @@ -0,0 +1,216 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +import math +from typing import Any, Optional, Tuple + +import numpy as np + +import torch +from torch import nn + + +class PositionEmbeddingSine(nn.Module): + """ + This is a more standard version of the position embedding, very similar to the one + used by the Attention is all you need paper, generalized to work on images. + """ + + def __init__( + self, + num_pos_feats, + temperature: int = 10000, + normalize: bool = True, + scale: Optional[float] = None, + ): + super().__init__() + assert num_pos_feats % 2 == 0, "Expecting even model width" + self.num_pos_feats = num_pos_feats // 2 + self.temperature = temperature + self.normalize = normalize + if scale is not None and normalize is False: + raise ValueError("normalize should be True if scale is passed") + if scale is None: + scale = 2 * math.pi + self.scale = scale + + self.cache = {} + + def _encode_xy(self, x, y): + # The positions are expected to be normalized + assert len(x) == len(y) and x.ndim == y.ndim == 1 + x_embed = x * self.scale + y_embed = y * self.scale + + dim_t = torch.arange(self.num_pos_feats, dtype=torch.float32, device=x.device) + dim_t = self.temperature ** (2 * (dim_t // 2) / self.num_pos_feats) + + pos_x = x_embed[:, None] / dim_t + pos_y = y_embed[:, None] / dim_t + pos_x = torch.stack( + (pos_x[:, 0::2].sin(), pos_x[:, 1::2].cos()), dim=2 + ).flatten(1) + pos_y = torch.stack( + (pos_y[:, 0::2].sin(), pos_y[:, 1::2].cos()), dim=2 + ).flatten(1) + return pos_x, pos_y + + @torch.no_grad() + def encode_boxes(self, x, y, w, h): + pos_x, pos_y = self._encode_xy(x, y) + pos = torch.cat((pos_y, pos_x, h[:, None], w[:, None]), dim=1) + return pos + + encode = encode_boxes # Backwards compatibility + + @torch.no_grad() + def encode_points(self, x, y, labels): + (bx, nx), (by, ny), (bl, nl) = x.shape, y.shape, labels.shape + assert bx == by and nx == ny and bx == bl and nx == nl + pos_x, pos_y = self._encode_xy(x.flatten(), y.flatten()) + pos_x, pos_y = pos_x.reshape(bx, nx, -1), pos_y.reshape(by, ny, -1) + pos = torch.cat((pos_y, pos_x, labels[:, :, None]), dim=2) + return pos + + @torch.no_grad() + def forward(self, x: torch.Tensor): + cache_key = (x.shape[-2], x.shape[-1]) + if cache_key in self.cache: + return self.cache[cache_key][None].repeat(x.shape[0], 1, 1, 1) + y_embed = ( + torch.arange(1, x.shape[-2] + 1, dtype=torch.float32, device=x.device) + .view(1, -1, 1) + .repeat(x.shape[0], 1, x.shape[-1]) + ) + x_embed = ( + torch.arange(1, x.shape[-1] + 1, dtype=torch.float32, device=x.device) + .view(1, 1, -1) + .repeat(x.shape[0], x.shape[-2], 1) + ) + + if self.normalize: + eps = 1e-6 + y_embed = y_embed / (y_embed[:, -1:, :] + eps) * self.scale + x_embed = x_embed / (x_embed[:, :, -1:] + eps) * self.scale + + dim_t = torch.arange(self.num_pos_feats, dtype=torch.float32, device=x.device) + dim_t = self.temperature ** (2 * (dim_t // 2) / self.num_pos_feats) + + pos_x = x_embed[:, :, :, None] / dim_t + pos_y = y_embed[:, :, :, None] / dim_t + pos_x = torch.stack( + (pos_x[:, :, :, 0::2].sin(), pos_x[:, :, :, 1::2].cos()), dim=4 + ).flatten(3) + pos_y = torch.stack( + (pos_y[:, :, :, 0::2].sin(), pos_y[:, :, :, 1::2].cos()), dim=4 + ).flatten(3) + pos = torch.cat((pos_y, pos_x), dim=3).permute(0, 3, 1, 2) + self.cache[cache_key] = pos[0] + return pos + + +class PositionEmbeddingRandom(nn.Module): + """ + Positional encoding using random spatial frequencies. + """ + + def __init__(self, num_pos_feats: int = 64, scale: Optional[float] = None) -> None: + super().__init__() + if scale is None or scale <= 0.0: + scale = 1.0 + self.register_buffer( + "positional_encoding_gaussian_matrix", + scale * torch.randn((2, num_pos_feats)), + ) + + def _pe_encoding(self, coords: torch.Tensor) -> torch.Tensor: + """Positionally encode points that are normalized to [0,1].""" + # assuming coords are in [0, 1]^2 square and have d_1 x ... x d_n x 2 shape + coords = 2 * coords - 1 + coords = coords @ self.positional_encoding_gaussian_matrix + coords = 2 * np.pi * coords + # outputs d_1 x ... x d_n x C shape + return torch.cat([torch.sin(coords), torch.cos(coords)], dim=-1) + + def forward(self, size: Tuple[int, int]) -> torch.Tensor: + """Generate positional encoding for a grid of the specified size.""" + h, w = size + device: Any = self.positional_encoding_gaussian_matrix.device + grid = torch.ones((h, w), device=device, dtype=torch.float32) + y_embed = grid.cumsum(dim=0) - 0.5 + x_embed = grid.cumsum(dim=1) - 0.5 + y_embed = y_embed / h + x_embed = x_embed / w + + pe = self._pe_encoding(torch.stack([x_embed, y_embed], dim=-1)) + return pe.permute(2, 0, 1) # C x H x W + + def forward_with_coords( + self, coords_input: torch.Tensor, image_size: Tuple[int, int] + ) -> torch.Tensor: + """Positionally encode points that are not normalized to [0,1].""" + coords = coords_input.clone() + coords[:, :, 0] = coords[:, :, 0] / image_size[1] + coords[:, :, 1] = coords[:, :, 1] / image_size[0] + return self._pe_encoding(coords.to(torch.float)) # B x N x C + + +# Rotary Positional Encoding, adapted from: +# 1. https://github.com/meta-llama/codellama/blob/main/llama/model.py +# 2. https://github.com/naver-ai/rope-vit +# 3. https://github.com/lucidrains/rotary-embedding-torch + + +def init_t_xy(end_x: int, end_y: int): + t = torch.arange(end_x * end_y, dtype=torch.float32) + t_x = (t % end_x).float() + t_y = torch.div(t, end_x, rounding_mode="floor").float() + return t_x, t_y + + +def compute_axial_cis(dim: int, end_x: int, end_y: int, theta: float = 10000.0): + freqs_x = 1.0 / (theta ** (torch.arange(0, dim, 4)[: (dim // 4)].float() / dim)) + freqs_y = 1.0 / (theta ** (torch.arange(0, dim, 4)[: (dim // 4)].float() / dim)) + + t_x, t_y = init_t_xy(end_x, end_y) + freqs_x = torch.outer(t_x, freqs_x) + freqs_y = torch.outer(t_y, freqs_y) + freqs_cis_x = torch.polar(torch.ones_like(freqs_x), freqs_x) + freqs_cis_y = torch.polar(torch.ones_like(freqs_y), freqs_y) + return torch.cat([freqs_cis_x, freqs_cis_y], dim=-1) + + +def reshape_for_broadcast(freqs_cis: torch.Tensor, x: torch.Tensor): + ndim = x.ndim + assert 0 <= 1 < ndim + assert freqs_cis.shape == (x.shape[-2], x.shape[-1]) + shape = [d if i >= ndim - 2 else 1 for i, d in enumerate(x.shape)] + return freqs_cis.view(*shape) + + +def apply_rotary_enc( + xq: torch.Tensor, + xk: torch.Tensor, + freqs_cis: torch.Tensor, + repeat_freqs_k: bool = False, +): + xq_ = torch.view_as_complex(xq.float().reshape(*xq.shape[:-1], -1, 2)) + xk_ = ( + torch.view_as_complex(xk.float().reshape(*xk.shape[:-1], -1, 2)) + if xk.shape[-2] != 0 + else None + ) + freqs_cis = reshape_for_broadcast(freqs_cis, xq_) + xq_out = torch.view_as_real(xq_ * freqs_cis).flatten(3) + if xk_ is None: + # no keys to rotate, due to dropout + return xq_out.type_as(xq).to(xq.device), xk + # repeat freqs along seq_len dim to match k seq_len + if repeat_freqs_k: + r = xk_.shape[-2] // xq_.shape[-2] + freqs_cis = freqs_cis.repeat(*([1] * (freqs_cis.ndim - 2)), r, 1) + xk_out = torch.view_as_real(xk_ * freqs_cis).flatten(3) + return xq_out.type_as(xq).to(xq.device), xk_out.type_as(xk).to(xk.device) diff --git a/sam2/modeling/sam/__init__.py b/sam2/modeling/sam/__init__.py new file mode 100644 index 0000000..5277f46 --- /dev/null +++ b/sam2/modeling/sam/__init__.py @@ -0,0 +1,5 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. diff --git a/sam2/modeling/sam/mask_decoder.py b/sam2/modeling/sam/mask_decoder.py new file mode 100644 index 0000000..b7c7dfd --- /dev/null +++ b/sam2/modeling/sam/mask_decoder.py @@ -0,0 +1,295 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +from typing import List, Optional, Tuple, Type + +import torch +from torch import nn + +from sam2.modeling.sam2_utils import LayerNorm2d, MLP + + +class MaskDecoder(nn.Module): + def __init__( + self, + *, + transformer_dim: int, + transformer: nn.Module, + num_multimask_outputs: int = 3, + activation: Type[nn.Module] = nn.GELU, + iou_head_depth: int = 3, + iou_head_hidden_dim: int = 256, + use_high_res_features: bool = False, + iou_prediction_use_sigmoid=False, + dynamic_multimask_via_stability=False, + dynamic_multimask_stability_delta=0.05, + dynamic_multimask_stability_thresh=0.98, + pred_obj_scores: bool = False, + pred_obj_scores_mlp: bool = False, + use_multimask_token_for_obj_ptr: bool = False, + ) -> None: + """ + Predicts masks given an image and prompt embeddings, using a + transformer architecture. + + Arguments: + transformer_dim (int): the channel dimension of the transformer + transformer (nn.Module): the transformer used to predict masks + num_multimask_outputs (int): the number of masks to predict + when disambiguating masks + activation (nn.Module): the type of activation to use when + upscaling masks + iou_head_depth (int): the depth of the MLP used to predict + mask quality + iou_head_hidden_dim (int): the hidden dimension of the MLP + used to predict mask quality + """ + super().__init__() + self.transformer_dim = transformer_dim + self.transformer = transformer + + self.num_multimask_outputs = num_multimask_outputs + + self.iou_token = nn.Embedding(1, transformer_dim) + self.num_mask_tokens = num_multimask_outputs + 1 + self.mask_tokens = nn.Embedding(self.num_mask_tokens, transformer_dim) + + self.pred_obj_scores = pred_obj_scores + if self.pred_obj_scores: + self.obj_score_token = nn.Embedding(1, transformer_dim) + self.use_multimask_token_for_obj_ptr = use_multimask_token_for_obj_ptr + + self.output_upscaling = nn.Sequential( + nn.ConvTranspose2d( + transformer_dim, transformer_dim // 4, kernel_size=2, stride=2 + ), + LayerNorm2d(transformer_dim // 4), + activation(), + nn.ConvTranspose2d( + transformer_dim // 4, transformer_dim // 8, kernel_size=2, stride=2 + ), + activation(), + ) + self.use_high_res_features = use_high_res_features + if use_high_res_features: + self.conv_s0 = nn.Conv2d( + transformer_dim, transformer_dim // 8, kernel_size=1, stride=1 + ) + self.conv_s1 = nn.Conv2d( + transformer_dim, transformer_dim // 4, kernel_size=1, stride=1 + ) + + self.output_hypernetworks_mlps = nn.ModuleList( + [ + MLP(transformer_dim, transformer_dim, transformer_dim // 8, 3) + for i in range(self.num_mask_tokens) + ] + ) + + self.iou_prediction_head = MLP( + transformer_dim, + iou_head_hidden_dim, + self.num_mask_tokens, + iou_head_depth, + sigmoid_output=iou_prediction_use_sigmoid, + ) + if self.pred_obj_scores: + self.pred_obj_score_head = nn.Linear(transformer_dim, 1) + if pred_obj_scores_mlp: + self.pred_obj_score_head = MLP(transformer_dim, transformer_dim, 1, 3) + + # When outputting a single mask, optionally we can dynamically fall back to the best + # multimask output token if the single mask output token gives low stability scores. + self.dynamic_multimask_via_stability = dynamic_multimask_via_stability + self.dynamic_multimask_stability_delta = dynamic_multimask_stability_delta + self.dynamic_multimask_stability_thresh = dynamic_multimask_stability_thresh + + def forward( + self, + image_embeddings: torch.Tensor, + image_pe: torch.Tensor, + sparse_prompt_embeddings: torch.Tensor, + dense_prompt_embeddings: torch.Tensor, + multimask_output: bool, + repeat_image: bool, + high_res_features: Optional[List[torch.Tensor]] = None, + ) -> Tuple[torch.Tensor, torch.Tensor]: + """ + Predict masks given image and prompt embeddings. + + Arguments: + image_embeddings (torch.Tensor): the embeddings from the image encoder + image_pe (torch.Tensor): positional encoding with the shape of image_embeddings + sparse_prompt_embeddings (torch.Tensor): the embeddings of the points and boxes + dense_prompt_embeddings (torch.Tensor): the embeddings of the mask inputs + multimask_output (bool): Whether to return multiple masks or a single + mask. + + Returns: + torch.Tensor: batched predicted masks + torch.Tensor: batched predictions of mask quality + torch.Tensor: batched SAM token for mask output + """ + masks, iou_pred, mask_tokens_out, object_score_logits = self.predict_masks( + image_embeddings=image_embeddings, + image_pe=image_pe, + sparse_prompt_embeddings=sparse_prompt_embeddings, + dense_prompt_embeddings=dense_prompt_embeddings, + repeat_image=repeat_image, + high_res_features=high_res_features, + ) + + # Select the correct mask or masks for output + if multimask_output: + masks = masks[:, 1:, :, :] + iou_pred = iou_pred[:, 1:] + elif self.dynamic_multimask_via_stability and not self.training: + masks, iou_pred = self._dynamic_multimask_via_stability(masks, iou_pred) + else: + masks = masks[:, 0:1, :, :] + iou_pred = iou_pred[:, 0:1] + + if multimask_output and self.use_multimask_token_for_obj_ptr: + sam_tokens_out = mask_tokens_out[:, 1:] # [b, 3, c] shape + else: + # Take the mask output token. Here we *always* use the token for single mask output. + # At test time, even if we track after 1-click (and using multimask_output=True), + # we still take the single mask token here. The rationale is that we always track + # after multiple clicks during training, so the past tokens seen during training + # are always the single mask token (and we'll let it be the object-memory token). + sam_tokens_out = mask_tokens_out[:, 0:1] # [b, 1, c] shape + + # Prepare output + return masks, iou_pred, sam_tokens_out, object_score_logits + + def predict_masks( + self, + image_embeddings: torch.Tensor, + image_pe: torch.Tensor, + sparse_prompt_embeddings: torch.Tensor, + dense_prompt_embeddings: torch.Tensor, + repeat_image: bool, + high_res_features: Optional[List[torch.Tensor]] = None, + ) -> Tuple[torch.Tensor, torch.Tensor]: + """Predicts masks. See 'forward' for more details.""" + # Concatenate output tokens + s = 0 + if self.pred_obj_scores: + output_tokens = torch.cat( + [ + self.obj_score_token.weight, + self.iou_token.weight, + self.mask_tokens.weight, + ], + dim=0, + ) + s = 1 + else: + output_tokens = torch.cat( + [self.iou_token.weight, self.mask_tokens.weight], dim=0 + ) + output_tokens = output_tokens.unsqueeze(0).expand( + sparse_prompt_embeddings.size(0), -1, -1 + ) + tokens = torch.cat((output_tokens, sparse_prompt_embeddings), dim=1) + + # Expand per-image data in batch direction to be per-mask + if repeat_image: + src = torch.repeat_interleave(image_embeddings, tokens.shape[0], dim=0) + else: + assert image_embeddings.shape[0] == tokens.shape[0] + src = image_embeddings + src = src + dense_prompt_embeddings + assert ( + image_pe.size(0) == 1 + ), "image_pe should have size 1 in batch dim (from `get_dense_pe()`)" + pos_src = torch.repeat_interleave(image_pe, tokens.shape[0], dim=0) + b, c, h, w = src.shape + + # Run the transformer + hs, src = self.transformer(src, pos_src, tokens) + iou_token_out = hs[:, s, :] + mask_tokens_out = hs[:, s + 1 : (s + 1 + self.num_mask_tokens), :] + + # Upscale mask embeddings and predict masks using the mask tokens + src = src.transpose(1, 2).view(b, c, h, w) + if not self.use_high_res_features: + upscaled_embedding = self.output_upscaling(src) + else: + dc1, ln1, act1, dc2, act2 = self.output_upscaling + feat_s0, feat_s1 = high_res_features + upscaled_embedding = act1(ln1(dc1(src) + feat_s1)) + upscaled_embedding = act2(dc2(upscaled_embedding) + feat_s0) + + hyper_in_list: List[torch.Tensor] = [] + for i in range(self.num_mask_tokens): + hyper_in_list.append( + self.output_hypernetworks_mlps[i](mask_tokens_out[:, i, :]) + ) + hyper_in = torch.stack(hyper_in_list, dim=1) + b, c, h, w = upscaled_embedding.shape + masks = (hyper_in @ upscaled_embedding.view(b, c, h * w)).view(b, -1, h, w) + + # Generate mask quality predictions + iou_pred = self.iou_prediction_head(iou_token_out) + if self.pred_obj_scores: + assert s == 1 + object_score_logits = self.pred_obj_score_head(hs[:, 0, :]) + else: + # Obj scores logits - default to 10.0, i.e. assuming the object is present, sigmoid(10)=1 + object_score_logits = 10.0 * iou_pred.new_ones(iou_pred.shape[0], 1) + + return masks, iou_pred, mask_tokens_out, object_score_logits + + def _get_stability_scores(self, mask_logits): + """ + Compute stability scores of the mask logits based on the IoU between upper and + lower thresholds, similar to https://github.com/fairinternal/onevision/pull/568. + """ + mask_logits = mask_logits.flatten(-2) + stability_delta = self.dynamic_multimask_stability_delta + area_i = torch.sum(mask_logits > stability_delta, dim=-1).float() + area_u = torch.sum(mask_logits > -stability_delta, dim=-1).float() + stability_scores = torch.where(area_u > 0, area_i / area_u, 1.0) + return stability_scores + + def _dynamic_multimask_via_stability(self, all_mask_logits, all_iou_scores): + """ + When outputting a single mask, if the stability score from the current single-mask + output (based on output token 0) falls below a threshold, we instead select from + multi-mask outputs (based on output token 1~3) the mask with the highest predicted + IoU score. This is intended to ensure a valid mask for both clicking and tracking. + """ + # The best mask from multimask output tokens (1~3) + multimask_logits = all_mask_logits[:, 1:, :, :] + multimask_iou_scores = all_iou_scores[:, 1:] + best_scores_inds = torch.argmax(multimask_iou_scores, dim=-1) + batch_inds = torch.arange( + multimask_iou_scores.size(0), device=all_iou_scores.device + ) + best_multimask_logits = multimask_logits[batch_inds, best_scores_inds] + best_multimask_logits = best_multimask_logits.unsqueeze(1) + best_multimask_iou_scores = multimask_iou_scores[batch_inds, best_scores_inds] + best_multimask_iou_scores = best_multimask_iou_scores.unsqueeze(1) + + # The mask from singlemask output token 0 and its stability score + singlemask_logits = all_mask_logits[:, 0:1, :, :] + singlemask_iou_scores = all_iou_scores[:, 0:1] + stability_scores = self._get_stability_scores(singlemask_logits) + is_stable = stability_scores >= self.dynamic_multimask_stability_thresh + + # Dynamically fall back to best multimask output upon low stability scores. + mask_logits_out = torch.where( + is_stable[..., None, None].expand_as(singlemask_logits), + singlemask_logits, + best_multimask_logits, + ) + iou_scores_out = torch.where( + is_stable.expand_as(singlemask_iou_scores), + singlemask_iou_scores, + best_multimask_iou_scores, + ) + return mask_logits_out, iou_scores_out diff --git a/sam2/modeling/sam/prompt_encoder.py b/sam2/modeling/sam/prompt_encoder.py new file mode 100644 index 0000000..6b3bbb9 --- /dev/null +++ b/sam2/modeling/sam/prompt_encoder.py @@ -0,0 +1,182 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +from typing import Optional, Tuple, Type + +import torch +from torch import nn + +from sam2.modeling.position_encoding import PositionEmbeddingRandom + +from sam2.modeling.sam2_utils import LayerNorm2d + + +class PromptEncoder(nn.Module): + def __init__( + self, + embed_dim: int, + image_embedding_size: Tuple[int, int], + input_image_size: Tuple[int, int], + mask_in_chans: int, + activation: Type[nn.Module] = nn.GELU, + ) -> None: + """ + Encodes prompts for input to SAM's mask decoder. + + Arguments: + embed_dim (int): The prompts' embedding dimension + image_embedding_size (tuple(int, int)): The spatial size of the + image embedding, as (H, W). + input_image_size (int): The padded size of the image as input + to the image encoder, as (H, W). + mask_in_chans (int): The number of hidden channels used for + encoding input masks. + activation (nn.Module): The activation to use when encoding + input masks. + """ + super().__init__() + self.embed_dim = embed_dim + self.input_image_size = input_image_size + self.image_embedding_size = image_embedding_size + self.pe_layer = PositionEmbeddingRandom(embed_dim // 2) + + self.num_point_embeddings: int = 4 # pos/neg point + 2 box corners + point_embeddings = [ + nn.Embedding(1, embed_dim) for i in range(self.num_point_embeddings) + ] + self.point_embeddings = nn.ModuleList(point_embeddings) + self.not_a_point_embed = nn.Embedding(1, embed_dim) + + self.mask_input_size = ( + 4 * image_embedding_size[0], + 4 * image_embedding_size[1], + ) + self.mask_downscaling = nn.Sequential( + nn.Conv2d(1, mask_in_chans // 4, kernel_size=2, stride=2), + LayerNorm2d(mask_in_chans // 4), + activation(), + nn.Conv2d(mask_in_chans // 4, mask_in_chans, kernel_size=2, stride=2), + LayerNorm2d(mask_in_chans), + activation(), + nn.Conv2d(mask_in_chans, embed_dim, kernel_size=1), + ) + self.no_mask_embed = nn.Embedding(1, embed_dim) + + def get_dense_pe(self) -> torch.Tensor: + """ + Returns the positional encoding used to encode point prompts, + applied to a dense set of points the shape of the image encoding. + + Returns: + torch.Tensor: Positional encoding with shape + 1x(embed_dim)x(embedding_h)x(embedding_w) + """ + return self.pe_layer(self.image_embedding_size).unsqueeze(0) + + def _embed_points( + self, + points: torch.Tensor, + labels: torch.Tensor, + pad: bool, + ) -> torch.Tensor: + """Embeds point prompts.""" + points = points + 0.5 # Shift to center of pixel + if pad: + padding_point = torch.zeros((points.shape[0], 1, 2), device=points.device) + padding_label = -torch.ones((labels.shape[0], 1), device=labels.device) + points = torch.cat([points, padding_point], dim=1) + labels = torch.cat([labels, padding_label], dim=1) + point_embedding = self.pe_layer.forward_with_coords( + points, self.input_image_size + ) + point_embedding[labels == -1] = 0.0 + point_embedding[labels == -1] += self.not_a_point_embed.weight + point_embedding[labels == 0] += self.point_embeddings[0].weight + point_embedding[labels == 1] += self.point_embeddings[1].weight + point_embedding[labels == 2] += self.point_embeddings[2].weight + point_embedding[labels == 3] += self.point_embeddings[3].weight + return point_embedding + + def _embed_boxes(self, boxes: torch.Tensor) -> torch.Tensor: + """Embeds box prompts.""" + boxes = boxes + 0.5 # Shift to center of pixel + coords = boxes.reshape(-1, 2, 2) + corner_embedding = self.pe_layer.forward_with_coords( + coords, self.input_image_size + ) + corner_embedding[:, 0, :] += self.point_embeddings[2].weight + corner_embedding[:, 1, :] += self.point_embeddings[3].weight + return corner_embedding + + def _embed_masks(self, masks: torch.Tensor) -> torch.Tensor: + """Embeds mask inputs.""" + mask_embedding = self.mask_downscaling(masks) + return mask_embedding + + def _get_batch_size( + self, + points: Optional[Tuple[torch.Tensor, torch.Tensor]], + boxes: Optional[torch.Tensor], + masks: Optional[torch.Tensor], + ) -> int: + """ + Gets the batch size of the output given the batch size of the input prompts. + """ + if points is not None: + return points[0].shape[0] + elif boxes is not None: + return boxes.shape[0] + elif masks is not None: + return masks.shape[0] + else: + return 1 + + def _get_device(self) -> torch.device: + return self.point_embeddings[0].weight.device + + def forward( + self, + points: Optional[Tuple[torch.Tensor, torch.Tensor]], + boxes: Optional[torch.Tensor], + masks: Optional[torch.Tensor], + ) -> Tuple[torch.Tensor, torch.Tensor]: + """ + Embeds different types of prompts, returning both sparse and dense + embeddings. + + Arguments: + points (tuple(torch.Tensor, torch.Tensor) or none): point coordinates + and labels to embed. + boxes (torch.Tensor or none): boxes to embed + masks (torch.Tensor or none): masks to embed + + Returns: + torch.Tensor: sparse embeddings for the points and boxes, with shape + BxNx(embed_dim), where N is determined by the number of input points + and boxes. + torch.Tensor: dense embeddings for the masks, in the shape + Bx(embed_dim)x(embed_H)x(embed_W) + """ + bs = self._get_batch_size(points, boxes, masks) + sparse_embeddings = torch.empty( + (bs, 0, self.embed_dim), device=self._get_device() + ) + if points is not None: + coords, labels = points + point_embeddings = self._embed_points(coords, labels, pad=(boxes is None)) + sparse_embeddings = torch.cat([sparse_embeddings, point_embeddings], dim=1) + if boxes is not None: + box_embeddings = self._embed_boxes(boxes) + sparse_embeddings = torch.cat([sparse_embeddings, box_embeddings], dim=1) + + if masks is not None: + dense_embeddings = self._embed_masks(masks) + else: + dense_embeddings = self.no_mask_embed.weight.reshape(1, -1, 1, 1).expand( + bs, -1, self.image_embedding_size[0], self.image_embedding_size[1] + ) + + return sparse_embeddings, dense_embeddings diff --git a/sam2/modeling/sam/transformer.py b/sam2/modeling/sam/transformer.py new file mode 100644 index 0000000..2a0b796 --- /dev/null +++ b/sam2/modeling/sam/transformer.py @@ -0,0 +1,327 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +import math +import warnings +from functools import partial +from typing import Tuple, Type + +import torch +import torch.nn.functional as F +from torch import nn, Tensor + +from sam2.modeling.position_encoding import apply_rotary_enc, compute_axial_cis + +from sam2.modeling.sam2_utils import MLP +from sam2.utils.misc import get_sdpa_settings + +warnings.simplefilter(action="ignore", category=FutureWarning) +OLD_GPU, USE_FLASH_ATTN, MATH_KERNEL_ON = get_sdpa_settings() + + +class TwoWayTransformer(nn.Module): + def __init__( + self, + depth: int, + embedding_dim: int, + num_heads: int, + mlp_dim: int, + activation: Type[nn.Module] = nn.ReLU, + attention_downsample_rate: int = 2, + ) -> None: + """ + A transformer decoder that attends to an input image using + queries whose positional embedding is supplied. + + Args: + depth (int): number of layers in the transformer + embedding_dim (int): the channel dimension for the input embeddings + num_heads (int): the number of heads for multihead attention. Must + divide embedding_dim + mlp_dim (int): the channel dimension internal to the MLP block + activation (nn.Module): the activation to use in the MLP block + """ + super().__init__() + self.depth = depth + self.embedding_dim = embedding_dim + self.num_heads = num_heads + self.mlp_dim = mlp_dim + self.layers = nn.ModuleList() + + for i in range(depth): + self.layers.append( + TwoWayAttentionBlock( + embedding_dim=embedding_dim, + num_heads=num_heads, + mlp_dim=mlp_dim, + activation=activation, + attention_downsample_rate=attention_downsample_rate, + skip_first_layer_pe=(i == 0), + ) + ) + + self.final_attn_token_to_image = Attention( + embedding_dim, num_heads, downsample_rate=attention_downsample_rate + ) + self.norm_final_attn = nn.LayerNorm(embedding_dim) + + def forward( + self, + image_embedding: Tensor, + image_pe: Tensor, + point_embedding: Tensor, + ) -> Tuple[Tensor, Tensor]: + """ + Args: + image_embedding (torch.Tensor): image to attend to. Should be shape + B x embedding_dim x h x w for any h and w. + image_pe (torch.Tensor): the positional encoding to add to the image. Must + have the same shape as image_embedding. + point_embedding (torch.Tensor): the embedding to add to the query points. + Must have shape B x N_points x embedding_dim for any N_points. + + Returns: + torch.Tensor: the processed point_embedding + torch.Tensor: the processed image_embedding + """ + # BxCxHxW -> BxHWxC == B x N_image_tokens x C + bs, c, h, w = image_embedding.shape + image_embedding = image_embedding.flatten(2).permute(0, 2, 1) + image_pe = image_pe.flatten(2).permute(0, 2, 1) + + # Prepare queries + queries = point_embedding + keys = image_embedding + + # Apply transformer blocks and final layernorm + for layer in self.layers: + queries, keys = layer( + queries=queries, + keys=keys, + query_pe=point_embedding, + key_pe=image_pe, + ) + + # Apply the final attention layer from the points to the image + q = queries + point_embedding + k = keys + image_pe + attn_out = self.final_attn_token_to_image(q=q, k=k, v=keys) + queries = queries + attn_out + queries = self.norm_final_attn(queries) + + return queries, keys + + +class TwoWayAttentionBlock(nn.Module): + def __init__( + self, + embedding_dim: int, + num_heads: int, + mlp_dim: int = 2048, + activation: Type[nn.Module] = nn.ReLU, + attention_downsample_rate: int = 2, + skip_first_layer_pe: bool = False, + ) -> None: + """ + A transformer block with four layers: (1) self-attention of sparse + inputs, (2) cross attention of sparse inputs to dense inputs, (3) mlp + block on sparse inputs, and (4) cross attention of dense inputs to sparse + inputs. + + Arguments: + embedding_dim (int): the channel dimension of the embeddings + num_heads (int): the number of heads in the attention layers + mlp_dim (int): the hidden dimension of the mlp block + activation (nn.Module): the activation of the mlp block + skip_first_layer_pe (bool): skip the PE on the first layer + """ + super().__init__() + self.self_attn = Attention(embedding_dim, num_heads) + self.norm1 = nn.LayerNorm(embedding_dim) + + self.cross_attn_token_to_image = Attention( + embedding_dim, num_heads, downsample_rate=attention_downsample_rate + ) + self.norm2 = nn.LayerNorm(embedding_dim) + + self.mlp = MLP( + embedding_dim, mlp_dim, embedding_dim, num_layers=2, activation=activation + ) + self.norm3 = nn.LayerNorm(embedding_dim) + + self.norm4 = nn.LayerNorm(embedding_dim) + self.cross_attn_image_to_token = Attention( + embedding_dim, num_heads, downsample_rate=attention_downsample_rate + ) + + self.skip_first_layer_pe = skip_first_layer_pe + + def forward( + self, queries: Tensor, keys: Tensor, query_pe: Tensor, key_pe: Tensor + ) -> Tuple[Tensor, Tensor]: + # Self attention block + if self.skip_first_layer_pe: + queries = self.self_attn(q=queries, k=queries, v=queries) + else: + q = queries + query_pe + attn_out = self.self_attn(q=q, k=q, v=queries) + queries = queries + attn_out + queries = self.norm1(queries) + + # Cross attention block, tokens attending to image embedding + q = queries + query_pe + k = keys + key_pe + attn_out = self.cross_attn_token_to_image(q=q, k=k, v=keys) + queries = queries + attn_out + queries = self.norm2(queries) + + # MLP block + mlp_out = self.mlp(queries) + queries = queries + mlp_out + queries = self.norm3(queries) + + # Cross attention block, image embedding attending to tokens + q = queries + query_pe + k = keys + key_pe + attn_out = self.cross_attn_image_to_token(q=k, k=q, v=queries) + keys = keys + attn_out + keys = self.norm4(keys) + + return queries, keys + + +class Attention(nn.Module): + """ + An attention layer that allows for downscaling the size of the embedding + after projection to queries, keys, and values. + """ + + def __init__( + self, + embedding_dim: int, + num_heads: int, + downsample_rate: int = 1, + dropout: float = 0.0, + kv_in_dim: int = None, + ) -> None: + super().__init__() + self.embedding_dim = embedding_dim + self.kv_in_dim = kv_in_dim if kv_in_dim is not None else embedding_dim + self.internal_dim = embedding_dim // downsample_rate + self.num_heads = num_heads + assert ( + self.internal_dim % num_heads == 0 + ), "num_heads must divide embedding_dim." + + self.q_proj = nn.Linear(embedding_dim, self.internal_dim) + self.k_proj = nn.Linear(self.kv_in_dim, self.internal_dim) + self.v_proj = nn.Linear(self.kv_in_dim, self.internal_dim) + self.out_proj = nn.Linear(self.internal_dim, embedding_dim) + + self.dropout_p = dropout + + def _separate_heads(self, x: Tensor, num_heads: int) -> Tensor: + b, n, c = x.shape + x = x.reshape(b, n, num_heads, c // num_heads) + return x.transpose(1, 2) # B x N_heads x N_tokens x C_per_head + + def _recombine_heads(self, x: Tensor) -> Tensor: + b, n_heads, n_tokens, c_per_head = x.shape + x = x.transpose(1, 2) + return x.reshape(b, n_tokens, n_heads * c_per_head) # B x N_tokens x C + + def forward(self, q: Tensor, k: Tensor, v: Tensor) -> Tensor: + # Input projections + q = self.q_proj(q) + k = self.k_proj(k) + v = self.v_proj(v) + + # Separate into heads + q = self._separate_heads(q, self.num_heads) + k = self._separate_heads(k, self.num_heads) + v = self._separate_heads(v, self.num_heads) + + dropout_p = self.dropout_p if self.training else 0.0 + # Attention + with torch.backends.cuda.sdp_kernel( + enable_flash=USE_FLASH_ATTN, + # if Flash attention kernel is off, then math kernel needs to be enabled + enable_math=(OLD_GPU and dropout_p > 0.0) or MATH_KERNEL_ON, + enable_mem_efficient=OLD_GPU, + ): + out = F.scaled_dot_product_attention(q, k, v, dropout_p=dropout_p) + + out = self._recombine_heads(out) + out = self.out_proj(out) + + return out + + +class RoPEAttention(Attention): + """Attention with rotary position encoding.""" + + def __init__( + self, + *args, + rope_theta=10000.0, + # whether to repeat q rope to match k length + # this is needed for cross-attention to memories + rope_k_repeat=False, + feat_sizes=(32, 32), # [w, h] for stride 16 feats at 512 resolution + **kwargs, + ): + super().__init__(*args, **kwargs) + + self.compute_cis = partial( + compute_axial_cis, dim=self.internal_dim // self.num_heads, theta=rope_theta + ) + freqs_cis = self.compute_cis(end_x=feat_sizes[0], end_y=feat_sizes[1]) + self.freqs_cis = freqs_cis + self.rope_k_repeat = rope_k_repeat + + def forward( + self, q: Tensor, k: Tensor, v: Tensor, num_k_exclude_rope: int = 0 + ) -> Tensor: + # Input projections + q = self.q_proj(q) + k = self.k_proj(k) + v = self.v_proj(v) + + # Separate into heads + q = self._separate_heads(q, self.num_heads) + k = self._separate_heads(k, self.num_heads) + v = self._separate_heads(v, self.num_heads) + + # Apply rotary position encoding + w = h = math.sqrt(q.shape[-2]) + self.freqs_cis = self.freqs_cis.to(q.device) + if self.freqs_cis.shape[0] != q.shape[-2]: + self.freqs_cis = self.compute_cis(end_x=w, end_y=h).to(q.device) + if q.shape[-2] != k.shape[-2]: + assert self.rope_k_repeat + + num_k_rope = k.size(-2) - num_k_exclude_rope + q, k[:, :, :num_k_rope] = apply_rotary_enc( + q, + k[:, :, :num_k_rope], + freqs_cis=self.freqs_cis, + repeat_freqs_k=self.rope_k_repeat, + ) + + dropout_p = self.dropout_p if self.training else 0.0 + # Attention + with torch.backends.cuda.sdp_kernel( + enable_flash=USE_FLASH_ATTN, + # if Flash attention kernel is off, then math kernel needs to be enabled + enable_math=(OLD_GPU and dropout_p > 0.0) or MATH_KERNEL_ON, + enable_mem_efficient=OLD_GPU, + ): + out = F.scaled_dot_product_attention(q, k, v, dropout_p=dropout_p) + + out = self._recombine_heads(out) + out = self.out_proj(out) + + return out diff --git a/sam2/modeling/sam2_base.py b/sam2/modeling/sam2_base.py new file mode 100644 index 0000000..2b5251f --- /dev/null +++ b/sam2/modeling/sam2_base.py @@ -0,0 +1,829 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +import torch +import torch.distributed +import torch.nn.functional as F + +from torch.nn.init import trunc_normal_ + +from sam2.modeling.sam.mask_decoder import MaskDecoder +from sam2.modeling.sam.prompt_encoder import PromptEncoder +from sam2.modeling.sam.transformer import TwoWayTransformer +from sam2.modeling.sam2_utils import get_1d_sine_pe, MLP, select_closest_cond_frames + +# a large negative value as a placeholder score for missing objects +NO_OBJ_SCORE = -1024.0 + + +class SAM2Base(torch.nn.Module): + def __init__( + self, + image_encoder, + memory_attention, + memory_encoder, + num_maskmem=7, # default 1 input frame + 6 previous frames + image_size=512, + backbone_stride=16, # stride of the image backbone output + sigmoid_scale_for_mem_enc=1.0, # scale factor for mask sigmoid prob + sigmoid_bias_for_mem_enc=0.0, # bias factor for mask sigmoid prob + # During evaluation, whether to binarize the sigmoid mask logits on interacted frames with clicks + binarize_mask_from_pts_for_mem_enc=False, + use_mask_input_as_output_without_sam=False, # on frames with mask input, whether to directly output the input mask without using a SAM prompt encoder + mask decoder + # The maximum number of conditioning frames to participate in the memory attention (-1 means no limit; if there are more conditioning frames than this limit, + # we only cross-attend to the temporally closest `max_cond_frames_in_attn` conditioning frames in the encoder when tracking each frame). This gives the model + # a temporal locality when handling a large number of annotated frames (since closer frames should be more important) and also avoids GPU OOM. + max_cond_frames_in_attn=-1, + # on the first frame, whether to directly add the no-memory embedding to the image feature + # (instead of using the transformer encoder) + directly_add_no_mem_embed=False, + # whether to use high-resolution feature maps in the SAM mask decoder + use_high_res_features_in_sam=False, + # whether to output multiple (3) masks for the first click on initial conditioning frames + multimask_output_in_sam=False, + # the minimum and maximum number of clicks to use multimask_output_in_sam (only relevant when `multimask_output_in_sam=True`; + # default is 1 for both, meaning that only the first click gives multimask output; also note that a box counts as two points) + multimask_min_pt_num=1, + multimask_max_pt_num=1, + # whether to also use multimask output for tracking (not just for the first click on initial conditioning frames; only relevant when `multimask_output_in_sam=True`) + multimask_output_for_tracking=False, + # Whether to use multimask tokens for obj ptr; Only relevant when both + # use_obj_ptrs_in_encoder=True and multimask_output_for_tracking=True + use_multimask_token_for_obj_ptr: bool = False, + # whether to use sigmoid to restrict ious prediction to [0-1] + iou_prediction_use_sigmoid=False, + # The memory bank's temporal stride during evaluation (i.e. the `r` parameter in XMem and Cutie; XMem and Cutie use r=5). + # For r>1, the (self.num_maskmem - 1) non-conditioning memory frames consist of + # (self.num_maskmem - 2) nearest frames from every r-th frames, plus the last frame. + memory_temporal_stride_for_eval=1, + # if `add_all_frames_to_correct_as_cond` is True, we also append to the conditioning frame list any frame that receives a later correction click + # if `add_all_frames_to_correct_as_cond` is False, we conditioning frame list to only use those initial conditioning frames + add_all_frames_to_correct_as_cond=False, + # whether to apply non-overlapping constraints on the object masks in the memory encoder during evaluation (to avoid/alleviate superposing masks) + non_overlap_masks_for_mem_enc=False, + # whether to cross-attend to object pointers from other frames (based on SAM output tokens) in the encoder + use_obj_ptrs_in_encoder=False, + # the maximum number of object pointers from other frames in encoder cross attention (only relevant when `use_obj_ptrs_in_encoder=True`) + max_obj_ptrs_in_encoder=16, + # whether to add temporal positional encoding to the object pointers in the encoder (only relevant when `use_obj_ptrs_in_encoder=True`) + add_tpos_enc_to_obj_ptrs=True, + # whether to add an extra linear projection layer for the temporal positional encoding in the object pointers to avoid potential interference + # with spatial positional encoding (only relevant when both `use_obj_ptrs_in_encoder=True` and `add_tpos_enc_to_obj_ptrs=True`) + proj_tpos_enc_in_obj_ptrs=False, + # whether to only attend to object pointers in the past (before the current frame) in the encoder during evaluation + # (only relevant when `use_obj_ptrs_in_encoder=True`; this might avoid pointer information too far in the future to distract the initial tracking) + only_obj_ptrs_in_the_past_for_eval=False, + # Whether to predict if there is an object in the frame + pred_obj_scores: bool = False, + # Whether to use an MLP to predict object scores + pred_obj_scores_mlp: bool = False, + # Only relevant if pred_obj_scores=True and use_obj_ptrs_in_encoder=True; + # Whether to have a fixed no obj pointer when there is no object present + # or to use it as an additive embedding with obj_ptr produced by decoder + fixed_no_obj_ptr: bool = False, + # Soft no object, i.e. mix in no_obj_ptr softly, + # hope to make recovery easier if there is a mistake and mitigate accumulation of errors + soft_no_obj_ptr: bool = False, + use_mlp_for_obj_ptr_proj: bool = False, + # extra arguments used to construct the SAM mask decoder; if not None, it should be a dict of kwargs to be passed into `MaskDecoder` class. + sam_mask_decoder_extra_args=None, + compile_image_encoder: bool = False, + ): + super().__init__() + + # Part 1: the image backbone + self.image_encoder = image_encoder + # Use level 0, 1, 2 for high-res setting, or just level 2 for the default setting + self.use_high_res_features_in_sam = use_high_res_features_in_sam + self.num_feature_levels = 3 if use_high_res_features_in_sam else 1 + self.use_obj_ptrs_in_encoder = use_obj_ptrs_in_encoder + self.max_obj_ptrs_in_encoder = max_obj_ptrs_in_encoder + if use_obj_ptrs_in_encoder: + # A conv layer to downsample the mask prompt to stride 4 (the same stride as + # low-res SAM mask logits) and to change its scales from 0~1 to SAM logit scale, + # so that it can be fed into the SAM mask decoder to generate a pointer. + self.mask_downsample = torch.nn.Conv2d(1, 1, kernel_size=4, stride=4) + self.add_tpos_enc_to_obj_ptrs = add_tpos_enc_to_obj_ptrs + if proj_tpos_enc_in_obj_ptrs: + assert add_tpos_enc_to_obj_ptrs # these options need to be used together + self.proj_tpos_enc_in_obj_ptrs = proj_tpos_enc_in_obj_ptrs + self.only_obj_ptrs_in_the_past_for_eval = only_obj_ptrs_in_the_past_for_eval + + # Part 2: memory attention to condition current frame's visual features + # with memories (and obj ptrs) from past frames + self.memory_attention = memory_attention + self.hidden_dim = memory_attention.d_model + + # Part 3: memory encoder for the previous frame's outputs + self.memory_encoder = memory_encoder + self.mem_dim = self.hidden_dim + if hasattr(self.memory_encoder, "out_proj") and hasattr( + self.memory_encoder.out_proj, "weight" + ): + # if there is compression of memories along channel dim + self.mem_dim = self.memory_encoder.out_proj.weight.shape[0] + self.num_maskmem = num_maskmem # Number of memories accessible + # Temporal encoding of the memories + self.maskmem_tpos_enc = torch.nn.Parameter( + torch.zeros(num_maskmem, 1, 1, self.mem_dim) + ) + trunc_normal_(self.maskmem_tpos_enc, std=0.02) + # a single token to indicate no memory embedding from previous frames + self.no_mem_embed = torch.nn.Parameter(torch.zeros(1, 1, self.hidden_dim)) + self.no_mem_pos_enc = torch.nn.Parameter(torch.zeros(1, 1, self.hidden_dim)) + trunc_normal_(self.no_mem_embed, std=0.02) + trunc_normal_(self.no_mem_pos_enc, std=0.02) + self.directly_add_no_mem_embed = directly_add_no_mem_embed + # Apply sigmoid to the output raw mask logits (to turn them from + # range (-inf, +inf) to range (0, 1)) before feeding them into the memory encoder + self.sigmoid_scale_for_mem_enc = sigmoid_scale_for_mem_enc + self.sigmoid_bias_for_mem_enc = sigmoid_bias_for_mem_enc + self.binarize_mask_from_pts_for_mem_enc = binarize_mask_from_pts_for_mem_enc + self.non_overlap_masks_for_mem_enc = non_overlap_masks_for_mem_enc + self.memory_temporal_stride_for_eval = memory_temporal_stride_for_eval + # On frames with mask input, whether to directly output the input mask without + # using a SAM prompt encoder + mask decoder + self.use_mask_input_as_output_without_sam = use_mask_input_as_output_without_sam + self.multimask_output_in_sam = multimask_output_in_sam + self.multimask_min_pt_num = multimask_min_pt_num + self.multimask_max_pt_num = multimask_max_pt_num + self.multimask_output_for_tracking = multimask_output_for_tracking + self.use_multimask_token_for_obj_ptr = use_multimask_token_for_obj_ptr + self.iou_prediction_use_sigmoid = iou_prediction_use_sigmoid + + # Part 4: SAM-style prompt encoder (for both mask and point inputs) + # and SAM-style mask decoder for the final mask output + self.image_size = image_size + self.backbone_stride = backbone_stride + self.sam_mask_decoder_extra_args = sam_mask_decoder_extra_args + self.pred_obj_scores = pred_obj_scores + self.pred_obj_scores_mlp = pred_obj_scores_mlp + self.fixed_no_obj_ptr = fixed_no_obj_ptr + self.soft_no_obj_ptr = soft_no_obj_ptr + if self.fixed_no_obj_ptr: + assert self.pred_obj_scores + assert self.use_obj_ptrs_in_encoder + if self.pred_obj_scores and self.use_obj_ptrs_in_encoder: + self.no_obj_ptr = torch.nn.Parameter(torch.zeros(1, self.hidden_dim)) + trunc_normal_(self.no_obj_ptr, std=0.02) + self.use_mlp_for_obj_ptr_proj = use_mlp_for_obj_ptr_proj + + self._build_sam_heads() + self.add_all_frames_to_correct_as_cond = add_all_frames_to_correct_as_cond + self.max_cond_frames_in_attn = max_cond_frames_in_attn + + # Model compilation + if compile_image_encoder: + # Compile the forward function (not the full module) to allow loading checkpoints. + print( + "Image encoder compilation is enabled. First forward pass will be slow." + ) + self.image_encoder.forward = torch.compile( + self.image_encoder.forward, + mode="max-autotune", + fullgraph=True, + dynamic=False, + ) + + @property + def device(self): + return next(self.parameters()).device + + def forward(self, *args, **kwargs): + raise NotImplementedError( + "Please use the corresponding methods in SAM2VideoPredictor for inference." + "See notebooks/video_predictor_example.ipynb for an example." + ) + + def _build_sam_heads(self): + """Build SAM-style prompt encoder and mask decoder.""" + self.sam_prompt_embed_dim = self.hidden_dim + self.sam_image_embedding_size = self.image_size // self.backbone_stride + + # build PromptEncoder and MaskDecoder from SAM + # (their hyperparameters like `mask_in_chans=16` are from SAM code) + self.sam_prompt_encoder = PromptEncoder( + embed_dim=self.sam_prompt_embed_dim, + image_embedding_size=( + self.sam_image_embedding_size, + self.sam_image_embedding_size, + ), + input_image_size=(self.image_size, self.image_size), + mask_in_chans=16, + ) + self.sam_mask_decoder = MaskDecoder( + num_multimask_outputs=3, + transformer=TwoWayTransformer( + depth=2, + embedding_dim=self.sam_prompt_embed_dim, + mlp_dim=2048, + num_heads=8, + ), + transformer_dim=self.sam_prompt_embed_dim, + iou_head_depth=3, + iou_head_hidden_dim=256, + use_high_res_features=self.use_high_res_features_in_sam, + iou_prediction_use_sigmoid=self.iou_prediction_use_sigmoid, + pred_obj_scores=self.pred_obj_scores, + pred_obj_scores_mlp=self.pred_obj_scores_mlp, + use_multimask_token_for_obj_ptr=self.use_multimask_token_for_obj_ptr, + **(self.sam_mask_decoder_extra_args or {}), + ) + if self.use_obj_ptrs_in_encoder: + # a linear projection on SAM output tokens to turn them into object pointers + self.obj_ptr_proj = torch.nn.Linear(self.hidden_dim, self.hidden_dim) + if self.use_mlp_for_obj_ptr_proj: + self.obj_ptr_proj = MLP( + self.hidden_dim, self.hidden_dim, self.hidden_dim, 3 + ) + else: + self.obj_ptr_proj = torch.nn.Identity() + if self.proj_tpos_enc_in_obj_ptrs: + # a linear projection on temporal positional encoding in object pointers to + # avoid potential interference with spatial positional encoding + self.obj_ptr_tpos_proj = torch.nn.Linear(self.hidden_dim, self.mem_dim) + else: + self.obj_ptr_tpos_proj = torch.nn.Identity() + + def _forward_sam_heads( + self, + backbone_features, + point_inputs=None, + mask_inputs=None, + high_res_features=None, + multimask_output=False, + ): + """ + Forward SAM prompt encoders and mask heads. + + Inputs: + - backbone_features: image features of [B, C, H, W] shape + - point_inputs: a dictionary with "point_coords" and "point_labels", where + 1) "point_coords" has [B, P, 2] shape and float32 dtype and contains the + absolute pixel-unit coordinate in (x, y) format of the P input points + 2) "point_labels" has shape [B, P] and int32 dtype, where 1 means + positive clicks, 0 means negative clicks, and -1 means padding + - mask_inputs: a mask of [B, 1, H*16, W*16] shape, float or bool, with the + same spatial size as the image. + - high_res_features: either 1) None or 2) or a list of length 2 containing + two feature maps of [B, C, 4*H, 4*W] and [B, C, 2*H, 2*W] shapes respectively, + which will be used as high-resolution feature maps for SAM decoder. + - multimask_output: if it's True, we output 3 candidate masks and their 3 + corresponding IoU estimates, and if it's False, we output only 1 mask and + its corresponding IoU estimate. + + Outputs: + - low_res_multimasks: [B, M, H*4, W*4] shape (where M = 3 if + `multimask_output=True` and M = 1 if `multimask_output=False`), the SAM + output mask logits (before sigmoid) for the low-resolution masks, with 4x + the resolution (1/4 stride) of the input backbone_features. + - high_res_multimasks: [B, M, H*16, W*16] shape (where M = 3 + if `multimask_output=True` and M = 1 if `multimask_output=False`), + upsampled from the low-resolution masks, with shape size as the image + (stride is 1 pixel). + - ious, [B, M] shape, where (where M = 3 if `multimask_output=True` and M = 1 + if `multimask_output=False`), the estimated IoU of each output mask. + - low_res_masks: [B, 1, H*4, W*4] shape, the best mask in `low_res_multimasks`. + If `multimask_output=True`, it's the mask with the highest IoU estimate. + If `multimask_output=False`, it's the same as `low_res_multimasks`. + - high_res_masks: [B, 1, H*16, W*16] shape, the best mask in `high_res_multimasks`. + If `multimask_output=True`, it's the mask with the highest IoU estimate. + If `multimask_output=False`, it's the same as `high_res_multimasks`. + - obj_ptr: [B, C] shape, the object pointer vector for the output mask, extracted + based on the output token from the SAM mask decoder. + """ + B = backbone_features.size(0) + device = backbone_features.device + assert backbone_features.size(1) == self.sam_prompt_embed_dim + assert backbone_features.size(2) == self.sam_image_embedding_size + assert backbone_features.size(3) == self.sam_image_embedding_size + + # a) Handle point prompts + if point_inputs is not None: + sam_point_coords = point_inputs["point_coords"] + sam_point_labels = point_inputs["point_labels"] + assert sam_point_coords.size(0) == B and sam_point_labels.size(0) == B + else: + # If no points are provide, pad with an empty point (with label -1) + sam_point_coords = torch.zeros(B, 1, 2, device=device) + sam_point_labels = -torch.ones(B, 1, dtype=torch.int32, device=device) + + # b) Handle mask prompts + if mask_inputs is not None: + # If mask_inputs is provided, downsize it into low-res mask input if needed + # and feed it as a dense mask prompt into the SAM mask encoder + assert len(mask_inputs.shape) == 4 and mask_inputs.shape[:2] == (B, 1) + if mask_inputs.shape[-2:] != self.sam_prompt_encoder.mask_input_size: + sam_mask_prompt = F.interpolate( + mask_inputs.float(), + size=self.sam_prompt_encoder.mask_input_size, + align_corners=False, + mode="bilinear", + antialias=True, # use antialias for downsampling + ) + else: + sam_mask_prompt = mask_inputs + else: + # Otherwise, simply feed None (and SAM's prompt encoder will add + # a learned `no_mask_embed` to indicate no mask input in this case). + sam_mask_prompt = None + + sparse_embeddings, dense_embeddings = self.sam_prompt_encoder( + points=(sam_point_coords, sam_point_labels), + boxes=None, + masks=sam_mask_prompt, + ) + ( + low_res_multimasks, + ious, + sam_output_tokens, + object_score_logits, + ) = self.sam_mask_decoder( + image_embeddings=backbone_features, + image_pe=self.sam_prompt_encoder.get_dense_pe(), + sparse_prompt_embeddings=sparse_embeddings, + dense_prompt_embeddings=dense_embeddings, + multimask_output=multimask_output, + repeat_image=False, # the image is already batched + high_res_features=high_res_features, + ) + if self.pred_obj_scores: + is_obj_appearing = object_score_logits > 0 + + # Mask used for spatial memories is always a *hard* choice between obj and no obj, + # consistent with the actual mask prediction + low_res_multimasks = torch.where( + is_obj_appearing[:, None, None], + low_res_multimasks, + NO_OBJ_SCORE, + ) + + # convert masks from possibly bfloat16 (or float16) to float32 + # (older PyTorch versions before 2.1 don't support `interpolate` on bf16) + low_res_multimasks = low_res_multimasks.float() + high_res_multimasks = F.interpolate( + low_res_multimasks, + size=(self.image_size, self.image_size), + mode="bilinear", + align_corners=False, + ) + + sam_output_token = sam_output_tokens[:, 0] + if multimask_output: + # take the best mask prediction (with the highest IoU estimation) + best_iou_inds = torch.argmax(ious, dim=-1) + batch_inds = torch.arange(B, device=device) + low_res_masks = low_res_multimasks[batch_inds, best_iou_inds].unsqueeze(1) + high_res_masks = high_res_multimasks[batch_inds, best_iou_inds].unsqueeze(1) + if sam_output_tokens.size(1) > 1: + sam_output_token = sam_output_tokens[batch_inds, best_iou_inds] + else: + low_res_masks, high_res_masks = low_res_multimasks, high_res_multimasks + + # Extract object pointer from the SAM output token (with occlusion handling) + obj_ptr = self.obj_ptr_proj(sam_output_token) + if self.pred_obj_scores: + # Allow *soft* no obj ptr, unlike for masks + if self.soft_no_obj_ptr: + # Only hard possible with gt + assert not self.teacher_force_obj_scores_for_mem + lambda_is_obj_appearing = object_score_logits.sigmoid() + else: + lambda_is_obj_appearing = is_obj_appearing.float() + + if self.fixed_no_obj_ptr: + obj_ptr = lambda_is_obj_appearing * obj_ptr + obj_ptr = obj_ptr + (1 - lambda_is_obj_appearing) * self.no_obj_ptr + + return ( + low_res_multimasks, + high_res_multimasks, + ious, + low_res_masks, + high_res_masks, + obj_ptr, + object_score_logits, + ) + + def _use_mask_as_output(self, backbone_features, high_res_features, mask_inputs): + """ + Directly turn binary `mask_inputs` into a output mask logits without using SAM. + (same input and output shapes as in _forward_sam_heads above). + """ + # Use -10/+10 as logits for neg/pos pixels (very close to 0/1 in prob after sigmoid). + out_scale, out_bias = 20.0, -10.0 # sigmoid(-10.0)=4.5398e-05 + mask_inputs_float = mask_inputs.float() + high_res_masks = mask_inputs_float * out_scale + out_bias + low_res_masks = F.interpolate( + high_res_masks, + size=(high_res_masks.size(-2) // 4, high_res_masks.size(-1) // 4), + align_corners=False, + mode="bilinear", + antialias=True, # use antialias for downsampling + ) + # a dummy IoU prediction of all 1's under mask input + ious = mask_inputs.new_ones(mask_inputs.size(0), 1).float() + if not self.use_obj_ptrs_in_encoder: + # all zeros as a dummy object pointer (of shape [B, C]) + obj_ptr = torch.zeros( + mask_inputs.size(0), self.hidden_dim, device=mask_inputs.device + ) + else: + # produce an object pointer using the SAM decoder from the mask input + _, _, _, _, _, obj_ptr, _ = self._forward_sam_heads( + backbone_features=backbone_features, + mask_inputs=self.mask_downsample(mask_inputs_float), + high_res_features=high_res_features, + ) + # In this method, we are treating mask_input as output, e.g. using it directly to create spatial mem; + # Below, we follow the same design axiom to use mask_input to decide if obj appears or not instead of relying + # on the object_scores from the SAM decoder. + is_obj_appearing = torch.any(mask_inputs.flatten(1).float() > 0.0, dim=1) + is_obj_appearing = is_obj_appearing[..., None] + lambda_is_obj_appearing = is_obj_appearing.float() + object_score_logits = out_scale * lambda_is_obj_appearing + out_bias + if self.pred_obj_scores: + if self.fixed_no_obj_ptr: + obj_ptr = lambda_is_obj_appearing * obj_ptr + obj_ptr = obj_ptr + (1 - lambda_is_obj_appearing) * self.no_obj_ptr + + return ( + low_res_masks, + high_res_masks, + ious, + low_res_masks, + high_res_masks, + obj_ptr, + object_score_logits, + ) + + def forward_image(self, img_batch: torch.Tensor): + """Get the image feature on the input batch.""" + backbone_out = self.image_encoder(img_batch) + if self.use_high_res_features_in_sam: + # precompute projected level 0 and level 1 features in SAM decoder + # to avoid running it again on every SAM click + backbone_out["backbone_fpn"][0] = self.sam_mask_decoder.conv_s0( + backbone_out["backbone_fpn"][0] + ) + backbone_out["backbone_fpn"][1] = self.sam_mask_decoder.conv_s1( + backbone_out["backbone_fpn"][1] + ) + return backbone_out + + def _prepare_backbone_features(self, backbone_out): + """Prepare and flatten visual features.""" + backbone_out = backbone_out.copy() + assert len(backbone_out["backbone_fpn"]) == len(backbone_out["vision_pos_enc"]) + assert len(backbone_out["backbone_fpn"]) >= self.num_feature_levels + + feature_maps = backbone_out["backbone_fpn"][-self.num_feature_levels :] + vision_pos_embeds = backbone_out["vision_pos_enc"][-self.num_feature_levels :] + + feat_sizes = [(x.shape[-2], x.shape[-1]) for x in vision_pos_embeds] + # flatten NxCxHxW to HWxNxC + vision_feats = [x.flatten(2).permute(2, 0, 1) for x in feature_maps] + vision_pos_embeds = [x.flatten(2).permute(2, 0, 1) for x in vision_pos_embeds] + + return backbone_out, vision_feats, vision_pos_embeds, feat_sizes + + def _prepare_memory_conditioned_features( + self, + frame_idx, + is_init_cond_frame, + current_vision_feats, + current_vision_pos_embeds, + feat_sizes, + output_dict, + num_frames, + track_in_reverse=False, # tracking in reverse time order (for demo usage) + ): + """Fuse the current frame's visual feature map with previous memory.""" + B = current_vision_feats[-1].size(1) # batch size on this frame + C = self.hidden_dim + H, W = feat_sizes[-1] # top-level (lowest-resolution) feature size + device = current_vision_feats[-1].device + # The case of `self.num_maskmem == 0` below is primarily used for reproducing SAM on images. + # In this case, we skip the fusion with any memory. + if self.num_maskmem == 0: # Disable memory and skip fusion + pix_feat = current_vision_feats[-1].permute(1, 2, 0).view(B, C, H, W) + return pix_feat + + num_obj_ptr_tokens = 0 + # Step 1: condition the visual features of the current frame on previous memories + if not is_init_cond_frame: + # Retrieve the memories encoded with the maskmem backbone + to_cat_memory, to_cat_memory_pos_embed = [], [] + # Add conditioning frames's output first (all cond frames have t_pos=0 for + # when getting temporal positional embedding below) + assert len(output_dict["cond_frame_outputs"]) > 0 + # Select a maximum number of temporally closest cond frames for cross attention + cond_outputs = output_dict["cond_frame_outputs"] + selected_cond_outputs, unselected_cond_outputs = select_closest_cond_frames( + frame_idx, cond_outputs, self.max_cond_frames_in_attn + ) + t_pos_and_prevs = [(0, out) for out in selected_cond_outputs.values()] + # Add last (self.num_maskmem - 1) frames before current frame for non-conditioning memory + # the earliest one has t_pos=1 and the latest one has t_pos=self.num_maskmem-1 + # We also allow taking the memory frame non-consecutively (with r>1), in which case + # we take (self.num_maskmem - 2) frames among every r-th frames plus the last frame. + r = self.memory_temporal_stride_for_eval + for t_pos in range(1, self.num_maskmem): + t_rel = self.num_maskmem - t_pos # how many frames before current frame + if t_rel == 1: + # for t_rel == 1, we take the last frame (regardless of r) + if not track_in_reverse: + # the frame immediately before this frame (i.e. frame_idx - 1) + prev_frame_idx = frame_idx - t_rel + else: + # the frame immediately after this frame (i.e. frame_idx + 1) + prev_frame_idx = frame_idx + t_rel + else: + # for t_rel >= 2, we take the memory frame from every r-th frames + if not track_in_reverse: + # first find the nearest frame among every r-th frames before this frame + # for r=1, this would be (frame_idx - 2) + prev_frame_idx = ((frame_idx - 2) // r) * r + # then seek further among every r-th frames + prev_frame_idx = prev_frame_idx - (t_rel - 2) * r + else: + # first find the nearest frame among every r-th frames after this frame + # for r=1, this would be (frame_idx + 2) + prev_frame_idx = -(-(frame_idx + 2) // r) * r + # then seek further among every r-th frames + prev_frame_idx = prev_frame_idx + (t_rel - 2) * r + out = output_dict["non_cond_frame_outputs"].get(prev_frame_idx, None) + if out is None: + # If an unselected conditioning frame is among the last (self.num_maskmem - 1) + # frames, we still attend to it as if it's a non-conditioning frame. + out = unselected_cond_outputs.get(prev_frame_idx, None) + t_pos_and_prevs.append((t_pos, out)) + + for t_pos, prev in t_pos_and_prevs: + if prev is None: + continue # skip padding frames + # "maskmem_features" might have been offloaded to CPU in demo use cases, + # so we load it back to GPU (it's a no-op if it's already on GPU). + feats = prev["maskmem_features"].cuda(non_blocking=True) + to_cat_memory.append(feats.flatten(2).permute(2, 0, 1)) + # Spatial positional encoding (it might have been offloaded to CPU in eval) + maskmem_enc = prev["maskmem_pos_enc"][-1].cuda() + maskmem_enc = maskmem_enc.flatten(2).permute(2, 0, 1) + # Temporal positional encoding + maskmem_enc = ( + maskmem_enc + self.maskmem_tpos_enc[self.num_maskmem - t_pos - 1] + ) + to_cat_memory_pos_embed.append(maskmem_enc) + + # Construct the list of past object pointers + if self.use_obj_ptrs_in_encoder: + max_obj_ptrs_in_encoder = min(num_frames, self.max_obj_ptrs_in_encoder) + # First add those object pointers from selected conditioning frames + # (optionally, only include object pointers in the past during evaluation) + if not self.training and self.only_obj_ptrs_in_the_past_for_eval: + ptr_cond_outputs = { + t: out + for t, out in selected_cond_outputs.items() + if (t >= frame_idx if track_in_reverse else t <= frame_idx) + } + else: + ptr_cond_outputs = selected_cond_outputs + pos_and_ptrs = [ + # Temporal pos encoding contains how far away each pointer is from current frame + (abs(frame_idx - t), out["obj_ptr"]) + for t, out in ptr_cond_outputs.items() + ] + # Add up to (max_obj_ptrs_in_encoder - 1) non-conditioning frames before current frame + for t_diff in range(1, max_obj_ptrs_in_encoder): + t = frame_idx + t_diff if track_in_reverse else frame_idx - t_diff + if t < 0 or (num_frames is not None and t >= num_frames): + break + out = output_dict["non_cond_frame_outputs"].get( + t, unselected_cond_outputs.get(t, None) + ) + if out is not None: + pos_and_ptrs.append((t_diff, out["obj_ptr"])) + # If we have at least one object pointer, add them to the across attention + if len(pos_and_ptrs) > 0: + pos_list, ptrs_list = zip(*pos_and_ptrs) + # stack object pointers along dim=0 into [ptr_seq_len, B, C] shape + obj_ptrs = torch.stack(ptrs_list, dim=0) + # a temporal positional embedding based on how far each object pointer is from + # the current frame (sine embedding normalized by the max pointer num). + if self.add_tpos_enc_to_obj_ptrs: + t_diff_max = max_obj_ptrs_in_encoder - 1 + tpos_dim = C if self.proj_tpos_enc_in_obj_ptrs else self.mem_dim + obj_pos = torch.tensor(pos_list, device=device) + obj_pos = get_1d_sine_pe(obj_pos / t_diff_max, dim=tpos_dim) + obj_pos = self.obj_ptr_tpos_proj(obj_pos) + obj_pos = obj_pos.unsqueeze(1).expand(-1, B, self.mem_dim) + else: + obj_pos = obj_ptrs.new_zeros(len(pos_list), B, self.mem_dim) + if self.mem_dim < C: + # split a pointer into (C // self.mem_dim) tokens for self.mem_dim < C + obj_ptrs = obj_ptrs.reshape( + -1, B, C // self.mem_dim, self.mem_dim + ) + obj_ptrs = obj_ptrs.permute(0, 2, 1, 3).flatten(0, 1) + obj_pos = obj_pos.repeat_interleave(C // self.mem_dim, dim=0) + to_cat_memory.append(obj_ptrs) + to_cat_memory_pos_embed.append(obj_pos) + num_obj_ptr_tokens = obj_ptrs.shape[0] + else: + num_obj_ptr_tokens = 0 + else: + # for initial conditioning frames, encode them without using any previous memory + if self.directly_add_no_mem_embed: + # directly add no-mem embedding (instead of using the transformer encoder) + pix_feat_with_mem = current_vision_feats[-1] + self.no_mem_embed + pix_feat_with_mem = pix_feat_with_mem.permute(1, 2, 0).view(B, C, H, W) + return pix_feat_with_mem + + # Use a dummy token on the first frame (to avoid emtpy memory input to tranformer encoder) + to_cat_memory = [self.no_mem_embed.expand(1, B, self.mem_dim)] + to_cat_memory_pos_embed = [self.no_mem_pos_enc.expand(1, B, self.mem_dim)] + + # Step 2: Concatenate the memories and forward through the transformer encoder + memory = torch.cat(to_cat_memory, dim=0) + memory_pos_embed = torch.cat(to_cat_memory_pos_embed, dim=0) + + pix_feat_with_mem = self.memory_attention( + curr=current_vision_feats, + curr_pos=current_vision_pos_embeds, + memory=memory, + memory_pos=memory_pos_embed, + num_obj_ptr_tokens=num_obj_ptr_tokens, + ) + # reshape the output (HW)BC => BCHW + pix_feat_with_mem = pix_feat_with_mem.permute(1, 2, 0).view(B, C, H, W) + return pix_feat_with_mem + + def _encode_new_memory( + self, + current_vision_feats, + feat_sizes, + pred_masks_high_res, + is_mask_from_pts, + ): + """Encode the current image and its prediction into a memory feature.""" + B = current_vision_feats[-1].size(1) # batch size on this frame + C = self.hidden_dim + H, W = feat_sizes[-1] # top-level (lowest-resolution) feature size + # top-level feature, (HW)BC => BCHW + pix_feat = current_vision_feats[-1].permute(1, 2, 0).view(B, C, H, W) + if self.non_overlap_masks_for_mem_enc and not self.training: + # optionally, apply non-overlapping constraints to the masks (it's applied + # in the batch dimension and should only be used during eval, where all + # the objects come from the same video under batch size 1). + pred_masks_high_res = self._apply_non_overlapping_constraints( + pred_masks_high_res + ) + # scale the raw mask logits with a temperature before applying sigmoid + binarize = self.binarize_mask_from_pts_for_mem_enc and is_mask_from_pts + if binarize and not self.training: + mask_for_mem = (pred_masks_high_res > 0).float() + else: + # apply sigmoid on the raw mask logits to turn them into range (0, 1) + mask_for_mem = torch.sigmoid(pred_masks_high_res) + # apply scale and bias terms to the sigmoid probabilities + if self.sigmoid_scale_for_mem_enc != 1.0: + mask_for_mem = mask_for_mem * self.sigmoid_scale_for_mem_enc + if self.sigmoid_bias_for_mem_enc != 0.0: + mask_for_mem = mask_for_mem + self.sigmoid_bias_for_mem_enc + maskmem_out = self.memory_encoder( + pix_feat, mask_for_mem, skip_mask_sigmoid=True # sigmoid already applied + ) + maskmem_features = maskmem_out["vision_features"] + maskmem_pos_enc = maskmem_out["vision_pos_enc"] + + return maskmem_features, maskmem_pos_enc + + def track_step( + self, + frame_idx, + is_init_cond_frame, + current_vision_feats, + current_vision_pos_embeds, + feat_sizes, + point_inputs, + mask_inputs, + output_dict, + num_frames, + track_in_reverse=False, # tracking in reverse time order (for demo usage) + # Whether to run the memory encoder on the predicted masks. Sometimes we might want + # to skip the memory encoder with `run_mem_encoder=False`. For example, + # in demo we might call `track_step` multiple times for each user click, + # and only encode the memory when the user finalizes their clicks. And in ablation + # settings like SAM training on static images, we don't need the memory encoder. + run_mem_encoder=True, + # The previously predicted SAM mask logits (which can be fed together with new clicks in demo). + prev_sam_mask_logits=None, + ): + current_out = {"point_inputs": point_inputs, "mask_inputs": mask_inputs} + # High-resolution feature maps for the SAM head, reshape (HW)BC => BCHW + if len(current_vision_feats) > 1: + high_res_features = [ + x.permute(1, 2, 0).view(x.size(1), x.size(2), *s) + for x, s in zip(current_vision_feats[:-1], feat_sizes[:-1]) + ] + else: + high_res_features = None + if mask_inputs is not None and self.use_mask_input_as_output_without_sam: + # When use_mask_input_as_output_without_sam=True, we directly output the mask input + # (see it as a GT mask) without using a SAM prompt encoder + mask decoder. + pix_feat = current_vision_feats[-1].permute(1, 2, 0) + pix_feat = pix_feat.view(-1, self.hidden_dim, *feat_sizes[-1]) + sam_outputs = self._use_mask_as_output( + pix_feat, high_res_features, mask_inputs + ) + else: + # fused the visual feature with previous memory features in the memory bank + pix_feat_with_mem = self._prepare_memory_conditioned_features( + frame_idx=frame_idx, + is_init_cond_frame=is_init_cond_frame, + current_vision_feats=current_vision_feats[-1:], + current_vision_pos_embeds=current_vision_pos_embeds[-1:], + feat_sizes=feat_sizes[-1:], + output_dict=output_dict, + num_frames=num_frames, + track_in_reverse=track_in_reverse, + ) + # apply SAM-style segmentation head + # here we might feed previously predicted low-res SAM mask logits into the SAM mask decoder, + # e.g. in demo where such logits come from earlier interaction instead of correction sampling + # (in this case, any `mask_inputs` shouldn't reach here as they are sent to _use_mask_as_output instead) + if prev_sam_mask_logits is not None: + assert point_inputs is not None and mask_inputs is None + mask_inputs = prev_sam_mask_logits + multimask_output = self._use_multimask(is_init_cond_frame, point_inputs) + sam_outputs = self._forward_sam_heads( + backbone_features=pix_feat_with_mem, + point_inputs=point_inputs, + mask_inputs=mask_inputs, + high_res_features=high_res_features, + multimask_output=multimask_output, + ) + ( + _, + _, + _, + low_res_masks, + high_res_masks, + obj_ptr, + _, + ) = sam_outputs + + current_out["pred_masks"] = low_res_masks + current_out["pred_masks_high_res"] = high_res_masks + current_out["obj_ptr"] = obj_ptr + + # Finally run the memory encoder on the predicted mask to encode + # it into a new memory feature (that can be used in future frames) + if run_mem_encoder and self.num_maskmem > 0: + high_res_masks_for_mem_enc = high_res_masks + maskmem_features, maskmem_pos_enc = self._encode_new_memory( + current_vision_feats=current_vision_feats, + feat_sizes=feat_sizes, + pred_masks_high_res=high_res_masks_for_mem_enc, + is_mask_from_pts=(point_inputs is not None), + ) + current_out["maskmem_features"] = maskmem_features + current_out["maskmem_pos_enc"] = maskmem_pos_enc + else: + current_out["maskmem_features"] = None + current_out["maskmem_pos_enc"] = None + + return current_out + + def _use_multimask(self, is_init_cond_frame, point_inputs): + """Whether to use multimask output in the SAM head.""" + num_pts = 0 if point_inputs is None else point_inputs["point_labels"].size(1) + multimask_output = ( + self.multimask_output_in_sam + and (is_init_cond_frame or self.multimask_output_for_tracking) + and (self.multimask_min_pt_num <= num_pts <= self.multimask_max_pt_num) + ) + return multimask_output + + def _apply_non_overlapping_constraints(self, pred_masks): + """ + Apply non-overlapping constraints to the object scores in pred_masks. Here we + keep only the highest scoring object at each spatial location in pred_masks. + """ + batch_size = pred_masks.size(0) + if batch_size == 1: + return pred_masks + + device = pred_masks.device + # "max_obj_inds": object index of the object with the highest score at each location + max_obj_inds = torch.argmax(pred_masks, dim=0, keepdim=True) + # "batch_obj_inds": object index of each object slice (along dim 0) in `pred_masks` + batch_obj_inds = torch.arange(batch_size, device=device)[:, None, None, None] + keep = max_obj_inds == batch_obj_inds + # suppress overlapping regions' scores below -10.0 so that the foreground regions + # don't overlap (here sigmoid(-10.0)=4.5398e-05) + pred_masks = torch.where(keep, pred_masks, torch.clamp(pred_masks, max=-10.0)) + return pred_masks diff --git a/sam2/modeling/sam2_utils.py b/sam2/modeling/sam2_utils.py new file mode 100644 index 0000000..6d97059 --- /dev/null +++ b/sam2/modeling/sam2_utils.py @@ -0,0 +1,149 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + + +import copy + +import torch +import torch.nn as nn +import torch.nn.functional as F + + +def select_closest_cond_frames(frame_idx, cond_frame_outputs, max_cond_frame_num): + """ + Select up to `max_cond_frame_num` conditioning frames from `cond_frame_outputs` + that are temporally closest to the current frame at `frame_idx`. Here, we take + - a) the closest conditioning frame before `frame_idx` (if any); + - b) the closest conditioning frame after `frame_idx` (if any); + - c) any other temporally closest conditioning frames until reaching a total + of `max_cond_frame_num` conditioning frames. + + Outputs: + - selected_outputs: selected items (keys & values) from `cond_frame_outputs`. + - unselected_outputs: items (keys & values) not selected in `cond_frame_outputs`. + """ + if max_cond_frame_num == -1 or len(cond_frame_outputs) <= max_cond_frame_num: + selected_outputs = cond_frame_outputs + unselected_outputs = {} + else: + assert max_cond_frame_num >= 2, "we should allow using 2+ conditioning frames" + selected_outputs = {} + + # the closest conditioning frame before `frame_idx` (if any) + idx_before = max((t for t in cond_frame_outputs if t < frame_idx), default=None) + if idx_before is not None: + selected_outputs[idx_before] = cond_frame_outputs[idx_before] + + # the closest conditioning frame after `frame_idx` (if any) + idx_after = min((t for t in cond_frame_outputs if t >= frame_idx), default=None) + if idx_after is not None: + selected_outputs[idx_after] = cond_frame_outputs[idx_after] + + # add other temporally closest conditioning frames until reaching a total + # of `max_cond_frame_num` conditioning frames. + num_remain = max_cond_frame_num - len(selected_outputs) + inds_remain = sorted( + (t for t in cond_frame_outputs if t not in selected_outputs), + key=lambda x: abs(x - frame_idx), + )[:num_remain] + selected_outputs.update((t, cond_frame_outputs[t]) for t in inds_remain) + unselected_outputs = { + t: v for t, v in cond_frame_outputs.items() if t not in selected_outputs + } + + return selected_outputs, unselected_outputs + + +def get_1d_sine_pe(pos_inds, dim, temperature=10000): + """ + Get 1D sine positional embedding as in the original Transformer paper. + """ + pe_dim = dim // 2 + dim_t = torch.arange(pe_dim, dtype=torch.float32, device=pos_inds.device) + dim_t = temperature ** (2 * (dim_t // 2) / pe_dim) + + pos_embed = pos_inds.unsqueeze(-1) / dim_t + pos_embed = torch.cat([pos_embed.sin(), pos_embed.cos()], dim=-1) + return pos_embed + + +def get_activation_fn(activation): + """Return an activation function given a string""" + if activation == "relu": + return F.relu + if activation == "gelu": + return F.gelu + if activation == "glu": + return F.glu + raise RuntimeError(f"activation should be relu/gelu, not {activation}.") + + +def get_clones(module, N): + return nn.ModuleList([copy.deepcopy(module) for i in range(N)]) + + +class DropPath(nn.Module): + # adapted from https://github.com/huggingface/pytorch-image-models/blob/main/timm/layers/drop.py + def __init__(self, drop_prob=0.0, scale_by_keep=True): + super(DropPath, self).__init__() + self.drop_prob = drop_prob + self.scale_by_keep = scale_by_keep + + def forward(self, x): + if self.drop_prob == 0.0 or not self.training: + return x + keep_prob = 1 - self.drop_prob + shape = (x.shape[0],) + (1,) * (x.ndim - 1) + random_tensor = x.new_empty(shape).bernoulli_(keep_prob) + if keep_prob > 0.0 and self.scale_by_keep: + random_tensor.div_(keep_prob) + return x * random_tensor + + +# Lightly adapted from +# https://github.com/facebookresearch/MaskFormer/blob/main/mask_former/modeling/transformer/transformer_predictor.py # noqa +class MLP(nn.Module): + def __init__( + self, + input_dim: int, + hidden_dim: int, + output_dim: int, + num_layers: int, + activation: nn.Module = nn.ReLU, + sigmoid_output: bool = False, + ) -> None: + super().__init__() + self.num_layers = num_layers + h = [hidden_dim] * (num_layers - 1) + self.layers = nn.ModuleList( + nn.Linear(n, k) for n, k in zip([input_dim] + h, h + [output_dim]) + ) + self.sigmoid_output = sigmoid_output + self.act = activation() + + def forward(self, x): + for i, layer in enumerate(self.layers): + x = self.act(layer(x)) if i < self.num_layers - 1 else layer(x) + if self.sigmoid_output: + x = F.sigmoid(x) + return x + + +# From https://github.com/facebookresearch/detectron2/blob/main/detectron2/layers/batch_norm.py # noqa +# Itself from https://github.com/facebookresearch/ConvNeXt/blob/d1fa8f6fef0a165b27399986cc2bdacc92777e40/models/convnext.py#L119 # noqa +class LayerNorm2d(nn.Module): + def __init__(self, num_channels: int, eps: float = 1e-6) -> None: + super().__init__() + self.weight = nn.Parameter(torch.ones(num_channels)) + self.bias = nn.Parameter(torch.zeros(num_channels)) + self.eps = eps + + def forward(self, x: torch.Tensor) -> torch.Tensor: + u = x.mean(1, keepdim=True) + s = (x - u).pow(2).mean(1, keepdim=True) + x = (x - u) / torch.sqrt(s + self.eps) + x = self.weight[:, None, None] * x + self.bias[:, None, None] + return x diff --git a/sam2/sam2_camera_predictor.py b/sam2/sam2_camera_predictor.py new file mode 100644 index 0000000..f61f70a --- /dev/null +++ b/sam2/sam2_camera_predictor.py @@ -0,0 +1,1165 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +from collections import OrderedDict + +import torch + +from tqdm import tqdm + +from sam2.modeling.sam2_base import NO_OBJ_SCORE, SAM2Base +from sam2.utils.misc import concat_points, fill_holes_in_mask_scores, load_video_frames +import numpy as np +import cv2 + + +class SAM2CameraPredictor(SAM2Base): + """The predictor class to handle user interactions and manage inference states.""" + + def __init__( + self, + fill_hole_area=0, + # whether to apply non-overlapping constraints on the output object masks + non_overlap_masks=False, + # whether to clear non-conditioning memory of the surrounding frames (which may contain outdated information) after adding correction clicks; + # note that this would only apply to *single-object tracking* unless `clear_non_cond_mem_for_multi_obj` is also set to True) + clear_non_cond_mem_around_input=False, + # whether to also clear non-conditioning memory of the surrounding frames (only effective when `clear_non_cond_mem_around_input` is True). + clear_non_cond_mem_for_multi_obj=False, + **kwargs, + ): + super().__init__(**kwargs) + self.fill_hole_area = fill_hole_area + self.non_overlap_masks = non_overlap_masks + self.clear_non_cond_mem_around_input = clear_non_cond_mem_around_input + self.clear_non_cond_mem_for_multi_obj = clear_non_cond_mem_for_multi_obj + self.condition_state = {} + self.frame_idx = 0 + + def prepare_data( + self, + img, + image_size=1024, + img_mean=(0.485, 0.456, 0.406), + img_std=(0.229, 0.224, 0.225), + ): + if isinstance(img, np.ndarray): + img_np = img + print(f"img_np type: {type(img_np)}, shape: {img_np.shape}") + img_np = cv2.resize(img_np, (image_size, image_size)) / 255.0 + height, width = img.shape[:2] + else: + img_np = ( + np.array(img.convert("RGB").resize((image_size, image_size))) / 255.0 + ) + width, height = img.size + img = torch.from_numpy(img_np).permute(2, 0, 1).float() + + img_mean = torch.tensor(img_mean, dtype=torch.float32)[:, None, None] + img_std = torch.tensor(img_std, dtype=torch.float32)[:, None, None] + img -= img_mean + img /= img_std + return img, width, height + + @torch.inference_mode() + def load_first_frame(self, img): + + self.condition_state = self._init_state( + offload_video_to_cpu=False, offload_state_to_cpu=False + ) + img, width, height = self.prepare_data(img, image_size=self.image_size) + self.condition_state["images"] = [img] + self.condition_state["num_frames"] = len(self.condition_state["images"]) + self.condition_state["video_height"] = height + self.condition_state["video_width"] = width + self._get_image_feature(frame_idx=0, batch_size=1) + + def add_conditioning_frame(self, img): + img, width, height = self.prepare_data(img, image_size=self.image_size) + self.condition_state["images"].append(img) + self.condition_state["num_frames"] = len(self.condition_state["images"]) + self._get_image_feature( + frame_idx=self.condition_state["num_frames"] - 1, batch_size=1 + ) + + def _init_state( + self, + offload_video_to_cpu=False, + offload_state_to_cpu=False, + ): + self.condition_state = {} + + # whether to offload the video frames to CPU memory + # turning on this option saves the GPU memory with only a very small overhead + self.condition_state["offload_video_to_cpu"] = offload_video_to_cpu + # whether to offload the inference state to CPU memory + # turning on this option saves the GPU memory at the cost of a lower tracking fps + # (e.g. in a test case of 768x768 model, fps dropped from 27 to 24 when tracking one object + # and from 24 to 21 when tracking two objects) + self.condition_state["offload_state_to_cpu"] = offload_state_to_cpu + # the original video height and width, used for resizing final output scores + + self.condition_state["device"] = torch.device("cuda") + if offload_state_to_cpu: + self.condition_state["storage_device"] = torch.device("cpu") + else: + self.condition_state["storage_device"] = torch.device("cuda") + # inputs on each frame + self.condition_state["point_inputs_per_obj"] = {} + self.condition_state["mask_inputs_per_obj"] = {} + # visual features on a small number of recently visited frames for quick interactions + self.condition_state["cached_features"] = {} + # values that don't change across frames (so we only need to hold one copy of them) + self.condition_state["constants"] = {} + # mapping between client-side object id and model-side object index + self.condition_state["obj_id_to_idx"] = OrderedDict() + self.condition_state["obj_idx_to_id"] = OrderedDict() + self.condition_state["obj_ids"] = [] + # A storage to hold the model's tracking results and states on each frame + self.condition_state["output_dict"] = { + "cond_frame_outputs": {}, # dict containing {frame_idx: } + "non_cond_frame_outputs": {}, # dict containing {frame_idx: } + } + # Slice (view) of each object tracking results, sharing the same memory with "output_dict" + self.condition_state["output_dict_per_obj"] = {} + # A temporary storage to hold new outputs when user interact with a frame + # to add clicks or mask (it's merged into "output_dict" before propagation starts) + self.condition_state["temp_output_dict_per_obj"] = {} + # Frames that already holds consolidated outputs from click or mask inputs + # (we directly use their consolidated outputs during tracking) + self.condition_state["consolidated_frame_inds"] = { + "cond_frame_outputs": set(), # set containing frame indices + "non_cond_frame_outputs": set(), # set containing frame indices + } + # metadata for each tracking frame (e.g. which direction it's tracked) + self.condition_state["tracking_has_started"] = False + self.condition_state["frames_already_tracked"] = {} + return self.condition_state + + def _obj_id_to_idx(self, obj_id): + """Map client-side object id to model-side object index.""" + obj_idx = self.condition_state["obj_id_to_idx"].get(obj_id, None) + if obj_idx is not None: + return obj_idx + + # This is a new object id not sent to the server before. We only allow adding + # new objects *before* the tracking starts. + allow_new_object = not self.condition_state["tracking_has_started"] + if allow_new_object: + # get the next object slot + obj_idx = len(self.condition_state["obj_id_to_idx"]) + self.condition_state["obj_id_to_idx"][obj_id] = obj_idx + self.condition_state["obj_idx_to_id"][obj_idx] = obj_id + self.condition_state["obj_ids"] = list( + self.condition_state["obj_id_to_idx"] + ) + # set up input and output structures for this object + self.condition_state["point_inputs_per_obj"][obj_idx] = {} + self.condition_state["mask_inputs_per_obj"][obj_idx] = {} + self.condition_state["output_dict_per_obj"][obj_idx] = { + "cond_frame_outputs": {}, # dict containing {frame_idx: } + "non_cond_frame_outputs": {}, # dict containing {frame_idx: } + } + self.condition_state["temp_output_dict_per_obj"][obj_idx] = { + "cond_frame_outputs": {}, # dict containing {frame_idx: } + "non_cond_frame_outputs": {}, # dict containing {frame_idx: } + } + return obj_idx + else: + raise RuntimeError( + f"Cannot add new object id {obj_id} after tracking starts. " + f"All existing object ids: {self.condition_state['obj_ids']}. " + f"Please call 'reset_state' to restart from scratch." + ) + + def _obj_idx_to_id(self, obj_idx): + """Map model-side object index to client-side object id.""" + return self.condition_state["obj_idx_to_id"][obj_idx] + + def _get_obj_num(self): + """Get the total number of unique object ids received so far in this session.""" + return len(self.condition_state["obj_idx_to_id"]) + + @torch.inference_mode() + def add_new_prompt( + self, + frame_idx, + obj_id, + points=None, + labels=None, + bbox=None, + clear_old_points=True, + normalize_coords=True, + ): + """Add new points to a frame.""" + obj_idx = self._obj_id_to_idx(obj_id) + point_inputs_per_frame = self.condition_state["point_inputs_per_obj"][obj_idx] + mask_inputs_per_frame = self.condition_state["mask_inputs_per_obj"][obj_idx] + + assert ( + bbox is not None or points is not None + ), "Either bbox or points is required" + + if points is None: + points = torch.zeros(0, 2, dtype=torch.float32) + elif not isinstance(points, torch.Tensor): + points = torch.tensor(points, dtype=torch.float32) + if labels is None: + labels = torch.zeros(0, dtype=torch.int32) + elif not isinstance(labels, torch.Tensor): + labels = torch.tensor(labels, dtype=torch.int32) + if points.dim() == 2: + points = points.unsqueeze(0) # add batch dimension + if labels.dim() == 1: + labels = labels.unsqueeze(0) # add batch dimension + if bbox is not None: + if not isinstance(bbox, torch.Tensor): + bbox = torch.tensor(bbox, dtype=torch.float32, device=points.device) + box_coords = bbox.reshape(1, 2, 2) + box_labels = torch.tensor([2, 3], dtype=torch.int32, device=labels.device) + box_labels = box_labels.reshape(1, 2) + points = torch.cat([box_coords, points], dim=1) + labels = torch.cat([box_labels, labels], dim=1) + if normalize_coords: + video_H = self.condition_state["video_height"] + video_W = self.condition_state["video_width"] + points = points / torch.tensor([video_W, video_H]).to(points.device) + # scale the (normalized) coordinates by the model's internal image size + points = points * self.image_size + points = points.to(self.condition_state["device"]) + labels = labels.to(self.condition_state["device"]) + + if not clear_old_points: + point_inputs = point_inputs_per_frame.get(frame_idx, None) + else: + point_inputs = None + point_inputs = concat_points(point_inputs, points, labels) + + point_inputs_per_frame[frame_idx] = point_inputs + mask_inputs_per_frame.pop(frame_idx, None) + # If this frame hasn't been tracked before, we treat it as an initial conditioning + # frame, meaning that the inputs points are to generate segments on this frame without + # using any memory from other frames, like in SAM. Otherwise (if it has been tracked), + # the input points will be used to correct the already tracked masks. + is_init_cond_frame = ( + frame_idx not in self.condition_state["frames_already_tracked"] + ) + # whether to track in reverse time order + if is_init_cond_frame: + reverse = False + else: + reverse = self.condition_state["frames_already_tracked"][frame_idx][ + "reverse" + ] + obj_output_dict = self.condition_state["output_dict_per_obj"][obj_idx] + obj_temp_output_dict = self.condition_state["temp_output_dict_per_obj"][obj_idx] + # Add a frame to conditioning output if it's an initial conditioning frame or + # if the model sees all frames receiving clicks/mask as conditioning frames. + is_cond = is_init_cond_frame or self.add_all_frames_to_correct_as_cond + storage_key = "cond_frame_outputs" if is_cond else "non_cond_frame_outputs" + + # Get any previously predicted mask logits on this object and feed it along with + # the new clicks into the SAM mask decoder. + prev_sam_mask_logits = None + # lookup temporary output dict first, which contains the most recent output + # (if not found, then lookup conditioning and non-conditioning frame output) + prev_out = obj_temp_output_dict[storage_key].get(frame_idx) + if prev_out is None: + prev_out = obj_output_dict["cond_frame_outputs"].get(frame_idx) + if prev_out is None: + prev_out = obj_output_dict["non_cond_frame_outputs"].get(frame_idx) + + if prev_out is not None and prev_out["pred_masks"] is not None: + prev_sam_mask_logits = prev_out["pred_masks"].cuda(non_blocking=True) + # Clamp the scale of prev_sam_mask_logits to avoid rare numerical issues. + prev_sam_mask_logits = torch.clamp(prev_sam_mask_logits, -32.0, 32.0) + current_out, _ = self._run_single_frame_inference( + output_dict=obj_output_dict, # run on the slice of a single object + frame_idx=frame_idx, + batch_size=1, # run on the slice of a single object + is_init_cond_frame=is_init_cond_frame, + point_inputs=point_inputs, + mask_inputs=None, + reverse=reverse, + # Skip the memory encoder when adding clicks or mask. We execute the memory encoder + # at the beginning of `propagate_in_video` (after user finalize their clicks). This + # allows us to enforce non-overlapping constraints on all objects before encoding + # them into memory. + run_mem_encoder=False, + prev_sam_mask_logits=prev_sam_mask_logits, + ) + # Add the output to the output dict (to be used as future memory) + obj_temp_output_dict[storage_key][frame_idx] = current_out + + # Resize the output mask to the original video resolution + obj_ids = self.condition_state["obj_ids"] + consolidated_out = self._consolidate_temp_output_across_obj( + frame_idx, + is_cond=is_cond, + run_mem_encoder=False, + consolidate_at_video_res=True, + ) + _, video_res_masks = self._get_orig_video_res_output( + consolidated_out["pred_masks_video_res"] + ) + return frame_idx, obj_ids, video_res_masks + + @torch.inference_mode() + def add_new_points( + self, + frame_idx, + obj_id, + points, + labels, + clear_old_points=True, + normalize_coords=True, + ): + """Add new points to a frame.""" + obj_idx = self._obj_id_to_idx(obj_id) + point_inputs_per_frame = self.condition_state["point_inputs_per_obj"][obj_idx] + mask_inputs_per_frame = self.condition_state["mask_inputs_per_obj"][obj_idx] + + if not isinstance(points, torch.Tensor): + points = torch.tensor(points, dtype=torch.float32) + if not isinstance(labels, torch.Tensor): + labels = torch.tensor(labels, dtype=torch.int32) + if points.dim() == 2: + points = points.unsqueeze(0) # add batch dimension + if labels.dim() == 1: + labels = labels.unsqueeze(0) # add batch dimension + if normalize_coords: + video_H = self.condition_state["video_height"] + video_W = self.condition_state["video_width"] + points = points / torch.tensor([video_W, video_H]).to(points.device) + # scale the (normalized) coordinates by the model's internal image size + points = points * self.image_size + points = points.to(self.condition_state["device"]) + labels = labels.to(self.condition_state["device"]) + + if not clear_old_points: + point_inputs = point_inputs_per_frame.get(frame_idx, None) + else: + point_inputs = None + point_inputs = concat_points(point_inputs, points, labels) + + point_inputs_per_frame[frame_idx] = point_inputs + mask_inputs_per_frame.pop(frame_idx, None) + # If this frame hasn't been tracked before, we treat it as an initial conditioning + # frame, meaning that the inputs points are to generate segments on this frame without + # using any memory from other frames, like in SAM. Otherwise (if it has been tracked), + # the input points will be used to correct the already tracked masks. + is_init_cond_frame = ( + frame_idx not in self.condition_state["frames_already_tracked"] + ) + # whether to track in reverse time order + if is_init_cond_frame: + reverse = False + else: + reverse = self.condition_state["frames_already_tracked"][frame_idx][ + "reverse" + ] + obj_output_dict = self.condition_state["output_dict_per_obj"][obj_idx] + obj_temp_output_dict = self.condition_state["temp_output_dict_per_obj"][obj_idx] + # Add a frame to conditioning output if it's an initial conditioning frame or + # if the model sees all frames receiving clicks/mask as conditioning frames. + is_cond = is_init_cond_frame or self.add_all_frames_to_correct_as_cond + storage_key = "cond_frame_outputs" if is_cond else "non_cond_frame_outputs" + + # Get any previously predicted mask logits on this object and feed it along with + # the new clicks into the SAM mask decoder. + prev_sam_mask_logits = None + # lookup temporary output dict first, which contains the most recent output + # (if not found, then lookup conditioning and non-conditioning frame output) + prev_out = obj_temp_output_dict[storage_key].get(frame_idx) + if prev_out is None: + prev_out = obj_output_dict["cond_frame_outputs"].get(frame_idx) + if prev_out is None: + prev_out = obj_output_dict["non_cond_frame_outputs"].get(frame_idx) + + if prev_out is not None and prev_out["pred_masks"] is not None: + prev_sam_mask_logits = prev_out["pred_masks"].cuda(non_blocking=True) + # Clamp the scale of prev_sam_mask_logits to avoid rare numerical issues. + prev_sam_mask_logits = torch.clamp(prev_sam_mask_logits, -32.0, 32.0) + current_out, _ = self._run_single_frame_inference( + output_dict=obj_output_dict, # run on the slice of a single object + frame_idx=frame_idx, + batch_size=1, # run on the slice of a single object + is_init_cond_frame=is_init_cond_frame, + point_inputs=point_inputs, + mask_inputs=None, + reverse=reverse, + # Skip the memory encoder when adding clicks or mask. We execute the memory encoder + # at the beginning of `propagate_in_video` (after user finalize their clicks). This + # allows us to enforce non-overlapping constraints on all objects before encoding + # them into memory. + run_mem_encoder=False, + prev_sam_mask_logits=prev_sam_mask_logits, + ) + # Add the output to the output dict (to be used as future memory) + obj_temp_output_dict[storage_key][frame_idx] = current_out + + # Resize the output mask to the original video resolution + obj_ids = self.condition_state["obj_ids"] + consolidated_out = self._consolidate_temp_output_across_obj( + frame_idx, + is_cond=is_cond, + run_mem_encoder=False, + consolidate_at_video_res=True, + ) + _, video_res_masks = self._get_orig_video_res_output( + consolidated_out["pred_masks_video_res"] + ) + return frame_idx, obj_ids, video_res_masks + + @torch.inference_mode() + def add_new_mask( + self, + frame_idx, + obj_id, + mask, + ): + """Add new mask to a frame.""" + obj_idx = self._obj_id_to_idx(obj_id) + point_inputs_per_frame = self.condition_state["point_inputs_per_obj"][obj_idx] + mask_inputs_per_frame = self.condition_state["mask_inputs_per_obj"][obj_idx] + + if not isinstance(mask, torch.Tensor): + mask = torch.tensor(mask, dtype=torch.bool) + assert mask.dim() == 2 + mask_H, mask_W = mask.shape + mask_inputs_orig = mask[None, None] # add batch and channel dimension + mask_inputs_orig = mask_inputs_orig.float().to(self.condition_state["device"]) + + # resize the mask if it doesn't match the model's image size + if mask_H != self.image_size or mask_W != self.image_size: + mask_inputs = torch.nn.functional.interpolate( + mask_inputs_orig, + size=(self.image_size, self.image_size), + align_corners=False, + mode="bilinear", + antialias=True, # use antialias for downsampling + ) + mask_inputs = (mask_inputs >= 0.5).float() + else: + mask_inputs = mask_inputs_orig + + mask_inputs_per_frame[frame_idx] = mask_inputs + point_inputs_per_frame.pop(frame_idx, None) + # If this frame hasn't been tracked before, we treat it as an initial conditioning + # frame, meaning that the inputs points are to generate segments on this frame without + # using any memory from other frames, like in SAM. Otherwise (if it has been tracked), + # the input points will be used to correct the already tracked masks. + is_init_cond_frame = ( + frame_idx not in self.condition_state["frames_already_tracked"] + ) + # whether to track in reverse time order + if is_init_cond_frame: + reverse = False + else: + reverse = self.condition_state["frames_already_tracked"][frame_idx][ + "reverse" + ] + obj_output_dict = self.condition_state["output_dict_per_obj"][obj_idx] + obj_temp_output_dict = self.condition_state["temp_output_dict_per_obj"][obj_idx] + # Add a frame to conditioning output if it's an initial conditioning frame or + # if the model sees all frames receiving clicks/mask as conditioning frames. + is_cond = is_init_cond_frame or self.add_all_frames_to_correct_as_cond + storage_key = "cond_frame_outputs" if is_cond else "non_cond_frame_outputs" + + current_out, _ = self._run_single_frame_inference( + output_dict=obj_output_dict, # run on the slice of a single object + frame_idx=frame_idx, + batch_size=1, # run on the slice of a single object + is_init_cond_frame=is_init_cond_frame, + point_inputs=None, + mask_inputs=mask_inputs, + reverse=reverse, + # Skip the memory encoder when adding clicks or mask. We execute the memory encoder + # at the beginning of `propagate_in_video` (after user finalize their clicks). This + # allows us to enforce non-overlapping constraints on all objects before encoding + # them into memory. + run_mem_encoder=False, + ) + # Add the output to the output dict (to be used as future memory) + obj_temp_output_dict[storage_key][frame_idx] = current_out + + # Resize the output mask to the original video resolution + obj_ids = self.condition_state["obj_ids"] + consolidated_out = self._consolidate_temp_output_across_obj( + frame_idx, + is_cond=is_cond, + run_mem_encoder=False, + consolidate_at_video_res=True, + ) + _, video_res_masks = self._get_orig_video_res_output( + consolidated_out["pred_masks_video_res"] + ) + return frame_idx, obj_ids, video_res_masks + + def _get_orig_video_res_output(self, any_res_masks): + """ + Resize the object scores to the original video resolution (video_res_masks) + and apply non-overlapping constraints for final output. + """ + device = self.condition_state["device"] + video_H = self.condition_state["video_height"] + video_W = self.condition_state["video_width"] + any_res_masks = any_res_masks.to(device, non_blocking=True) + if any_res_masks.shape[-2:] == (video_H, video_W): + video_res_masks = any_res_masks + else: + video_res_masks = torch.nn.functional.interpolate( + any_res_masks, + size=(video_H, video_W), + mode="bilinear", + align_corners=False, + ) + if self.non_overlap_masks: + video_res_masks = self._apply_non_overlapping_constraints(video_res_masks) + return any_res_masks, video_res_masks + + def _consolidate_temp_output_across_obj( + self, + frame_idx, + is_cond, + run_mem_encoder, + consolidate_at_video_res=False, + ): + """ + Consolidate the per-object temporary outputs in `temp_output_dict_per_obj` on + a frame into a single output for all objects, including + 1) fill any missing objects either from `output_dict_per_obj` (if they exist in + `output_dict_per_obj` for this frame) or leave them as placeholder values + (if they don't exist in `output_dict_per_obj` for this frame); + 2) if specified, rerun memory encoder after apply non-overlapping constraints + on the object scores. + """ + batch_size = self._get_obj_num() + storage_key = "cond_frame_outputs" if is_cond else "non_cond_frame_outputs" + # Optionally, we allow consolidating the temporary outputs at the original + # video resolution (to provide a better editing experience for mask prompts). + if consolidate_at_video_res: + assert not run_mem_encoder, "memory encoder cannot run at video resolution" + consolidated_H = self.condition_state["video_height"] + consolidated_W = self.condition_state["video_width"] + consolidated_mask_key = "pred_masks_video_res" + else: + consolidated_H = consolidated_W = self.image_size // 4 + consolidated_mask_key = "pred_masks" + + # Initialize `consolidated_out`. Its "maskmem_features" and "maskmem_pos_enc" + # will be added when rerunning the memory encoder after applying non-overlapping + # constraints to object scores. Its "pred_masks" are prefilled with a large + # negative value (NO_OBJ_SCORE) to represent missing objects. + consolidated_out = { + "maskmem_features": None, + "maskmem_pos_enc": None, + consolidated_mask_key: torch.full( + size=(batch_size, 1, consolidated_H, consolidated_W), + fill_value=NO_OBJ_SCORE, + dtype=torch.float32, + device=self.condition_state["storage_device"], + ), + "obj_ptr": torch.full( + size=(batch_size, self.hidden_dim), + fill_value=NO_OBJ_SCORE, + dtype=torch.float32, + device=self.condition_state["device"], + ), + } + empty_mask_ptr = None + for obj_idx in range(batch_size): + obj_temp_output_dict = self.condition_state["temp_output_dict_per_obj"][ + obj_idx + ] + obj_output_dict = self.condition_state["output_dict_per_obj"][obj_idx] + out = obj_temp_output_dict[storage_key].get(frame_idx, None) + # If the object doesn't appear in "temp_output_dict_per_obj" on this frame, + # we fall back and look up its previous output in "output_dict_per_obj". + # We look up both "cond_frame_outputs" and "non_cond_frame_outputs" in + # "output_dict_per_obj" to find a previous output for this object. + if out is None: + out = obj_output_dict["cond_frame_outputs"].get(frame_idx, None) + if out is None: + out = obj_output_dict["non_cond_frame_outputs"].get(frame_idx, None) + # If the object doesn't appear in "output_dict_per_obj" either, we skip it + # and leave its mask scores to the default scores (i.e. the NO_OBJ_SCORE + # placeholder above) and set its object pointer to be a dummy pointer. + if out is None: + # Fill in dummy object pointers for those objects without any inputs or + # tracking outcomes on this frame (only do it under `run_mem_encoder=True`, + # i.e. when we need to build the memory for tracking). + if run_mem_encoder: + if empty_mask_ptr is None: + empty_mask_ptr = self._get_empty_mask_ptr(frame_idx) + # fill object pointer with a dummy pointer (based on an empty mask) + consolidated_out["obj_ptr"][obj_idx : obj_idx + 1] = empty_mask_ptr + continue + # Add the temporary object output mask to consolidated output mask + obj_mask = out["pred_masks"] + consolidated_pred_masks = consolidated_out[consolidated_mask_key] + if obj_mask.shape[-2:] == consolidated_pred_masks.shape[-2:]: + consolidated_pred_masks[obj_idx : obj_idx + 1] = obj_mask + else: + # Resize first if temporary object mask has a different resolution + resized_obj_mask = torch.nn.functional.interpolate( + obj_mask, + size=consolidated_pred_masks.shape[-2:], + mode="bilinear", + align_corners=False, + ) + consolidated_pred_masks[obj_idx : obj_idx + 1] = resized_obj_mask + consolidated_out["obj_ptr"][obj_idx : obj_idx + 1] = out["obj_ptr"] + + # Optionally, apply non-overlapping constraints on the consolidated scores + # and rerun the memory encoder + if run_mem_encoder: + device = self.condition_state["device"] + high_res_masks = torch.nn.functional.interpolate( + consolidated_out["pred_masks"].to(device, non_blocking=True), + size=(self.image_size, self.image_size), + mode="bilinear", + align_corners=False, + ) + if self.non_overlap_masks_for_mem_enc: + high_res_masks = self._apply_non_overlapping_constraints(high_res_masks) + maskmem_features, maskmem_pos_enc = self._run_memory_encoder( + frame_idx=frame_idx, + batch_size=batch_size, + high_res_masks=high_res_masks, + is_mask_from_pts=True, # these frames are what the user interacted with + ) + consolidated_out["maskmem_features"] = maskmem_features + consolidated_out["maskmem_pos_enc"] = maskmem_pos_enc + + return consolidated_out + + def _get_empty_mask_ptr(self, frame_idx): + """Get a dummy object pointer based on an empty mask on the current frame.""" + # A dummy (empty) mask with a single object + batch_size = 1 + mask_inputs = torch.zeros( + (batch_size, 1, self.image_size, self.image_size), + dtype=torch.float32, + device=self.condition_state["device"], + ) + + # Retrieve correct image features + ( + _, + _, + current_vision_feats, + current_vision_pos_embeds, + feat_sizes, + ) = self._get_image_feature(frame_idx, batch_size) + + # Feed the empty mask and image feature above to get a dummy object pointer + current_out = self.track_step( + frame_idx=frame_idx, + is_init_cond_frame=True, + current_vision_feats=current_vision_feats, + current_vision_pos_embeds=current_vision_pos_embeds, + feat_sizes=feat_sizes, + point_inputs=None, + mask_inputs=mask_inputs, + output_dict={}, + num_frames=self.condition_state["num_frames"], + track_in_reverse=False, + run_mem_encoder=False, + prev_sam_mask_logits=None, + ) + return current_out["obj_ptr"] + + @torch.inference_mode() + def propagate_in_video_preflight(self): + """Prepare self.condition_state and consolidate temporary outputs before tracking.""" + # Tracking has started and we don't allow adding new objects until session is reset. + self.condition_state["tracking_has_started"] = True + batch_size = self._get_obj_num() + + # Consolidate per-object temporary outputs in "temp_output_dict_per_obj" and + # add them into "output_dict". + temp_output_dict_per_obj = self.condition_state["temp_output_dict_per_obj"] + output_dict = self.condition_state["output_dict"] + # "consolidated_frame_inds" contains indices of those frames where consolidated + # temporary outputs have been added (either in this call or any previous calls + # to `propagate_in_video_preflight`). + consolidated_frame_inds = self.condition_state["consolidated_frame_inds"] + for is_cond in [False, True]: + # Separately consolidate conditioning and non-conditioning temp outptus + storage_key = "cond_frame_outputs" if is_cond else "non_cond_frame_outputs" + # Find all the frames that contain temporary outputs for any objects + # (these should be the frames that have just received clicks for mask inputs + # via `add_new_points` or `add_new_mask`) + temp_frame_inds = set() + for obj_temp_output_dict in temp_output_dict_per_obj.values(): + temp_frame_inds.update(obj_temp_output_dict[storage_key].keys()) + consolidated_frame_inds[storage_key].update(temp_frame_inds) + # consolidate the temprary output across all objects on this frame + for frame_idx in temp_frame_inds: + consolidated_out = self._consolidate_temp_output_across_obj( + frame_idx, is_cond=is_cond, run_mem_encoder=True + ) + # merge them into "output_dict" and also create per-object slices + output_dict[storage_key][frame_idx] = consolidated_out + self._add_output_per_object(frame_idx, consolidated_out, storage_key) + clear_non_cond_mem = self.clear_non_cond_mem_around_input and ( + self.clear_non_cond_mem_for_multi_obj or batch_size <= 1 + ) + if clear_non_cond_mem: + # clear non-conditioning memory of the surrounding frames + self._clear_non_cond_mem_around_input(frame_idx) + + # clear temporary outputs in `temp_output_dict_per_obj` + for obj_temp_output_dict in temp_output_dict_per_obj.values(): + obj_temp_output_dict[storage_key].clear() + + # edge case: if an output is added to "cond_frame_outputs", we remove any prior + # output on the same frame in "non_cond_frame_outputs" + for frame_idx in output_dict["cond_frame_outputs"]: + output_dict["non_cond_frame_outputs"].pop(frame_idx, None) + for obj_output_dict in self.condition_state["output_dict_per_obj"].values(): + for frame_idx in obj_output_dict["cond_frame_outputs"]: + obj_output_dict["non_cond_frame_outputs"].pop(frame_idx, None) + for frame_idx in consolidated_frame_inds["cond_frame_outputs"]: + assert frame_idx in output_dict["cond_frame_outputs"] + consolidated_frame_inds["non_cond_frame_outputs"].discard(frame_idx) + + # Make sure that the frame indices in "consolidated_frame_inds" are exactly those frames + # with either points or mask inputs (which should be true under a correct workflow). + all_consolidated_frame_inds = ( + consolidated_frame_inds["cond_frame_outputs"] + | consolidated_frame_inds["non_cond_frame_outputs"] + ) + input_frames_inds = set() + for point_inputs_per_frame in self.condition_state[ + "point_inputs_per_obj" + ].values(): + input_frames_inds.update(point_inputs_per_frame.keys()) + for mask_inputs_per_frame in self.condition_state[ + "mask_inputs_per_obj" + ].values(): + input_frames_inds.update(mask_inputs_per_frame.keys()) + assert all_consolidated_frame_inds == input_frames_inds + + @torch.inference_mode() + def track( + self, + img, + ): + self.frame_idx += 1 + self.condition_state["num_frames"] += 1 + if not self.condition_state["tracking_has_started"]: + self.propagate_in_video_preflight() + + img, _, _ = self.prepare_data(img, image_size=self.image_size) + + output_dict = self.condition_state["output_dict"] + obj_ids = self.condition_state["obj_ids"] + batch_size = self._get_obj_num() + + # Retrieve correct image features + ( + _, + _, + current_vision_feats, + current_vision_pos_embeds, + feat_sizes, + ) = self._get_feature(img, batch_size) + + current_out = self.track_step( + frame_idx=self.frame_idx, + is_init_cond_frame=False, + current_vision_feats=current_vision_feats, + current_vision_pos_embeds=current_vision_pos_embeds, + feat_sizes=feat_sizes, + point_inputs=None, + mask_inputs=None, + output_dict=output_dict, + num_frames=self.condition_state["num_frames"], + track_in_reverse=False, + run_mem_encoder=True, + prev_sam_mask_logits=None, + ) + + # optionally offload the output to CPU memory to save GPU space + storage_device = self.condition_state["storage_device"] + maskmem_features = current_out["maskmem_features"] + if maskmem_features is not None: + maskmem_features = maskmem_features.to(torch.bfloat16) + maskmem_features = maskmem_features.to(storage_device, non_blocking=True) + pred_masks_gpu = current_out["pred_masks"] + # potentially fill holes in the predicted masks + if self.fill_hole_area > 0: + pred_masks_gpu = fill_holes_in_mask_scores( + pred_masks_gpu, self.fill_hole_area + ) + pred_masks = pred_masks_gpu.to(storage_device, non_blocking=True) + # "maskmem_pos_enc" is the same across frames, so we only need to store one copy of it + maskmem_pos_enc = self._get_maskmem_pos_enc(current_out) + # object pointer is a small tensor, so we always keep it on GPU memory for fast access + obj_ptr = current_out["obj_ptr"] + # make a compact version of this frame's output to reduce the state size + current_out = { + "maskmem_features": maskmem_features, + "maskmem_pos_enc": maskmem_pos_enc, + "pred_masks": pred_masks, + "obj_ptr": obj_ptr, + } + + # output_dict[storage_key][self.frame_idx] = current_out + self._manage_memory_obj(self.frame_idx, current_out) + + _, video_res_masks = self._get_orig_video_res_output(pred_masks_gpu) + return obj_ids, video_res_masks + + def _manage_memory_obj(self, frame_idx, current_out): + output_dict = self.condition_state["output_dict"] + non_cond_frame_outputs = output_dict["non_cond_frame_outputs"] + non_cond_frame_outputs[frame_idx] = current_out + + key_list = [key for key in output_dict["non_cond_frame_outputs"]] + #! TODO: better way to manage memory + if len(non_cond_frame_outputs) > self.num_maskmem: + for t in range(0, len(non_cond_frame_outputs) - self.num_maskmem): + # key, Value = non_cond_frame_outputs.popitem(last=False) + _ = non_cond_frame_outputs.pop(key_list[t], None) + + @torch.inference_mode() + def propagate_in_video( + self, + start_frame_idx=None, + max_frame_num_to_track=None, + reverse=False, + ): + """Propagate the input points across frames to track in the entire video.""" + + self.propagate_in_video_preflight(self.condition_state) + + output_dict = self.condition_state["output_dict"] + consolidated_frame_inds = self.condition_state["consolidated_frame_inds"] + obj_ids = self.condition_state["obj_ids"] + num_frames = self.condition_state["num_frames"] + batch_size = self._get_obj_num() + if len(output_dict["cond_frame_outputs"]) == 0: + raise RuntimeError("No points are provided; please add points first") + clear_non_cond_mem = self.clear_non_cond_mem_around_input and ( + self.clear_non_cond_mem_for_multi_obj or batch_size <= 1 + ) + + # set start index, end index, and processing order + if start_frame_idx is None: + # default: start from the earliest frame with input points + start_frame_idx = min(output_dict["cond_frame_outputs"]) + if max_frame_num_to_track is None: + # default: track all the frames in the video + max_frame_num_to_track = num_frames + if reverse: + end_frame_idx = max(start_frame_idx - max_frame_num_to_track, 0) + if start_frame_idx > 0: + processing_order = range(start_frame_idx, end_frame_idx - 1, -1) + else: + processing_order = [] # skip reverse tracking if starting from frame 0 + else: + end_frame_idx = min( + start_frame_idx + max_frame_num_to_track, num_frames - 1 + ) + processing_order = range(start_frame_idx, end_frame_idx + 1) + + for frame_idx in tqdm(processing_order, desc="propagate in video"): + # We skip those frames already in consolidated outputs (these are frames + # that received input clicks or mask). Note that we cannot directly run + # batched forward on them via `_run_single_frame_inference` because the + # number of clicks on each object might be different. + if frame_idx in consolidated_frame_inds["cond_frame_outputs"]: + storage_key = "cond_frame_outputs" + current_out = output_dict[storage_key][frame_idx] + pred_masks = current_out["pred_masks"] + if clear_non_cond_mem: + # clear non-conditioning memory of the surrounding frames + self._clear_non_cond_mem_around_input(frame_idx) + + elif frame_idx in consolidated_frame_inds["non_cond_frame_outputs"]: + storage_key = "non_cond_frame_outputs" + current_out = output_dict[storage_key][frame_idx] + pred_masks = current_out["pred_masks"] + else: + storage_key = "non_cond_frame_outputs" + current_out, pred_masks = self._run_single_frame_inference( + output_dict=output_dict, + frame_idx=frame_idx, + batch_size=batch_size, + is_init_cond_frame=False, + point_inputs=None, + mask_inputs=None, + reverse=reverse, + run_mem_encoder=True, + ) + output_dict[storage_key][frame_idx] = current_out + + # Create slices of per-object outputs for subsequent interaction with each + # individual object after tracking. + self._add_output_per_object(frame_idx, current_out, storage_key) + self.condition_state["frames_already_tracked"][frame_idx] = { + "reverse": reverse + } + + # Resize the output mask to the original video resolution (we directly use + # the mask scores on GPU for output to avoid any CPU conversion in between) + _, video_res_masks = self._get_orig_video_res_output(pred_masks) + yield frame_idx, obj_ids, video_res_masks + + def _add_output_per_object(self, frame_idx, current_out, storage_key): + """ + Split a multi-object output into per-object output slices and add them into + `output_dict_per_obj`. The resulting slices share the same tensor storage. + """ + maskmem_features = current_out["maskmem_features"] + assert maskmem_features is None or isinstance(maskmem_features, torch.Tensor) + + maskmem_pos_enc = current_out["maskmem_pos_enc"] + assert maskmem_pos_enc is None or isinstance(maskmem_pos_enc, list) + + output_dict_per_obj = self.condition_state["output_dict_per_obj"] + for obj_idx, obj_output_dict in output_dict_per_obj.items(): + obj_slice = slice(obj_idx, obj_idx + 1) + obj_out = { + "maskmem_features": None, + "maskmem_pos_enc": None, + "pred_masks": current_out["pred_masks"][obj_slice], + "obj_ptr": current_out["obj_ptr"][obj_slice], + } + if maskmem_features is not None: + obj_out["maskmem_features"] = maskmem_features[obj_slice] + if maskmem_pos_enc is not None: + obj_out["maskmem_pos_enc"] = [x[obj_slice] for x in maskmem_pos_enc] + obj_output_dict[storage_key][frame_idx] = obj_out + + @torch.inference_mode() + def reset_state(self): + """Remove all input points or mask in all frames throughout the video.""" + self._reset_tracking_results() + # Remove all object ids + self.condition_state["obj_id_to_idx"].clear() + self.condition_state["obj_idx_to_id"].clear() + self.condition_state["obj_ids"].clear() + self.condition_state["point_inputs_per_obj"].clear() + self.condition_state["mask_inputs_per_obj"].clear() + self.condition_state["output_dict_per_obj"].clear() + self.condition_state["temp_output_dict_per_obj"].clear() + + def _reset_tracking_results(self): + """Reset all tracking inputs and results across the videos.""" + for v in self.condition_state["point_inputs_per_obj"].values(): + v.clear() + for v in self.condition_state["mask_inputs_per_obj"].values(): + v.clear() + for v in self.condition_state["output_dict_per_obj"].values(): + v["cond_frame_outputs"].clear() + v["non_cond_frame_outputs"].clear() + for v in self.condition_state["temp_output_dict_per_obj"].values(): + v["cond_frame_outputs"].clear() + v["non_cond_frame_outputs"].clear() + self.condition_state["output_dict"]["cond_frame_outputs"].clear() + self.condition_state["output_dict"]["non_cond_frame_outputs"].clear() + self.condition_state["consolidated_frame_inds"]["cond_frame_outputs"].clear() + self.condition_state["consolidated_frame_inds"][ + "non_cond_frame_outputs" + ].clear() + self.condition_state["tracking_has_started"] = False + self.condition_state["frames_already_tracked"].clear() + + def _get_image_feature(self, frame_idx, batch_size): + """Compute the image features on a given frame.""" + # Look up in the cache first + image, backbone_out = self.condition_state["cached_features"].get( + frame_idx, (None, None) + ) + if backbone_out is None: + # Cache miss -- we will run inference on a single image + image = ( + self.condition_state["images"][frame_idx].cuda().float().unsqueeze(0) + ) + backbone_out = self.forward_image(image) + # Cache the most recent frame's feature (for repeated interactions with + # a frame; we can use an LRU cache for more frames in the future). + self.condition_state["cached_features"] = {frame_idx: (image, backbone_out)} + + # expand the features to have the same dimension as the number of objects + expanded_image = image.expand(batch_size, -1, -1, -1) + expanded_backbone_out = { + "backbone_fpn": backbone_out["backbone_fpn"].copy(), + "vision_pos_enc": backbone_out["vision_pos_enc"].copy(), + } + for i, feat in enumerate(expanded_backbone_out["backbone_fpn"]): + expanded_backbone_out["backbone_fpn"][i] = feat.expand( + batch_size, -1, -1, -1 + ) + for i, pos in enumerate(expanded_backbone_out["vision_pos_enc"]): + pos = pos.expand(batch_size, -1, -1, -1) + expanded_backbone_out["vision_pos_enc"][i] = pos + + features = self._prepare_backbone_features(expanded_backbone_out) + features = (expanded_image,) + features + return features + + def _get_feature(self, img, batch_size): + image = img.cuda().float().unsqueeze(0) + backbone_out = self.forward_image(image) + expanded_image = image.expand(batch_size, -1, -1, -1) + expanded_backbone_out = { + "backbone_fpn": backbone_out["backbone_fpn"].copy(), + "vision_pos_enc": backbone_out["vision_pos_enc"].copy(), + } + for i, feat in enumerate(expanded_backbone_out["backbone_fpn"]): + expanded_backbone_out["backbone_fpn"][i] = feat.expand( + batch_size, -1, -1, -1 + ) + for i, pos in enumerate(expanded_backbone_out["vision_pos_enc"]): + pos = pos.expand(batch_size, -1, -1, -1) + expanded_backbone_out["vision_pos_enc"][i] = pos + + features = self._prepare_backbone_features(expanded_backbone_out) + features = (expanded_image,) + features + return features + + def _run_single_frame_inference( + self, + output_dict, + frame_idx, + batch_size, + is_init_cond_frame, + point_inputs, + mask_inputs, + reverse, + run_mem_encoder, + prev_sam_mask_logits=None, + ): + """Run tracking on a single frame based on current inputs and previous memory.""" + # Retrieve correct image features + ( + _, + _, + current_vision_feats, + current_vision_pos_embeds, + feat_sizes, + ) = self._get_image_feature(frame_idx, batch_size) + + # point and mask should not appear as input simultaneously on the same frame + assert point_inputs is None or mask_inputs is None + current_out = self.track_step( + frame_idx=frame_idx, + is_init_cond_frame=is_init_cond_frame, + current_vision_feats=current_vision_feats, + current_vision_pos_embeds=current_vision_pos_embeds, + feat_sizes=feat_sizes, + point_inputs=point_inputs, + mask_inputs=mask_inputs, + output_dict=output_dict, + num_frames=self.condition_state["num_frames"], + track_in_reverse=reverse, + run_mem_encoder=run_mem_encoder, + prev_sam_mask_logits=prev_sam_mask_logits, + ) + + # optionally offload the output to CPU memory to save GPU space + storage_device = self.condition_state["storage_device"] + maskmem_features = current_out["maskmem_features"] + if maskmem_features is not None: + maskmem_features = maskmem_features.to(torch.bfloat16) + maskmem_features = maskmem_features.to(storage_device, non_blocking=True) + pred_masks_gpu = current_out["pred_masks"] + # potentially fill holes in the predicted masks + if self.fill_hole_area > 0: + pred_masks_gpu = fill_holes_in_mask_scores( + pred_masks_gpu, self.fill_hole_area + ) + pred_masks = pred_masks_gpu.to(storage_device, non_blocking=True) + # "maskmem_pos_enc" is the same across frames, so we only need to store one copy of it + maskmem_pos_enc = self._get_maskmem_pos_enc(current_out) + # object pointer is a small tensor, so we always keep it on GPU memory for fast access + obj_ptr = current_out["obj_ptr"] + # make a compact version of this frame's output to reduce the state size + compact_current_out = { + "maskmem_features": maskmem_features, + "maskmem_pos_enc": maskmem_pos_enc, + "pred_masks": pred_masks, + "obj_ptr": obj_ptr, + } + return compact_current_out, pred_masks_gpu + + def _run_memory_encoder( + self, frame_idx, batch_size, high_res_masks, is_mask_from_pts + ): + """ + Run the memory encoder on `high_res_masks`. This is usually after applying + non-overlapping constraints to object scores. Since their scores changed, their + memory also need to be computed again with the memory encoder. + """ + # Retrieve correct image features + _, _, current_vision_feats, _, feat_sizes = self._get_image_feature( + frame_idx, batch_size + ) + maskmem_features, maskmem_pos_enc = self._encode_new_memory( + current_vision_feats=current_vision_feats, + feat_sizes=feat_sizes, + pred_masks_high_res=high_res_masks, + is_mask_from_pts=is_mask_from_pts, + ) + + # optionally offload the output to CPU memory to save GPU space + storage_device = self.condition_state["storage_device"] + maskmem_features = maskmem_features.to(torch.bfloat16) + maskmem_features = maskmem_features.to(storage_device, non_blocking=True) + # "maskmem_pos_enc" is the same across frames, so we only need to store one copy of it + maskmem_pos_enc = self._get_maskmem_pos_enc( + {"maskmem_pos_enc": maskmem_pos_enc} + ) + return maskmem_features, maskmem_pos_enc + + def _get_maskmem_pos_enc(self, current_out): + """ + `maskmem_pos_enc` is the same across frames and objects, so we cache it as + a constant in the inference session to reduce session storage size. + """ + model_constants = self.condition_state["constants"] + # "out_maskmem_pos_enc" should be either a list of tensors or None + out_maskmem_pos_enc = current_out["maskmem_pos_enc"] + if out_maskmem_pos_enc is not None: + if "maskmem_pos_enc" not in model_constants: + assert isinstance(out_maskmem_pos_enc, list) + # only take the slice for one object, since it's same across objects + maskmem_pos_enc = [x[0:1].clone() for x in out_maskmem_pos_enc] + model_constants["maskmem_pos_enc"] = maskmem_pos_enc + else: + maskmem_pos_enc = model_constants["maskmem_pos_enc"] + # expand the cached maskmem_pos_enc to the actual batch size + batch_size = out_maskmem_pos_enc[0].size(0) + expanded_maskmem_pos_enc = [ + x.expand(batch_size, -1, -1, -1) for x in maskmem_pos_enc + ] + else: + expanded_maskmem_pos_enc = None + return expanded_maskmem_pos_enc + + def _clear_non_cond_mem_around_input(self, frame_idx): + """ + Remove the non-conditioning memory around the input frame. When users provide + correction clicks, the surrounding frames' non-conditioning memories can still + contain outdated object appearance information and could confuse the model. + + This method clears those non-conditioning memories surrounding the interacted + frame to avoid giving the model both old and new information about the object. + """ + r = self.memory_temporal_stride_for_eval + frame_idx_begin = frame_idx - r * self.num_maskmem + frame_idx_end = frame_idx + r * self.num_maskmem + output_dict = self.condition_state["output_dict"] + non_cond_frame_outputs = output_dict["non_cond_frame_outputs"] + for t in range(frame_idx_begin, frame_idx_end + 1): + non_cond_frame_outputs.pop(t, None) + for obj_output_dict in self.condition_state["output_dict_per_obj"].values(): + obj_output_dict["non_cond_frame_outputs"].pop(t, None) diff --git a/sam2/sam2_image_predictor.py b/sam2/sam2_image_predictor.py new file mode 100644 index 0000000..9411131 --- /dev/null +++ b/sam2/sam2_image_predictor.py @@ -0,0 +1,446 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +import logging + +from typing import List, Optional, Tuple, Union + +import numpy as np +import torch +from PIL.Image import Image + +from sam2.modeling.sam2_base import SAM2Base + +from sam2.utils.transforms import SAM2Transforms + + +class SAM2ImagePredictor: + def __init__( + self, + sam_model: SAM2Base, + mask_threshold=0.0, + max_hole_area=0.0, + max_sprinkle_area=0.0, + ) -> None: + """ + Uses SAM-2 to calculate the image embedding for an image, and then + allow repeated, efficient mask prediction given prompts. + + Arguments: + sam_model (Sam-2): The model to use for mask prediction. + mask_threshold (float): The threshold to use when converting mask logits + to binary masks. Masks are thresholded at 0 by default. + fill_hole_area (int): If fill_hole_area > 0, we fill small holes in up to + the maximum area of fill_hole_area in low_res_masks. + """ + super().__init__() + self.model = sam_model + self._transforms = SAM2Transforms( + resolution=self.model.image_size, + mask_threshold=mask_threshold, + max_hole_area=max_hole_area, + max_sprinkle_area=max_sprinkle_area, + ) + + # Predictor state + self._is_image_set = False + self._features = None + self._orig_hw = None + # Whether the predictor is set for single image or a batch of images + self._is_batch = False + + # Predictor config + self.mask_threshold = mask_threshold + + # Spatial dim for backbone feature maps + self._bb_feat_sizes = [ + (256, 256), + (128, 128), + (64, 64), + ] + + @torch.no_grad() + def set_image( + self, + image: Union[np.ndarray, Image], + ) -> None: + """ + Calculates the image embeddings for the provided image, allowing + masks to be predicted with the 'predict' method. + + Arguments: + image (np.ndarray or PIL Image): The input image to embed in RGB format. The image should be in HWC format if np.ndarray, or WHC format if PIL Image + with pixel values in [0, 255]. + image_format (str): The color format of the image, in ['RGB', 'BGR']. + """ + self.reset_predictor() + # Transform the image to the form expected by the model + if isinstance(image, np.ndarray): + logging.info("For numpy array image, we assume (HxWxC) format") + self._orig_hw = [image.shape[:2]] + elif isinstance(image, Image): + w, h = image.size + self._orig_hw = [(h, w)] + else: + raise NotImplementedError("Image format not supported") + + input_image = self._transforms(image) + input_image = input_image[None, ...].to(self.device) + + assert ( + len(input_image.shape) == 4 and input_image.shape[1] == 3 + ), f"input_image must be of size 1x3xHxW, got {input_image.shape}" + logging.info("Computing image embeddings for the provided image...") + backbone_out = self.model.forward_image(input_image) + _, vision_feats, _, _ = self.model._prepare_backbone_features(backbone_out) + # Add no_mem_embed, which is added to the lowest rest feat. map during training on videos + if self.model.directly_add_no_mem_embed: + vision_feats[-1] = vision_feats[-1] + self.model.no_mem_embed + + feats = [ + feat.permute(1, 2, 0).view(1, -1, *feat_size) + for feat, feat_size in zip(vision_feats[::-1], self._bb_feat_sizes[::-1]) + ][::-1] + self._features = {"image_embed": feats[-1], "high_res_feats": feats[:-1]} + self._is_image_set = True + logging.info("Image embeddings computed.") + + @torch.no_grad() + def set_image_batch( + self, + image_list: List[Union[np.ndarray]], + ) -> None: + """ + Calculates the image embeddings for the provided image batch, allowing + masks to be predicted with the 'predict_batch' method. + + Arguments: + image_list (List[np.ndarray]): The input images to embed in RGB format. The image should be in HWC format if np.ndarray + with pixel values in [0, 255]. + """ + self.reset_predictor() + assert isinstance(image_list, list) + self._orig_hw = [] + for image in image_list: + assert isinstance( + image, np.ndarray + ), "Images are expected to be an np.ndarray in RGB format, and of shape HWC" + self._orig_hw.append(image.shape[:2]) + # Transform the image to the form expected by the model + img_batch = self._transforms.forward_batch(image_list) + img_batch = img_batch.to(self.device) + batch_size = img_batch.shape[0] + assert ( + len(img_batch.shape) == 4 and img_batch.shape[1] == 3 + ), f"img_batch must be of size Bx3xHxW, got {img_batch.shape}" + logging.info("Computing image embeddings for the provided images...") + backbone_out = self.model.forward_image(img_batch) + _, vision_feats, _, _ = self.model._prepare_backbone_features(backbone_out) + # Add no_mem_embed, which is added to the lowest rest feat. map during training on videos + if self.model.directly_add_no_mem_embed: + vision_feats[-1] = vision_feats[-1] + self.model.no_mem_embed + + feats = [ + feat.permute(1, 2, 0).view(batch_size, -1, *feat_size) + for feat, feat_size in zip(vision_feats[::-1], self._bb_feat_sizes[::-1]) + ][::-1] + self._features = {"image_embed": feats[-1], "high_res_feats": feats[:-1]} + self._is_image_set = True + self._is_batch = True + logging.info("Image embeddings computed.") + + def predict_batch( + self, + point_coords_batch: List[np.ndarray] = None, + point_labels_batch: List[np.ndarray] = None, + box_batch: List[np.ndarray] = None, + mask_input_batch: List[np.ndarray] = None, + multimask_output: bool = True, + return_logits: bool = False, + normalize_coords=True, + ) -> Tuple[List[np.ndarray], List[np.ndarray], List[np.ndarray]]: + """This function is very similar to predict(...), however it is used for batched mode, when the model is expected to generate predictions on multiple images. + It returns a tupele of lists of masks, ious, and low_res_masks_logits. + """ + assert self._is_batch, "This function should only be used when in batched mode" + if not self._is_image_set: + raise RuntimeError( + "An image must be set with .set_image_batch(...) before mask prediction." + ) + num_images = len(self._features["image_embed"]) + all_masks = [] + all_ious = [] + all_low_res_masks = [] + for img_idx in range(num_images): + # Transform input prompts + point_coords = ( + point_coords_batch[img_idx] if point_coords_batch is not None else None + ) + point_labels = ( + point_labels_batch[img_idx] if point_labels_batch is not None else None + ) + box = box_batch[img_idx] if box_batch is not None else None + mask_input = ( + mask_input_batch[img_idx] if mask_input_batch is not None else None + ) + mask_input, unnorm_coords, labels, unnorm_box = self._prep_prompts( + point_coords, + point_labels, + box, + mask_input, + normalize_coords, + img_idx=img_idx, + ) + masks, iou_predictions, low_res_masks = self._predict( + unnorm_coords, + labels, + unnorm_box, + mask_input, + multimask_output, + return_logits=return_logits, + img_idx=img_idx, + ) + masks_np = masks.squeeze(0).float().detach().cpu().numpy() + iou_predictions_np = ( + iou_predictions.squeeze(0).float().detach().cpu().numpy() + ) + low_res_masks_np = low_res_masks.squeeze(0).float().detach().cpu().numpy() + all_masks.append(masks_np) + all_ious.append(iou_predictions_np) + all_low_res_masks.append(low_res_masks_np) + + return all_masks, all_ious, all_low_res_masks + + def predict( + self, + point_coords: Optional[np.ndarray] = None, + point_labels: Optional[np.ndarray] = None, + box: Optional[np.ndarray] = None, + mask_input: Optional[np.ndarray] = None, + multimask_output: bool = True, + return_logits: bool = False, + normalize_coords=True, + ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + """ + Predict masks for the given input prompts, using the currently set image. + + Arguments: + point_coords (np.ndarray or None): A Nx2 array of point prompts to the + model. Each point is in (X,Y) in pixels. + point_labels (np.ndarray or None): A length N array of labels for the + point prompts. 1 indicates a foreground point and 0 indicates a + background point. + box (np.ndarray or None): A length 4 array given a box prompt to the + model, in XYXY format. + mask_input (np.ndarray): A low resolution mask input to the model, typically + coming from a previous prediction iteration. Has form 1xHxW, where + for SAM, H=W=256. + multimask_output (bool): If true, the model will return three masks. + For ambiguous input prompts (such as a single click), this will often + produce better masks than a single prediction. If only a single + mask is needed, the model's predicted quality score can be used + to select the best mask. For non-ambiguous prompts, such as multiple + input prompts, multimask_output=False can give better results. + return_logits (bool): If true, returns un-thresholded masks logits + instead of a binary mask. + normalize_coords (bool): If true, the point coordinates will be normalized to the range [0,1] and point_coords is expected to be wrt. image dimensions. + + Returns: + (np.ndarray): The output masks in CxHxW format, where C is the + number of masks, and (H, W) is the original image size. + (np.ndarray): An array of length C containing the model's + predictions for the quality of each mask. + (np.ndarray): An array of shape CxHxW, where C is the number + of masks and H=W=256. These low resolution logits can be passed to + a subsequent iteration as mask input. + """ + if not self._is_image_set: + raise RuntimeError( + "An image must be set with .set_image(...) before mask prediction." + ) + + # Transform input prompts + + mask_input, unnorm_coords, labels, unnorm_box = self._prep_prompts( + point_coords, point_labels, box, mask_input, normalize_coords + ) + + masks, iou_predictions, low_res_masks = self._predict( + unnorm_coords, + labels, + unnorm_box, + mask_input, + multimask_output, + return_logits=return_logits, + ) + + masks_np = masks.squeeze(0).float().detach().cpu().numpy() + iou_predictions_np = iou_predictions.squeeze(0).float().detach().cpu().numpy() + low_res_masks_np = low_res_masks.squeeze(0).float().detach().cpu().numpy() + return masks_np, iou_predictions_np, low_res_masks_np + + def _prep_prompts( + self, point_coords, point_labels, box, mask_logits, normalize_coords, img_idx=-1 + ): + + unnorm_coords, labels, unnorm_box, mask_input = None, None, None, None + if point_coords is not None: + assert ( + point_labels is not None + ), "point_labels must be supplied if point_coords is supplied." + point_coords = torch.as_tensor( + point_coords, dtype=torch.float, device=self.device + ) + unnorm_coords = self._transforms.transform_coords( + point_coords, normalize=normalize_coords, orig_hw=self._orig_hw[img_idx] + ) + labels = torch.as_tensor(point_labels, dtype=torch.int, device=self.device) + if len(unnorm_coords.shape) == 2: + unnorm_coords, labels = unnorm_coords[None, ...], labels[None, ...] + if box is not None: + box = torch.as_tensor(box, dtype=torch.float, device=self.device) + unnorm_box = self._transforms.transform_boxes( + box, normalize=normalize_coords, orig_hw=self._orig_hw[img_idx] + ) # Bx2x2 + if mask_logits is not None: + mask_input = torch.as_tensor( + mask_logits, dtype=torch.float, device=self.device + ) + if len(mask_input.shape) == 3: + mask_input = mask_input[None, :, :, :] + return mask_input, unnorm_coords, labels, unnorm_box + + @torch.no_grad() + def _predict( + self, + point_coords: Optional[torch.Tensor], + point_labels: Optional[torch.Tensor], + boxes: Optional[torch.Tensor] = None, + mask_input: Optional[torch.Tensor] = None, + multimask_output: bool = True, + return_logits: bool = False, + img_idx: int = -1, + ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + """ + Predict masks for the given input prompts, using the currently set image. + Input prompts are batched torch tensors and are expected to already be + transformed to the input frame using SAM2Transforms. + + Arguments: + point_coords (torch.Tensor or None): A BxNx2 array of point prompts to the + model. Each point is in (X,Y) in pixels. + point_labels (torch.Tensor or None): A BxN array of labels for the + point prompts. 1 indicates a foreground point and 0 indicates a + background point. + boxes (np.ndarray or None): A Bx4 array given a box prompt to the + model, in XYXY format. + mask_input (np.ndarray): A low resolution mask input to the model, typically + coming from a previous prediction iteration. Has form Bx1xHxW, where + for SAM, H=W=256. Masks returned by a previous iteration of the + predict method do not need further transformation. + multimask_output (bool): If true, the model will return three masks. + For ambiguous input prompts (such as a single click), this will often + produce better masks than a single prediction. If only a single + mask is needed, the model's predicted quality score can be used + to select the best mask. For non-ambiguous prompts, such as multiple + input prompts, multimask_output=False can give better results. + return_logits (bool): If true, returns un-thresholded masks logits + instead of a binary mask. + + Returns: + (torch.Tensor): The output masks in BxCxHxW format, where C is the + number of masks, and (H, W) is the original image size. + (torch.Tensor): An array of shape BxC containing the model's + predictions for the quality of each mask. + (torch.Tensor): An array of shape BxCxHxW, where C is the number + of masks and H=W=256. These low res logits can be passed to + a subsequent iteration as mask input. + """ + if not self._is_image_set: + raise RuntimeError( + "An image must be set with .set_image(...) before mask prediction." + ) + + if point_coords is not None: + concat_points = (point_coords, point_labels) + else: + concat_points = None + + # Embed prompts + if boxes is not None: + box_coords = boxes.reshape(-1, 2, 2) + box_labels = torch.tensor([[2, 3]], dtype=torch.int, device=boxes.device) + box_labels = box_labels.repeat(boxes.size(0), 1) + # we merge "boxes" and "points" into a single "concat_points" input (where + # boxes are added at the beginning) to sam_prompt_encoder + if concat_points is not None: + concat_coords = torch.cat([box_coords, concat_points[0]], dim=1) + concat_labels = torch.cat([box_labels, concat_points[1]], dim=1) + concat_points = (concat_coords, concat_labels) + else: + concat_points = (box_coords, box_labels) + + sparse_embeddings, dense_embeddings = self.model.sam_prompt_encoder( + points=concat_points, + boxes=None, + masks=mask_input, + ) + + # Predict masks + batched_mode = ( + concat_points is not None and concat_points[0].shape[0] > 1 + ) # multi object prediction + high_res_features = [ + feat_level[img_idx].unsqueeze(0) + for feat_level in self._features["high_res_feats"] + ] + low_res_masks, iou_predictions, _, _ = self.model.sam_mask_decoder( + image_embeddings=self._features["image_embed"][img_idx].unsqueeze(0), + image_pe=self.model.sam_prompt_encoder.get_dense_pe(), + sparse_prompt_embeddings=sparse_embeddings, + dense_prompt_embeddings=dense_embeddings, + multimask_output=multimask_output, + repeat_image=batched_mode, + high_res_features=high_res_features, + ) + + # Upscale the masks to the original image resolution + masks = self._transforms.postprocess_masks( + low_res_masks, self._orig_hw[img_idx] + ) + low_res_masks = torch.clamp(low_res_masks, -32.0, 32.0) + if not return_logits: + masks = masks > self.mask_threshold + + return masks, iou_predictions, low_res_masks + + def get_image_embedding(self) -> torch.Tensor: + """ + Returns the image embeddings for the currently set image, with + shape 1xCxHxW, where C is the embedding dimension and (H,W) are + the embedding spatial dimension of SAM (typically C=256, H=W=64). + """ + if not self._is_image_set: + raise RuntimeError( + "An image must be set with .set_image(...) to generate an embedding." + ) + assert ( + self._features is not None + ), "Features must exist if an image has been set." + return self._features["image_embed"] + + @property + def device(self) -> torch.device: + return self.model.device + + def reset_predictor(self) -> None: + """ + Resets the image embeddings and other state variables. + """ + self._is_image_set = False + self._features = None + self._orig_hw = None + self._is_batch = False diff --git a/sam2/sam2_video_predictor.py b/sam2/sam2_video_predictor.py new file mode 100644 index 0000000..0defcec --- /dev/null +++ b/sam2/sam2_video_predictor.py @@ -0,0 +1,898 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +from collections import OrderedDict + +import torch + +from tqdm import tqdm + +from sam2.modeling.sam2_base import NO_OBJ_SCORE, SAM2Base +from sam2.utils.misc import concat_points, fill_holes_in_mask_scores, load_video_frames + + +class SAM2VideoPredictor(SAM2Base): + """The predictor class to handle user interactions and manage inference states.""" + + def __init__( + self, + fill_hole_area=0, + # whether to apply non-overlapping constraints on the output object masks + non_overlap_masks=False, + # whether to clear non-conditioning memory of the surrounding frames (which may contain outdated information) after adding correction clicks; + # note that this would only apply to *single-object tracking* unless `clear_non_cond_mem_for_multi_obj` is also set to True) + clear_non_cond_mem_around_input=False, + # whether to also clear non-conditioning memory of the surrounding frames (only effective when `clear_non_cond_mem_around_input` is True). + clear_non_cond_mem_for_multi_obj=False, + **kwargs, + ): + super().__init__(**kwargs) + self.fill_hole_area = fill_hole_area + self.non_overlap_masks = non_overlap_masks + self.clear_non_cond_mem_around_input = clear_non_cond_mem_around_input + self.clear_non_cond_mem_for_multi_obj = clear_non_cond_mem_for_multi_obj + + @torch.inference_mode() + def init_state( + self, + video_path, + offload_video_to_cpu=False, + offload_state_to_cpu=False, + async_loading_frames=False, + ): + """Initialize a inference state.""" + images, video_height, video_width = load_video_frames( + video_path=video_path, + image_size=self.image_size, + offload_video_to_cpu=offload_video_to_cpu, + async_loading_frames=async_loading_frames, + ) + inference_state = {} + inference_state["images"] = images + inference_state["num_frames"] = len(images) + # whether to offload the video frames to CPU memory + # turning on this option saves the GPU memory with only a very small overhead + inference_state["offload_video_to_cpu"] = offload_video_to_cpu + # whether to offload the inference state to CPU memory + # turning on this option saves the GPU memory at the cost of a lower tracking fps + # (e.g. in a test case of 768x768 model, fps dropped from 27 to 24 when tracking one object + # and from 24 to 21 when tracking two objects) + inference_state["offload_state_to_cpu"] = offload_state_to_cpu + # the original video height and width, used for resizing final output scores + inference_state["video_height"] = video_height + inference_state["video_width"] = video_width + inference_state["device"] = torch.device("cuda") + if offload_state_to_cpu: + inference_state["storage_device"] = torch.device("cpu") + else: + inference_state["storage_device"] = torch.device("cuda") + # inputs on each frame + inference_state["point_inputs_per_obj"] = {} + inference_state["mask_inputs_per_obj"] = {} + # visual features on a small number of recently visited frames for quick interactions + inference_state["cached_features"] = {} + # values that don't change across frames (so we only need to hold one copy of them) + inference_state["constants"] = {} + # mapping between client-side object id and model-side object index + inference_state["obj_id_to_idx"] = OrderedDict() + inference_state["obj_idx_to_id"] = OrderedDict() + inference_state["obj_ids"] = [] + # A storage to hold the model's tracking results and states on each frame + inference_state["output_dict"] = { + "cond_frame_outputs": {}, # dict containing {frame_idx: } + "non_cond_frame_outputs": {}, # dict containing {frame_idx: } + } + # Slice (view) of each object tracking results, sharing the same memory with "output_dict" + inference_state["output_dict_per_obj"] = {} + # A temporary storage to hold new outputs when user interact with a frame + # to add clicks or mask (it's merged into "output_dict" before propagation starts) + inference_state["temp_output_dict_per_obj"] = {} + # Frames that already holds consolidated outputs from click or mask inputs + # (we directly use their consolidated outputs during tracking) + inference_state["consolidated_frame_inds"] = { + "cond_frame_outputs": set(), # set containing frame indices + "non_cond_frame_outputs": set(), # set containing frame indices + } + # metadata for each tracking frame (e.g. which direction it's tracked) + inference_state["tracking_has_started"] = False + inference_state["frames_already_tracked"] = {} + # Warm up the visual backbone and cache the image feature on frame 0 + self._get_image_feature(inference_state, frame_idx=0, batch_size=1) + return inference_state + + def _obj_id_to_idx(self, inference_state, obj_id): + """Map client-side object id to model-side object index.""" + obj_idx = inference_state["obj_id_to_idx"].get(obj_id, None) + if obj_idx is not None: + return obj_idx + + # This is a new object id not sent to the server before. We only allow adding + # new objects *before* the tracking starts. + allow_new_object = not inference_state["tracking_has_started"] + if allow_new_object: + # get the next object slot + obj_idx = len(inference_state["obj_id_to_idx"]) + inference_state["obj_id_to_idx"][obj_id] = obj_idx + inference_state["obj_idx_to_id"][obj_idx] = obj_id + inference_state["obj_ids"] = list(inference_state["obj_id_to_idx"]) + # set up input and output structures for this object + inference_state["point_inputs_per_obj"][obj_idx] = {} + inference_state["mask_inputs_per_obj"][obj_idx] = {} + inference_state["output_dict_per_obj"][obj_idx] = { + "cond_frame_outputs": {}, # dict containing {frame_idx: } + "non_cond_frame_outputs": {}, # dict containing {frame_idx: } + } + inference_state["temp_output_dict_per_obj"][obj_idx] = { + "cond_frame_outputs": {}, # dict containing {frame_idx: } + "non_cond_frame_outputs": {}, # dict containing {frame_idx: } + } + return obj_idx + else: + raise RuntimeError( + f"Cannot add new object id {obj_id} after tracking starts. " + f"All existing object ids: {inference_state['obj_ids']}. " + f"Please call 'reset_state' to restart from scratch." + ) + + def _obj_idx_to_id(self, inference_state, obj_idx): + """Map model-side object index to client-side object id.""" + return inference_state["obj_idx_to_id"][obj_idx] + + def _get_obj_num(self, inference_state): + """Get the total number of unique object ids received so far in this session.""" + return len(inference_state["obj_idx_to_id"]) + + @torch.inference_mode() + def add_new_points( + self, + inference_state, + frame_idx, + obj_id, + points, + labels, + clear_old_points=True, + normalize_coords=True, + ): + """Add new points to a frame.""" + obj_idx = self._obj_id_to_idx(inference_state, obj_id) + point_inputs_per_frame = inference_state["point_inputs_per_obj"][obj_idx] + mask_inputs_per_frame = inference_state["mask_inputs_per_obj"][obj_idx] + + if not isinstance(points, torch.Tensor): + points = torch.tensor(points, dtype=torch.float32) + if not isinstance(labels, torch.Tensor): + labels = torch.tensor(labels, dtype=torch.int32) + if points.dim() == 2: + points = points.unsqueeze(0) # add batch dimension + if labels.dim() == 1: + labels = labels.unsqueeze(0) # add batch dimension + if normalize_coords: + video_H = inference_state["video_height"] + video_W = inference_state["video_width"] + points = points / torch.tensor([video_W, video_H]).to(points.device) + # scale the (normalized) coordinates by the model's internal image size + points = points * self.image_size + points = points.to(inference_state["device"]) + labels = labels.to(inference_state["device"]) + + if not clear_old_points: + point_inputs = point_inputs_per_frame.get(frame_idx, None) + else: + point_inputs = None + point_inputs = concat_points(point_inputs, points, labels) + + point_inputs_per_frame[frame_idx] = point_inputs + mask_inputs_per_frame.pop(frame_idx, None) + # If this frame hasn't been tracked before, we treat it as an initial conditioning + # frame, meaning that the inputs points are to generate segments on this frame without + # using any memory from other frames, like in SAM. Otherwise (if it has been tracked), + # the input points will be used to correct the already tracked masks. + is_init_cond_frame = frame_idx not in inference_state["frames_already_tracked"] + # whether to track in reverse time order + if is_init_cond_frame: + reverse = False + else: + reverse = inference_state["frames_already_tracked"][frame_idx]["reverse"] + obj_output_dict = inference_state["output_dict_per_obj"][obj_idx] + obj_temp_output_dict = inference_state["temp_output_dict_per_obj"][obj_idx] + # Add a frame to conditioning output if it's an initial conditioning frame or + # if the model sees all frames receiving clicks/mask as conditioning frames. + is_cond = is_init_cond_frame or self.add_all_frames_to_correct_as_cond + storage_key = "cond_frame_outputs" if is_cond else "non_cond_frame_outputs" + + # Get any previously predicted mask logits on this object and feed it along with + # the new clicks into the SAM mask decoder. + prev_sam_mask_logits = None + # lookup temporary output dict first, which contains the most recent output + # (if not found, then lookup conditioning and non-conditioning frame output) + prev_out = obj_temp_output_dict[storage_key].get(frame_idx) + if prev_out is None: + prev_out = obj_output_dict["cond_frame_outputs"].get(frame_idx) + if prev_out is None: + prev_out = obj_output_dict["non_cond_frame_outputs"].get(frame_idx) + + if prev_out is not None and prev_out["pred_masks"] is not None: + prev_sam_mask_logits = prev_out["pred_masks"].cuda(non_blocking=True) + # Clamp the scale of prev_sam_mask_logits to avoid rare numerical issues. + prev_sam_mask_logits = torch.clamp(prev_sam_mask_logits, -32.0, 32.0) + current_out, _ = self._run_single_frame_inference( + inference_state=inference_state, + output_dict=obj_output_dict, # run on the slice of a single object + frame_idx=frame_idx, + batch_size=1, # run on the slice of a single object + is_init_cond_frame=is_init_cond_frame, + point_inputs=point_inputs, + mask_inputs=None, + reverse=reverse, + # Skip the memory encoder when adding clicks or mask. We execute the memory encoder + # at the beginning of `propagate_in_video` (after user finalize their clicks). This + # allows us to enforce non-overlapping constraints on all objects before encoding + # them into memory. + run_mem_encoder=False, + prev_sam_mask_logits=prev_sam_mask_logits, + ) + # Add the output to the output dict (to be used as future memory) + obj_temp_output_dict[storage_key][frame_idx] = current_out + + # Resize the output mask to the original video resolution + obj_ids = inference_state["obj_ids"] + consolidated_out = self._consolidate_temp_output_across_obj( + inference_state, + frame_idx, + is_cond=is_cond, + run_mem_encoder=False, + consolidate_at_video_res=True, + ) + _, video_res_masks = self._get_orig_video_res_output( + inference_state, consolidated_out["pred_masks_video_res"] + ) + return frame_idx, obj_ids, video_res_masks + + @torch.inference_mode() + def add_new_mask( + self, + inference_state, + frame_idx, + obj_id, + mask, + ): + """Add new mask to a frame.""" + obj_idx = self._obj_id_to_idx(inference_state, obj_id) + point_inputs_per_frame = inference_state["point_inputs_per_obj"][obj_idx] + mask_inputs_per_frame = inference_state["mask_inputs_per_obj"][obj_idx] + + if not isinstance(mask, torch.Tensor): + mask = torch.tensor(mask, dtype=torch.bool) + assert mask.dim() == 2 + mask_H, mask_W = mask.shape + mask_inputs_orig = mask[None, None] # add batch and channel dimension + mask_inputs_orig = mask_inputs_orig.float().to(inference_state["device"]) + + # resize the mask if it doesn't match the model's image size + if mask_H != self.image_size or mask_W != self.image_size: + mask_inputs = torch.nn.functional.interpolate( + mask_inputs_orig, + size=(self.image_size, self.image_size), + align_corners=False, + mode="bilinear", + antialias=True, # use antialias for downsampling + ) + mask_inputs = (mask_inputs >= 0.5).float() + else: + mask_inputs = mask_inputs_orig + + mask_inputs_per_frame[frame_idx] = mask_inputs + point_inputs_per_frame.pop(frame_idx, None) + # If this frame hasn't been tracked before, we treat it as an initial conditioning + # frame, meaning that the inputs points are to generate segments on this frame without + # using any memory from other frames, like in SAM. Otherwise (if it has been tracked), + # the input points will be used to correct the already tracked masks. + is_init_cond_frame = frame_idx not in inference_state["frames_already_tracked"] + # whether to track in reverse time order + if is_init_cond_frame: + reverse = False + else: + reverse = inference_state["frames_already_tracked"][frame_idx]["reverse"] + obj_output_dict = inference_state["output_dict_per_obj"][obj_idx] + obj_temp_output_dict = inference_state["temp_output_dict_per_obj"][obj_idx] + # Add a frame to conditioning output if it's an initial conditioning frame or + # if the model sees all frames receiving clicks/mask as conditioning frames. + is_cond = is_init_cond_frame or self.add_all_frames_to_correct_as_cond + storage_key = "cond_frame_outputs" if is_cond else "non_cond_frame_outputs" + + current_out, _ = self._run_single_frame_inference( + inference_state=inference_state, + output_dict=obj_output_dict, # run on the slice of a single object + frame_idx=frame_idx, + batch_size=1, # run on the slice of a single object + is_init_cond_frame=is_init_cond_frame, + point_inputs=None, + mask_inputs=mask_inputs, + reverse=reverse, + # Skip the memory encoder when adding clicks or mask. We execute the memory encoder + # at the beginning of `propagate_in_video` (after user finalize their clicks). This + # allows us to enforce non-overlapping constraints on all objects before encoding + # them into memory. + run_mem_encoder=False, + ) + # Add the output to the output dict (to be used as future memory) + obj_temp_output_dict[storage_key][frame_idx] = current_out + + # Resize the output mask to the original video resolution + obj_ids = inference_state["obj_ids"] + consolidated_out = self._consolidate_temp_output_across_obj( + inference_state, + frame_idx, + is_cond=is_cond, + run_mem_encoder=False, + consolidate_at_video_res=True, + ) + _, video_res_masks = self._get_orig_video_res_output( + inference_state, consolidated_out["pred_masks_video_res"] + ) + return frame_idx, obj_ids, video_res_masks + + def _get_orig_video_res_output(self, inference_state, any_res_masks): + """ + Resize the object scores to the original video resolution (video_res_masks) + and apply non-overlapping constraints for final output. + """ + device = inference_state["device"] + video_H = inference_state["video_height"] + video_W = inference_state["video_width"] + any_res_masks = any_res_masks.to(device, non_blocking=True) + if any_res_masks.shape[-2:] == (video_H, video_W): + video_res_masks = any_res_masks + else: + video_res_masks = torch.nn.functional.interpolate( + any_res_masks, + size=(video_H, video_W), + mode="bilinear", + align_corners=False, + ) + if self.non_overlap_masks: + video_res_masks = self._apply_non_overlapping_constraints(video_res_masks) + return any_res_masks, video_res_masks + + def _consolidate_temp_output_across_obj( + self, + inference_state, + frame_idx, + is_cond, + run_mem_encoder, + consolidate_at_video_res=False, + ): + """ + Consolidate the per-object temporary outputs in `temp_output_dict_per_obj` on + a frame into a single output for all objects, including + 1) fill any missing objects either from `output_dict_per_obj` (if they exist in + `output_dict_per_obj` for this frame) or leave them as placeholder values + (if they don't exist in `output_dict_per_obj` for this frame); + 2) if specified, rerun memory encoder after apply non-overlapping constraints + on the object scores. + """ + batch_size = self._get_obj_num(inference_state) + storage_key = "cond_frame_outputs" if is_cond else "non_cond_frame_outputs" + # Optionally, we allow consolidating the temporary outputs at the original + # video resolution (to provide a better editing experience for mask prompts). + if consolidate_at_video_res: + assert not run_mem_encoder, "memory encoder cannot run at video resolution" + consolidated_H = inference_state["video_height"] + consolidated_W = inference_state["video_width"] + consolidated_mask_key = "pred_masks_video_res" + else: + consolidated_H = consolidated_W = self.image_size // 4 + consolidated_mask_key = "pred_masks" + + # Initialize `consolidated_out`. Its "maskmem_features" and "maskmem_pos_enc" + # will be added when rerunning the memory encoder after applying non-overlapping + # constraints to object scores. Its "pred_masks" are prefilled with a large + # negative value (NO_OBJ_SCORE) to represent missing objects. + consolidated_out = { + "maskmem_features": None, + "maskmem_pos_enc": None, + consolidated_mask_key: torch.full( + size=(batch_size, 1, consolidated_H, consolidated_W), + fill_value=NO_OBJ_SCORE, + dtype=torch.float32, + device=inference_state["storage_device"], + ), + "obj_ptr": torch.full( + size=(batch_size, self.hidden_dim), + fill_value=NO_OBJ_SCORE, + dtype=torch.float32, + device=inference_state["device"], + ), + } + empty_mask_ptr = None + for obj_idx in range(batch_size): + obj_temp_output_dict = inference_state["temp_output_dict_per_obj"][obj_idx] + obj_output_dict = inference_state["output_dict_per_obj"][obj_idx] + out = obj_temp_output_dict[storage_key].get(frame_idx, None) + # If the object doesn't appear in "temp_output_dict_per_obj" on this frame, + # we fall back and look up its previous output in "output_dict_per_obj". + # We look up both "cond_frame_outputs" and "non_cond_frame_outputs" in + # "output_dict_per_obj" to find a previous output for this object. + if out is None: + out = obj_output_dict["cond_frame_outputs"].get(frame_idx, None) + if out is None: + out = obj_output_dict["non_cond_frame_outputs"].get(frame_idx, None) + # If the object doesn't appear in "output_dict_per_obj" either, we skip it + # and leave its mask scores to the default scores (i.e. the NO_OBJ_SCORE + # placeholder above) and set its object pointer to be a dummy pointer. + if out is None: + # Fill in dummy object pointers for those objects without any inputs or + # tracking outcomes on this frame (only do it under `run_mem_encoder=True`, + # i.e. when we need to build the memory for tracking). + if run_mem_encoder: + if empty_mask_ptr is None: + empty_mask_ptr = self._get_empty_mask_ptr( + inference_state, frame_idx + ) + # fill object pointer with a dummy pointer (based on an empty mask) + consolidated_out["obj_ptr"][obj_idx : obj_idx + 1] = empty_mask_ptr + continue + # Add the temporary object output mask to consolidated output mask + obj_mask = out["pred_masks"] + consolidated_pred_masks = consolidated_out[consolidated_mask_key] + if obj_mask.shape[-2:] == consolidated_pred_masks.shape[-2:]: + consolidated_pred_masks[obj_idx : obj_idx + 1] = obj_mask + else: + # Resize first if temporary object mask has a different resolution + resized_obj_mask = torch.nn.functional.interpolate( + obj_mask, + size=consolidated_pred_masks.shape[-2:], + mode="bilinear", + align_corners=False, + ) + consolidated_pred_masks[obj_idx : obj_idx + 1] = resized_obj_mask + consolidated_out["obj_ptr"][obj_idx : obj_idx + 1] = out["obj_ptr"] + + # Optionally, apply non-overlapping constraints on the consolidated scores + # and rerun the memory encoder + if run_mem_encoder: + device = inference_state["device"] + high_res_masks = torch.nn.functional.interpolate( + consolidated_out["pred_masks"].to(device, non_blocking=True), + size=(self.image_size, self.image_size), + mode="bilinear", + align_corners=False, + ) + if self.non_overlap_masks_for_mem_enc: + high_res_masks = self._apply_non_overlapping_constraints(high_res_masks) + maskmem_features, maskmem_pos_enc = self._run_memory_encoder( + inference_state=inference_state, + frame_idx=frame_idx, + batch_size=batch_size, + high_res_masks=high_res_masks, + is_mask_from_pts=True, # these frames are what the user interacted with + ) + consolidated_out["maskmem_features"] = maskmem_features + consolidated_out["maskmem_pos_enc"] = maskmem_pos_enc + + return consolidated_out + + def _get_empty_mask_ptr(self, inference_state, frame_idx): + """Get a dummy object pointer based on an empty mask on the current frame.""" + # A dummy (empty) mask with a single object + batch_size = 1 + mask_inputs = torch.zeros( + (batch_size, 1, self.image_size, self.image_size), + dtype=torch.float32, + device=inference_state["device"], + ) + + # Retrieve correct image features + ( + _, + _, + current_vision_feats, + current_vision_pos_embeds, + feat_sizes, + ) = self._get_image_feature(inference_state, frame_idx, batch_size) + + # Feed the empty mask and image feature above to get a dummy object pointer + current_out = self.track_step( + frame_idx=frame_idx, + is_init_cond_frame=True, + current_vision_feats=current_vision_feats, + current_vision_pos_embeds=current_vision_pos_embeds, + feat_sizes=feat_sizes, + point_inputs=None, + mask_inputs=mask_inputs, + output_dict={}, + num_frames=inference_state["num_frames"], + track_in_reverse=False, + run_mem_encoder=False, + prev_sam_mask_logits=None, + ) + return current_out["obj_ptr"] + + @torch.inference_mode() + def propagate_in_video_preflight(self, inference_state): + """Prepare inference_state and consolidate temporary outputs before tracking.""" + # Tracking has started and we don't allow adding new objects until session is reset. + inference_state["tracking_has_started"] = True + batch_size = self._get_obj_num(inference_state) + + # Consolidate per-object temporary outputs in "temp_output_dict_per_obj" and + # add them into "output_dict". + temp_output_dict_per_obj = inference_state["temp_output_dict_per_obj"] + output_dict = inference_state["output_dict"] + # "consolidated_frame_inds" contains indices of those frames where consolidated + # temporary outputs have been added (either in this call or any previous calls + # to `propagate_in_video_preflight`). + consolidated_frame_inds = inference_state["consolidated_frame_inds"] + for is_cond in [False, True]: + # Separately consolidate conditioning and non-conditioning temp outptus + storage_key = "cond_frame_outputs" if is_cond else "non_cond_frame_outputs" + # Find all the frames that contain temporary outputs for any objects + # (these should be the frames that have just received clicks for mask inputs + # via `add_new_points` or `add_new_mask`) + temp_frame_inds = set() + for obj_temp_output_dict in temp_output_dict_per_obj.values(): + temp_frame_inds.update(obj_temp_output_dict[storage_key].keys()) + consolidated_frame_inds[storage_key].update(temp_frame_inds) + # consolidate the temprary output across all objects on this frame + for frame_idx in temp_frame_inds: + consolidated_out = self._consolidate_temp_output_across_obj( + inference_state, frame_idx, is_cond=is_cond, run_mem_encoder=True + ) + # merge them into "output_dict" and also create per-object slices + output_dict[storage_key][frame_idx] = consolidated_out + self._add_output_per_object( + inference_state, frame_idx, consolidated_out, storage_key + ) + clear_non_cond_mem = self.clear_non_cond_mem_around_input and ( + self.clear_non_cond_mem_for_multi_obj or batch_size <= 1 + ) + if clear_non_cond_mem: + # clear non-conditioning memory of the surrounding frames + self._clear_non_cond_mem_around_input(inference_state, frame_idx) + + # clear temporary outputs in `temp_output_dict_per_obj` + for obj_temp_output_dict in temp_output_dict_per_obj.values(): + obj_temp_output_dict[storage_key].clear() + + # edge case: if an output is added to "cond_frame_outputs", we remove any prior + # output on the same frame in "non_cond_frame_outputs" + for frame_idx in output_dict["cond_frame_outputs"]: + output_dict["non_cond_frame_outputs"].pop(frame_idx, None) + for obj_output_dict in inference_state["output_dict_per_obj"].values(): + for frame_idx in obj_output_dict["cond_frame_outputs"]: + obj_output_dict["non_cond_frame_outputs"].pop(frame_idx, None) + for frame_idx in consolidated_frame_inds["cond_frame_outputs"]: + assert frame_idx in output_dict["cond_frame_outputs"] + consolidated_frame_inds["non_cond_frame_outputs"].discard(frame_idx) + + # Make sure that the frame indices in "consolidated_frame_inds" are exactly those frames + # with either points or mask inputs (which should be true under a correct workflow). + all_consolidated_frame_inds = ( + consolidated_frame_inds["cond_frame_outputs"] + | consolidated_frame_inds["non_cond_frame_outputs"] + ) + input_frames_inds = set() + for point_inputs_per_frame in inference_state["point_inputs_per_obj"].values(): + input_frames_inds.update(point_inputs_per_frame.keys()) + for mask_inputs_per_frame in inference_state["mask_inputs_per_obj"].values(): + input_frames_inds.update(mask_inputs_per_frame.keys()) + assert all_consolidated_frame_inds == input_frames_inds + + @torch.inference_mode() + def propagate_in_video( + self, + inference_state, + start_frame_idx=None, + max_frame_num_to_track=None, + reverse=False, + ): + """Propagate the input points across frames to track in the entire video.""" + self.propagate_in_video_preflight(inference_state) + + output_dict = inference_state["output_dict"] + consolidated_frame_inds = inference_state["consolidated_frame_inds"] + obj_ids = inference_state["obj_ids"] + num_frames = inference_state["num_frames"] + batch_size = self._get_obj_num(inference_state) + if len(output_dict["cond_frame_outputs"]) == 0: + raise RuntimeError("No points are provided; please add points first") + clear_non_cond_mem = self.clear_non_cond_mem_around_input and ( + self.clear_non_cond_mem_for_multi_obj or batch_size <= 1 + ) + + # set start index, end index, and processing order + if start_frame_idx is None: + # default: start from the earliest frame with input points + start_frame_idx = min(output_dict["cond_frame_outputs"]) + if max_frame_num_to_track is None: + # default: track all the frames in the video + max_frame_num_to_track = num_frames + if reverse: + end_frame_idx = max(start_frame_idx - max_frame_num_to_track, 0) + if start_frame_idx > 0: + processing_order = range(start_frame_idx, end_frame_idx - 1, -1) + else: + processing_order = [] # skip reverse tracking if starting from frame 0 + else: + end_frame_idx = min( + start_frame_idx + max_frame_num_to_track, num_frames - 1 + ) + processing_order = range(start_frame_idx, end_frame_idx + 1) + + for frame_idx in tqdm(processing_order, desc="propagate in video"): + # We skip those frames already in consolidated outputs (these are frames + # that received input clicks or mask). Note that we cannot directly run + # batched forward on them via `_run_single_frame_inference` because the + # number of clicks on each object might be different. + if frame_idx in consolidated_frame_inds["cond_frame_outputs"]: + storage_key = "cond_frame_outputs" + current_out = output_dict[storage_key][frame_idx] + pred_masks = current_out["pred_masks"] + if clear_non_cond_mem: + # clear non-conditioning memory of the surrounding frames + self._clear_non_cond_mem_around_input(inference_state, frame_idx) + elif frame_idx in consolidated_frame_inds["non_cond_frame_outputs"]: + storage_key = "non_cond_frame_outputs" + current_out = output_dict[storage_key][frame_idx] + pred_masks = current_out["pred_masks"] + else: + storage_key = "non_cond_frame_outputs" + current_out, pred_masks = self._run_single_frame_inference( + inference_state=inference_state, + output_dict=output_dict, + frame_idx=frame_idx, + batch_size=batch_size, + is_init_cond_frame=False, + point_inputs=None, + mask_inputs=None, + reverse=reverse, + run_mem_encoder=True, + ) + output_dict[storage_key][frame_idx] = current_out + # Create slices of per-object outputs for subsequent interaction with each + # individual object after tracking. + self._add_output_per_object( + inference_state, frame_idx, current_out, storage_key + ) + inference_state["frames_already_tracked"][frame_idx] = {"reverse": reverse} + + # Resize the output mask to the original video resolution (we directly use + # the mask scores on GPU for output to avoid any CPU conversion in between) + _, video_res_masks = self._get_orig_video_res_output( + inference_state, pred_masks + ) + yield frame_idx, obj_ids, video_res_masks + + def _add_output_per_object( + self, inference_state, frame_idx, current_out, storage_key + ): + """ + Split a multi-object output into per-object output slices and add them into + `output_dict_per_obj`. The resulting slices share the same tensor storage. + """ + maskmem_features = current_out["maskmem_features"] + assert maskmem_features is None or isinstance(maskmem_features, torch.Tensor) + + maskmem_pos_enc = current_out["maskmem_pos_enc"] + assert maskmem_pos_enc is None or isinstance(maskmem_pos_enc, list) + + output_dict_per_obj = inference_state["output_dict_per_obj"] + for obj_idx, obj_output_dict in output_dict_per_obj.items(): + obj_slice = slice(obj_idx, obj_idx + 1) + obj_out = { + "maskmem_features": None, + "maskmem_pos_enc": None, + "pred_masks": current_out["pred_masks"][obj_slice], + "obj_ptr": current_out["obj_ptr"][obj_slice], + } + if maskmem_features is not None: + obj_out["maskmem_features"] = maskmem_features[obj_slice] + if maskmem_pos_enc is not None: + obj_out["maskmem_pos_enc"] = [x[obj_slice] for x in maskmem_pos_enc] + obj_output_dict[storage_key][frame_idx] = obj_out + + @torch.inference_mode() + def reset_state(self, inference_state): + """Remove all input points or mask in all frames throughout the video.""" + self._reset_tracking_results(inference_state) + # Remove all object ids + inference_state["obj_id_to_idx"].clear() + inference_state["obj_idx_to_id"].clear() + inference_state["obj_ids"].clear() + inference_state["point_inputs_per_obj"].clear() + inference_state["mask_inputs_per_obj"].clear() + inference_state["output_dict_per_obj"].clear() + inference_state["temp_output_dict_per_obj"].clear() + + def _reset_tracking_results(self, inference_state): + """Reset all tracking inputs and results across the videos.""" + for v in inference_state["point_inputs_per_obj"].values(): + v.clear() + for v in inference_state["mask_inputs_per_obj"].values(): + v.clear() + for v in inference_state["output_dict_per_obj"].values(): + v["cond_frame_outputs"].clear() + v["non_cond_frame_outputs"].clear() + for v in inference_state["temp_output_dict_per_obj"].values(): + v["cond_frame_outputs"].clear() + v["non_cond_frame_outputs"].clear() + inference_state["output_dict"]["cond_frame_outputs"].clear() + inference_state["output_dict"]["non_cond_frame_outputs"].clear() + inference_state["consolidated_frame_inds"]["cond_frame_outputs"].clear() + inference_state["consolidated_frame_inds"]["non_cond_frame_outputs"].clear() + inference_state["tracking_has_started"] = False + inference_state["frames_already_tracked"].clear() + + def _get_image_feature(self, inference_state, frame_idx, batch_size): + """Compute the image features on a given frame.""" + # Look up in the cache first + image, backbone_out = inference_state["cached_features"].get( + frame_idx, (None, None) + ) + if backbone_out is None: + # Cache miss -- we will run inference on a single image + image = inference_state["images"][frame_idx].cuda().float().unsqueeze(0) + backbone_out = self.forward_image(image) + # Cache the most recent frame's feature (for repeated interactions with + # a frame; we can use an LRU cache for more frames in the future). + inference_state["cached_features"] = {frame_idx: (image, backbone_out)} + + # expand the features to have the same dimension as the number of objects + expanded_image = image.expand(batch_size, -1, -1, -1) + expanded_backbone_out = { + "backbone_fpn": backbone_out["backbone_fpn"].copy(), + "vision_pos_enc": backbone_out["vision_pos_enc"].copy(), + } + for i, feat in enumerate(expanded_backbone_out["backbone_fpn"]): + expanded_backbone_out["backbone_fpn"][i] = feat.expand( + batch_size, -1, -1, -1 + ) + for i, pos in enumerate(expanded_backbone_out["vision_pos_enc"]): + pos = pos.expand(batch_size, -1, -1, -1) + expanded_backbone_out["vision_pos_enc"][i] = pos + + features = self._prepare_backbone_features(expanded_backbone_out) + features = (expanded_image,) + features + return features + + def _run_single_frame_inference( + self, + inference_state, + output_dict, + frame_idx, + batch_size, + is_init_cond_frame, + point_inputs, + mask_inputs, + reverse, + run_mem_encoder, + prev_sam_mask_logits=None, + ): + """Run tracking on a single frame based on current inputs and previous memory.""" + # Retrieve correct image features + ( + _, + _, + current_vision_feats, + current_vision_pos_embeds, + feat_sizes, + ) = self._get_image_feature(inference_state, frame_idx, batch_size) + + # point and mask should not appear as input simultaneously on the same frame + assert point_inputs is None or mask_inputs is None + current_out = self.track_step( + frame_idx=frame_idx, + is_init_cond_frame=is_init_cond_frame, + current_vision_feats=current_vision_feats, + current_vision_pos_embeds=current_vision_pos_embeds, + feat_sizes=feat_sizes, + point_inputs=point_inputs, + mask_inputs=mask_inputs, + output_dict=output_dict, + num_frames=inference_state["num_frames"], + track_in_reverse=reverse, + run_mem_encoder=run_mem_encoder, + prev_sam_mask_logits=prev_sam_mask_logits, + ) + + # optionally offload the output to CPU memory to save GPU space + storage_device = inference_state["storage_device"] + maskmem_features = current_out["maskmem_features"] + if maskmem_features is not None: + maskmem_features = maskmem_features.to(torch.bfloat16) + maskmem_features = maskmem_features.to(storage_device, non_blocking=True) + pred_masks_gpu = current_out["pred_masks"] + # potentially fill holes in the predicted masks + if self.fill_hole_area > 0: + pred_masks_gpu = fill_holes_in_mask_scores( + pred_masks_gpu, self.fill_hole_area + ) + pred_masks = pred_masks_gpu.to(storage_device, non_blocking=True) + # "maskmem_pos_enc" is the same across frames, so we only need to store one copy of it + maskmem_pos_enc = self._get_maskmem_pos_enc(inference_state, current_out) + # object pointer is a small tensor, so we always keep it on GPU memory for fast access + obj_ptr = current_out["obj_ptr"] + # make a compact version of this frame's output to reduce the state size + compact_current_out = { + "maskmem_features": maskmem_features, + "maskmem_pos_enc": maskmem_pos_enc, + "pred_masks": pred_masks, + "obj_ptr": obj_ptr, + } + return compact_current_out, pred_masks_gpu + + def _run_memory_encoder( + self, inference_state, frame_idx, batch_size, high_res_masks, is_mask_from_pts + ): + """ + Run the memory encoder on `high_res_masks`. This is usually after applying + non-overlapping constraints to object scores. Since their scores changed, their + memory also need to be computed again with the memory encoder. + """ + # Retrieve correct image features + _, _, current_vision_feats, _, feat_sizes = self._get_image_feature( + inference_state, frame_idx, batch_size + ) + maskmem_features, maskmem_pos_enc = self._encode_new_memory( + current_vision_feats=current_vision_feats, + feat_sizes=feat_sizes, + pred_masks_high_res=high_res_masks, + is_mask_from_pts=is_mask_from_pts, + ) + + # optionally offload the output to CPU memory to save GPU space + storage_device = inference_state["storage_device"] + maskmem_features = maskmem_features.to(torch.bfloat16) + maskmem_features = maskmem_features.to(storage_device, non_blocking=True) + # "maskmem_pos_enc" is the same across frames, so we only need to store one copy of it + maskmem_pos_enc = self._get_maskmem_pos_enc( + inference_state, {"maskmem_pos_enc": maskmem_pos_enc} + ) + return maskmem_features, maskmem_pos_enc + + def _get_maskmem_pos_enc(self, inference_state, current_out): + """ + `maskmem_pos_enc` is the same across frames and objects, so we cache it as + a constant in the inference session to reduce session storage size. + """ + model_constants = inference_state["constants"] + # "out_maskmem_pos_enc" should be either a list of tensors or None + out_maskmem_pos_enc = current_out["maskmem_pos_enc"] + if out_maskmem_pos_enc is not None: + if "maskmem_pos_enc" not in model_constants: + assert isinstance(out_maskmem_pos_enc, list) + # only take the slice for one object, since it's same across objects + maskmem_pos_enc = [x[0:1].clone() for x in out_maskmem_pos_enc] + model_constants["maskmem_pos_enc"] = maskmem_pos_enc + else: + maskmem_pos_enc = model_constants["maskmem_pos_enc"] + # expand the cached maskmem_pos_enc to the actual batch size + batch_size = out_maskmem_pos_enc[0].size(0) + expanded_maskmem_pos_enc = [ + x.expand(batch_size, -1, -1, -1) for x in maskmem_pos_enc + ] + else: + expanded_maskmem_pos_enc = None + return expanded_maskmem_pos_enc + + def _clear_non_cond_mem_around_input(self, inference_state, frame_idx): + """ + Remove the non-conditioning memory around the input frame. When users provide + correction clicks, the surrounding frames' non-conditioning memories can still + contain outdated object appearance information and could confuse the model. + + This method clears those non-conditioning memories surrounding the interacted + frame to avoid giving the model both old and new information about the object. + """ + r = self.memory_temporal_stride_for_eval + frame_idx_begin = frame_idx - r * self.num_maskmem + frame_idx_end = frame_idx + r * self.num_maskmem + output_dict = inference_state["output_dict"] + non_cond_frame_outputs = output_dict["non_cond_frame_outputs"] + for t in range(frame_idx_begin, frame_idx_end + 1): + non_cond_frame_outputs.pop(t, None) + for obj_output_dict in inference_state["output_dict_per_obj"].values(): + obj_output_dict["non_cond_frame_outputs"].pop(t, None) diff --git a/sam2/utils/__init__.py b/sam2/utils/__init__.py new file mode 100644 index 0000000..5277f46 --- /dev/null +++ b/sam2/utils/__init__.py @@ -0,0 +1,5 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. diff --git a/sam2/utils/amg.py b/sam2/utils/amg.py new file mode 100644 index 0000000..9868429 --- /dev/null +++ b/sam2/utils/amg.py @@ -0,0 +1,348 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +import math +from copy import deepcopy +from itertools import product +from typing import Any, Dict, Generator, ItemsView, List, Tuple + +import numpy as np +import torch + +# Very lightly adapted from https://github.com/facebookresearch/segment-anything/blob/main/segment_anything/utils/amg.py + + +class MaskData: + """ + A structure for storing masks and their related data in batched format. + Implements basic filtering and concatenation. + """ + + def __init__(self, **kwargs) -> None: + for v in kwargs.values(): + assert isinstance( + v, (list, np.ndarray, torch.Tensor) + ), "MaskData only supports list, numpy arrays, and torch tensors." + self._stats = dict(**kwargs) + + def __setitem__(self, key: str, item: Any) -> None: + assert isinstance( + item, (list, np.ndarray, torch.Tensor) + ), "MaskData only supports list, numpy arrays, and torch tensors." + self._stats[key] = item + + def __delitem__(self, key: str) -> None: + del self._stats[key] + + def __getitem__(self, key: str) -> Any: + return self._stats[key] + + def items(self) -> ItemsView[str, Any]: + return self._stats.items() + + def filter(self, keep: torch.Tensor) -> None: + for k, v in self._stats.items(): + if v is None: + self._stats[k] = None + elif isinstance(v, torch.Tensor): + self._stats[k] = v[torch.as_tensor(keep, device=v.device)] + elif isinstance(v, np.ndarray): + self._stats[k] = v[keep.detach().cpu().numpy()] + elif isinstance(v, list) and keep.dtype == torch.bool: + self._stats[k] = [a for i, a in enumerate(v) if keep[i]] + elif isinstance(v, list): + self._stats[k] = [v[i] for i in keep] + else: + raise TypeError(f"MaskData key {k} has an unsupported type {type(v)}.") + + def cat(self, new_stats: "MaskData") -> None: + for k, v in new_stats.items(): + if k not in self._stats or self._stats[k] is None: + self._stats[k] = deepcopy(v) + elif isinstance(v, torch.Tensor): + self._stats[k] = torch.cat([self._stats[k], v], dim=0) + elif isinstance(v, np.ndarray): + self._stats[k] = np.concatenate([self._stats[k], v], axis=0) + elif isinstance(v, list): + self._stats[k] = self._stats[k] + deepcopy(v) + else: + raise TypeError(f"MaskData key {k} has an unsupported type {type(v)}.") + + def to_numpy(self) -> None: + for k, v in self._stats.items(): + if isinstance(v, torch.Tensor): + self._stats[k] = v.float().detach().cpu().numpy() + + +def is_box_near_crop_edge( + boxes: torch.Tensor, crop_box: List[int], orig_box: List[int], atol: float = 20.0 +) -> torch.Tensor: + """Filter masks at the edge of a crop, but not at the edge of the original image.""" + crop_box_torch = torch.as_tensor(crop_box, dtype=torch.float, device=boxes.device) + orig_box_torch = torch.as_tensor(orig_box, dtype=torch.float, device=boxes.device) + boxes = uncrop_boxes_xyxy(boxes, crop_box).float() + near_crop_edge = torch.isclose(boxes, crop_box_torch[None, :], atol=atol, rtol=0) + near_image_edge = torch.isclose(boxes, orig_box_torch[None, :], atol=atol, rtol=0) + near_crop_edge = torch.logical_and(near_crop_edge, ~near_image_edge) + return torch.any(near_crop_edge, dim=1) + + +def box_xyxy_to_xywh(box_xyxy: torch.Tensor) -> torch.Tensor: + box_xywh = deepcopy(box_xyxy) + box_xywh[2] = box_xywh[2] - box_xywh[0] + box_xywh[3] = box_xywh[3] - box_xywh[1] + return box_xywh + + +def batch_iterator(batch_size: int, *args) -> Generator[List[Any], None, None]: + assert len(args) > 0 and all( + len(a) == len(args[0]) for a in args + ), "Batched iteration must have inputs of all the same size." + n_batches = len(args[0]) // batch_size + int(len(args[0]) % batch_size != 0) + for b in range(n_batches): + yield [arg[b * batch_size : (b + 1) * batch_size] for arg in args] + + +def mask_to_rle_pytorch(tensor: torch.Tensor) -> List[Dict[str, Any]]: + """ + Encodes masks to an uncompressed RLE, in the format expected by + pycoco tools. + """ + # Put in fortran order and flatten h,w + b, h, w = tensor.shape + tensor = tensor.permute(0, 2, 1).flatten(1) + + # Compute change indices + diff = tensor[:, 1:] ^ tensor[:, :-1] + change_indices = diff.nonzero() + + # Encode run length + out = [] + for i in range(b): + cur_idxs = change_indices[change_indices[:, 0] == i, 1] + cur_idxs = torch.cat( + [ + torch.tensor([0], dtype=cur_idxs.dtype, device=cur_idxs.device), + cur_idxs + 1, + torch.tensor([h * w], dtype=cur_idxs.dtype, device=cur_idxs.device), + ] + ) + btw_idxs = cur_idxs[1:] - cur_idxs[:-1] + counts = [] if tensor[i, 0] == 0 else [0] + counts.extend(btw_idxs.detach().cpu().tolist()) + out.append({"size": [h, w], "counts": counts}) + return out + + +def rle_to_mask(rle: Dict[str, Any]) -> np.ndarray: + """Compute a binary mask from an uncompressed RLE.""" + h, w = rle["size"] + mask = np.empty(h * w, dtype=bool) + idx = 0 + parity = False + for count in rle["counts"]: + mask[idx : idx + count] = parity + idx += count + parity ^= True + mask = mask.reshape(w, h) + return mask.transpose() # Put in C order + + +def area_from_rle(rle: Dict[str, Any]) -> int: + return sum(rle["counts"][1::2]) + + +def calculate_stability_score( + masks: torch.Tensor, mask_threshold: float, threshold_offset: float +) -> torch.Tensor: + """ + Computes the stability score for a batch of masks. The stability + score is the IoU between the binary masks obtained by thresholding + the predicted mask logits at high and low values. + """ + # One mask is always contained inside the other. + # Save memory by preventing unnecessary cast to torch.int64 + intersections = ( + (masks > (mask_threshold + threshold_offset)) + .sum(-1, dtype=torch.int16) + .sum(-1, dtype=torch.int32) + ) + unions = ( + (masks > (mask_threshold - threshold_offset)) + .sum(-1, dtype=torch.int16) + .sum(-1, dtype=torch.int32) + ) + return intersections / unions + + +def build_point_grid(n_per_side: int) -> np.ndarray: + """Generates a 2D grid of points evenly spaced in [0,1]x[0,1].""" + offset = 1 / (2 * n_per_side) + points_one_side = np.linspace(offset, 1 - offset, n_per_side) + points_x = np.tile(points_one_side[None, :], (n_per_side, 1)) + points_y = np.tile(points_one_side[:, None], (1, n_per_side)) + points = np.stack([points_x, points_y], axis=-1).reshape(-1, 2) + return points + + +def build_all_layer_point_grids( + n_per_side: int, n_layers: int, scale_per_layer: int +) -> List[np.ndarray]: + """Generates point grids for all crop layers.""" + points_by_layer = [] + for i in range(n_layers + 1): + n_points = int(n_per_side / (scale_per_layer**i)) + points_by_layer.append(build_point_grid(n_points)) + return points_by_layer + + +def generate_crop_boxes( + im_size: Tuple[int, ...], n_layers: int, overlap_ratio: float +) -> Tuple[List[List[int]], List[int]]: + """ + Generates a list of crop boxes of different sizes. Each layer + has (2**i)**2 boxes for the ith layer. + """ + crop_boxes, layer_idxs = [], [] + im_h, im_w = im_size + short_side = min(im_h, im_w) + + # Original image + crop_boxes.append([0, 0, im_w, im_h]) + layer_idxs.append(0) + + def crop_len(orig_len, n_crops, overlap): + return int(math.ceil((overlap * (n_crops - 1) + orig_len) / n_crops)) + + for i_layer in range(n_layers): + n_crops_per_side = 2 ** (i_layer + 1) + overlap = int(overlap_ratio * short_side * (2 / n_crops_per_side)) + + crop_w = crop_len(im_w, n_crops_per_side, overlap) + crop_h = crop_len(im_h, n_crops_per_side, overlap) + + crop_box_x0 = [int((crop_w - overlap) * i) for i in range(n_crops_per_side)] + crop_box_y0 = [int((crop_h - overlap) * i) for i in range(n_crops_per_side)] + + # Crops in XYWH format + for x0, y0 in product(crop_box_x0, crop_box_y0): + box = [x0, y0, min(x0 + crop_w, im_w), min(y0 + crop_h, im_h)] + crop_boxes.append(box) + layer_idxs.append(i_layer + 1) + + return crop_boxes, layer_idxs + + +def uncrop_boxes_xyxy(boxes: torch.Tensor, crop_box: List[int]) -> torch.Tensor: + x0, y0, _, _ = crop_box + offset = torch.tensor([[x0, y0, x0, y0]], device=boxes.device) + # Check if boxes has a channel dimension + if len(boxes.shape) == 3: + offset = offset.unsqueeze(1) + return boxes + offset + + +def uncrop_points(points: torch.Tensor, crop_box: List[int]) -> torch.Tensor: + x0, y0, _, _ = crop_box + offset = torch.tensor([[x0, y0]], device=points.device) + # Check if points has a channel dimension + if len(points.shape) == 3: + offset = offset.unsqueeze(1) + return points + offset + + +def uncrop_masks( + masks: torch.Tensor, crop_box: List[int], orig_h: int, orig_w: int +) -> torch.Tensor: + x0, y0, x1, y1 = crop_box + if x0 == 0 and y0 == 0 and x1 == orig_w and y1 == orig_h: + return masks + # Coordinate transform masks + pad_x, pad_y = orig_w - (x1 - x0), orig_h - (y1 - y0) + pad = (x0, pad_x - x0, y0, pad_y - y0) + return torch.nn.functional.pad(masks, pad, value=0) + + +def remove_small_regions( + mask: np.ndarray, area_thresh: float, mode: str +) -> Tuple[np.ndarray, bool]: + """ + Removes small disconnected regions and holes in a mask. Returns the + mask and an indicator of if the mask has been modified. + """ + import cv2 # type: ignore + + assert mode in ["holes", "islands"] + correct_holes = mode == "holes" + working_mask = (correct_holes ^ mask).astype(np.uint8) + n_labels, regions, stats, _ = cv2.connectedComponentsWithStats(working_mask, 8) + sizes = stats[:, -1][1:] # Row 0 is background label + small_regions = [i + 1 for i, s in enumerate(sizes) if s < area_thresh] + if len(small_regions) == 0: + return mask, False + fill_labels = [0] + small_regions + if not correct_holes: + fill_labels = [i for i in range(n_labels) if i not in fill_labels] + # If every region is below threshold, keep largest + if len(fill_labels) == 0: + fill_labels = [int(np.argmax(sizes)) + 1] + mask = np.isin(regions, fill_labels) + return mask, True + + +def coco_encode_rle(uncompressed_rle: Dict[str, Any]) -> Dict[str, Any]: + from pycocotools import mask as mask_utils # type: ignore + + h, w = uncompressed_rle["size"] + rle = mask_utils.frPyObjects(uncompressed_rle, h, w) + rle["counts"] = rle["counts"].decode("utf-8") # Necessary to serialize with json + return rle + + +def batched_mask_to_box(masks: torch.Tensor) -> torch.Tensor: + """ + Calculates boxes in XYXY format around masks. Return [0,0,0,0] for + an empty mask. For input shape C1xC2x...xHxW, the output shape is C1xC2x...x4. + """ + # torch.max below raises an error on empty inputs, just skip in this case + if torch.numel(masks) == 0: + return torch.zeros(*masks.shape[:-2], 4, device=masks.device) + + # Normalize shape to CxHxW + shape = masks.shape + h, w = shape[-2:] + if len(shape) > 2: + masks = masks.flatten(0, -3) + else: + masks = masks.unsqueeze(0) + + # Get top and bottom edges + in_height, _ = torch.max(masks, dim=-1) + in_height_coords = in_height * torch.arange(h, device=in_height.device)[None, :] + bottom_edges, _ = torch.max(in_height_coords, dim=-1) + in_height_coords = in_height_coords + h * (~in_height) + top_edges, _ = torch.min(in_height_coords, dim=-1) + + # Get left and right edges + in_width, _ = torch.max(masks, dim=-2) + in_width_coords = in_width * torch.arange(w, device=in_width.device)[None, :] + right_edges, _ = torch.max(in_width_coords, dim=-1) + in_width_coords = in_width_coords + w * (~in_width) + left_edges, _ = torch.min(in_width_coords, dim=-1) + + # If the mask is empty the right edge will be to the left of the left edge. + # Replace these boxes with [0, 0, 0, 0] + empty_filter = (right_edges < left_edges) | (bottom_edges < top_edges) + out = torch.stack([left_edges, top_edges, right_edges, bottom_edges], dim=-1) + out = out * (~empty_filter).unsqueeze(-1) + + # Return to original shape + if len(shape) > 2: + out = out.reshape(*shape[:-2], 4) + else: + out = out[0] + + return out diff --git a/sam2/utils/misc.py b/sam2/utils/misc.py new file mode 100644 index 0000000..bf6a179 --- /dev/null +++ b/sam2/utils/misc.py @@ -0,0 +1,238 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +import os +import warnings +from threading import Thread + +import numpy as np +import torch +from PIL import Image +from tqdm import tqdm + + +def get_sdpa_settings(): + if torch.cuda.is_available(): + old_gpu = torch.cuda.get_device_properties(0).major < 7 + # only use Flash Attention on Ampere (8.0) or newer GPUs + use_flash_attn = torch.cuda.get_device_properties(0).major >= 8 + if not use_flash_attn: + warnings.warn( + "Flash Attention is disabled as it requires a GPU with Ampere (8.0) CUDA capability.", + category=UserWarning, + stacklevel=2, + ) + # keep math kernel for PyTorch versions before 2.2 (Flash Attention v2 is only + # available on PyTorch 2.2+, while Flash Attention v1 cannot handle all cases) + pytorch_version = tuple(int(v) for v in torch.__version__.split(".")[:2]) + if pytorch_version < (2, 2): + warnings.warn( + f"You are using PyTorch {torch.__version__} without Flash Attention v2 support. " + "Consider upgrading to PyTorch 2.2+ for Flash Attention v2 (which could be faster).", + category=UserWarning, + stacklevel=2, + ) + math_kernel_on = pytorch_version < (2, 2) or not use_flash_attn + else: + old_gpu = True + use_flash_attn = False + math_kernel_on = True + + return old_gpu, use_flash_attn, math_kernel_on + + +def get_connected_components(mask): + """ + Get the connected components (8-connectivity) of binary masks of shape (N, 1, H, W). + + Inputs: + - mask: A binary mask tensor of shape (N, 1, H, W), where 1 is foreground and 0 is + background. + + Outputs: + - labels: A tensor of shape (N, 1, H, W) containing the connected component labels + for foreground pixels and 0 for background pixels. + - counts: A tensor of shape (N, 1, H, W) containing the area of the connected + components for foreground pixels and 0 for background pixels. + """ + from sam2 import _C + + return _C.get_connected_componnets(mask.to(torch.uint8).contiguous()) + + +def mask_to_box(masks: torch.Tensor): + """ + compute bounding box given an input mask + + Inputs: + - masks: [B, 1, H, W] boxes, dtype=torch.Tensor + + Returns: + - box_coords: [B, 1, 4], contains (x, y) coordinates of top left and bottom right box corners, dtype=torch.Tensor + """ + B, _, h, w = masks.shape + device = masks.device + xs = torch.arange(w, device=device, dtype=torch.int32) + ys = torch.arange(h, device=device, dtype=torch.int32) + grid_xs, grid_ys = torch.meshgrid(xs, ys, indexing="xy") + grid_xs = grid_xs[None, None, ...].expand(B, 1, h, w) + grid_ys = grid_ys[None, None, ...].expand(B, 1, h, w) + min_xs, _ = torch.min(torch.where(masks, grid_xs, w).flatten(-2), dim=-1) + max_xs, _ = torch.max(torch.where(masks, grid_xs, -1).flatten(-2), dim=-1) + min_ys, _ = torch.min(torch.where(masks, grid_ys, h).flatten(-2), dim=-1) + max_ys, _ = torch.max(torch.where(masks, grid_ys, -1).flatten(-2), dim=-1) + bbox_coords = torch.stack((min_xs, min_ys, max_xs, max_ys), dim=-1) + + return bbox_coords + + +def _load_img_as_tensor(img_path, image_size): + img_pil = Image.open(img_path) + img_np = np.array(img_pil.convert("RGB").resize((image_size, image_size))) + if img_np.dtype == np.uint8: # np.uint8 is expected for JPEG images + img_np = img_np / 255.0 + else: + raise RuntimeError(f"Unknown image dtype: {img_np.dtype} on {img_path}") + img = torch.from_numpy(img_np).permute(2, 0, 1) + video_width, video_height = img_pil.size # the original video size + return img, video_height, video_width + + +class AsyncVideoFrameLoader: + """ + A list of video frames to be load asynchronously without blocking session start. + """ + + def __init__(self, img_paths, image_size, offload_video_to_cpu, img_mean, img_std): + self.img_paths = img_paths + self.image_size = image_size + self.offload_video_to_cpu = offload_video_to_cpu + self.img_mean = img_mean + self.img_std = img_std + # items in `self._images` will be loaded asynchronously + self.images = [None] * len(img_paths) + # catch and raise any exceptions in the async loading thread + self.exception = None + # video_height and video_width be filled when loading the first image + self.video_height = None + self.video_width = None + + # load the first frame to fill video_height and video_width and also + # to cache it (since it's most likely where the user will click) + self.__getitem__(0) + + # load the rest of frames asynchronously without blocking the session start + def _load_frames(): + try: + for n in tqdm(range(len(self.images)), desc="frame loading (JPEG)"): + self.__getitem__(n) + except Exception as e: + self.exception = e + + self.thread = Thread(target=_load_frames, daemon=True) + self.thread.start() + + def __getitem__(self, index): + if self.exception is not None: + raise RuntimeError("Failure in frame loading thread") from self.exception + + img = self.images[index] + if img is not None: + return img + + img, video_height, video_width = _load_img_as_tensor( + self.img_paths[index], self.image_size + ) + self.video_height = video_height + self.video_width = video_width + # normalize by mean and std + img -= self.img_mean + img /= self.img_std + if not self.offload_video_to_cpu: + img = img.cuda(non_blocking=True) + self.images[index] = img + return img + + def __len__(self): + return len(self.images) + + +def load_video_frames( + video_path, + image_size, + offload_video_to_cpu, + img_mean=(0.485, 0.456, 0.406), + img_std=(0.229, 0.224, 0.225), + async_loading_frames=False, +): + """ + Load the video frames from a directory of JPEG files (".jpg" format). + + The frames are resized to image_size x image_size and are loaded to GPU if + `offload_video_to_cpu` is `False` and to CPU if `offload_video_to_cpu` is `True`. + + You can load a frame asynchronously by setting `async_loading_frames` to `True`. + """ + if isinstance(video_path, str) and os.path.isdir(video_path): + jpg_folder = video_path + else: + raise NotImplementedError("Only JPEG frames are supported at this moment") + + frame_names = [ + p + for p in os.listdir(jpg_folder) + if os.path.splitext(p)[-1] in [".jpg", ".jpeg", ".JPG", ".JPEG"] + ] + frame_names.sort(key=lambda p: int(os.path.splitext(p)[0])) + num_frames = len(frame_names) + if num_frames == 0: + raise RuntimeError(f"no images found in {jpg_folder}") + img_paths = [os.path.join(jpg_folder, frame_name) for frame_name in frame_names] + img_mean = torch.tensor(img_mean, dtype=torch.float32)[:, None, None] + img_std = torch.tensor(img_std, dtype=torch.float32)[:, None, None] + + if async_loading_frames: + lazy_images = AsyncVideoFrameLoader( + img_paths, image_size, offload_video_to_cpu, img_mean, img_std + ) + return lazy_images, lazy_images.video_height, lazy_images.video_width + + images = torch.zeros(num_frames, 3, image_size, image_size, dtype=torch.float32) + for n, img_path in enumerate(tqdm(img_paths, desc="frame loading (JPEG)")): + images[n], video_height, video_width = _load_img_as_tensor(img_path, image_size) + if not offload_video_to_cpu: + images = images.cuda() + img_mean = img_mean.cuda() + img_std = img_std.cuda() + # normalize by mean and std + images -= img_mean + images /= img_std + return images, video_height, video_width + + +def fill_holes_in_mask_scores(mask, max_area): + """ + A post processor to fill small holes in mask scores with area under `max_area`. + """ + # Holes are those connected components in background with area <= self.max_area + # (background regions are those with mask scores <= 0) + assert max_area > 0, "max_area must be positive" + labels, areas = get_connected_components(mask <= 0) + is_hole = (labels > 0) & (areas <= max_area) + # We fill holes with a small positive mask score (0.1) to change them to foreground. + mask = torch.where(is_hole, 0.1, mask) + return mask + + +def concat_points(old_point_inputs, new_points, new_labels): + """Add new points and labels to previous point inputs (add at the end).""" + if old_point_inputs is None: + points, labels = new_points, new_labels + else: + points = torch.cat([old_point_inputs["point_coords"], new_points], dim=1) + labels = torch.cat([old_point_inputs["point_labels"], new_labels], dim=1) + + return {"point_coords": points, "point_labels": labels} diff --git a/sam2/utils/transforms.py b/sam2/utils/transforms.py new file mode 100644 index 0000000..d05cd3e --- /dev/null +++ b/sam2/utils/transforms.py @@ -0,0 +1,99 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +import torch +import torch.nn as nn +import torch.nn.functional as F +from torchvision.transforms import Normalize, Resize, ToTensor + + +class SAM2Transforms(nn.Module): + def __init__( + self, resolution, mask_threshold, max_hole_area=0.0, max_sprinkle_area=0.0 + ): + """ + Transforms for SAM2. + """ + super().__init__() + self.resolution = resolution + self.mask_threshold = mask_threshold + self.max_hole_area = max_hole_area + self.max_sprinkle_area = max_sprinkle_area + self.mean = [0.485, 0.456, 0.406] + self.std = [0.229, 0.224, 0.225] + self.to_tensor = ToTensor() + self.transforms = torch.jit.script( + nn.Sequential( + Resize((self.resolution, self.resolution)), + Normalize(self.mean, self.std), + ) + ) + + def __call__(self, x): + x = self.to_tensor(x) + return self.transforms(x) + + def forward_batch(self, img_list): + img_batch = [self.transforms(self.to_tensor(img)) for img in img_list] + img_batch = torch.stack(img_batch, dim=0) + return img_batch + + def transform_coords( + self, coords: torch.Tensor, normalize=False, orig_hw=None + ) -> torch.Tensor: + """ + Expects a torch tensor with length 2 in the last dimension. The coordinates can be in absolute image or normalized coordinates, + If the coords are in absolute image coordinates, normalize should be set to True and original image size is required. + + Returns + Un-normalized coordinates in the range of [0, 1] which is expected by the SAM2 model. + """ + if normalize: + assert orig_hw is not None + h, w = orig_hw + coords = coords.clone() + coords[..., 0] = coords[..., 0] / w + coords[..., 1] = coords[..., 1] / h + + coords = coords * self.resolution # unnormalize coords + return coords + + def transform_boxes( + self, boxes: torch.Tensor, normalize=False, orig_hw=None + ) -> torch.Tensor: + """ + Expects a tensor of shape Bx4. The coordinates can be in absolute image or normalized coordinates, + if the coords are in absolute image coordinates, normalize should be set to True and original image size is required. + """ + boxes = self.transform_coords(boxes.reshape(-1, 2, 2), normalize, orig_hw) + return boxes + + def postprocess_masks(self, masks: torch.Tensor, orig_hw) -> torch.Tensor: + """ + Perform PostProcessing on output masks. + """ + from sam2.utils.misc import get_connected_components + + masks = masks.float() + if self.max_hole_area > 0: + # Holes are those connected components in background with area <= self.fill_hole_area + # (background regions are those with mask scores <= self.mask_threshold) + mask_flat = masks.flatten(0, 1).unsqueeze(1) # flatten as 1-channel image + labels, areas = get_connected_components(mask_flat <= self.mask_threshold) + is_hole = (labels > 0) & (areas <= self.max_hole_area) + is_hole = is_hole.reshape_as(masks) + # We fill holes with a small positive mask score (10.0) to change them to foreground. + masks = torch.where(is_hole, self.mask_threshold + 10.0, masks) + + if self.max_sprinkle_area > 0: + labels, areas = get_connected_components(mask_flat > self.mask_threshold) + is_hole = (labels > 0) & (areas <= self.max_sprinkle_area) + is_hole = is_hole.reshape_as(masks) + # We fill holes with negative mask score (-10.0) to change them to background. + masks = torch.where(is_hole, self.mask_threshold - 10.0, masks) + + masks = F.interpolate(masks, orig_hw, mode="bilinear", align_corners=False) + return masks diff --git a/sam2_configs/__init__.py b/sam2_configs/__init__.py new file mode 100644 index 0000000..5277f46 --- /dev/null +++ b/sam2_configs/__init__.py @@ -0,0 +1,5 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. diff --git a/sam2_configs/sam2_hiera_t.yaml b/sam2_configs/sam2_hiera_t.yaml new file mode 100644 index 0000000..a62c903 --- /dev/null +++ b/sam2_configs/sam2_hiera_t.yaml @@ -0,0 +1,118 @@ +# @package _global_ + +# Model +model: + _target_: sam2.modeling.sam2_base.SAM2Base + image_encoder: + _target_: sam2.modeling.backbones.image_encoder.ImageEncoder + scalp: 1 + trunk: + _target_: sam2.modeling.backbones.hieradet.Hiera + embed_dim: 96 + num_heads: 1 + stages: [1, 2, 7, 2] + global_att_blocks: [5, 7, 9] + window_pos_embed_bkg_spatial_size: [7, 7] + neck: + _target_: sam2.modeling.backbones.image_encoder.FpnNeck + position_encoding: + _target_: sam2.modeling.position_encoding.PositionEmbeddingSine + num_pos_feats: 256 + normalize: true + scale: null + temperature: 10000 + d_model: 256 + backbone_channel_list: [768, 384, 192, 96] + fpn_top_down_levels: [2, 3] # output level 0 and 1 directly use the backbone features + fpn_interp_model: nearest + + memory_attention: + _target_: sam2.modeling.memory_attention.MemoryAttention + d_model: 256 + pos_enc_at_input: true + layer: + _target_: sam2.modeling.memory_attention.MemoryAttentionLayer + activation: relu + dim_feedforward: 2048 + dropout: 0.1 + pos_enc_at_attn: false + self_attention: + _target_: sam2.modeling.sam.transformer.RoPEAttention + rope_theta: 10000.0 + feat_sizes: [32, 32] + embedding_dim: 256 + num_heads: 1 + downsample_rate: 1 + dropout: 0.1 + d_model: 256 + pos_enc_at_cross_attn_keys: true + pos_enc_at_cross_attn_queries: false + cross_attention: + _target_: sam2.modeling.sam.transformer.RoPEAttention + rope_theta: 10000.0 + feat_sizes: [32, 32] + rope_k_repeat: True + embedding_dim: 256 + num_heads: 1 + downsample_rate: 1 + dropout: 0.1 + kv_in_dim: 64 + num_layers: 4 + + memory_encoder: + _target_: sam2.modeling.memory_encoder.MemoryEncoder + out_dim: 64 + position_encoding: + _target_: sam2.modeling.position_encoding.PositionEmbeddingSine + num_pos_feats: 64 + normalize: true + scale: null + temperature: 10000 + mask_downsampler: + _target_: sam2.modeling.memory_encoder.MaskDownSampler + kernel_size: 3 + stride: 2 + padding: 1 + fuser: + _target_: sam2.modeling.memory_encoder.Fuser + layer: + _target_: sam2.modeling.memory_encoder.CXBlock + dim: 256 + kernel_size: 7 + padding: 3 + layer_scale_init_value: 1e-6 + use_dwconv: True # depth-wise convs + num_layers: 2 + + num_maskmem: 7 + image_size: 1024 + # apply scaled sigmoid on mask logits for memory encoder, and directly feed input mask as output mask + # SAM decoder + sigmoid_scale_for_mem_enc: 20.0 + sigmoid_bias_for_mem_enc: -10.0 + use_mask_input_as_output_without_sam: true + # Memory + directly_add_no_mem_embed: true + # use high-resolution feature map in the SAM mask decoder + use_high_res_features_in_sam: true + # output 3 masks on the first click on initial conditioning frames + multimask_output_in_sam: true + # SAM heads + iou_prediction_use_sigmoid: True + # cross-attend to object pointers from other frames (based on SAM output tokens) in the encoder + use_obj_ptrs_in_encoder: true + add_tpos_enc_to_obj_ptrs: false + only_obj_ptrs_in_the_past_for_eval: true + # object occlusion prediction + pred_obj_scores: true + pred_obj_scores_mlp: true + fixed_no_obj_ptr: true + # multimask tracking settings + multimask_output_for_tracking: true + use_multimask_token_for_obj_ptr: true + multimask_min_pt_num: 0 + multimask_max_pt_num: 1 + use_mlp_for_obj_ptr_proj: true + # Compilation flag + # HieraT does not currently support compilation, should always be set to False + compile_image_encoder: False diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..42b3ec0 --- /dev/null +++ b/setup.py @@ -0,0 +1,59 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. + +# This source code is licensed under the license found in the +# LICENSE file in the root directory of this source tree. + +from setuptools import find_packages, setup +from torch.utils.cpp_extension import BuildExtension, CUDAExtension + +# Package metadata +NAME = "SAM 2 Realtime" +VERSION = "1.0" +DESCRIPTION = "SAM 2 Realtime: Segment Anything for Video Streams" +URL = "https://github.com/facebookresearch/segment-anything-2" +AUTHOR = "Peter Schroedl" +AUTHOR_EMAIL = "peter_schroedl@me.com" +LICENSE = "Apache 2.0" + +# Required dependencies +REQUIRED_PACKAGES = [ + "torch>=2.3.1", + "torchvision>=0.18.1", + "numpy>=1.24.4", + "tqdm>=4.66.1", + "hydra-core>=1.3.2", + "iopath>=0.1.10", + "pillow>=9.4.0", +] + +def get_extensions(): + srcs = ["sam2/csrc/connected_components.cu"] + compile_args = { + "cxx": [], + "nvcc": [ + "-DCUDA_HAS_FP16=1", + "-D__CUDA_NO_HALF_OPERATORS__", + "-D__CUDA_NO_HALF_CONVERSIONS__", + "-D__CUDA_NO_HALF2_OPERATORS__", + ], + } + ext_modules = [CUDAExtension("sam2._C", srcs, extra_compile_args=compile_args)] + return ext_modules + + +# Setup configuration +setup( + name=NAME, + version=VERSION, + description=DESCRIPTION, + url=URL, + author=AUTHOR, + author_email=AUTHOR_EMAIL, + license=LICENSE, + packages=find_packages(), + install_requires=REQUIRED_PACKAGES, + python_requires=">=3.10.0", + ext_modules=get_extensions(), + cmdclass={"build_ext": BuildExtension.with_options(no_python_abi_suffix=True)}, +)