diff --git a/.gitignore b/.gitignore index 0f902b1..f75b5a0 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ export/ data/ .vscode/ tests/failure/ +installer/ # Copied from https://github.com/github/gitignore/blob/main/Jekyll.gitignore # Ignore metadata generated by Jekyll diff --git a/aitviewer.py b/aitviewer.py new file mode 100644 index 0000000..91b83f2 --- /dev/null +++ b/aitviewer.py @@ -0,0 +1,8 @@ +""" +Entry point of the aitviewer binary dist. +""" + +from aitviewer.viewer import Viewer + +v = Viewer() +v.run() diff --git a/aitviewer/configuration.py b/aitviewer/configuration.py index cd38026..0170b12 100644 --- a/aitviewer/configuration.py +++ b/aitviewer/configuration.py @@ -16,7 +16,7 @@ """ import os -import torch +# import torch from omegaconf import OmegaConf from omegaconf.dictconfig import DictConfig @@ -47,7 +47,7 @@ def __init__(self): self._conf.merge_with(conf) self._gui_counter = 0 - self._gpu_available = torch.cuda.is_available() + # self._gpu_available = torch.cuda.is_available() def update_conf(self, conf_obj): """Update the configuration with another configuration file or another OmegaConf configuration object.""" @@ -59,15 +59,15 @@ def update_conf(self, conf_obj): def __getattr__(self, item): if hasattr(self._conf, item): - # Some attributes of the config are converted to torch objects automatically. - if item == "device": - return torch.device(self._conf.get("device", "cuda:0") if self._gpu_available else "cpu") - elif item == "f_precision": - return getattr(torch, "float{}".format(self._conf.get("f_precision", 32))) - elif item == "i_precision": - return getattr(torch, "int{}".format(self._conf.get("i_precision", 64))) - else: - return getattr(self._conf, item) + # # Some attributes of the config are converted to torch objects automatically. + # if item == "device": + # return torch.device(self._conf.get("device", "cuda:0") if self._gpu_available else "cpu") + # elif item == "f_precision": + # return getattr(torch, "float{}".format(self._conf.get("f_precision", 32))) + # elif item == "i_precision": + # return getattr(torch, "int{}".format(self._conf.get("i_precision", 64))) + # else: + return getattr(self._conf, item) else: # Default behavior. return self.__getattribute__(item) diff --git a/aitviewer/renderables/billboard.py b/aitviewer/renderables/billboard.py index 29da0c9..078347e 100644 --- a/aitviewer/renderables/billboard.py +++ b/aitviewer/renderables/billboard.py @@ -18,11 +18,12 @@ import pickle from typing import List, Union -import cv2 +# import cv2 import moderngl import numpy as np from moderngl_window.opengl.vao import VAO -from pxr import Gf, Sdf, UsdGeom + +# from pxr import Gf, Sdf, UsdGeom from trimesh.triangles import points_to_barycentric from aitviewer.scene.camera import Camera, OpenCVCamera diff --git a/aitviewer/renderables/meshes.py b/aitviewer/renderables/meshes.py index 591a823..c5e467a 100644 --- a/aitviewer/renderables/meshes.py +++ b/aitviewer/renderables/meshes.py @@ -26,7 +26,8 @@ import trimesh.geometry from moderngl_window.opengl.vao import VAO from PIL import Image -from pxr import Gf, Sdf, UsdGeom + +# from pxr import Gf, Sdf, UsdGeom from trimesh.triangles import points_to_barycentric from aitviewer.scene.node import Node diff --git a/aitviewer/renderables/plane.py b/aitviewer/renderables/plane.py index da2d525..e441d89 100644 --- a/aitviewer/renderables/plane.py +++ b/aitviewer/renderables/plane.py @@ -16,7 +16,6 @@ """ import moderngl import numpy as np -from pxr import Gf, UsdGeom from aitviewer.renderables.meshes import Meshes from aitviewer.scene.node import Node @@ -24,6 +23,8 @@ from aitviewer.utils import set_lights_in_program, set_material_properties from aitviewer.utils.decorators import hooked +# from pxr import Gf, UsdGeom + class Plane(Node): """ diff --git a/aitviewer/scene/node.py b/aitviewer/scene/node.py index bfbda4e..5fce8cd 100644 --- a/aitviewer/scene/node.py +++ b/aitviewer/scene/node.py @@ -738,7 +738,7 @@ def export_usd(self, stage, usd_path: str, directory: str = None, verbose=False) :param stage: an object of type Usd.Stage into which to export the node :param usd_path: the path of the parent object in the USD file scene hierarchy. """ - from pxr import Gf, UsdGeom + # from pxr import Gf, UsdGeom usd_path = f"{usd_path}/{self.name.replace(' ', '_')}_{self.uid:03}" diff --git a/aitviewer/streamables/webcam.py b/aitviewer/streamables/webcam.py index 2ff8157..cb6fc11 100644 --- a/aitviewer/streamables/webcam.py +++ b/aitviewer/streamables/webcam.py @@ -14,7 +14,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ -import cv2 +# import cv2 import numpy as np from moderngl_window import geometry diff --git a/aitviewer/utils/so3.py b/aitviewer/utils/so3.py index 75abc59..7b29ac3 100644 --- a/aitviewer/utils/so3.py +++ b/aitviewer/utils/so3.py @@ -15,30 +15,30 @@ along with this program. If not, see . """ import numpy as np -import roma -import torch + +# import roma +# import torch from scipy.spatial.transform import Rotation as R from scipy.spatial.transform import RotationSpline - -def rot2aa_torch(rotation_matrices): - """ - Convert rotation matrices to rotation vectors (angle-axis representation). - :param rotation_matrices: A torch tensor of shape (..., 3, 3). - :return: A torch tensor of shape (..., 3). - """ - assert isinstance(rotation_matrices, torch.Tensor) - return roma.rotmat_to_rotvec(rotation_matrices) - - -def aa2rot_torch(rotation_vectors): - """ - Convert rotation vectors (angle-axis representation) to rotation matrices. - :param rotation_vectors: A torch tensor of shape (..., 3). - :return: A torch tensor of shape (..., 3, 3). - """ - assert isinstance(rotation_vectors, torch.Tensor) - return roma.rotvec_to_rotmat(rotation_vectors) +# def rot2aa_torch(rotation_matrices): +# """ +# Convert rotation matrices to rotation vectors (angle-axis representation). +# :param rotation_matrices: A torch tensor of shape (..., 3, 3). +# :return: A torch tensor of shape (..., 3). +# """ +# assert isinstance(rotation_matrices, torch.Tensor) +# return roma.rotmat_to_rotvec(rotation_matrices) + + +# def aa2rot_torch(rotation_vectors): +# """ +# Convert rotation vectors (angle-axis representation) to rotation matrices. +# :param rotation_vectors: A torch tensor of shape (..., 3). +# :return: A torch tensor of shape (..., 3, 3). +# """ +# assert isinstance(rotation_vectors, torch.Tensor) +# return roma.rotvec_to_rotmat(rotation_vectors) def rot2aa_numpy(rotation_matrices): diff --git a/aitviewer/utils/usd.py b/aitviewer/utils/usd.py index 3b80333..9095444 100644 --- a/aitviewer/utils/usd.py +++ b/aitviewer/utils/usd.py @@ -3,7 +3,8 @@ import numpy as np from PIL import Image -from pxr import Sdf, UsdShade + +# from pxr import Sdf, UsdShade def _get_texture_paths(path, name, directory): diff --git a/aitviewer/utils/utils.py b/aitviewer/utils/utils.py index 3bce463..537b069 100644 --- a/aitviewer/utils/utils.py +++ b/aitviewer/utils/utils.py @@ -18,19 +18,20 @@ import subprocess import numpy as np -import torch + +# import torch from scipy.interpolate import CubicSpline -from aitviewer.utils.so3 import aa2rot_torch as aa2rot -from aitviewer.utils.so3 import rot2aa_torch as rot2aa +# from aitviewer.utils.so3 import aa2rot_torch as aa2rot +# from aitviewer.utils.so3 import rot2aa_torch as rot2aa -def to_torch(x, dtype, device): - if x is None: - return None - if isinstance(x, np.ndarray): - return torch.from_numpy(x).to(dtype=dtype, device=device) - return x.to(dtype=dtype, device=device) +# def to_torch(x, dtype, device): +# if x is None: +# return None +# if isinstance(x, np.ndarray): +# return torch.from_numpy(x).to(dtype=dtype, device=device) +# return x.to(dtype=dtype, device=device) def to_numpy(x): @@ -166,28 +167,28 @@ def resample_positions(positions, fps_in, fps_out): return interpolate_positions(positions, ts_in, ts_out) -def compute_vertex_and_face_normals_torch(vertices, faces, vertex_faces, normalize=False): - """ - Compute (unnormalized) vertex normals for the given vertices. - :param vertices: A tensor of shape (N, V, 3). - :param faces: A tensor of shape (F, 3) indexing into `vertices`. - :param vertex_faces: A tensor of shape (V, MAX_VERTEX_DEGREE) that lists the face IDs each vertex is a part of. - :param normalize: Whether to make the normals unit length or not. - :return: The vertex and face normals as tensors of shape (N, V, 3) and (N, F, 3) respectively. - """ - vs = vertices[:, faces.to(dtype=torch.long)] - face_normals = torch.cross(vs[:, :, 1] - vs[:, :, 0], vs[:, :, 2] - vs[:, :, 0], dim=-1) # (N, F, 3) +# def compute_vertex_and_face_normals_torch(vertices, faces, vertex_faces, normalize=False): +# """ +# Compute (unnormalized) vertex normals for the given vertices. +# :param vertices: A tensor of shape (N, V, 3). +# :param faces: A tensor of shape (F, 3) indexing into `vertices`. +# :param vertex_faces: A tensor of shape (V, MAX_VERTEX_DEGREE) that lists the face IDs each vertex is a part of. +# :param normalize: Whether to make the normals unit length or not. +# :return: The vertex and face normals as tensors of shape (N, V, 3) and (N, F, 3) respectively. +# """ +# vs = vertices[:, faces.to(dtype=torch.long)] +# face_normals = torch.cross(vs[:, :, 1] - vs[:, :, 0], vs[:, :, 2] - vs[:, :, 0], dim=-1) # (N, F, 3) - ns_all_faces = face_normals[:, vertex_faces] # (N, V, MAX_VERTEX_DEGREE, 3) - ns_all_faces[:, vertex_faces == -1] = 0.0 - vertex_degrees = (vertex_faces > -1).sum(dim=-1).to(dtype=ns_all_faces.dtype) - vertex_normals = ns_all_faces.sum(dim=-2) / vertex_degrees[None, :, None] # (N, V, 3) +# ns_all_faces = face_normals[:, vertex_faces] # (N, V, MAX_VERTEX_DEGREE, 3) +# ns_all_faces[:, vertex_faces == -1] = 0.0 +# vertex_degrees = (vertex_faces > -1).sum(dim=-1).to(dtype=ns_all_faces.dtype) +# vertex_normals = ns_all_faces.sum(dim=-2) / vertex_degrees[None, :, None] # (N, V, 3) - if normalize: - face_normals = face_normals / torch.norm(face_normals, dim=-1).unsqueeze(-1) - vertex_normals = vertex_normals / torch.norm(vertex_normals, dim=-1).unsqueeze(-1) +# if normalize: +# face_normals = face_normals / torch.norm(face_normals, dim=-1).unsqueeze(-1) +# vertex_normals = vertex_normals / torch.norm(vertex_normals, dim=-1).unsqueeze(-1) - return vertex_normals, face_normals +# return vertex_normals, face_normals def compute_vertex_and_face_normals(vertices, faces, vertex_faces, normalize=False): @@ -329,38 +330,38 @@ def compute_union_of_current_bounds(nodes): return bounds -def local_to_global(poses, parents, output_format="aa", input_format="aa"): - """ - Convert relative joint angles to global ones by unrolling the kinematic chain. - :param poses: A tensor of shape (N, N_JOINTS*3) defining the relative poses in angle-axis format. - :param parents: A list of parents for each joint j, i.e. parent[j] is the parent of joint j. - :param output_format: 'aa' or 'rotmat'. - :param input_format: 'aa' or 'rotmat' - :return: The global joint angles as a tensor of shape (N, N_JOINTS*DOF). - """ - assert output_format in ["aa", "rotmat"] - assert input_format in ["aa", "rotmat"] - dof = 3 if input_format == "aa" else 9 - n_joints = poses.shape[-1] // dof - if input_format == "aa": - local_oris = aa2rot(poses.reshape((-1, 3))) - else: - local_oris = poses - local_oris = local_oris.reshape((-1, n_joints, 3, 3)) - global_oris = torch.zeros_like(local_oris) - - for j in range(n_joints): - if parents[j] < 0: - # root rotation - global_oris[..., j, :, :] = local_oris[..., j, :, :] - else: - parent_rot = global_oris[..., parents[j], :, :] - local_rot = local_oris[..., j, :, :] - global_oris[..., j, :, :] = torch.matmul(parent_rot, local_rot) - - if output_format == "aa": - global_oris = rot2aa(global_oris.reshape((-1, 3, 3))) - res = global_oris.reshape((-1, n_joints * 3)) - else: - res = global_oris.reshape((-1, n_joints * 3 * 3)) - return res +# def local_to_global(poses, parents, output_format="aa", input_format="aa"): +# """ +# Convert relative joint angles to global ones by unrolling the kinematic chain. +# :param poses: A tensor of shape (N, N_JOINTS*3) defining the relative poses in angle-axis format. +# :param parents: A list of parents for each joint j, i.e. parent[j] is the parent of joint j. +# :param output_format: 'aa' or 'rotmat'. +# :param input_format: 'aa' or 'rotmat' +# :return: The global joint angles as a tensor of shape (N, N_JOINTS*DOF). +# """ +# assert output_format in ["aa", "rotmat"] +# assert input_format in ["aa", "rotmat"] +# dof = 3 if input_format == "aa" else 9 +# n_joints = poses.shape[-1] // dof +# if input_format == "aa": +# local_oris = aa2rot(poses.reshape((-1, 3))) +# else: +# local_oris = poses +# local_oris = local_oris.reshape((-1, n_joints, 3, 3)) +# global_oris = torch.zeros_like(local_oris) + +# for j in range(n_joints): +# if parents[j] < 0: +# # root rotation +# global_oris[..., j, :, :] = local_oris[..., j, :, :] +# else: +# parent_rot = global_oris[..., parents[j], :, :] +# local_rot = local_oris[..., j, :, :] +# global_oris[..., j, :, :] = torch.matmul(parent_rot, local_rot) + +# if output_format == "aa": +# global_oris = rot2aa(global_oris.reshape((-1, 3, 3))) +# res = global_oris.reshape((-1, n_joints * 3)) +# else: +# res = global_oris.reshape((-1, n_joints * 3 * 3)) +# return res diff --git a/aitviewer/viewer.py b/aitviewer/viewer.py index 8f0dae2..253d2f4 100644 --- a/aitviewer/viewer.py +++ b/aitviewer/viewer.py @@ -38,7 +38,8 @@ from aitviewer.scene.camera import PinholeCamera, ViewerCamera from aitviewer.scene.node import Node from aitviewer.scene.scene import Scene -from aitviewer.server import ViewerServer + +# from aitviewer.server import ViewerServer from aitviewer.shaders import clear_shader_cache from aitviewer.streamables.streamable import Streamable from aitviewer.utils import path @@ -1753,7 +1754,7 @@ def export_frame(self, file_path, scale_factor: float = None, transparent_backgr self._last_frame_rendered_at = self.timer.time def export_usd(self, path: str, export_as_directory=False, verbose=False): - from pxr import Usd, UsdGeom + # from pxr import Usd, UsdGeom if export_as_directory: if path.endswith(".usd"): diff --git a/installer.py b/installer.py new file mode 100644 index 0000000..4df4224 --- /dev/null +++ b/installer.py @@ -0,0 +1,39 @@ +import PyInstaller.__main__ +import os +import shutil + +OUTPUT_DIR = "installer" +BUILD_PATH = os.path.join(OUTPUT_DIR, "build") +DIST_PATH = os.path.join(OUTPUT_DIR, "dist") + +os.makedirs(OUTPUT_DIR, exist_ok=True) + +PyInstaller.__main__.run([ + "aitviewer.py", + "--noconfirm", + "--windowed", + # "--exclude", "PyQt5", + "--exclude", "matplotlib", + "--exclude", "pandas", + "--exclude", "cv2", + "--exclude", "open3d", + "--hidden-import", "moderngl_window.loaders.program.separate", + "--hidden-import", "moderngl_window.loaders.program.single", + "--hidden-import", "moderngl_window.loaders.program", + "--hidden-import", "moderngl_window.context.pyglet", + "--hidden-import", "glcontext", + "--workpath", BUILD_PATH, + "--distpath", DIST_PATH, + "--specpath", OUTPUT_DIR, + "--add-data", os.path.join("..", "aitviewer", "resources", "*") + os.pathsep + os.path.join("aitviewer", "resources"), + "--add-data", os.path.join("..", "aitviewer", "shaders", "**", "*") + os.pathsep + os.path.join("aitviewer", "shaders"), + "--add-data", os.path.join("..", "aitviewer", "aitvconfig.yaml") + os.pathsep + os.path.join("aitviewer"), +]) + +root = os.path.join(DIST_PATH, "aitviewer") +shutil.copytree(os.path.join("aitviewer", "resources"), os.path.join(root, "aitviewer", "resources"), dirs_exist_ok=True) +shutil.copytree(os.path.join("aitviewer", "shaders"), os.path.join(root, "aitviewer", "shaders"), dirs_exist_ok=True) +shutil.copy(os.path.join("aitviewer", "aitvconfig.yaml"), os.path.join(root, "aitviewer")) + +# moderngl.window requires a scene folder to exist. +os.makedirs(os.path.join(OUTPUT_DIR, "dist", "aitviewer", "moderngl_window", "scene", "programs")) \ No newline at end of file