{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":49349,"databundleVersionId":5447706,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":8290453,"sourceType":"datasetVersion","datasetId":4924785},{"sourceId":12010068,"sourceType":"datasetVersion","datasetId":7555674},{"sourceId":237741314,"sourceType":"kernelVersion"},{"sourceId":4534,"sourceType":"modelInstanceVersion","isSourceIdPinned":false,"modelInstanceId":3326,"modelId":986},{"sourceId":17191,"sourceType":"modelInstanceVersion","isSourceIdPinned":false,"modelInstanceId":14317,"modelId":21716},{"sourceId":17555,"sourceType":"modelInstanceVersion","isSourceIdPinned":false,"modelInstanceId":14611,"modelId":22086},{"sourceId":418473,"sourceType":"modelInstanceVersion","isSourceIdPinned":false,"modelInstanceId":341361,"modelId":362614},{"sourceId":419315,"sourceType":"modelInstanceVersion","isSourceIdPinned":false,"modelInstanceId":341424,"modelId":362663}],"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install -q /kaggle/input/einops-v0-8-0/einops-0.8.0-py3-none-any.whl --no-index --find-links /kaggle/input/einops-v0-8-0\n!pip install /kaggle/input/imc2023-vggt-whl/* --no-deps --no-index --find-links /kaggle/input/imc2023-vggt-whl\n\n!mkdir -p /root/.cache/torch/hub/checkpoints\n!cp /kaggle/input/aliked/pytorch/aliked-n16/1/aliked-n16.pth /root/.cache/torch/hub/checkpoints/\n!cp /kaggle/input/lightglue/pytorch/aliked/1/aliked_lightglue.pth /root/.cache/torch/hub/checkpoints/\n!cp /kaggle/input/lightglue/pytorch/aliked/1/aliked_lightglue.pth /root/.cache/torch/hub/checkpoints/aliked_lightglue_v0-1_arxiv-pth\n!cp /kaggle/input/vggt-object-tracker/pytorch/v1/4/superpoint_v1.pth /root/.cache/torch/hub/checkpoints/","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-05-31T16:54:48.158447Z","iopub.execute_input":"2025-05-31T16:54:48.158986Z","iopub.status.idle":"2025-05-31T16:54:56.422230Z","shell.execute_reply.started":"2025-05-31T16:54:48.158962Z","shell.execute_reply":"2025-05-31T16:54:56.421407Z"},"_kg_hide-output":true,"editable":false},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!cd /kaggle/input/pkg-colmap/colmap_offline/ && dpkg -i *.deb","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-31T16:54:56.423676Z","iopub.execute_input":"2025-05-31T16:54:56.423931Z","iopub.status.idle":"2025-05-31T16:55:05.760277Z","shell.execute_reply.started":"2025-05-31T16:54:56.423907Z","shell.execute_reply":"2025-05-31T16:55:05.759345Z"},"editable":false},"outputs":[],"execution_count":null},{"cell_type":"code","source":"_RESNET_MEAN = [0.485, 0.456, 0.406]\n_RESNET_STD = [0.229, 0.224, 0.225]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-31T16:55:05.761367Z","iopub.execute_input":"2025-05-31T16:55:05.761633Z","iopub.status.idle":"2025-05-31T16:55:05.765865Z","shell.execute_reply.started":"2025-05-31T16:55:05.761585Z","shell.execute_reply":"2025-05-31T16:55:05.765307Z"},"editable":false},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!cp -r /kaggle/input/vggt/pytorch/default/1/vggt/vggt ./","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-31T16:55:05.767451Z","iopub.execute_input":"2025-05-31T16:55:05.767934Z","iopub.status.idle":"2025-05-31T16:55:06.021153Z","shell.execute_reply.started":"2025-05-31T16:55:05.767916Z","shell.execute_reply":"2025-05-31T16:55:06.020115Z"},"editable":false},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import argparse\nimport random\nimport numpy as np\nimport glob\nimport os\nimport copy\nimport torch\nimport torch.nn.functional as F\nimport gc\nimport subprocess\nimport shutil\n\n# Configure CUDA settings\ntorch.backends.cudnn.enabled = True\ntorch.backends.cudnn.benchmark = True\ntorch.backends.cudnn.deterministic = False\n\nimport argparse\nfrom pathlib import Path\nimport trimesh\nimport pycolmap\nfrom transformers import AutoImageProcessor, AutoModel\n\n\nfrom vggt.models.vggt import VGGT\nfrom vggt.utils.load_fn import load_and_preprocess_images_square\nfrom vggt.utils.pose_enc import pose_encoding_to_extri_intri\nfrom vggt.utils.geometry import unproject_depth_map_to_point_map\nfrom vggt.utils.helper import create_pixel_coordinate_grid, randomly_limit_trues\n# from vggt.dependency.np_to_pycolmap import batch_np_matrix_to_pycolmap, batch_np_matrix_to_pycolmap_wo_track,\nfrom vggt.dependency.np_to_pycolmap import _build_pycolmap_intri\nfrom vggt.dependency.projection import project_3D_points_np\n\nfrom vggt.dependency.vggsfm_utils import *\nfrom vggt.dependency.track_predict import _forward_on_query, _augment_non_visible_frames","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-31T16:55:06.022299Z","iopub.execute_input":"2025-05-31T16:55:06.022586Z","iopub.status.idle":"2025-05-31T16:55:33.337681Z","shell.execute_reply.started":"2025-05-31T16:55:06.022554Z","shell.execute_reply":"2025-05-31T16:55:33.337100Z"},"editable":false},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def run_VGGT(model, images, dtype, resolution=518):\n    # images: [B, 3, H, W]\n\n    assert len(images.shape) == 4\n    assert images.shape[1] == 3\n\n    # hard-coded to use 518 for VGGT\n    images = F.interpolate(images, size=(resolution, resolution), mode=\"bilinear\", align_corners=False)\n\n    with torch.no_grad():\n        with torch.cuda.amp.autocast(dtype=dtype):\n            images = images[None]  # add batch dimension\n            aggregated_tokens_list, ps_idx = model.aggregator(images)\n\n        # Predict Cameras\n        pose_enc = model.camera_head(aggregated_tokens_list)[-1]\n        # Extrinsic and intrinsic matrices, following OpenCV convention (camera from world)\n        extrinsic, intrinsic = pose_encoding_to_extri_intri(pose_enc, images.shape[-2:])\n        # Predict Depth Maps\n        depth_map, depth_conf = model.depth_head(aggregated_tokens_list, images, ps_idx)\n\n    extrinsic = extrinsic.squeeze(0).cpu().numpy()\n    intrinsic = intrinsic.squeeze(0).cpu().numpy()\n    depth_map = depth_map.squeeze(0).cpu().numpy()\n    depth_conf = depth_conf.squeeze(0).cpu().numpy()\n    return extrinsic, intrinsic, depth_map, depth_conf","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-31T16:55:33.338460Z","iopub.execute_input":"2025-05-31T16:55:33.339087Z","iopub.status.idle":"2025-05-31T16:55:33.345291Z","shell.execute_reply.started":"2025-05-31T16:55:33.339043Z","shell.execute_reply":"2025-05-31T16:55:33.344549Z"},"editable":false},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def batch_np_matrix_to_pycolmap(\n    points3d,\n    extrinsics,\n    intrinsics,\n    tracks,\n    image_size,\n    image_basenames,\n    name_to_image_id,\n    masks=None,\n    max_reproj_error=None,\n    max_points3D_val=3000,\n    shared_camera=False,\n    camera_type=\"SIMPLE_PINHOLE\",\n    extra_params=None,\n    min_inlier_per_frame=64,\n    points_rgb=None,\n):\n    \"\"\"\n    Convert Batched NumPy Arrays to PyCOLMAP\n\n    Check https://github.com/colmap/pycolmap for more details about its format\n\n    NOTE that colmap expects images/cameras/points3D to be 1-indexed\n    so there is a +1 offset between colmap index and batch index\n\n\n    NOTE: different from VGGSfM, this function:\n    1. Use np instead of torch\n    2. Frame index and camera id starts from 1 rather than 0 (to fit the format of PyCOLMAP)\n    \"\"\"\n    # points3d: Px3\n    # extrinsics: Nx3x4\n    # intrinsics: Nx3x3\n    # tracks: NxPx2\n    # masks: NxP\n    # image_size: 2, assume all the frames have been padded to the same size\n    # where N is the number of frames and P is the number of tracks\n\n    N, P, _ = tracks.shape\n    assert len(extrinsics) == N\n    assert len(intrinsics) == N\n    assert len(points3d) == P\n    assert image_size.shape[0] == 2\n\n    if max_reproj_error is not None:\n        projected_points_2d, projected_points_cam = project_3D_points_np(points3d, extrinsics, intrinsics)\n        projected_diff = np.linalg.norm(projected_points_2d - tracks, axis=-1)\n        projected_points_2d[projected_points_cam[:, -1] <= 0] = 1e6\n        reproj_mask = projected_diff < max_reproj_error\n\n    if masks is not None and reproj_mask is not None:\n        masks = np.logical_and(masks, reproj_mask)\n    elif masks is not None:\n        masks = masks\n    else:\n        masks = reproj_mask\n\n    assert masks is not None\n\n    if masks.sum(1).min() < min_inlier_per_frame:\n        print(f\"Not enough inliers per frame, skip BA.\")\n        return None, None\n\n    # Reconstruction object, following the format of PyCOLMAP/COLMAP\n    reconstruction = pycolmap.Reconstruction()\n\n    inlier_num = masks.sum(0)\n    valid_mask = inlier_num >= 2  # a track is invalid if without two inliers\n    valid_idx = np.nonzero(valid_mask)[0]\n\n    # Only add 3D points that have sufficient 2D points\n    for vidx in valid_idx:\n        # Use RGB colors if provided, otherwise use zeros\n        rgb = points_rgb[vidx] if points_rgb is not None else np.zeros(3)\n        reconstruction.add_point3D(points3d[vidx], pycolmap.Track(), rgb)\n\n    num_points3D = len(valid_idx)\n    camera = None\n    # frame idx\n    for fidx in range(N):\n        # set camera\n        image_id = name_to_image_id[image_basenames[fidx]]\n        if camera is None or (not shared_camera):\n            pycolmap_intri = _build_pycolmap_intri(fidx, intrinsics, camera_type, extra_params)\n\n            camera = pycolmap.Camera(\n                model=camera_type, width=image_size[0], height=image_size[1], params=pycolmap_intri, camera_id=fidx + 1\n            )\n\n            # add camera\n            reconstruction.add_camera(camera)\n\n        # set image\n        cam_from_world = pycolmap.Rigid3d(\n            pycolmap.Rotation3d(extrinsics[fidx][:3, :3]), extrinsics[fidx][:3, 3]\n        )  # Rot and Trans\n\n        image = pycolmap.Image(\n            id=image_id, name=f\"image_{image_id}\", camera_id=camera.camera_id, cam_from_world=cam_from_world\n        )\n\n        points2D_list = []\n\n        point2D_idx = 0\n\n        # NOTE point3D_id start by 1\n        for point3D_id in range(1, num_points3D + 1):\n            original_track_idx = valid_idx[point3D_id - 1]\n\n            if (reconstruction.points3D[point3D_id].xyz < max_points3D_val).all():\n                if masks[fidx][original_track_idx]:\n                    # It seems we don't need +0.5 for BA\n                    point2D_xy = tracks[fidx][original_track_idx]\n                    # Please note when adding the Point2D object\n                    # It not only requires the 2D xy location, but also the id to 3D point\n                    points2D_list.append(pycolmap.Point2D(point2D_xy, point3D_id))\n\n                    # add element\n                    track = reconstruction.points3D[point3D_id].track\n                    track.add_element(image_id, point2D_idx)\n                    point2D_idx += 1\n\n        assert point2D_idx == len(points2D_list)\n\n        try:\n            image.points2D = pycolmap.ListPoint2D(points2D_list)\n            image.registered = True\n        except:\n            print(f\"frame {fidx + 1} is out of BA\")\n            image.registered = False\n\n        # add image\n        reconstruction.add_image(image)\n\n    return reconstruction, valid_mask","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-31T16:55:33.346118Z","iopub.execute_input":"2025-05-31T16:55:33.346330Z","iopub.status.idle":"2025-05-31T16:55:33.577934Z","shell.execute_reply.started":"2025-05-31T16:55:33.346314Z","shell.execute_reply":"2025-05-31T16:55:33.577079Z"},"editable":false},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def rename_colmap_recons_and_rescale_camera(\n    reconstruction, \n    image_paths_in_batch,\n    original_coords_in_batch,\n    img_size,\n    image_id_to_name,\n    image_id_to_batch_id,\n    shift_point2d_to_original_res=False, \n    shared_camera=False\n):\n    rescale_camera = True\n\n    # Image paths in batch should be the base names, not full paths for pyimage.name\n    base_image_paths_in_batch = [os.path.basename(p) for p in image_paths_in_batch]\n\n    for pyimageid in list(reconstruction.images.keys()): # Iterate over a copy of keys if modifying\n        pyimage = reconstruction.images[pyimageid]\n        pycamera = reconstruction.cameras[pyimage.camera_id]\n        batch_image_id = image_id_to_batch_id[pyimageid]\n        # pyimageid is 1-indexed. Ensure image_paths_in_batch is correctly indexed.\n        if (batch_image_id - 1) < len(base_image_paths_in_batch):\n            pyimage.name = image_id_to_name[pyimageid]\n        else:\n            print(f\"Warning: batch_image_id {batch_image_id} out of range for image_paths_in_batch (len: {len(base_image_paths_in_batch)})\")\n            continue # Or handle error appropriately\n\n        if rescale_camera:            \n            if (batch_image_id - 1) < len(original_coords_in_batch):\n                real_image_size = original_coords_in_batch[batch_image_id - 1, -2:]\n                resize_ratio = max(real_image_size) / img_size # img_size is the reconstruction_resolution\n                pred_params = pred_params * resize_ratio\n                real_pp = real_image_size / 2\n                pred_params[-2:] = real_pp\n                pycamera.params = pred_params\n                pycamera.width = int(real_image_size[0])\n                pycamera.height = int(real_image_size[1])\n            else:\n                print(f\"Warning: batch_image_id {batch_image_id} out of range for original_coords_in_batch (len: {len(original_coords_in_batch)})\")\n                # Decide how to handle this: skip rescaling for this camera, or raise error\n                continue\n\n\n        if shift_point2d_to_original_res:\n            if (batch_image_id - 1) < len(original_coords_in_batch):\n                top_left = original_coords_in_batch[batch_image_id - 1, :2]\n                # resize_ratio needs to be defined here as well if not falling through from above\n                real_image_size_for_ratio = original_coords_in_batch[batch_image_id - 1, -2:]\n                resize_ratio_for_points = max(real_image_size_for_ratio) / img_size\n\n                for point_idx in range(len(pyimage.points2D)):\n                    point2D = pyimage.points2D[point_idx]\n                    # Ensure point2D.xy is a numpy array for subtraction\n                    xy_np = np.array(point2D.xy)\n                    top_left_np = np.array(top_left)\n                    point2D.xy = (xy_np - top_left_np) * resize_ratio_for_points\n            else:\n                 print(f\"Warning: batch_image_id {batch_image_id} out of range for original_coords_in_batch during point2D shift.\")\n\n\n        if shared_camera:\n            rescale_camera = False\n    return reconstruction","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-31T16:55:33.578762Z","iopub.execute_input":"2025-05-31T16:55:33.579041Z","iopub.status.idle":"2025-05-31T16:55:33.592964Z","shell.execute_reply.started":"2025-05-31T16:55:33.579020Z","shell.execute_reply":"2025-05-31T16:55:33.592360Z"},"editable":false},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict_tracks(\n    images,\n    conf=None,\n    points_3d=None,\n    masks=None,\n    max_query_pts=2048,\n    query_frame_num=5,\n    keypoint_extractor=\"aliked+sp\",\n    max_points_num=163840,\n    fine_tracking=True,\n    complete_non_vis=True,\n):\n    \"\"\"\n    Predict tracks for the given images and masks.\n\n    TODO: support non-square images\n    TODO: support masks\n\n\n    This function predicts the tracks for the given images and masks using the specified query method\n    and track predictor. It finds query points, and predicts the tracks, visibility, and scores for the query frames.\n\n    Args:\n        images: Tensor of shape [S, 3, H, W] containing the input images.\n        conf: Tensor of shape [S, 1, H, W] containing the confidence scores. Default is None.\n        points_3d: Tensor containing 3D points. Default is None.\n        masks: Optional tensor of shape [S, 1, H, W] containing masks. Default is None.\n        max_query_pts: Maximum number of query points. Default is 2048.\n        query_frame_num: Number of query frames to use. Default is 5.\n        keypoint_extractor: Method for keypoint extraction. Default is \"aliked+sp\".\n        max_points_num: Maximum number of points to process at once. Default is 163840.\n        fine_tracking: Whether to use fine tracking. Default is True.\n        complete_non_vis: Whether to augment non-visible frames. Default is True.\n\n    Returns:\n        pred_tracks: Numpy array containing the predicted tracks.\n        pred_vis_scores: Numpy array containing the visibility scores for the tracks.\n        pred_confs: Numpy array containing the confidence scores for the tracks.\n        pred_points_3d: Numpy array containing the 3D points for the tracks.\n        pred_colors: Numpy array containing the point colors for the tracks. (0, 255)\n    \"\"\"\n\n    device = images.device\n    dtype = images.dtype\n    model_path = \"/kaggle/input/vggt-object-tracker/pytorch/v1/4/vggsfm_v2_tracker.pt\"\n    tracker = build_vggsfm_tracker(model_path).to(device, dtype)\n\n    # Find query frames\n    query_frame_indexes = generate_rank_by_dino_(images, query_frame_num=query_frame_num, device=device)\n\n    # Add the first image to the front if not already present\n    if 0 in query_frame_indexes:\n        query_frame_indexes.remove(0)\n    query_frame_indexes = [0, *query_frame_indexes]\n\n    # TODO: add the functionality to handle the masks\n    keypoint_extractors = initialize_feature_extractors(\n        max_query_pts, extractor_method=keypoint_extractor, device=device\n    )\n\n    pred_tracks = []\n    pred_vis_scores = []\n    pred_confs = []\n    pred_points_3d = []\n    pred_colors = []\n\n    fmaps_for_tracker = tracker.process_images_to_fmaps(images)\n\n    if fine_tracking:\n        print(\"For faster inference, consider disabling fine_tracking\")\n\n    for query_index in query_frame_indexes:\n        print(f\"Predicting tracks for query frame {query_index}\")\n        pred_track, pred_vis, pred_conf, pred_point_3d, pred_color = _forward_on_query(\n            query_index,\n            images,\n            conf,\n            points_3d,\n            fmaps_for_tracker,\n            keypoint_extractors,\n            tracker,\n            max_points_num,\n            fine_tracking,\n            device,\n        )\n\n        pred_tracks.append(pred_track)\n        pred_vis_scores.append(pred_vis)\n        pred_confs.append(pred_conf)\n        pred_points_3d.append(pred_point_3d)\n        pred_colors.append(pred_color)\n\n    if complete_non_vis:\n        pred_tracks, pred_vis_scores, pred_confs, pred_points_3d, pred_colors = _augment_non_visible_frames(\n            pred_tracks,\n            pred_vis_scores,\n            pred_confs,\n            pred_points_3d,\n            pred_colors,\n            images,\n            conf,\n            points_3d,\n            fmaps_for_tracker,\n            keypoint_extractors,\n            tracker,\n            max_points_num,\n            fine_tracking,\n            min_vis=500,\n            non_vis_thresh=0.1,\n            device=device,\n        )\n\n    pred_tracks = np.concatenate(pred_tracks, axis=1)\n    pred_vis_scores = np.concatenate(pred_vis_scores, axis=1)\n    pred_confs = np.concatenate(pred_confs, axis=0) if pred_confs else None\n    pred_points_3d = np.concatenate(pred_points_3d, axis=0) if pred_points_3d else None\n    pred_colors = np.concatenate(pred_colors, axis=0) if pred_colors else None\n\n    # from vggt.utils.visual_track import visualize_tracks_on_images\n    # visualize_tracks_on_images(images[None], torch.from_numpy(pred_tracks[None]), torch.from_numpy(pred_vis_scores[None])>0.2, out_dir=\"track_visuals\")\n\n    return pred_tracks, pred_vis_scores, pred_confs, pred_points_3d, pred_colors","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-31T16:55:33.593824Z","iopub.execute_input":"2025-05-31T16:55:33.594476Z","iopub.status.idle":"2025-05-31T16:55:33.616747Z","shell.execute_reply.started":"2025-05-31T16:55:33.594457Z","shell.execute_reply":"2025-05-31T16:55:33.616038Z"},"editable":false},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def generate_rank_by_dino_(\n    images, query_frame_num, image_size=336, model_name=\"dinov2_vitb14_reg\", device=\"cuda\", spatial_similarity=False\n):\n    images_resized = F.interpolate(images, (image_size, image_size), mode=\"bilinear\", align_corners=False)\n    \n    dino_model_path = '/kaggle/input/dinov2/pytorch/base/1/' # Ensure this is correct\n    # Load DINO model only when needed and ensure it's on the correct device\n    dino_v2_model = AutoModel.from_pretrained(dino_model_path).eval().to(device)\n    \n    resnet_mean = torch.tensor(_RESNET_MEAN, device=device).view(1, 3, 1, 1)\n    resnet_std = torch.tensor(_RESNET_STD, device=device).view(1, 3, 1, 1)\n    images_resnet_norm = (images_resized - resnet_mean) / resnet_std\n    \n    with torch.no_grad():\n        outputs = dino_v2_model(images_resnet_norm)\n        if hasattr(outputs, 'last_hidden_state'):\n            hidden_states = outputs.last_hidden_state\n            cls_token = hidden_states[:, 0, :]\n            patch_tokens = hidden_states[:, 1:, :]\n            frame_feat = {\n                \"x_norm_clstoken\": F.normalize(cls_token, p=2, dim=1),\n                \"x_norm_patchtokens\": F.normalize(patch_tokens, p=2, dim=2)\n            }\n        else:\n            # This part might need adjustment based on the exact DINOv2 output for your model version\n            # The original notebook code had a more direct way to get x_norm_clstoken and x_norm_patchtokens\n            # If the AutoModel output is already a dict with these keys, use that directly.\n            # For now, proceeding with HuggingFace typical output structure.\n            print(\"Warning: DINOv2 output format might differ from original implementation. Check `frame_feat` structure.\")\n            if isinstance(outputs, dict) and \"x_norm_clstoken\" in outputs and \"x_norm_patchtokens\" in outputs:\n                 frame_feat = outputs\n            else:\n                raise ValueError(\"Unexpected DINO model output format from AutoModel\")\n\n    if spatial_similarity:\n        frame_feat_data = frame_feat[\"x_norm_patchtokens\"]\n        frame_feat_norm = frame_feat_data.permute(1, 0, 2) # Check dimensions for bmm\n        similarity_matrix = torch.bmm(frame_feat_norm, frame_feat_norm.transpose(-1, -2))\n        similarity_matrix = similarity_matrix.mean(dim=0)\n    else:\n        frame_feat_data = frame_feat[\"x_norm_clstoken\"]\n        similarity_matrix = torch.mm(frame_feat_data, frame_feat_data.transpose(-1, -2))\n    \n    distance_matrix = 100 - similarity_matrix.clone()\n    similarity_matrix.fill_diagonal_(-100)\n    similarity_sum = similarity_matrix.sum(dim=1)\n    most_common_frame_index = torch.argmax(similarity_sum).item()\n    \n    # Ensure farthest_point_sampling is defined and accessible\n    fps_idx = farthest_point_sampling(distance_matrix, query_frame_num, most_common_frame_index)\n    \n    del frame_feat, frame_feat_data, similarity_matrix, distance_matrix, similarity_sum\n    del dino_v2_model, images_resized, images_resnet_norm, outputs, cls_token, patch_tokens, hidden_states\n    gc.collect()\n    torch.cuda.empty_cache()\n    \n    return fps_idx","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-31T16:55:33.618891Z","iopub.execute_input":"2025-05-31T16:55:33.619146Z","iopub.status.idle":"2025-05-31T16:55:33.636518Z","shell.execute_reply.started":"2025-05-31T16:55:33.619131Z","shell.execute_reply":"2025-05-31T16:55:33.635930Z"},"editable":false},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Main Modified Function ---\ndef demo_fn_batched(args):\n    print(\"Arguments:\", vars(args))\n\n    np.random.seed(args.seed)\n    torch.manual_seed(args.seed)\n    random.seed(args.seed)\n    if torch.cuda.is_available():\n        torch.cuda.manual_seed(args.seed)\n        torch.cuda.manual_seed_all(args.seed)\n    print(f\"Setting seed as: {args.seed}\")\n\n    scene_dir = f\"/kaggle/input/image-matching-challenge-2023/{args.variant}/{args.dataset}/{args.scene}\"\n    image_dir = os.path.join(scene_dir, \"images\")\n    all_image_path_list = sorted(glob.glob(os.path.join(image_dir, \"*\"))) # Sort for consistent batching\n\n    if not all_image_path_list:\n        raise ValueError(f\"No images found in {image_dir}\")\n\n    # Shuffle once before batching if desired (as per discussion \"random shuffle then sequential\")\n    # However, for SfM, a somewhat coherent sequence is often better for merging.\n    # If random shuffle is critical, uncomment below. Otherwise, sequential slicing is often preferred.\n    # random.shuffle(all_image_path_list)\n\n    dtype = torch.bfloat16 if torch.cuda.is_available() and torch.cuda.get_device_capability()[0] >= 8 else torch.float16\n    device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n    print(f\"Using device: {device}, Using dtype: {dtype}\")\n\n    model = VGGT()\n    model_path = \"/kaggle/input/vggt-object-tracker/pytorch/v1/4/model.pt\" # Ensure this path is correct\n    model.load_state_dict(torch.load(model_path))\n    model.eval().to(device)\n    print(\"VGGT Model loaded\")\n\n    vggt_fixed_resolution = 518\n    img_load_resolution = 1024 # Resolution for initial loading and track prediction\n\n    # --- Batching Logic ---\n    batch_size = args.batch_size\n    overlap = args.batch_overlap\n    num_images = len(all_image_path_list)\n    print(f\"Total number of images: {num_images}\")\n    all_image_basenames = [os.path.basename(p) for p in all_image_path_list]\n    # Ensure unique basenames if not already guaranteed; if duplicates exist, this strategy needs refinement.\n    # For simplicity, assuming basenames are unique identifiers for unique images.\n    name_to_image_id = {name: image_id+1 for image_id, name in enumerate(all_image_basenames)}\n    image_id_to_name = {image_id: name for name, image_id in name_to_image_id.items()}\n    \n    batch_reconstruction_paths = []\n    \n    # Create a main output directory for this scene\n    scene_output_dir = f\"/kaggle/working/{args.variant}/{args.dataset}/{args.scene}\"\n    os.makedirs(scene_output_dir, exist_ok=True)\n\n    for i in range(0, num_images, batch_size - overlap):\n        start_idx = i\n        end_idx = min(i + batch_size, num_images)\n        if end_idx - start_idx < batch_size: # Need at least 2 images for reconstruction\n            current_batch_image_paths = all_image_path_list[start_idx:end_idx]\n            num_remain = batch_size - (end_idx - start_idx)\n            current_batch_image_paths = all_image_path_list[0:num_remain] + current_batch_image_paths\n        else:\n            current_batch_image_paths = all_image_path_list[start_idx:end_idx]\n        \n        if len(current_batch_image_paths) < 2: # Check again if slicing resulted in too few images\n            print(f\"Skipping batch {i//(batch_size-overlap) +1} after slicing: Not enough images ({len(current_batch_image_paths)})\")\n            continue\n\n        assert len(current_batch_image_paths) == batch_size, f\"Num of image not matched, {len(current_batch_image_paths)} != batch size {batch_size}\"\n        image_basenames = [os.path.basename(p) for p in current_batch_image_paths]\n        image_id_to_batch_id = {name_to_image_id[name]: batch_id for batch_id, name in enumerate(image_basenames)}\n\n        batch_num = i // (batch_size - overlap) + 1\n        print(f\"\\n--- Processing Batch {batch_num} ({len(current_batch_image_paths)} images from index {start_idx} to {end_idx-1}) ---\")\n        \n        batch_output_dir = os.path.join(scene_output_dir, f\"batch_{batch_num}\")\n        os.makedirs(batch_output_dir, exist_ok=True)\n        \n        images, original_coords_batch = load_and_preprocess_images_square(current_batch_image_paths, img_load_resolution)\n        images = images.to(device) # Shape: [S, 3, H, W] where S is num images in batch\n        original_coords_batch = original_coords_batch.to(device) # Shape: [S, 4]\n        print(f\"Loaded {len(images)} images for batch {batch_num}\")\n\n        # Run VGGT for this batch\n        extrinsic_batch, intrinsic_batch, depth_map_batch, depth_conf_batch = run_VGGT(model, images, dtype, vggt_fixed_resolution)\n        points_3d_vggt_batch = unproject_depth_map_to_point_map(depth_map_batch, extrinsic_batch, intrinsic_batch)\n\n        reconstruction_batch = None\n        batch_reconstruction_resolution = vggt_fixed_resolution # Default for no-BA path\n\n        if args.use_ba_per_batch:\n            print(f\"Attempting BA for batch {batch_num}\")\n            image_size_ba = np.array(images.shape[-2:]) # This is img_load_resolution\n            scale_ba = img_load_resolution / vggt_fixed_resolution\n            shared_camera_ba = args.shared_camera\n\n            with torch.cuda.amp.autocast(dtype=dtype):\n                pred_tracks_batch, pred_vis_scores_batch, _, pred_points_3d_colmap_batch, points_rgb_batch = predict_tracks(\n                    images,\n                    conf=depth_conf_batch,\n                    points_3d=points_3d_vggt_batch,\n                    masks=None,\n                    max_query_pts=args.max_query_pts,\n                    query_frame_num=args.query_frame_num,\n                    keypoint_extractor=\"aliked+sp\",\n                    fine_tracking=args.fine_tracking,\n                )\n                # pred_tracks_batch, pred_vis_scores_batch, _, pred_points_3d_colmap_batch, points_rgb_batch = predict_tracks(\n                #     images, # These are at img_load_resolution\n                #     conf=torch.from_numpy(depth_conf_batch).to(device).unsqueeze(1), # depth_conf_batch needs to be [S,1,H,W] and on device\n                #     points_3d_vggt=torch.from_numpy(points_3d_vggt_batch).to(device), # points_3d_vggt_batch needs to be tensor and on device\n                #     max_query_pts=args.max_query_pts,\n                #     query_frame_num=args.query_frame_num,\n                #     fine_tracking=args.fine_tracking,\n                # )\n            torch.cuda.empty_cache()\n            gc.collect()\n\n            if pred_tracks_batch.size == 0 or pred_points_3d_colmap_batch is None or pred_points_3d_colmap_batch.size == 0:\n                print(f\"Warning: No tracks or 3D points from predict_tracks for batch {batch_num}. Skipping BA for this batch.\")\n                # Fallback to no-BA reconstruction for this batch\n            else:\n                intrinsic_ba_batch = intrinsic_batch.copy() # Intrinsic from VGGT is for vggt_fixed_resolution\n                intrinsic_ba_batch[:, :2, :] *= scale_ba # Rescale for img_load_resolution (where tracks are)\n                \n                track_mask_batch = pred_vis_scores_batch > args.vis_thresh\n\n                reconstruction_batch, _ = batch_np_matrix_to_pycolmap(\n                    pred_points_3d_colmap_batch, # From predict_tracks\n                    extrinsic_batch, # From VGGT (poses)\n                    intrinsic_ba_batch, # Rescaled intrinsics\n                    pred_tracks_batch,\n                    image_size_ba, # img_load_resolution\n                    image_basenames,\n                    name_to_image_id,\n                    masks=track_mask_batch,\n                    max_reproj_error=args.max_reproj_error,\n                    shared_camera=shared_camera_ba,\n                    camera_type=args.camera_type,\n                    points_rgb=points_rgb_batch,\n                )\n\n                if reconstruction_batch and len(reconstruction_batch.images)>0 :\n                    print(f\"Performing per-batch BA for batch {batch_num}\")\n                    ba_options = pycolmap.BundleAdjustmentOptions()\n                    # Configure BA options if needed, e.g., ba_options.solver_options.max_num_iterations = 20\n                    pycolmap.bundle_adjustment(reconstruction_batch, ba_options)\n                    batch_reconstruction_resolution = img_load_resolution\n                else:\n                    print(f\"Failed to create initial reconstruction for BA for batch {batch_num}. Falling back.\")\n                    # reconstruction_batch = None # Ensure it's None to trigger no-BA path\n                    batch_reconstruction_resolution = img_load_resolution\n\n        # if reconstruction_batch is None: # Fallback if BA failed or not used\n        #     print(f\"Using feedforward reconstruction (no BA) for batch {batch_num}\")\n        #     conf_thres_value = 5\n        #     max_points_for_colmap = 100000\n            \n        #     # image_size for no-BA path is vggt_fixed_resolution\n        #     image_size_no_ba = np.array([vggt_fixed_resolution, vggt_fixed_resolution])\n        #     num_frames_batch, height_batch, width_batch, _ = points_3d_vggt_batch.shape # From VGGT output\n\n        #     # Resize source images to vggt_fixed_resolution for RGB color sampling\n        #     points_rgb_no_ba_src_images = F.interpolate(\n        #         images, size=(vggt_fixed_resolution, vggt_fixed_resolution), mode=\"bilinear\", align_corners=False\n        #     )\n        #     points_rgb_no_ba = (points_rgb_no_ba_src_images.cpu().numpy() * 255).astype(np.uint8)\n        #     points_rgb_no_ba = points_rgb_no_ba.transpose(0, 2, 3, 1) # S, H, W, 3\n\n        #     points_xyf_batch = create_pixel_coordinate_grid(num_frames_batch, height_batch, width_batch)\n            \n        #     # depth_conf_batch is (S, H, W) from VGGT\n        #     conf_mask_batch = depth_conf_batch >= conf_thres_value \n        #     conf_mask_batch = randomly_limit_trues(conf_mask_batch, max_points_for_colmap)\n\n        #     points_3d_filtered = points_3d_vggt_batch[conf_mask_batch]\n        #     points_xyf_filtered = points_xyf_batch[conf_mask_batch]\n        #     points_rgb_filtered = points_rgb_no_ba[conf_mask_batch]\n\n        #     if points_3d_filtered.shape[0] > 0:\n        #          reconstruction_batch = batch_np_matrix_to_pycolmap_wo_track(\n        #             points_3d_filtered,\n        #             points_xyf_filtered,\n        #             points_rgb_filtered,\n        #             extrinsic_batch, # from VGGT\n        #             intrinsic_batch, # from VGGT (for vggt_fixed_resolution)\n        #             image_size_no_ba, # vggt_fixed_resolution\n        #             shared_camera=False, # Typically False for no-BA from VGGT\n        #             camera_type=\"PINHOLE\", # Typically PINHOLE for no-BA from VGGT\n        #         )\n        #     else:\n        #         print(f\"Error: No 3D points left after filtering for batch {batch_num}. Skipping this batch.\")\n        #         del images, original_coords_batch, extrinsic_batch, intrinsic_batch, depth_map_batch, depth_conf_batch, points_3d_vggt_batch\n        #         gc.collect()\n        #         torch.cuda.empty_cache()\n        #         continue\n\n\n        #     batch_reconstruction_resolution = vggt_fixed_resolution\n        \n        if reconstruction_batch and len(reconstruction_batch.images) > 0 :\n            reconstruction_batch = rename_colmap_recons_and_rescale_camera(\n                reconstruction_batch,\n                current_batch_image_paths, # Full paths for the current batch\n                original_coords_batch.cpu().numpy(), # Original coords for the current batch\n                img_size=batch_reconstruction_resolution, # Resolution at which reconstruction_batch was made\n                image_id_to_name=image_id_to_name,\n                image_id_to_batch_id=image_id_to_batch_id,\n                shift_point2d_to_original_res=True, # Important for consistency\n                shared_camera=args.shared_camera if args.use_ba_per_batch else False,\n            )\n            \n            batch_sparse_dir = os.path.join(batch_output_dir, \"sparse\")\n            os.makedirs(batch_sparse_dir, exist_ok=True)\n            reconstruction_batch.write(batch_sparse_dir)\n            batch_reconstruction_paths.append(batch_sparse_dir)\n            print(f\"Saved reconstruction for batch {batch_num} to {batch_sparse_dir}\")\n        else:\n            print(f\"Error: Reconstruction for batch {batch_num} is empty or invalid. Skipping.\")\n\n        del images, original_coords_batch, extrinsic_batch, intrinsic_batch, depth_map_batch, depth_conf_batch, points_3d_vggt_batch, reconstruction_batch\n        if 'pred_tracks_batch' in locals(): del pred_tracks_batch # Clean up BA specific variables\n        gc.collect()\n        torch.cuda.empty_cache()\n    \n    del model # Free VGGT model memory\n    gc.collect()\n    torch.cuda.empty_cache()\n\n    # --- Merging Reconstructions ---\n    if not batch_reconstruction_paths:\n        print(\"No batch reconstructions were generated. Exiting.\")\n        return False\n    \n    if len(batch_reconstruction_paths) == 1:\n        print(\"Only one batch was processed. Using its reconstruction as the merged result.\")\n        merged_model_path = batch_reconstruction_paths[0]\n    else:\n        print(\"\\n--- Merging Batch Reconstructions ---\")\n        merged_dir = os.path.join(scene_output_dir, \"merged_stages\")\n        os.makedirs(merged_dir, exist_ok=True)\n        \n        current_merged_path = batch_reconstruction_paths[0]\n        \n        for i in range(1, len(batch_reconstruction_paths)):\n            input_path1 = current_merged_path\n            input_path2 = batch_reconstruction_paths[i]\n            output_path_temp = os.path.join(merged_dir, f\"merged_step_{i-1}_to_{i}\")\n            if os.path.exists(output_path_temp): # Clean up previous attempt for this step\n                shutil.rmtree(output_path_temp)\n            os.makedirs(output_path_temp, exist_ok=True)\n\n            print(f\"Merging: '{os.path.basename(os.path.dirname(input_path1))}' and '{os.path.basename(os.path.dirname(input_path2))}' into '{os.path.basename(output_path_temp)}'\")\n            \n            colmap_executable = \"colmap\" # Assumes it's in PATH after dpkg -i\n            \n            merge_cmd = [\n                colmap_executable, \"model_merger\",\n                \"--input_path1\", input_path1,\n                \"--input_path2\", input_path2,\n                \"--output_path\", output_path_temp,\n                # Add other model_merger options if needed, e.g.:\n                # \"--robust_merge_max_reproj_error\", \"16\", # Default is 8\n                # \"--robust_merge_min_inlier_ratio\", \"0.05\", # Default is 0.1\n            ]\n            print(f\"Executing: {' '.join(merge_cmd)}\")\n            try:\n                completed_process = subprocess.run(merge_cmd, check=True, capture_output=True, text=True)\n                print(\"Merge stdout:\", completed_process.stdout)\n                print(\"Merge successful for this step.\")\n                current_merged_path = output_path_temp\n            except subprocess.CalledProcessError as e:\n                print(f\"Error during model merging step {i}:\")\n                print(\"Command:\", e.cmd)\n                print(\"Return code:\", e.returncode)\n                print(\"Stdout:\", e.stdout)\n                print(\"Stderr:\", e.stderr)\n                print(f\"Failed to merge {input_path2}. Trying to continue with previous merged model if possible, or stopping.\")\n                # Decide on error strategy: stop, or try to merge next available into current_merged_path\n                # For now, if a merge fails, the chain breaks.\n                # A more robust strategy might try to merge batch_reconstruction_paths[i+1] into current_merged_path\n                return False # Stop if any merge fails\n        merged_model_path = current_merged_path\n\n    print(f\"\\nFinal merged model (before global BA) is at: {merged_model_path}\")\n\n    # --- Final Bundle Adjustment ---\n    if args.use_global_ba:\n        print(\"\\n--- Performing Final Global Bundle Adjustment ---\")\n        try:\n            final_reconstruction = pycolmap.read_model(merged_model_path, format=\".bin\") # or .txt if saved as text\n            print(f\"Loaded final merged reconstruction with {len(final_reconstruction.images)} images and {len(final_reconstruction.points3D)} points.\")\n            \n            if len(final_reconstruction.images) > 0:\n                ba_options_global = pycolmap.BundleAdjustmentOptions()\n                # Configure global BA options if needed (e.g., more iterations)\n                # ba_options_global.solver_options.max_num_iterations = 50\n                pycolmap.bundle_adjustment(final_reconstruction, ba_options_global)\n                print(\"Global BA successful.\")\n\n                final_sparse_dir = os.path.join(scene_output_dir, \"sparse_global_ba\")\n                os.makedirs(final_sparse_dir, exist_ok=True)\n                final_reconstruction.write(final_sparse_dir)\n                print(f\"Saved final BA reconstruction to: {final_sparse_dir}\")\n\n                # Optional: Save point cloud of the final model\n                if len(final_reconstruction.points3D) > 0:\n                    points3D_final = np.array([p.xyz for p in final_reconstruction.points3D.values()])\n                    colors_final = np.array([p.color for p in final_reconstruction.points3D.values()])\n                    if points3D_final.size > 0:\n                         trimesh.PointCloud(points3D_final, colors=colors_final).export(os.path.join(final_sparse_dir, \"points_final_ba.ply\"))\n                         print(f\"Saved final point cloud to {final_sparse_dir}/points_final_ba.ply\")\n\n            else:\n                print(\"Merged reconstruction is empty. Skipping final BA.\")\n        except Exception as e:\n            print(f\"Error during final BA or saving: {e}\")\n            print(\"The merged model without final BA is available at:\", merged_model_path)\n    else:\n        print(\"Skipping final global BA as per arguments.\")\n        final_output_dir_no_ba = os.path.join(scene_output_dir, \"sparse_merged_no_global_ba\")\n        if os.path.exists(merged_model_path) and merged_model_path != final_output_dir_no_ba :\n             if os.path.exists(final_output_dir_no_ba): # shutil.copytree needs dst to not exist\n                 shutil.rmtree(final_output_dir_no_ba)\n             shutil.copytree(merged_model_path, final_output_dir_no_ba) # Copy to a final named directory\n             print(f\"Final merged reconstruction (no global BA) saved to: {final_output_dir_no_ba}\")\n\n\n    # Clean up intermediate batch directories if desired\n    if args.cleanup_batch_dirs and len(batch_reconstruction_paths) > 1: # Only if merging happened\n        print(\"\\nCleaning up intermediate batch reconstruction directories...\")\n        for batch_path in batch_reconstruction_paths:\n            parent_dir = os.path.dirname(batch_path) # Gets to batch_X\n            if os.path.exists(parent_dir):\n                shutil.rmtree(parent_dir)\n                print(f\"Removed: {parent_dir}\")\n    # Clean up merged_stages if desired\n    if args.cleanup_merge_stages and 'merged_dir' in locals() and os.path.exists(merged_dir):\n        print(\"Cleaning up intermediate merge stage directories...\")\n        shutil.rmtree(merged_dir)\n        print(f\"Removed: {merged_dir}\")\n\n\n    print(\"\\nProcessing finished.\")\n    return True","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-31T16:55:33.637444Z","iopub.execute_input":"2025-05-31T16:55:33.637718Z","iopub.status.idle":"2025-05-31T16:55:33.667332Z","shell.execute_reply.started":"2025-05-31T16:55:33.637692Z","shell.execute_reply":"2025-05-31T16:55:33.666749Z"},"editable":false},"outputs":[],"execution_count":null},{"cell_type":"code","source":"args = argparse.Namespace(\n    variant=\"train\",\n    dataset=\"phototourism\", # Example, change as needed\n    scene=\"st_pauls_cathedral\",     # Example, change as needed\n    seed=42,\n    \n    batch_size=10, # New: Number of images per batch\n    batch_overlap=5, # New: Number of overlapping images between consecutive batches\n    \n    use_ba_per_batch=True, # Original 'use_ba', now for per-batch BA\n    max_reproj_error=8.0,\n    shared_camera=False, # For per-batch BA\n    camera_type=\"SIMPLE_PINHOLE\", # For per-batch BA. COLMAP merger works best if cameras are consistent or can be resolved.\n    vis_thresh=0.2,\n    query_frame_num=5, # Per batch\n    max_query_pts=2048, # Per batch\n    fine_tracking=False, # Per batch\n    \n    use_global_ba=True, # New: Whether to perform BA after merging all batches\n    \n    cleanup_batch_dirs=False, # New: Remove individual batch reconstruction folders after merging\n    cleanup_merge_stages=False # New: Remove intermediate merged_step_* folders\n)\nargs.dataset = 'haiper'\nargs.scene = 'bike'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-31T16:55:33.668146Z","iopub.execute_input":"2025-05-31T16:55:33.668442Z","iopub.status.idle":"2025-05-31T16:55:33.686780Z","shell.execute_reply.started":"2025-05-31T16:55:33.668416Z","shell.execute_reply":"2025-05-31T16:55:33.686070Z"},"editable":false},"outputs":[],"execution_count":null},{"cell_type":"code","source":"with torch.no_grad():\n    demo_fn_batched(args)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-31T16:55:33.687464Z","iopub.execute_input":"2025-05-31T16:55:33.687683Z","iopub.status.idle":"2025-05-31T16:57:11.539142Z","shell.execute_reply.started":"2025-05-31T16:55:33.687664Z","shell.execute_reply":"2025-05-31T16:57:11.538165Z"},"editable":false},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# print(\"\\n--- Merging Batch Reconstructions ---\")\n# scene_output_dir = \"/kaggle/working/train/phototourism/st_pauls_cathedral\"\n# merged_dir = os.path.join(scene_output_dir, \"merged_stages\")\n# os.makedirs(merged_dir, exist_ok=True)\n# batch_reconstruction_paths = [f\"/kaggle/working/train/phototourism/st_pauls_cathedral/batch_{i}/sparse\" for i in range(1, 16)]\n# current_merged_path = batch_reconstruction_paths[0]\n\n# for i in range(1, len(batch_reconstruction_paths)):\n#     input_path1 = current_merged_path\n#     input_path2 = batch_reconstruction_paths[i]\n#     output_path_temp = os.path.join(merged_dir, f\"merged_step_{i-1}_to_{i}\")\n#     if os.path.exists(output_path_temp): # Clean up previous attempt for this step\n#         shutil.rmtree(output_path_temp)\n#     os.makedirs(output_path_temp, exist_ok=True)\n\n#     print(f\"Merging: '{os.path.basename(os.path.dirname(input_path1))}' and '{os.path.basename(os.path.dirname(input_path2))}' into '{os.path.basename(output_path_temp)}'\")\n    \n#     colmap_executable = \"colmap\" # Assumes it's in PATH after dpkg -i\n    \n#     merge_cmd = [\n#         colmap_executable, \"model_merger\",\n#         \"--input_path1\", input_path1,\n#         \"--input_path2\", input_path2,\n#         \"--output_path\", output_path_temp,\n#         # Add other model_merger options if needed, e.g.:\n#         # \"--robust_merge_max_reproj_error\", \"16\", # Default is 8\n#         # \"--robust_merge_min_inlier_ratio\", \"0.05\", # Default is 0.1\n#     ]\n#     print(f\"Executing: {' '.join(merge_cmd)}\")\n#     try:\n#         completed_process = subprocess.run(merge_cmd, check=True, capture_output=True, text=True)\n#         print(\"Merge stdout:\", completed_process.stdout)\n#         print(\"Merge successful for this step.\")\n#         current_merged_path = output_path_temp\n#     except subprocess.CalledProcessError as e:\n#         print(f\"Error during model merging step {i}:\")\n#         print(\"Command:\", e.cmd)\n#         print(\"Return code:\", e.returncode)\n#         print(\"Stdout:\", e.stdout)\n#         print(\"Stderr:\", e.stderr)\n#         print(f\"Failed to merge {input_path2}. Trying to continue with previous merged model if possible, or stopping.\")\n#         # Decide on error strategy: stop, or try to merge next available into current_merged_path\n#         # For now, if a merge fails, the chain breaks.\n#         # A more robust strategy might try to merge batch_reconstruction_paths[i+1] into current_merged_path\n\n# merged_model_path = current_merged_path","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-31T16:57:11.539539Z","iopub.status.idle":"2025-05-31T16:57:11.539769Z","shell.execute_reply.started":"2025-05-31T16:57:11.539666Z","shell.execute_reply":"2025-05-31T16:57:11.539676Z"},"editable":false},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell to run after your main script/function call\n\n# 1. Delete any global variables that might still hold GPU tensors\n#    (Adapt this list based on what might be left in the global scope)\nprint(\"Deleting global variables that might hold GPU tensors...\")\nvars_to_delete = ['model', 'images_global_ref', 'optimizer_global', 'dino_v2_model', 'tracker'] # Example variable names\nfor var_name in vars_to_delete:\n    if var_name in globals():\n        del globals()[var_name]\n        print(f\"Deleted global variable: {var_name}\")\n\n# 2. Run garbage collection\nimport gc\nprint(\"Running garbage collection...\")\ngc.collect()\n\n# 3. Empty PyTorch CUDA cache\nimport torch\nif torch.cuda.is_available():\n    print(\"Emptying CUDA cache...\")\n    torch.cuda.empty_cache()\n    print(\"CUDA cache emptied.\")\nelse:\n    print(\"CUDA not available, skipping cache empty.\")\n\nprint(\"GPU memory release steps attempted.\")\n\n# You can check memory usage now using:\n# !nvidia-smi","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-31T16:57:11.540802Z","iopub.status.idle":"2025-05-31T16:57:11.541046Z","shell.execute_reply.started":"2025-05-31T16:57:11.540919Z","shell.execute_reply":"2025-05-31T16:57:11.540929Z"},"editable":false},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true,"editable":false},"outputs":[],"execution_count":null}]}