We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Feature extraction/matching doesn't work when using all the frames from a video with COLMAP and ns-process-data
**Describe the bug**
COLMAP isn't able to run feature extraction and subsequent bundle adjustment when I use all the frames from a video. Everything works fine when I use the default ~300 input frames. Have anynone experienced the same?

For every image it tries to extract features from it exits with `ERROR: Failed to read image file format.`, although the file format is PNG. I see that the resulting files are 2-3 times larger (totalling ~11Mb) than what the ones extracted with the thumbnail-option is. Is there possibly an issue with convert_video_to_images and the ffmpeg_cmd?

**To Reproduce**
Steps to reproduce the behavior:
1. Run `ns-process-data video` with an input-video and `--num-frames-target` equal (or approx. equal) to the video's number of frames. And `--verbose`
2. This results in `spacing` in `process_data_utils.py` to be 1
3. Observe that all phases before Feature extraction works as expected
4. Observe that for each line of "Processed file" during feature extraction, an error `ERROR: Failed to read image file format.` is logged.
5. Observe that `/colmap/sparse/0` isn't generated which causes the Bundle adjustment step to fail.

**Expected behavior**
Feature extraction should generate features and match them, resulting in a `/colmap/sparse/0`-dir that bundle adjustment can operate on.

**Screenshots**
![image](https://user-images.githubusercontent.com/27623888/205260865-432a5f98-1eb8-469f-b316-da8a2e1641f2.png)
![image](https://user-images.githubusercontent.com/27623888/205260889-42aae792-6538-4f17-bd3c-a921f1e52648.png)
![image](https://user-images.githubusercontent.com/27623888/205260911-ff5cddb2-b9e2-432a-81ce-e86e41a995b5.png)


**Additional context**
- COLMAP version: 3.6
- FFMPEG version: 4.2.2
- The video contains 1416 frames, captured at 30Hz.
- I have tried running the ffmpeg_cmd with -r 30 

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `nerfstudio/process_data/process_data_utils.py`
Content:
```
1 # Copyright 2022 The Nerfstudio Team. All rights reserved.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 
15 """Helper utils for processing data into the nerfstudio format."""
16 
17 import shutil
18 import sys
19 from enum import Enum
20 from pathlib import Path
21 from typing import List, Optional, Tuple
22 
23 from rich.console import Console
24 from typing_extensions import Literal
25 
26 from nerfstudio.utils.rich_utils import status
27 from nerfstudio.utils.scripts import run_command
28 
29 CONSOLE = Console(width=120)
30 
31 
32 class CameraModel(Enum):
33     """Enum for camera types."""
34 
35     OPENCV = "OPENCV"
36     OPENCV_FISHEYE = "OPENCV_FISHEYE"
37 
38 
39 CAMERA_MODELS = {
40     "perspective": CameraModel.OPENCV,
41     "fisheye": CameraModel.OPENCV_FISHEYE,
42 }
43 
44 
45 def get_num_frames_in_video(video: Path) -> int:
46     """Returns the number of frames in a video.
47 
48     Args:
49         video: Path to a video.
50 
51     Returns:
52         The number of frames in a video.
53     """
54     cmd = f"ffprobe -v error -select_streams v:0 -count_packets \
55             -show_entries stream=nb_read_packets -of csv=p=0 {video}"
56     output = run_command(cmd)
57     assert output is not None
58     output = output.strip(" ,\t\n\r")
59     return int(output)
60 
61 
62 def convert_video_to_images(
63     video_path: Path, image_dir: Path, num_frames_target: int, verbose: bool = False
64 ) -> Tuple[List[str], int]:
65     """Converts a video into a sequence of images.
66 
67     Args:
68         video_path: Path to the video.
69         output_dir: Path to the output directory.
70         num_frames_target: Number of frames to extract.
71         verbose: If True, logs the output of the command.
72     Returns:
73         A tuple containing summary of the conversion and the number of extracted frames.
74     """
75 
76     with status(msg="Converting video to images...", spinner="bouncingBall", verbose=verbose):
77         # delete existing images in folder
78         for img in image_dir.glob("*.png"):
79             if verbose:
80                 CONSOLE.log(f"Deleting {img}")
81             img.unlink()
82 
83         num_frames = get_num_frames_in_video(video_path)
84         if num_frames == 0:
85             CONSOLE.print(f"[bold red]Error: Video has no frames: {video_path}")
86             sys.exit(1)
87         print("Number of frames in video:", num_frames)
88 
89         out_filename = image_dir / "frame_%05d.png"
90         ffmpeg_cmd = f"ffmpeg -i {video_path}"
91         spacing = num_frames // num_frames_target
92 
93         if spacing > 1:
94             ffmpeg_cmd += f" -vf thumbnail={spacing},setpts=N/TB -r 1"
95         else:
96             CONSOLE.print("[bold red]Can't satify requested number of frames. Extracting all frames.")
97 
98         ffmpeg_cmd += f" {out_filename}"
99 
100         run_command(ffmpeg_cmd, verbose=verbose)
101 
102     num_final_frames = len(list(image_dir.glob("*.png")))
103     summary_log = []
104     summary_log.append(f"Starting with {num_frames} video frames")
105     summary_log.append(f"We extracted {num_final_frames} images")
106     CONSOLE.log("[bold green]:tada: Done converting video to images.")
107 
108     return summary_log, num_final_frames
109 
110 
111 def copy_images_list(
112     image_paths: List[Path], image_dir: Path, crop_border_pixels: Optional[int] = None, verbose: bool = False
113 ) -> List[Path]:
114     """Copy all images in a list of Paths. Useful for filtering from a directory.
115     Args:
116         image_paths: List of Paths of images to copy to a new directory.
117         image_dir: Path to the output directory.
118         crop_border_pixels: If not None, crops each edge by the specified number of pixels.
119         verbose: If True, print extra logging.
120     Returns:
121         A list of the copied image Paths.
122     """
123 
124     # Remove original directory only if we provide a proper image folder path
125     if image_dir.is_dir() and len(image_paths):
126         shutil.rmtree(image_dir, ignore_errors=True)
127         image_dir.mkdir(exist_ok=True, parents=True)
128 
129     copied_image_paths = []
130 
131     # Images should be 1-indexed for the rest of the pipeline.
132     for idx, image_path in enumerate(image_paths):
133         if verbose:
134             CONSOLE.log(f"Copying image {idx + 1} of {len(image_paths)}...")
135         copied_image_path = image_dir / f"frame_{idx + 1:05d}{image_path.suffix}"
136         shutil.copy(image_path, copied_image_path)
137         copied_image_paths.append(copied_image_path)
138 
139     if crop_border_pixels is not None:
140         file_type = image_paths[0].suffix
141         filename = f"frame_%05d{file_type}"
142         crop = f"crop=iw-{crop_border_pixels*2}:ih-{crop_border_pixels*2}"
143         ffmpeg_cmd = f"ffmpeg -y -i {image_dir / filename} -q:v 2 -vf {crop} {image_dir / filename}"
144         run_command(ffmpeg_cmd, verbose=verbose)
145 
146     num_frames = len(image_paths)
147 
148     if num_frames == 0:
149         CONSOLE.log("[bold red]:skull: No usable images in the data folder.")
150     else:
151         CONSOLE.log("[bold green]:tada: Done copying images.")
152 
153     return copied_image_paths
154 
155 
156 def copy_images(data: Path, image_dir: Path, verbose) -> int:
157     """Copy images from a directory to a new directory.
158 
159     Args:
160         data: Path to the directory of images.
161         image_dir: Path to the output directory.
162         verbose: If True, print extra logging.
163     Returns:
164         The number of images copied.
165     """
166     with status(msg="[bold yellow]Copying images...", spinner="bouncingBall", verbose=verbose):
167         allowed_exts = [".jpg", ".jpeg", ".png", ".tif", ".tiff"]
168         image_paths = sorted([p for p in data.glob("[!.]*") if p.suffix.lower() in allowed_exts])
169 
170         if len(image_paths) == 0:
171             CONSOLE.log("[bold red]:skull: No usable images in the data folder.")
172             sys.exit(1)
173 
174         num_frames = len(copy_images_list(image_paths, image_dir, verbose))
175 
176     return num_frames
177 
178 
179 def downscale_images(image_dir: Path, num_downscales: int, verbose: bool = False) -> str:
180     """Downscales the images in the directory. Uses FFMPEG.
181 
182     Assumes images are named frame_00001.png, frame_00002.png, etc.
183 
184     Args:
185         image_dir: Path to the directory containing the images.
186         num_downscales: Number of times to downscale the images. Downscales by 2 each time.
187         verbose: If True, logs the output of the command.
188 
189     Returns:
190         Summary of downscaling.
191     """
192 
193     if num_downscales == 0:
194         return "No downscaling performed."
195 
196     with status(msg="[bold yellow]Downscaling images...", spinner="growVertical", verbose=verbose):
197         downscale_factors = [2**i for i in range(num_downscales + 1)[1:]]
198         for downscale_factor in downscale_factors:
199             assert downscale_factor > 1
200             assert isinstance(downscale_factor, int)
201             downscale_dir = image_dir.parent / f"images_{downscale_factor}"
202             downscale_dir.mkdir(parents=True, exist_ok=True)
203             file_type = image_dir.glob("frame_*").__next__().suffix
204             filename = f"frame_%05d{file_type}"
205             ffmpeg_cmd = [
206                 f"ffmpeg -i {image_dir / filename} ",
207                 f"-q:v 2 -vf scale=iw/{downscale_factor}:ih/{downscale_factor} ",
208                 f"{downscale_dir / filename}",
209             ]
210             ffmpeg_cmd = " ".join(ffmpeg_cmd)
211             run_command(ffmpeg_cmd, verbose=verbose)
212 
213     CONSOLE.log("[bold green]:tada: Done downscaling images.")
214     downscale_text = [f"[bold blue]{2**(i+1)}x[/bold blue]" for i in range(num_downscales)]
215     downscale_text = ", ".join(downscale_text[:-1]) + " and " + downscale_text[-1]
216     return f"We downsampled the images by {downscale_text}"
217 
218 
219 def find_tool_feature_matcher_combination(
220     sfm_tool: Literal["any", "colmap", "hloc"],
221     feature_type: Literal[
222         "any",
223         "sift",
224         "superpoint",
225         "superpoint_aachen",
226         "superpoint_max",
227         "superpoint_inloc",
228         "r2d2",
229         "d2net-ss",
230         "sosnet",
231         "disk",
232     ],
233     matcher_type: Literal[
234         "any", "NN", "superglue", "superglue-fast", "NN-superpoint", "NN-ratio", "NN-mutual", "adalam"
235     ],
236 ):
237     """Find a valid combination of sfm tool, feature type, and matcher type.
238     Basically, replace the default parameters 'any' by usable value
239 
240     Args:
241         sfm_tool: Sfm tool name (any, colmap, hloc)
242         feature_type: Type of image features (any, sift, superpoint, ...)
243         matcher_type: Type of matching algorithm (any, NN, superglue,...)
244 
245     Returns:
246         Tuple of sfm tool, feature type, and matcher type.
247         Returns (None,None,None) if no valid combination can be found
248     """
249     if sfm_tool == "any":
250         if (feature_type in ("any", "sift")) and (matcher_type in ("any", "NN")):
251             sfm_tool = "colmap"
252         else:
253             sfm_tool = "hloc"
254 
255     if sfm_tool == "colmap":
256         if (feature_type not in ("any", "sift")) or (matcher_type not in ("any", "NN")):
257             return (None, None, None)
258         return ("colmap", "sift", "NN")
259     if sfm_tool == "hloc":
260         if feature_type in ("any", "superpoint"):
261             feature_type = "superpoint_aachen"
262 
263         if matcher_type == "any":
264             matcher_type = "superglue"
265         elif matcher_type == "NN":
266             matcher_type = "NN-mutual"
267 
268         return (sfm_tool, feature_type, matcher_type)
269     return (None, None, None)
270
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/nerfstudio/process_data/process_data_utils.py b/nerfstudio/process_data/process_data_utils.py
--- a/nerfstudio/process_data/process_data_utils.py
+++ b/nerfstudio/process_data/process_data_utils.py
@@ -94,6 +94,7 @@
             ffmpeg_cmd += f" -vf thumbnail={spacing},setpts=N/TB -r 1"
         else:
             CONSOLE.print("[bold red]Can't satify requested number of frames. Extracting all frames.")
+            ffmpeg_cmd += " -pix_fmt bgr8"
 
         ffmpeg_cmd += f" {out_filename}"

{"golden_diff": "diff --git a/nerfstudio/process_data/process_data_utils.py b/nerfstudio/process_data/process_data_utils.py\n--- a/nerfstudio/process_data/process_data_utils.py\n+++ b/nerfstudio/process_data/process_data_utils.py\n@@ -94,6 +94,7 @@\n             ffmpeg_cmd += f\" -vf thumbnail={spacing},setpts=N/TB -r 1\"\n         else:\n             CONSOLE.print(\"[bold red]Can't satify requested number of frames. Extracting all frames.\")\n+            ffmpeg_cmd += \" -pix_fmt bgr8\"\n \n         ffmpeg_cmd += f\" {out_filename}\"\n", "issue": "Feature extraction/matching doesn't work when using all the frames from a video with COLMAP and ns-process-data\n**Describe the bug**\r\nCOLMAP isn't able to run feature extraction and subsequent bundle adjustment when I use all the frames from a video. Everything works fine when I use the default ~300 input frames. Have anynone experienced the same?\r\n\r\nFor every image it tries to extract features from it exits with `ERROR: Failed to read image file format.`, although the file format is PNG. I see that the resulting files are 2-3 times larger (totalling ~11Mb) than what the ones extracted with the thumbnail-option is. Is there possibly an issue with convert_video_to_images and the ffmpeg_cmd?\r\n\r\n**To Reproduce**\r\nSteps to reproduce the behavior:\r\n1. Run `ns-process-data video` with an input-video and `--num-frames-target` equal (or approx. equal) to the video's number of frames. And `--verbose`\r\n2. This results in `spacing` in `process_data_utils.py` to be 1\r\n3. Observe that all phases before Feature extraction works as expected\r\n4. Observe that for each line of \"Processed file\" during feature extraction, an error `ERROR: Failed to read image file format.` is logged.\r\n5. Observe that `/colmap/sparse/0` isn't generated which causes the Bundle adjustment step to fail.\r\n\r\n**Expected behavior**\r\nFeature extraction should generate features and match them, resulting in a `/colmap/sparse/0`-dir that bundle adjustment can operate on.\r\n\r\n**Screenshots**\r\n![image](https://user-images.githubusercontent.com/27623888/205260865-432a5f98-1eb8-469f-b316-da8a2e1641f2.png)\r\n![image](https://user-images.githubusercontent.com/27623888/205260889-42aae792-6538-4f17-bd3c-a921f1e52648.png)\r\n![image](https://user-images.githubusercontent.com/27623888/205260911-ff5cddb2-b9e2-432a-81ce-e86e41a995b5.png)\r\n\r\n\r\n**Additional context**\r\n- COLMAP version: 3.6\r\n- FFMPEG version: 4.2.2\r\n- The video contains 1416 frames, captured at 30Hz.\r\n- I have tried running the ffmpeg_cmd with -r 30 \n", "before_files": [{"content": "# Copyright 2022 The Nerfstudio Team. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Helper utils for processing data into the nerfstudio format.\"\"\"\n\nimport shutil\nimport sys\nfrom enum import Enum\nfrom pathlib import Path\nfrom typing import List, Optional, Tuple\n\nfrom rich.console import Console\nfrom typing_extensions import Literal\n\nfrom nerfstudio.utils.rich_utils import status\nfrom nerfstudio.utils.scripts import run_command\n\nCONSOLE = Console(width=120)\n\n\nclass CameraModel(Enum):\n    \"\"\"Enum for camera types.\"\"\"\n\n    OPENCV = \"OPENCV\"\n    OPENCV_FISHEYE = \"OPENCV_FISHEYE\"\n\n\nCAMERA_MODELS = {\n    \"perspective\": CameraModel.OPENCV,\n    \"fisheye\": CameraModel.OPENCV_FISHEYE,\n}\n\n\ndef get_num_frames_in_video(video: Path) -> int:\n    \"\"\"Returns the number of frames in a video.\n\n    Args:\n        video: Path to a video.\n\n    Returns:\n        The number of frames in a video.\n    \"\"\"\n    cmd = f\"ffprobe -v error -select_streams v:0 -count_packets \\\n            -show_entries stream=nb_read_packets -of csv=p=0 {video}\"\n    output = run_command(cmd)\n    assert output is not None\n    output = output.strip(\" ,\\t\\n\\r\")\n    return int(output)\n\n\ndef convert_video_to_images(\n    video_path: Path, image_dir: Path, num_frames_target: int, verbose: bool = False\n) -> Tuple[List[str], int]:\n    \"\"\"Converts a video into a sequence of images.\n\n    Args:\n        video_path: Path to the video.\n        output_dir: Path to the output directory.\n        num_frames_target: Number of frames to extract.\n        verbose: If True, logs the output of the command.\n    Returns:\n        A tuple containing summary of the conversion and the number of extracted frames.\n    \"\"\"\n\n    with status(msg=\"Converting video to images...\", spinner=\"bouncingBall\", verbose=verbose):\n        # delete existing images in folder\n        for img in image_dir.glob(\"*.png\"):\n            if verbose:\n                CONSOLE.log(f\"Deleting {img}\")\n            img.unlink()\n\n        num_frames = get_num_frames_in_video(video_path)\n        if num_frames == 0:\n            CONSOLE.print(f\"[bold red]Error: Video has no frames: {video_path}\")\n            sys.exit(1)\n        print(\"Number of frames in video:\", num_frames)\n\n        out_filename = image_dir / \"frame_%05d.png\"\n        ffmpeg_cmd = f\"ffmpeg -i {video_path}\"\n        spacing = num_frames // num_frames_target\n\n        if spacing > 1:\n            ffmpeg_cmd += f\" -vf thumbnail={spacing},setpts=N/TB -r 1\"\n        else:\n            CONSOLE.print(\"[bold red]Can't satify requested number of frames. Extracting all frames.\")\n\n        ffmpeg_cmd += f\" {out_filename}\"\n\n        run_command(ffmpeg_cmd, verbose=verbose)\n\n    num_final_frames = len(list(image_dir.glob(\"*.png\")))\n    summary_log = []\n    summary_log.append(f\"Starting with {num_frames} video frames\")\n    summary_log.append(f\"We extracted {num_final_frames} images\")\n    CONSOLE.log(\"[bold green]:tada: Done converting video to images.\")\n\n    return summary_log, num_final_frames\n\n\ndef copy_images_list(\n    image_paths: List[Path], image_dir: Path, crop_border_pixels: Optional[int] = None, verbose: bool = False\n) -> List[Path]:\n    \"\"\"Copy all images in a list of Paths. Useful for filtering from a directory.\n    Args:\n        image_paths: List of Paths of images to copy to a new directory.\n        image_dir: Path to the output directory.\n        crop_border_pixels: If not None, crops each edge by the specified number of pixels.\n        verbose: If True, print extra logging.\n    Returns:\n        A list of the copied image Paths.\n    \"\"\"\n\n    # Remove original directory only if we provide a proper image folder path\n    if image_dir.is_dir() and len(image_paths):\n        shutil.rmtree(image_dir, ignore_errors=True)\n        image_dir.mkdir(exist_ok=True, parents=True)\n\n    copied_image_paths = []\n\n    # Images should be 1-indexed for the rest of the pipeline.\n    for idx, image_path in enumerate(image_paths):\n        if verbose:\n            CONSOLE.log(f\"Copying image {idx + 1} of {len(image_paths)}...\")\n        copied_image_path = image_dir / f\"frame_{idx + 1:05d}{image_path.suffix}\"\n        shutil.copy(image_path, copied_image_path)\n        copied_image_paths.append(copied_image_path)\n\n    if crop_border_pixels is not None:\n        file_type = image_paths[0].suffix\n        filename = f\"frame_%05d{file_type}\"\n        crop = f\"crop=iw-{crop_border_pixels*2}:ih-{crop_border_pixels*2}\"\n        ffmpeg_cmd = f\"ffmpeg -y -i {image_dir / filename} -q:v 2 -vf {crop} {image_dir / filename}\"\n        run_command(ffmpeg_cmd, verbose=verbose)\n\n    num_frames = len(image_paths)\n\n    if num_frames == 0:\n        CONSOLE.log(\"[bold red]:skull: No usable images in the data folder.\")\n    else:\n        CONSOLE.log(\"[bold green]:tada: Done copying images.\")\n\n    return copied_image_paths\n\n\ndef copy_images(data: Path, image_dir: Path, verbose) -> int:\n    \"\"\"Copy images from a directory to a new directory.\n\n    Args:\n        data: Path to the directory of images.\n        image_dir: Path to the output directory.\n        verbose: If True, print extra logging.\n    Returns:\n        The number of images copied.\n    \"\"\"\n    with status(msg=\"[bold yellow]Copying images...\", spinner=\"bouncingBall\", verbose=verbose):\n        allowed_exts = [\".jpg\", \".jpeg\", \".png\", \".tif\", \".tiff\"]\n        image_paths = sorted([p for p in data.glob(\"[!.]*\") if p.suffix.lower() in allowed_exts])\n\n        if len(image_paths) == 0:\n            CONSOLE.log(\"[bold red]:skull: No usable images in the data folder.\")\n            sys.exit(1)\n\n        num_frames = len(copy_images_list(image_paths, image_dir, verbose))\n\n    return num_frames\n\n\ndef downscale_images(image_dir: Path, num_downscales: int, verbose: bool = False) -> str:\n    \"\"\"Downscales the images in the directory. Uses FFMPEG.\n\n    Assumes images are named frame_00001.png, frame_00002.png, etc.\n\n    Args:\n        image_dir: Path to the directory containing the images.\n        num_downscales: Number of times to downscale the images. Downscales by 2 each time.\n        verbose: If True, logs the output of the command.\n\n    Returns:\n        Summary of downscaling.\n    \"\"\"\n\n    if num_downscales == 0:\n        return \"No downscaling performed.\"\n\n    with status(msg=\"[bold yellow]Downscaling images...\", spinner=\"growVertical\", verbose=verbose):\n        downscale_factors = [2**i for i in range(num_downscales + 1)[1:]]\n        for downscale_factor in downscale_factors:\n            assert downscale_factor > 1\n            assert isinstance(downscale_factor, int)\n            downscale_dir = image_dir.parent / f\"images_{downscale_factor}\"\n            downscale_dir.mkdir(parents=True, exist_ok=True)\n            file_type = image_dir.glob(\"frame_*\").__next__().suffix\n            filename = f\"frame_%05d{file_type}\"\n            ffmpeg_cmd = [\n                f\"ffmpeg -i {image_dir / filename} \",\n                f\"-q:v 2 -vf scale=iw/{downscale_factor}:ih/{downscale_factor} \",\n                f\"{downscale_dir / filename}\",\n            ]\n            ffmpeg_cmd = \" \".join(ffmpeg_cmd)\n            run_command(ffmpeg_cmd, verbose=verbose)\n\n    CONSOLE.log(\"[bold green]:tada: Done downscaling images.\")\n    downscale_text = [f\"[bold blue]{2**(i+1)}x[/bold blue]\" for i in range(num_downscales)]\n    downscale_text = \", \".join(downscale_text[:-1]) + \" and \" + downscale_text[-1]\n    return f\"We downsampled the images by {downscale_text}\"\n\n\ndef find_tool_feature_matcher_combination(\n    sfm_tool: Literal[\"any\", \"colmap\", \"hloc\"],\n    feature_type: Literal[\n        \"any\",\n        \"sift\",\n        \"superpoint\",\n        \"superpoint_aachen\",\n        \"superpoint_max\",\n        \"superpoint_inloc\",\n        \"r2d2\",\n        \"d2net-ss\",\n        \"sosnet\",\n        \"disk\",\n    ],\n    matcher_type: Literal[\n        \"any\", \"NN\", \"superglue\", \"superglue-fast\", \"NN-superpoint\", \"NN-ratio\", \"NN-mutual\", \"adalam\"\n    ],\n):\n    \"\"\"Find a valid combination of sfm tool, feature type, and matcher type.\n    Basically, replace the default parameters 'any' by usable value\n\n    Args:\n        sfm_tool: Sfm tool name (any, colmap, hloc)\n        feature_type: Type of image features (any, sift, superpoint, ...)\n        matcher_type: Type of matching algorithm (any, NN, superglue,...)\n\n    Returns:\n        Tuple of sfm tool, feature type, and matcher type.\n        Returns (None,None,None) if no valid combination can be found\n    \"\"\"\n    if sfm_tool == \"any\":\n        if (feature_type in (\"any\", \"sift\")) and (matcher_type in (\"any\", \"NN\")):\n            sfm_tool = \"colmap\"\n        else:\n            sfm_tool = \"hloc\"\n\n    if sfm_tool == \"colmap\":\n        if (feature_type not in (\"any\", \"sift\")) or (matcher_type not in (\"any\", \"NN\")):\n            return (None, None, None)\n        return (\"colmap\", \"sift\", \"NN\")\n    if sfm_tool == \"hloc\":\n        if feature_type in (\"any\", \"superpoint\"):\n            feature_type = \"superpoint_aachen\"\n\n        if matcher_type == \"any\":\n            matcher_type = \"superglue\"\n        elif matcher_type == \"NN\":\n            matcher_type = \"NN-mutual\"\n\n        return (sfm_tool, feature_type, matcher_type)\n    return (None, None, None)\n", "path": "nerfstudio/process_data/process_data_utils.py"}], "after_files": [{"content": "# Copyright 2022 The Nerfstudio Team. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Helper utils for processing data into the nerfstudio format.\"\"\"\n\nimport shutil\nimport sys\nfrom enum import Enum\nfrom pathlib import Path\nfrom typing import List, Optional, Tuple\n\nfrom rich.console import Console\nfrom typing_extensions import Literal\n\nfrom nerfstudio.utils.rich_utils import status\nfrom nerfstudio.utils.scripts import run_command\n\nCONSOLE = Console(width=120)\n\n\nclass CameraModel(Enum):\n    \"\"\"Enum for camera types.\"\"\"\n\n    OPENCV = \"OPENCV\"\n    OPENCV_FISHEYE = \"OPENCV_FISHEYE\"\n\n\nCAMERA_MODELS = {\n    \"perspective\": CameraModel.OPENCV,\n    \"fisheye\": CameraModel.OPENCV_FISHEYE,\n}\n\n\ndef get_num_frames_in_video(video: Path) -> int:\n    \"\"\"Returns the number of frames in a video.\n\n    Args:\n        video: Path to a video.\n\n    Returns:\n        The number of frames in a video.\n    \"\"\"\n    cmd = f\"ffprobe -v error -select_streams v:0 -count_packets \\\n            -show_entries stream=nb_read_packets -of csv=p=0 {video}\"\n    output = run_command(cmd)\n    assert output is not None\n    output = output.strip(\" ,\\t\\n\\r\")\n    return int(output)\n\n\ndef convert_video_to_images(\n    video_path: Path, image_dir: Path, num_frames_target: int, verbose: bool = False\n) -> Tuple[List[str], int]:\n    \"\"\"Converts a video into a sequence of images.\n\n    Args:\n        video_path: Path to the video.\n        output_dir: Path to the output directory.\n        num_frames_target: Number of frames to extract.\n        verbose: If True, logs the output of the command.\n    Returns:\n        A tuple containing summary of the conversion and the number of extracted frames.\n    \"\"\"\n\n    with status(msg=\"Converting video to images...\", spinner=\"bouncingBall\", verbose=verbose):\n        # delete existing images in folder\n        for img in image_dir.glob(\"*.png\"):\n            if verbose:\n                CONSOLE.log(f\"Deleting {img}\")\n            img.unlink()\n\n        num_frames = get_num_frames_in_video(video_path)\n        if num_frames == 0:\n            CONSOLE.print(f\"[bold red]Error: Video has no frames: {video_path}\")\n            sys.exit(1)\n        print(\"Number of frames in video:\", num_frames)\n\n        out_filename = image_dir / \"frame_%05d.png\"\n        ffmpeg_cmd = f\"ffmpeg -i {video_path}\"\n        spacing = num_frames // num_frames_target\n\n        if spacing > 1:\n            ffmpeg_cmd += f\" -vf thumbnail={spacing},setpts=N/TB -r 1\"\n        else:\n            CONSOLE.print(\"[bold red]Can't satify requested number of frames. Extracting all frames.\")\n            ffmpeg_cmd += \" -pix_fmt bgr8\"\n\n        ffmpeg_cmd += f\" {out_filename}\"\n\n        run_command(ffmpeg_cmd, verbose=verbose)\n\n    num_final_frames = len(list(image_dir.glob(\"*.png\")))\n    summary_log = []\n    summary_log.append(f\"Starting with {num_frames} video frames\")\n    summary_log.append(f\"We extracted {num_final_frames} images\")\n    CONSOLE.log(\"[bold green]:tada: Done converting video to images.\")\n\n    return summary_log, num_final_frames\n\n\ndef copy_images_list(\n    image_paths: List[Path], image_dir: Path, crop_border_pixels: Optional[int] = None, verbose: bool = False\n) -> List[Path]:\n    \"\"\"Copy all images in a list of Paths. Useful for filtering from a directory.\n    Args:\n        image_paths: List of Paths of images to copy to a new directory.\n        image_dir: Path to the output directory.\n        crop_border_pixels: If not None, crops each edge by the specified number of pixels.\n        verbose: If True, print extra logging.\n    Returns:\n        A list of the copied image Paths.\n    \"\"\"\n\n    # Remove original directory only if we provide a proper image folder path\n    if image_dir.is_dir() and len(image_paths):\n        shutil.rmtree(image_dir, ignore_errors=True)\n        image_dir.mkdir(exist_ok=True, parents=True)\n\n    copied_image_paths = []\n\n    # Images should be 1-indexed for the rest of the pipeline.\n    for idx, image_path in enumerate(image_paths):\n        if verbose:\n            CONSOLE.log(f\"Copying image {idx + 1} of {len(image_paths)}...\")\n        copied_image_path = image_dir / f\"frame_{idx + 1:05d}{image_path.suffix}\"\n        shutil.copy(image_path, copied_image_path)\n        copied_image_paths.append(copied_image_path)\n\n    if crop_border_pixels is not None:\n        file_type = image_paths[0].suffix\n        filename = f\"frame_%05d{file_type}\"\n        crop = f\"crop=iw-{crop_border_pixels*2}:ih-{crop_border_pixels*2}\"\n        ffmpeg_cmd = f\"ffmpeg -y -i {image_dir / filename} -q:v 2 -vf {crop} {image_dir / filename}\"\n        run_command(ffmpeg_cmd, verbose=verbose)\n\n    num_frames = len(image_paths)\n\n    if num_frames == 0:\n        CONSOLE.log(\"[bold red]:skull: No usable images in the data folder.\")\n    else:\n        CONSOLE.log(\"[bold green]:tada: Done copying images.\")\n\n    return copied_image_paths\n\n\ndef copy_images(data: Path, image_dir: Path, verbose) -> int:\n    \"\"\"Copy images from a directory to a new directory.\n\n    Args:\n        data: Path to the directory of images.\n        image_dir: Path to the output directory.\n        verbose: If True, print extra logging.\n    Returns:\n        The number of images copied.\n    \"\"\"\n    with status(msg=\"[bold yellow]Copying images...\", spinner=\"bouncingBall\", verbose=verbose):\n        allowed_exts = [\".jpg\", \".jpeg\", \".png\", \".tif\", \".tiff\"]\n        image_paths = sorted([p for p in data.glob(\"[!.]*\") if p.suffix.lower() in allowed_exts])\n\n        if len(image_paths) == 0:\n            CONSOLE.log(\"[bold red]:skull: No usable images in the data folder.\")\n            sys.exit(1)\n\n        num_frames = len(copy_images_list(image_paths, image_dir, verbose))\n\n    return num_frames\n\n\ndef downscale_images(image_dir: Path, num_downscales: int, verbose: bool = False) -> str:\n    \"\"\"Downscales the images in the directory. Uses FFMPEG.\n\n    Assumes images are named frame_00001.png, frame_00002.png, etc.\n\n    Args:\n        image_dir: Path to the directory containing the images.\n        num_downscales: Number of times to downscale the images. Downscales by 2 each time.\n        verbose: If True, logs the output of the command.\n\n    Returns:\n        Summary of downscaling.\n    \"\"\"\n\n    if num_downscales == 0:\n        return \"No downscaling performed.\"\n\n    with status(msg=\"[bold yellow]Downscaling images...\", spinner=\"growVertical\", verbose=verbose):\n        downscale_factors = [2**i for i in range(num_downscales + 1)[1:]]\n        for downscale_factor in downscale_factors:\n            assert downscale_factor > 1\n            assert isinstance(downscale_factor, int)\n            downscale_dir = image_dir.parent / f\"images_{downscale_factor}\"\n            downscale_dir.mkdir(parents=True, exist_ok=True)\n            file_type = image_dir.glob(\"frame_*\").__next__().suffix\n            filename = f\"frame_%05d{file_type}\"\n            ffmpeg_cmd = [\n                f\"ffmpeg -i {image_dir / filename} \",\n                f\"-q:v 2 -vf scale=iw/{downscale_factor}:ih/{downscale_factor} \",\n                f\"{downscale_dir / filename}\",\n            ]\n            ffmpeg_cmd = \" \".join(ffmpeg_cmd)\n            run_command(ffmpeg_cmd, verbose=verbose)\n\n    CONSOLE.log(\"[bold green]:tada: Done downscaling images.\")\n    downscale_text = [f\"[bold blue]{2**(i+1)}x[/bold blue]\" for i in range(num_downscales)]\n    downscale_text = \", \".join(downscale_text[:-1]) + \" and \" + downscale_text[-1]\n    return f\"We downsampled the images by {downscale_text}\"\n\n\ndef find_tool_feature_matcher_combination(\n    sfm_tool: Literal[\"any\", \"colmap\", \"hloc\"],\n    feature_type: Literal[\n        \"any\",\n        \"sift\",\n        \"superpoint\",\n        \"superpoint_aachen\",\n        \"superpoint_max\",\n        \"superpoint_inloc\",\n        \"r2d2\",\n        \"d2net-ss\",\n        \"sosnet\",\n        \"disk\",\n    ],\n    matcher_type: Literal[\n        \"any\", \"NN\", \"superglue\", \"superglue-fast\", \"NN-superpoint\", \"NN-ratio\", \"NN-mutual\", \"adalam\"\n    ],\n):\n    \"\"\"Find a valid combination of sfm tool, feature type, and matcher type.\n    Basically, replace the default parameters 'any' by usable value\n\n    Args:\n        sfm_tool: Sfm tool name (any, colmap, hloc)\n        feature_type: Type of image features (any, sift, superpoint, ...)\n        matcher_type: Type of matching algorithm (any, NN, superglue,...)\n\n    Returns:\n        Tuple of sfm tool, feature type, and matcher type.\n        Returns (None,None,None) if no valid combination can be found\n    \"\"\"\n    if sfm_tool == \"any\":\n        if (feature_type in (\"any\", \"sift\")) and (matcher_type in (\"any\", \"NN\")):\n            sfm_tool = \"colmap\"\n        else:\n            sfm_tool = \"hloc\"\n\n    if sfm_tool == \"colmap\":\n        if (feature_type not in (\"any\", \"sift\")) or (matcher_type not in (\"any\", \"NN\")):\n            return (None, None, None)\n        return (\"colmap\", \"sift\", \"NN\")\n    if sfm_tool == \"hloc\":\n        if feature_type in (\"any\", \"superpoint\"):\n            feature_type = \"superpoint_aachen\"\n\n        if matcher_type == \"any\":\n            matcher_type = \"superglue\"\n        elif matcher_type == \"NN\":\n            matcher_type = \"NN-mutual\"\n\n        return (sfm_tool, feature_type, matcher_type)\n    return (None, None, None)\n", "path": "nerfstudio/process_data/process_data_utils.py"}]}