nvs2colmap 0.4.1__tar.gz → 0.5.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. {nvs2colmap-0.4.1 → nvs2colmap-0.5.2}/PKG-INFO +1 -1
  2. nvs2colmap-0.5.2/nvs2colmap/blender/__main__.py +54 -0
  3. nvs2colmap-0.5.2/nvs2colmap/blender/render.py +42 -0
  4. nvs2colmap-0.5.2/nvs2colmap/blender/sample_cameras.py +101 -0
  5. {nvs2colmap-0.4.1 → nvs2colmap-0.5.2}/nvs2colmap.egg-info/PKG-INFO +1 -1
  6. {nvs2colmap-0.4.1 → nvs2colmap-0.5.2}/nvs2colmap.egg-info/SOURCES.txt +6 -0
  7. {nvs2colmap-0.4.1 → nvs2colmap-0.5.2}/nvs2colmap.egg-info/entry_points.txt +2 -0
  8. {nvs2colmap-0.4.1 → nvs2colmap-0.5.2}/pyproject.toml +3 -1
  9. {nvs2colmap-0.4.1 → nvs2colmap-0.5.2}/LICENSE +0 -0
  10. {nvs2colmap-0.4.1 → nvs2colmap-0.5.2}/README.md +0 -0
  11. {nvs2colmap-0.4.1 → nvs2colmap-0.5.2}/nvs2colmap/__init__.py +0 -0
  12. {nvs2colmap-0.4.1 → nvs2colmap-0.5.2}/nvs2colmap/colmap.py +0 -0
  13. {nvs2colmap-0.4.1 → nvs2colmap-0.5.2}/nvs2colmap/dynamic3dgs/__main__.py +0 -0
  14. {nvs2colmap-0.4.1 → nvs2colmap-0.5.2}/nvs2colmap/dynamic3dgs/camera_meta.py +0 -0
  15. {nvs2colmap-0.4.1 → nvs2colmap-0.5.2}/nvs2colmap/dynamic3dgs/link_frames.py +0 -0
  16. {nvs2colmap-0.4.1 → nvs2colmap-0.5.2}/nvs2colmap/dynamic3dgs/points.py +0 -0
  17. {nvs2colmap-0.4.1 → nvs2colmap-0.5.2}/nvs2colmap/extract_videos.py +0 -0
  18. {nvs2colmap-0.4.1 → nvs2colmap-0.5.2}/nvs2colmap/n3dv/__main__.py +0 -0
  19. {nvs2colmap-0.4.1 → nvs2colmap-0.5.2}/nvs2colmap/n3dv/extract_videos.py +0 -0
  20. {nvs2colmap-0.4.1 → nvs2colmap-0.5.2}/nvs2colmap/n3dv/poses_bounds.py +0 -0
  21. {nvs2colmap-0.4.1 → nvs2colmap-0.5.2}/nvs2colmap/stnerf/__init__.py +0 -0
  22. {nvs2colmap-0.4.1 → nvs2colmap-0.5.2}/nvs2colmap/utils/__init__.py +0 -0
  23. {nvs2colmap-0.4.1 → nvs2colmap-0.5.2}/nvs2colmap/utils/colmap.py +0 -0
  24. {nvs2colmap-0.4.1 → nvs2colmap-0.5.2}/nvs2colmap/utils/ffmpeg.py +0 -0
  25. {nvs2colmap-0.4.1 → nvs2colmap-0.5.2}/nvs2colmap/utils/rotation.py +0 -0
  26. {nvs2colmap-0.4.1 → nvs2colmap-0.5.2}/nvs2colmap/write_model.py +0 -0
  27. {nvs2colmap-0.4.1 → nvs2colmap-0.5.2}/nvs2colmap/write_pcd.py +0 -0
  28. {nvs2colmap-0.4.1 → nvs2colmap-0.5.2}/nvs2colmap.egg-info/dependency_links.txt +0 -0
  29. {nvs2colmap-0.4.1 → nvs2colmap-0.5.2}/nvs2colmap.egg-info/requires.txt +0 -0
  30. {nvs2colmap-0.4.1 → nvs2colmap-0.5.2}/nvs2colmap.egg-info/top_level.txt +0 -0
  31. {nvs2colmap-0.4.1 → nvs2colmap-0.5.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nvs2colmap
3
- Version: 0.4.1
3
+ Version: 0.5.2
4
4
  Summary: Utilities for converting novel view synthesis datasets to COLMAP format.
5
5
  Author-email: Howard Yin <yindaheng98@gmail.com>
6
6
  Maintainer-email: Howard Yin <yindaheng98@gmail.com>
@@ -0,0 +1,54 @@
1
+ """Render a Blender file into an n3dv scene directory."""
2
+
3
+ import argparse
4
+ import json
5
+ import subprocess
6
+ from pathlib import Path
7
+
8
+ import numpy as np
9
+
10
+ from nvs2colmap.n3dv.poses_bounds import read_camera_meta_n3dv
11
+
12
+
13
+ def main() -> None:
14
+ parser = argparse.ArgumentParser(
15
+ description="Render a .blend file into camera videos next to poses_bounds.npy."
16
+ )
17
+ parser.add_argument("--path", type=Path, required=True, help="Scene directory containing poses_bounds.npy.")
18
+ parser.add_argument("--blend", type=Path, required=True, help="Blender file to render.")
19
+ parser.add_argument("--blender", default="blender", help="Blender executable.")
20
+ args = parser.parse_args()
21
+
22
+ folder = args.path.resolve()
23
+ n_cameras, Rs, Ts, hwf, _ = read_camera_meta_n3dv(folder)
24
+ w2c = np.tile(np.eye(4), (n_cameras, 1, 1))
25
+ w2c[:, :3, :3] = Rs.numpy()
26
+ w2c[:, :3, 3] = Ts.numpy()
27
+ # OpenCV camera (+Z forward, +Y down) to Blender camera (-Z forward, +Y up).
28
+ c2w = np.linalg.inv(w2c) @ np.diag([1.0, -1.0, -1.0, 1.0])
29
+ hwf = hwf.numpy()
30
+ pad = max(2, len(str(n_cameras - 1)))
31
+ cameras = [
32
+ [f"cam{i:0{pad}d}", int(round(float(hwf[i, 0]))), int(round(float(hwf[i, 1]))), float(hwf[i, 2]), c2w[i].tolist()]
33
+ for i in range(n_cameras)
34
+ ]
35
+ subprocess.run(
36
+ [
37
+ args.blender,
38
+ str(args.blend.resolve()),
39
+ "--background",
40
+ "--python",
41
+ str(Path(__file__).with_name("render.py")),
42
+ "--",
43
+ "--output",
44
+ str(folder),
45
+ "--cameras",
46
+ json.dumps(cameras, separators=(",", ":")),
47
+ ],
48
+ check=True,
49
+ )
50
+ print(f"Done: {folder}")
51
+
52
+
53
+ if __name__ == "__main__":
54
+ main()
@@ -0,0 +1,42 @@
1
+ """Render one video per camera. Executed by Blender, not by the project interpreter."""
2
+
3
+ import argparse
4
+ import json
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ import bpy
9
+ import mathutils
10
+
11
+
12
+ def main() -> None:
13
+ parser = argparse.ArgumentParser()
14
+ parser.add_argument("--output", type=Path, required=True)
15
+ parser.add_argument("--cameras", type=json.loads, required=True)
16
+ args = parser.parse_args(sys.argv[sys.argv.index("--") + 1:])
17
+
18
+ scene = bpy.context.scene
19
+ cam_obj = scene.camera
20
+ cam_obj.animation_data_clear()
21
+ cam = cam_obj.data
22
+ cam.sensor_fit = "HORIZONTAL"
23
+ cam.sensor_width = 36.0
24
+ scene.render.resolution_percentage = 100
25
+ scene.render.image_settings.file_format = "FFMPEG"
26
+ scene.render.ffmpeg.format = "MPEG4"
27
+ scene.render.ffmpeg.codec = "H264"
28
+ scene.render.ffmpeg.constant_rate_factor = "MEDIUM"
29
+ scene.render.ffmpeg.audio_codec = "NONE"
30
+ scene.render.use_file_extension = False
31
+
32
+ for name, height, width, focal, c2w in args.cameras:
33
+ scene.render.resolution_x = width
34
+ scene.render.resolution_y = height
35
+ cam.lens = focal * cam.sensor_width / width
36
+ cam_obj.matrix_world = mathutils.Matrix(c2w)
37
+ scene.render.filepath = str(args.output / f"{name}.mp4")
38
+ bpy.ops.render.render(animation=True)
39
+
40
+
41
+ if __name__ == "__main__":
42
+ main()
@@ -0,0 +1,101 @@
1
+ """Sample cameras on a spherical cap and write poses_bounds.npy."""
2
+
3
+ import argparse
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ import numpy as np
8
+
9
+
10
+ def cap_direction(axis, cap_angle, rng):
11
+ z = rng.uniform(np.cos(cap_angle), 1.0)
12
+ phi = rng.uniform(0, 2 * np.pi)
13
+ xy = np.sqrt(1 - z * z)
14
+ local = np.array([xy * np.cos(phi), xy * np.sin(phi), z])
15
+ helper = np.array([0.0, 1.0, 0.0]) if abs(axis[2]) > 0.999 else np.array([0.0, 0.0, 1.0])
16
+ tangent = np.cross(helper, axis)
17
+ tangent /= np.linalg.norm(tangent)
18
+ bitangent = np.cross(axis, tangent)
19
+ return tangent * local[0] + bitangent * local[1] + axis * local[2]
20
+
21
+
22
+ def main() -> None:
23
+ parser = argparse.ArgumentParser()
24
+ parser.add_argument("--output", type=Path, required=True)
25
+ parser.add_argument("--n-cameras", type=int, required=True)
26
+ parser.add_argument("--height", type=int, required=True)
27
+ parser.add_argument("--width", type=int, required=True)
28
+ parser.add_argument("--focal", type=float, required=True, help="Focal length in pixels. fx = fy, principal point at the image center.")
29
+ parser.add_argument("--center", type=float, nargs=3, required=True, help="Shell center and look-at point.")
30
+ parser.add_argument("--radius-min", type=float, default=10.0)
31
+ parser.add_argument("--radius-max", type=float, default=12.0)
32
+ parser.add_argument("--azimuth", type=float, default=0.0, help="Cap axis azimuth in degrees.")
33
+ parser.add_argument("--elevation", type=float, default=90.0, help="Cap axis elevation in degrees. 90 looks straight up.")
34
+ parser.add_argument("--cap-angle", type=float, default=90.0, help="Cap half-angle in degrees. 90 is a hemisphere; smaller clusters cameras on one side.")
35
+ parser.add_argument("--min-angle", type=float, default=10.0, help="Minimum angle in degrees between a camera and its nearest neighbor.")
36
+ parser.add_argument("--max-angle", type=float, default=45.0, help="Maximum angle in degrees between a camera and its nearest neighbor.")
37
+ argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else sys.argv[1:]
38
+ args = parser.parse_args(argv)
39
+
40
+ azimuth = np.deg2rad(args.azimuth)
41
+ elevation = np.deg2rad(args.elevation)
42
+ axis = np.array([
43
+ np.cos(elevation) * np.cos(azimuth),
44
+ np.cos(elevation) * np.sin(azimuth),
45
+ np.sin(elevation),
46
+ ])
47
+ cap_angle = np.deg2rad(args.cap_angle)
48
+ min_angle = np.deg2rad(args.min_angle)
49
+ max_angle = np.deg2rad(args.max_angle)
50
+ center = np.array(args.center)
51
+ rng = np.random.default_rng()
52
+
53
+ directions = []
54
+ radii = []
55
+ tries = 0
56
+ while len(directions) < args.n_cameras:
57
+ tries += 1
58
+ if tries > 100:
59
+ raise RuntimeError("Could not place that many cameras on this cap.")
60
+ direction = cap_direction(axis, cap_angle, rng)
61
+ if directions:
62
+ nearest = np.arccos(np.clip(np.stack(directions) @ direction, -1.0, 1.0)).min()
63
+ if nearest < min_angle or nearest > max_angle:
64
+ continue
65
+ directions.append(direction)
66
+ radii.append(rng.uniform(args.radius_min, args.radius_max))
67
+
68
+ rows = []
69
+ for direction, radius in zip(directions, radii):
70
+ position = center + direction * radius
71
+ forward = center - position
72
+ forward /= np.linalg.norm(forward)
73
+ up = np.array([0.0, 0.0, 1.0])
74
+ right = np.cross(forward, up)
75
+ if np.dot(right, right) == 0:
76
+ up = np.array([0.0, 1.0, 0.0])
77
+ right = np.cross(forward, up)
78
+ right /= np.linalg.norm(right)
79
+ down = np.cross(forward, right)
80
+ c2w = np.eye(4)
81
+ c2w[:3, 0] = right
82
+ c2w[:3, 1] = down
83
+ c2w[:3, 2] = forward
84
+ c2w[:3, 3] = position
85
+ # Inverse of read_camera_meta_n3dv: poses_bounds columns are [down, right, back, position, hwf].
86
+ pose = np.zeros((3, 5))
87
+ pose[:3, 0] = c2w[:3, 1]
88
+ pose[:3, 1] = c2w[:3, 0]
89
+ pose[:3, 2] = -c2w[:3, 2]
90
+ pose[:3, 3] = c2w[:3, 3]
91
+ pose[:, 4] = (args.height, args.width, args.focal)
92
+ rows.append(np.concatenate([pose.reshape(-1), [radius * 0.1, radius * 2]]))
93
+
94
+ args.output.mkdir(parents=True, exist_ok=True)
95
+ path = args.output / "poses_bounds.npy"
96
+ np.save(path, np.stack(rows))
97
+ print(f"Done: {path}")
98
+
99
+
100
+ if __name__ == "__main__":
101
+ main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nvs2colmap
3
- Version: 0.4.1
3
+ Version: 0.5.2
4
4
  Summary: Utilities for converting novel view synthesis datasets to COLMAP format.
5
5
  Author-email: Howard Yin <yindaheng98@gmail.com>
6
6
  Maintainer-email: Howard Yin <yindaheng98@gmail.com>
@@ -6,6 +6,9 @@ pyproject.toml
6
6
  ./nvs2colmap/extract_videos.py
7
7
  ./nvs2colmap/write_model.py
8
8
  ./nvs2colmap/write_pcd.py
9
+ ./nvs2colmap/blender/__main__.py
10
+ ./nvs2colmap/blender/render.py
11
+ ./nvs2colmap/blender/sample_cameras.py
9
12
  ./nvs2colmap/dynamic3dgs/__main__.py
10
13
  ./nvs2colmap/dynamic3dgs/camera_meta.py
11
14
  ./nvs2colmap/dynamic3dgs/link_frames.py
@@ -29,6 +32,9 @@ nvs2colmap.egg-info/dependency_links.txt
29
32
  nvs2colmap.egg-info/entry_points.txt
30
33
  nvs2colmap.egg-info/requires.txt
31
34
  nvs2colmap.egg-info/top_level.txt
35
+ nvs2colmap/blender/__main__.py
36
+ nvs2colmap/blender/render.py
37
+ nvs2colmap/blender/sample_cameras.py
32
38
  nvs2colmap/dynamic3dgs/__main__.py
33
39
  nvs2colmap/dynamic3dgs/camera_meta.py
34
40
  nvs2colmap/dynamic3dgs/link_frames.py
@@ -1,4 +1,6 @@
1
1
  [console_scripts]
2
+ nvs2colmap-blender = nvs2colmap.blender.__main__:main
2
3
  nvs2colmap-dynamic3dgs = nvs2colmap.dynamic3dgs.__main__:main
3
4
  nvs2colmap-extract-videos = nvs2colmap.extract_videos:main
4
5
  nvs2colmap-n3dv = nvs2colmap.n3dv.__main__:main
6
+ nvs2colmap-sample-cameras = nvs2colmap.blender.sample_cameras:main
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "nvs2colmap"
7
- version = "0.4.1"
7
+ version = "0.5.2"
8
8
  description = "Utilities for converting novel view synthesis datasets to COLMAP format."
9
9
  readme = "README.md"
10
10
  authors = [{ name = "Howard Yin", email = "yindaheng98@gmail.com" }]
@@ -45,6 +45,8 @@ Issues = "https://github.com/yindaheng98/NVS2COLMAP/issues"
45
45
  nvs2colmap-extract-videos = "nvs2colmap.extract_videos:main"
46
46
  nvs2colmap-n3dv = "nvs2colmap.n3dv.__main__:main"
47
47
  nvs2colmap-dynamic3dgs = "nvs2colmap.dynamic3dgs.__main__:main"
48
+ nvs2colmap-blender = "nvs2colmap.blender.__main__:main"
49
+ nvs2colmap-sample-cameras = "nvs2colmap.blender.sample_cameras:main"
48
50
 
49
51
  [tool.setuptools]
50
52
  packages = { find = { include = ["nvs2colmap*"] } }
File without changes
File without changes
File without changes