From 224db7a9a2adf80e5bbc1265da725c8d356ff691 Mon Sep 17 00:00:00 2001 From: SamTov Date: Sun, 8 Oct 2023 13:17:57 +0200 Subject: [PATCH 01/13] updates --- examples/rendering.py | 69 ++++++++++++++++++++++++++++++++++ znvis/visualizer/visualizer.py | 2 +- 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 examples/rendering.py diff --git a/examples/rendering.py b/examples/rendering.py new file mode 100644 index 0000000..5b2f88b --- /dev/null +++ b/examples/rendering.py @@ -0,0 +1,69 @@ +import open3d as o3d +import mitsuba as mi + + +def render_mesh(mesh, mesh_center): + scene = mi.load_dict({ + 'type': 'scene', + 'integrator': { + 'type': 'path' + }, + 'light': { + 'type': 'constant', + 'radiance': { + 'type': 'rgb', + 'value': 1.0 + } + # NOTE: For better results comment out the constant emitter above + # and uncomment out the lines below changing the filename to an HDRI + # envmap you have. + # 'type': 'envmap', + # 'filename': '/home/renes/Downloads/solitude_interior_4k.exr' + }, + 'sensor': { + 'type': + 'perspective', + 'focal_length': + '50mm', + 'to_world': + mi.ScalarTransform4f.look_at(origin=[0, 0, 5], + target=mesh_center, + up=[0, 1, 0]), + 'thefilm': { + 'type': 'hdrfilm', + 'width': 1024, + 'height': 768, + }, + 'thesampler': { + 'type': 'multijitter', + 'sample_count': 64, + }, + }, + 'themesh': mesh, + }) + + img = mi.render(scene, spp=256) + return img + + +# Default to LLVM variant which should be available on all +# platforms. If you have a system with a CUDA device then comment out LLVM +# variant and uncomment cuda variant +mi.set_variant('llvm_ad_rgb') +# mi.set_variant('cuda_ad_rgb') + + +mesh = o3d.t.geometry.TriangleMesh.create_sphere(radius=1.0) +mesh.compute_vertex_normals() +mesh_center = mesh.get_axis_aligned_bounding_box().get_center() + + +# mesh.material.set_default_properties() +print('Render mesh with material converted to Mitsuba principled BSDF') +# mi_mesh = mesh.to_mitsuba('sphere') +# img = render_mesh(mi_mesh, mesh_center.numpy()) +# mi.Bitmap(img).write('test.exr') + + +# Render with Open3D +o3d.visualization.draw(mesh) \ No newline at end of file diff --git a/znvis/visualizer/visualizer.py b/znvis/visualizer/visualizer.py index ea347fa..d6032aa 100644 --- a/znvis/visualizer/visualizer.py +++ b/znvis/visualizer/visualizer.py @@ -394,7 +394,7 @@ def save_callable(): mesh += item.mesh_list[self.counter] o3d.io.write_triangle_mesh( - (self.obj_folder / f"export_mesh_{self.counter}.obj").as_posix(), mesh + (self.obj_folder / f"export_mesh_{self.counter}.ply").as_posix(), mesh ) self.save_thread_finished = True From d3cb1f4e77f8b2f844e8646b6055e61c59c8ddfc Mon Sep 17 00:00:00 2001 From: SamTov Date: Sun, 8 Oct 2023 22:56:37 +0200 Subject: [PATCH 02/13] Add mitsuba rendering. --- examples/rendering.py | 69 -------------- examples/simple_spheres.py | 14 +-- znvis/__init__.py | 2 + znvis/material/material.py | 4 + znvis/rendering/__init__.py | 25 +++++ znvis/rendering/mitsuba.py | 165 +++++++++++++++++++++++++++++++++ znvis/visualizer/visualizer.py | 49 ++++++++-- 7 files changed, 246 insertions(+), 82 deletions(-) delete mode 100644 examples/rendering.py create mode 100644 znvis/rendering/__init__.py create mode 100644 znvis/rendering/mitsuba.py diff --git a/examples/rendering.py b/examples/rendering.py deleted file mode 100644 index 5b2f88b..0000000 --- a/examples/rendering.py +++ /dev/null @@ -1,69 +0,0 @@ -import open3d as o3d -import mitsuba as mi - - -def render_mesh(mesh, mesh_center): - scene = mi.load_dict({ - 'type': 'scene', - 'integrator': { - 'type': 'path' - }, - 'light': { - 'type': 'constant', - 'radiance': { - 'type': 'rgb', - 'value': 1.0 - } - # NOTE: For better results comment out the constant emitter above - # and uncomment out the lines below changing the filename to an HDRI - # envmap you have. - # 'type': 'envmap', - # 'filename': '/home/renes/Downloads/solitude_interior_4k.exr' - }, - 'sensor': { - 'type': - 'perspective', - 'focal_length': - '50mm', - 'to_world': - mi.ScalarTransform4f.look_at(origin=[0, 0, 5], - target=mesh_center, - up=[0, 1, 0]), - 'thefilm': { - 'type': 'hdrfilm', - 'width': 1024, - 'height': 768, - }, - 'thesampler': { - 'type': 'multijitter', - 'sample_count': 64, - }, - }, - 'themesh': mesh, - }) - - img = mi.render(scene, spp=256) - return img - - -# Default to LLVM variant which should be available on all -# platforms. If you have a system with a CUDA device then comment out LLVM -# variant and uncomment cuda variant -mi.set_variant('llvm_ad_rgb') -# mi.set_variant('cuda_ad_rgb') - - -mesh = o3d.t.geometry.TriangleMesh.create_sphere(radius=1.0) -mesh.compute_vertex_normals() -mesh_center = mesh.get_axis_aligned_bounding_box().get_center() - - -# mesh.material.set_default_properties() -print('Render mesh with material converted to Mitsuba principled BSDF') -# mi_mesh = mesh.to_mitsuba('sphere') -# img = render_mesh(mi_mesh, mesh_center.numpy()) -# mi.Bitmap(img).write('test.exr') - - -# Render with Open3D -o3d.visualization.draw(mesh) \ No newline at end of file diff --git a/examples/simple_spheres.py b/examples/simple_spheres.py index 4a55279..b19df00 100644 --- a/examples/simple_spheres.py +++ b/examples/simple_spheres.py @@ -30,17 +30,19 @@ """ material_1 = vis.Material(colour=np.array([30, 144, 255]) / 255, alpha=0.6) # Define the first particle. - trajectory = np.random.uniform(-10, 10, (10, 10, 3)) - mesh = vis.Sphere(radius=2.0, resolution=50, material=material_1) - particle = vis.Particle(name="Blue", mesh=mesh, position=trajectory, smoothing=True) + trajectory = np.random.uniform(-100, 100, (100, 1000, 3)) + mesh = vis.Sphere(radius=2.0, resolution=10, material=material_1) + particle = vis.Particle( + name="Blue", mesh=mesh, position=trajectory, smoothing=False + ) material_2 = vis.Material(colour=np.array([255, 140, 0]) / 255, alpha=1.0) # Define the second particle. - trajectory_2 = np.random.uniform(-10, 10, (10, 10, 3)) - mesh_2 = vis.Sphere(radius=1.0, resolution=50, material=material_2) + trajectory_2 = np.random.uniform(-10, 10, (100, 1000, 3)) + mesh_2 = vis.Sphere(radius=1.0, resolution=10, material=material_2) particle_2 = vis.Particle( - name="Orange", mesh=mesh_2, position=trajectory_2, smoothing=True + name="Orange", mesh=mesh_2, position=trajectory_2, smoothing=False ) # Construct the visualizer and run diff --git a/znvis/__init__.py b/znvis/__init__.py index 0e45046..8e2b20e 100644 --- a/znvis/__init__.py +++ b/znvis/__init__.py @@ -21,6 +21,7 @@ ------- init file for the main ZnVis package. """ +from znvis import rendering from znvis.bounding_objects.bounding_box import BoundingBox from znvis.material.material import Material from znvis.mesh.custom import CustomMesh @@ -37,4 +38,5 @@ CustomMesh.__name__, BoundingBox.__name__, Material.__name__, + rendering.__name__, ] diff --git a/znvis/material/material.py b/znvis/material/material.py index e099d60..99db158 100644 --- a/znvis/material/material.py +++ b/znvis/material/material.py @@ -45,6 +45,8 @@ class Material: How reflective the material is. anisotropy: float How anisotopic the material is. + mitsuba_bsdf: mitsuba.bsdf (default: None) + Mitsuba bsdf object. """ colour: np.ndarray = np.array([59.0, 53.0, 97.0]) / 255 @@ -53,3 +55,5 @@ class Material: metallic: float = 0.0 reflectance: float = 0.4 anisotropy: float = 0.4 + + mitsuba_bsdf = None diff --git a/znvis/rendering/__init__.py b/znvis/rendering/__init__.py new file mode 100644 index 0000000..b200eb6 --- /dev/null +++ b/znvis/rendering/__init__.py @@ -0,0 +1,25 @@ +""" +ZnVis: A Zincwarecode package. +License +------- +This program and the accompanying materials are made available under the terms +of the Eclipse Public License v2.0 which accompanies this distribution, and is +available at https://www.eclipse.org/legal/epl-v20.html +SPDX-License-Identifier: EPL-2.0 +Copyright Contributors to the Zincwarecode Project. +Contact Information +------------------- +email: zincwarecode@gmail.com +github: https://github.com/zincware +web: https://zincwarecode.com/ +Citation +-------- +If you use this module please cite us with: + +Summary +------- +Init file for the rendering module. +""" +from znvis.rendering.mitsuba import Mitsuba + +__all__ = [Mitsuba.__name__] diff --git a/znvis/rendering/mitsuba.py b/znvis/rendering/mitsuba.py new file mode 100644 index 0000000..2302950 --- /dev/null +++ b/znvis/rendering/mitsuba.py @@ -0,0 +1,165 @@ +""" +ZnVis: A Zincwarecode package. +License +------- +This program and the accompanying materials are made available under the terms +of the Eclipse Public License v2.0 which accompanies this distribution, and is +available at https://www.eclipse.org/legal/epl-v20.html +SPDX-License-Identifier: EPL-2.0 +Copyright Contributors to the Zincwarecode Project. +Contact Information +------------------- +email: zincwarecode@gmail.com +github: https://github.com/zincware +web: https://zincwarecode.com/ +Citation +-------- +If you use this module please cite us with: + +Summary +------- +Mitsuba rendering module. +""" +import os + +import mitsuba as mi +import numpy as np +import open3d as o3d + +mi.set_variant("llvm_ad_rgb") + + +# Default scene dict. +default_scene_dict = { + "type": "scene", + "integrator": {"type": "path"}, + "light": {"type": "constant", "radiance": {"type": "rgb", "value": 1.0}}, + "sensor": { + "type": "perspective", + "fov": 60, + "thefilm": { + "type": "hdrfilm", + "width": 4096, + "height": 2160, + }, + "thesampler": { + "type": "multijitter", + "sample_count": 20, + }, + }, +} + + +class Mitsuba: + """ + Class for Mitsuba rendering. + """ + + def __init__(self, scene_dict: dict = None, update_camera: bool = True) -> None: + """ + Initialize the Mitsuba renderer. + + Parameters + ---------- + scene_dict : dict (default = None) + Dictionary containing the scene information for Mitsuba. + The mesh objects will be added to this dictionary. Do not + include mesh material information here unless an additional + mesh is being added to the scene from outside of ZnVis. + If no dict is provided, a templated dict will be used. + update_camera : bool (default = True) + If True, the camera will be updated to look at the mesh center. + Set this to False if you want to use a custom camera which is + defined in the scene_dict. + """ + if scene_dict is None: + scene_dict = default_scene_dict + self.scene_dict = scene_dict + self.update_camera = update_camera + + def _update_camera(self, view_matrix: np.ndarray) -> None: + """ + Update the camera to look at the mesh center. + + Parameters + ---------- + view_matrix : np.ndarray + View matrix for the camera from open3d. + + Notes + ----- + This function updates the camera in the scene_dict. + It should be called before rendering. + """ + camera_position = view_matrix[:3, 3] + forward_direction = view_matrix[:3, 2] + camera_target = -1 * camera_position + forward_direction + up_direction = view_matrix[:3, 1] + + view_matrix = mi.ScalarTransform4f.look_at( + origin=camera_position, target=camera_target, up=up_direction + ) + self.scene_dict["sensor"]["to_world"] = view_matrix + + def render_mesh_objects( + self, + mesh_objects: dict, + view_matrix: np.ndarray, + save_dir: str = "./", + save_name: str = "znvis_render.exr", + ): + """ + Render mesh objects. + + Parameters + ---------- + mesh_objects : list + List of mesh objects to render. + view_matrix : np.ndarray + View matrix for the camera from open3d. + save_dir : str (default = "./") + Directory to save the rendered image. + save_name : str (default = "znvis_render.exr") + Name of the rendered image. + + Returns + ------- + Saves an image to disk. + """ + # Update camera. + if self.update_camera: + self._update_camera(view_matrix) + + # Add mesh objects to scene dict. + for mesh_name in mesh_objects: + bsdf = mesh_objects[mesh_name]["bsdf"] + material = mesh_objects[mesh_name]["material"] + + # Convert to a tensor mesh. + # print(mesh_objects[mesh_name]["mesh"].material) + mesh = o3d.t.geometry.TriangleMesh.from_legacy( + mesh_objects[mesh_name]["mesh"] + ) + + if bsdf is None: + mesh.material.set_default_properties() + mesh.material.material_name = "defaultLit" + mesh.material.vector_properties["base_color"] = material.base_color + mesh.material.scalar_properties["roughness"] = material.base_roughness + mesh.material.scalar_properties["metallic"] = material.base_metallic + mesh.material.scalar_properties[ + "reflectance" + ] = material.base_reflectance + mesh.material.scalar_properties["anisotropy"] = material.base_anisotropy + + # Convert to Mitsuba mesh + mitsuba_mesh = mesh.to_mitsuba(mesh_name, bsdf=bsdf) + # Add to scene dict. + self.scene_dict[mesh_name] = mitsuba_mesh + + # Render the scene. + scene = mi.load_dict(self.scene_dict) + + img = mi.render(scene) + bmp = mi.Bitmap(img) + bmp.write(os.path.join(save_dir, save_name)) diff --git a/znvis/visualizer/visualizer.py b/znvis/visualizer/visualizer.py index 0370486..a41ae23 100644 --- a/znvis/visualizer/visualizer.py +++ b/znvis/visualizer/visualizer.py @@ -20,6 +20,9 @@ ------- Main visualizer class. """ +import os + +os.environ["OPENCV_IO_ENABLE_OPENEXR"] = "1" import pathlib import re import shutil @@ -33,6 +36,7 @@ from rich.progress import Progress, track import znvis +from znvis.rendering import Mitsuba class Visualizer: @@ -60,7 +64,8 @@ def __init__( number_of_steps: int = None, keep_frames: bool = False, bounding_box: znvis.BoundingBox = None, - video_format: str = "mp4", + video_format: str = "avi", + renderer: Mitsuba = Mitsuba(), ): """ Constructor for the visualizer. @@ -93,6 +98,7 @@ def __init__( self.frame_folder = self.output_folder / "video_frames" self.video_format = video_format self.keep_frames = keep_frames + self.renderer = renderer self.obj_folder = self.output_folder / "obj_files" @@ -192,7 +198,7 @@ def _create_movie(self): image storing thread can run to completion before this one is called. (GIL stuff) """ - images = [f.as_posix() for f in self.frame_folder.glob("*.png")] + images = [f.as_posix() for f in self.frame_folder.glob("*.exr")] # Sort images by number images = sorted(images, key=lambda s: int(re.search(r"\d+", s).group())) @@ -258,10 +264,26 @@ def _take_screenshot(self, vis): ------- Takes a screenshot and dumps it """ - vis.export_current_image( - (self.output_folder / f"screenshot_{self.counter}.png").as_posix() + old_state = self.interrupt # get old state + self.interrupt = 0 # stop live feed if running. + mesh_dict = {} + + for item in self.particles: + mesh_dict[item.name] = { + "mesh": item.mesh_list[self.counter], + "bsdf": item.mesh.material.mitsuba_bsdf, + "material": item.mesh.o3d_material, + } + + view_matrix = vis.scene.camera.get_view_matrix() + self.renderer.render_mesh_objects( + mesh_dict, view_matrix, save_name=f"frame_{self.counter}.exr" ) + # Restart live feed if it was running before the export. + if old_state == 1: + self._continuous_trajectory(vis) + def _initialize_particles(self): """ Initialize the particles in the simulation. @@ -346,14 +368,27 @@ def save_callable(): """ Function to be called on thread to save image. """ - self.vis.export_current_image( - (self.frame_folder / f"frame_{self.counter:0>6}.png").as_posix() + mesh_dict = {} + + for item in self.particles: + mesh_dict[item.name] = { + "mesh": item.mesh_list[self.counter], + "bsdf": item.mesh.material.mitsuba_bsdf, + "material": item.mesh.o3d_material, + } + + view_matrix = self.vis.scene.camera.get_view_matrix() + self.renderer.render_mesh_objects( + mesh_dict, + view_matrix, + save_dir=self.frame_folder, + save_name=f"frame_{self.counter:0>6}.exr", ) + self.save_thread_finished = True with Progress() as progress: task = progress.add_task("Saving scenes...", total=self.number_of_steps) - # while self.counter < (self.number_of_steps - 1): while not progress.finished: time.sleep(1 / self.frame_rate) From c8caa670ace7755a30d503bba214aa7df26fd80d Mon Sep 17 00:00:00 2001 From: SamTov Date: Tue, 10 Oct 2023 16:00:16 +0200 Subject: [PATCH 03/13] Add materail example --- examples/material_spheres.py | 71 ++++++++++++++++++++++++++++++++++ znvis/material/material.py | 3 +- znvis/rendering/mitsuba.py | 18 +++------ znvis/visualizer/visualizer.py | 17 +++++--- 4 files changed, 89 insertions(+), 20 deletions(-) create mode 100644 examples/material_spheres.py diff --git a/examples/material_spheres.py b/examples/material_spheres.py new file mode 100644 index 0000000..d8be0a7 --- /dev/null +++ b/examples/material_spheres.py @@ -0,0 +1,71 @@ +""" +ZnVis: A Zincwarecode package. +License +------- +This program and the accompanying materials are made available under the terms +of the Eclipse Public License v2.0 which accompanies this distribution, and is +available at https://www.eclipse.org/legal/epl-v20.html +SPDX-License-Identifier: EPL-2.0 +Copyright Contributors to the Zincwarecode Project. +Contact Information +------------------- +email: zincwarecode@gmail.com +github: https://github.com/zincware +web: https://zincwarecode.com/ +Citation +-------- +If you use this module please cite us with: + +Summary +------- +Tutorial script to visualize simple spheres over a random trajectory. +""" +import numpy as np + +import znvis as vis + +import mitsuba as mi + + +if __name__ == "__main__": + """ + Run the simple spheres example. + """ + # Particle 1 definition + hairy = mi.load_dict({'type': 'conductor', +'material': 'Au'} +) + material_1 = vis.Material(colour=np.array([30, 144, 255]) / 255, alpha=0.6, mitsuba_bsdf=hairy) + + # Define the first particle. + trajectory = np.random.uniform(-100, 100, (100, 1000, 3)) + mesh = vis.Sphere(radius=2.0, resolution=10, material=material_1) + particle = vis.Particle( + name="Blue", mesh=mesh, position=trajectory, smoothing=False + ) + + # Define the second particle. + bsdf_smooth_plastic = mi.load_dict({ + 'type': 'plastic', + 'diffuse_reflectance': { + 'type': 'rgb', + 'value': [0.1, 0.27, 0.36] + }, + 'int_ior': 1.9 +}) + material_2 = vis.Material( + colour=np.array([255, 140, 0]) / 255, + alpha=1.0, + mitsuba_bsdf=bsdf_smooth_plastic + ) + + # Define the second particle. + trajectory_2 = np.random.uniform(-10, 10, (100, 1000, 3)) + mesh_2 = vis.Sphere(radius=1.0, resolution=10, material=material_2) + particle_2 = vis.Particle( + name="Orange", mesh=mesh_2, position=trajectory_2, smoothing=False + ) + + # Construct the visualizer and run + visualizer = vis.Visualizer(particles=[particle, particle_2], frame_rate=20) + visualizer.run_visualization() diff --git a/znvis/material/material.py b/znvis/material/material.py index 99db158..e727760 100644 --- a/znvis/material/material.py +++ b/znvis/material/material.py @@ -55,5 +55,4 @@ class Material: metallic: float = 0.0 reflectance: float = 0.4 anisotropy: float = 0.4 - - mitsuba_bsdf = None + mitsuba_bsdf: object = None diff --git a/znvis/rendering/mitsuba.py b/znvis/rendering/mitsuba.py index 2302950..4357d73 100644 --- a/znvis/rendering/mitsuba.py +++ b/znvis/rendering/mitsuba.py @@ -26,7 +26,7 @@ import numpy as np import open3d as o3d -mi.set_variant("llvm_ad_rgb") +mi.set_variant("cuda_ad_rgb") # Default scene dict. @@ -36,7 +36,7 @@ "light": {"type": "constant", "radiance": {"type": "rgb", "value": 1.0}}, "sensor": { "type": "perspective", - "fov": 60, + "fov": 90, "thefilm": { "type": "hdrfilm", "width": 4096, @@ -44,7 +44,7 @@ }, "thesampler": { "type": "multijitter", - "sample_count": 20, + "sample_count": 64, }, }, } @@ -91,15 +91,7 @@ def _update_camera(self, view_matrix: np.ndarray) -> None: This function updates the camera in the scene_dict. It should be called before rendering. """ - camera_position = view_matrix[:3, 3] - forward_direction = view_matrix[:3, 2] - camera_target = -1 * camera_position + forward_direction - up_direction = view_matrix[:3, 1] - - view_matrix = mi.ScalarTransform4f.look_at( - origin=camera_position, target=camera_target, up=up_direction - ) - self.scene_dict["sensor"]["to_world"] = view_matrix + self.scene_dict["sensor"]["to_world"] = mi.ScalarTransform4f(view_matrix) def render_mesh_objects( self, @@ -162,4 +154,6 @@ def render_mesh_objects( img = mi.render(scene) bmp = mi.Bitmap(img) + + bmp = bmp.convert(mi.Bitmap.PixelFormat.RGB, mi.Struct.Type.UInt8, True) bmp.write(os.path.join(save_dir, save_name)) diff --git a/znvis/visualizer/visualizer.py b/znvis/visualizer/visualizer.py index a41ae23..b764202 100644 --- a/znvis/visualizer/visualizer.py +++ b/znvis/visualizer/visualizer.py @@ -37,6 +37,7 @@ import znvis from znvis.rendering import Mitsuba +import numpy as np class Visualizer: @@ -62,9 +63,9 @@ def __init__( output_folder: typing.Union[str, pathlib.Path] = "./", frame_rate: int = 24, number_of_steps: int = None, - keep_frames: bool = False, + keep_frames: bool = True, bounding_box: znvis.BoundingBox = None, - video_format: str = "avi", + video_format: str = "mp4", renderer: Mitsuba = Mitsuba(), ): """ @@ -198,7 +199,7 @@ def _create_movie(self): image storing thread can run to completion before this one is called. (GIL stuff) """ - images = [f.as_posix() for f in self.frame_folder.glob("*.exr")] + images = [f.as_posix() for f in self.frame_folder.glob("*.png")] # Sort images by number images = sorted(images, key=lambda s: int(re.search(r"\d+", s).group())) @@ -267,17 +268,21 @@ def _take_screenshot(self, vis): old_state = self.interrupt # get old state self.interrupt = 0 # stop live feed if running. mesh_dict = {} - + mesh_center = [] for item in self.particles: mesh_dict[item.name] = { "mesh": item.mesh_list[self.counter], "bsdf": item.mesh.material.mitsuba_bsdf, "material": item.mesh.o3d_material, } + mesh_center.append(item.mesh_list[self.counter].get_axis_aligned_bounding_box().get_center()) view_matrix = vis.scene.camera.get_view_matrix() + self.renderer.render_mesh_objects( - mesh_dict, view_matrix, save_name=f"frame_{self.counter}.exr" + mesh_dict, + view_matrix, + save_name=f"frame_{self.counter}.png" ) # Restart live feed if it was running before the export. @@ -382,7 +387,7 @@ def save_callable(): mesh_dict, view_matrix, save_dir=self.frame_folder, - save_name=f"frame_{self.counter:0>6}.exr", + save_name=f"frame_{self.counter:0>6}.png", ) self.save_thread_finished = True From 88d4270fb26a03c7d05915ff277cff98ad0908e7 Mon Sep 17 00:00:00 2001 From: SamTov Date: Wed, 14 Feb 2024 17:46:55 +0100 Subject: [PATCH 04/13] updates --- examples/material_spheres.py | 37 ++++++++++++++++------------------ znvis/visualizer/visualizer.py | 11 +++++----- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/examples/material_spheres.py b/examples/material_spheres.py index d8be0a7..7183e64 100644 --- a/examples/material_spheres.py +++ b/examples/material_spheres.py @@ -20,23 +20,21 @@ ------- Tutorial script to visualize simple spheres over a random trajectory. """ +import mitsuba as mi import numpy as np import znvis as vis -import mitsuba as mi - - if __name__ == "__main__": """ Run the simple spheres example. """ # Particle 1 definition - hairy = mi.load_dict({'type': 'conductor', -'material': 'Au'} -) - material_1 = vis.Material(colour=np.array([30, 144, 255]) / 255, alpha=0.6, mitsuba_bsdf=hairy) - + hairy = mi.load_dict({"type": "conductor", "material": "Au"}) + material_1 = vis.Material( + colour=np.array([30, 144, 255]) / 255, alpha=0.6, mitsuba_bsdf=hairy + ) + # Define the first particle. trajectory = np.random.uniform(-100, 100, (100, 1000, 3)) mesh = vis.Sphere(radius=2.0, resolution=10, material=material_1) @@ -45,19 +43,18 @@ ) # Define the second particle. - bsdf_smooth_plastic = mi.load_dict({ - 'type': 'plastic', - 'diffuse_reflectance': { - 'type': 'rgb', - 'value': [0.1, 0.27, 0.36] - }, - 'int_ior': 1.9 -}) + bsdf_smooth_plastic = mi.load_dict( + { + "type": "plastic", + "diffuse_reflectance": {"type": "rgb", "value": [0.1, 0.27, 0.36]}, + "int_ior": 1.9, + } + ) material_2 = vis.Material( - colour=np.array([255, 140, 0]) / 255, - alpha=1.0, - mitsuba_bsdf=bsdf_smooth_plastic - ) + colour=np.array([255, 140, 0]) / 255, + alpha=1.0, + mitsuba_bsdf=bsdf_smooth_plastic, + ) # Define the second particle. trajectory_2 = np.random.uniform(-10, 10, (100, 1000, 3)) diff --git a/znvis/visualizer/visualizer.py b/znvis/visualizer/visualizer.py index b764202..fa75b38 100644 --- a/znvis/visualizer/visualizer.py +++ b/znvis/visualizer/visualizer.py @@ -37,7 +37,6 @@ import znvis from znvis.rendering import Mitsuba -import numpy as np class Visualizer: @@ -275,14 +274,16 @@ def _take_screenshot(self, vis): "bsdf": item.mesh.material.mitsuba_bsdf, "material": item.mesh.o3d_material, } - mesh_center.append(item.mesh_list[self.counter].get_axis_aligned_bounding_box().get_center()) + mesh_center.append( + item.mesh_list[self.counter] + .get_axis_aligned_bounding_box() + .get_center() + ) view_matrix = vis.scene.camera.get_view_matrix() self.renderer.render_mesh_objects( - mesh_dict, - view_matrix, - save_name=f"frame_{self.counter}.png" + mesh_dict, view_matrix, save_name=f"frame_{self.counter}.png" ) # Restart live feed if it was running before the export. From 5a9e434f391c0868fb96ae264502f487053810eb Mon Sep 17 00:00:00 2001 From: SamTov Date: Tue, 14 May 2024 12:36:47 +0200 Subject: [PATCH 05/13] Fix render camera transform --- znvis/mesh/custom.py | 4 +++- znvis/mesh/cylinder.py | 2 +- znvis/mesh/mesh.py | 1 + znvis/mesh/sphere.py | 2 +- znvis/rendering/mitsuba.py | 12 +++++++++++- 5 files changed, 17 insertions(+), 4 deletions(-) diff --git a/znvis/mesh/custom.py b/znvis/mesh/custom.py index ef25d0a..0a208e3 100644 --- a/znvis/mesh/custom.py +++ b/znvis/mesh/custom.py @@ -45,6 +45,7 @@ class CustomMesh(Mesh): """ file: str = None + scale: float = 1.0 def create_mesh( self, starting_position: np.ndarray, starting_orientation: np.ndarray = None @@ -65,9 +66,10 @@ def create_mesh( """ mesh = o3d.io.read_triangle_mesh(self.file) mesh.compute_vertex_normals() + mesh.scale(self.scale, center=mesh.get_center()) mesh.translate(starting_position.astype(float)) if starting_orientation is not None: - matrix = rotation_matrix(np.array([0, 0, 1]), starting_orientation) + matrix = rotation_matrix(self.base_direction, starting_orientation) mesh.rotate(matrix) return mesh diff --git a/znvis/mesh/cylinder.py b/znvis/mesh/cylinder.py index 6ada1bf..cde29eb 100644 --- a/znvis/mesh/cylinder.py +++ b/znvis/mesh/cylinder.py @@ -81,7 +81,7 @@ def create_mesh( cylinder.compute_vertex_normals() cylinder.translate(starting_position.astype(float)) if starting_orientation is not None: - matrix = rotation_matrix(np.array([0, 0, 1]), starting_orientation) + matrix = rotation_matrix(self.base_direction, starting_orientation) cylinder.rotate(matrix) return cylinder diff --git a/znvis/mesh/mesh.py b/znvis/mesh/mesh.py index 39dcb63..ca27e81 100644 --- a/znvis/mesh/mesh.py +++ b/znvis/mesh/mesh.py @@ -40,6 +40,7 @@ class Mesh: """ material: Material = Material() + base_direction = np.array([1, 0, 0]) def __post_init__(self): """ diff --git a/znvis/mesh/sphere.py b/znvis/mesh/sphere.py index 85487cd..0021dcf 100644 --- a/znvis/mesh/sphere.py +++ b/znvis/mesh/sphere.py @@ -69,7 +69,7 @@ def create_mesh( sphere.compute_vertex_normals() sphere.translate(starting_position.astype(float)) if starting_orientation is not None: - matrix = rotation_matrix(np.array([0, 0, 1]), starting_orientation) + matrix = rotation_matrix(self.base_direction, starting_orientation) sphere.rotate(matrix) return sphere diff --git a/znvis/rendering/mitsuba.py b/znvis/rendering/mitsuba.py index 4357d73..2ab2f43 100644 --- a/znvis/rendering/mitsuba.py +++ b/znvis/rendering/mitsuba.py @@ -91,7 +91,17 @@ def _update_camera(self, view_matrix: np.ndarray) -> None: This function updates the camera in the scene_dict. It should be called before rendering. """ - self.scene_dict["sensor"]["to_world"] = mi.ScalarTransform4f(view_matrix) + to_world_matrix = np.linalg.inv(view_matrix) + + # Step 2: Adjust the coordinate system by flipping the Z-axis + z_flip_matrix = np.array( + [[-1, 0, 0, 0], [0, 1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]] + ) + adjusted_to_world_matrix = np.dot(to_world_matrix, z_flip_matrix) + + self.scene_dict["sensor"]["to_world"] = mi.ScalarTransform4f( + adjusted_to_world_matrix + ) def render_mesh_objects( self, From ae83e47105624a48f02f1b74c8db63795f26b5d0 Mon Sep 17 00:00:00 2001 From: SamTov Date: Tue, 14 May 2024 12:40:35 +0200 Subject: [PATCH 06/13] Update pre-commit and linting --- .pre-commit-config.yaml | 2 +- examples/material_spheres.py | 1 + znvis/__init__.py | 2 +- znvis/rendering/__init__.py | 1 + znvis/rendering/mitsuba.py | 7 ++++--- znvis/visualizer/visualizer.py | 1 + 6 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 209ad2f..3b9d647 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ fail_fast: true repos: - repo: https://github.com/psf/black - rev: 24.2.0 + rev: 24.4.2 hooks: - id: black diff --git a/examples/material_spheres.py b/examples/material_spheres.py index 7183e64..6081e76 100644 --- a/examples/material_spheres.py +++ b/examples/material_spheres.py @@ -20,6 +20,7 @@ ------- Tutorial script to visualize simple spheres over a random trajectory. """ + import mitsuba as mi import numpy as np diff --git a/znvis/__init__.py b/znvis/__init__.py index 458e1ec..ba99d48 100644 --- a/znvis/__init__.py +++ b/znvis/__init__.py @@ -21,8 +21,8 @@ ------- init file for the main ZnVis package. """ -from znvis import rendering +from znvis import rendering from znvis.bounding_objects.bounding_box import BoundingBox from znvis.material.material import Material from znvis.mesh.custom import CustomMesh diff --git a/znvis/rendering/__init__.py b/znvis/rendering/__init__.py index b200eb6..d852917 100644 --- a/znvis/rendering/__init__.py +++ b/znvis/rendering/__init__.py @@ -20,6 +20,7 @@ ------- Init file for the rendering module. """ + from znvis.rendering.mitsuba import Mitsuba __all__ = [Mitsuba.__name__] diff --git a/znvis/rendering/mitsuba.py b/znvis/rendering/mitsuba.py index 2ab2f43..d9f07bc 100644 --- a/znvis/rendering/mitsuba.py +++ b/znvis/rendering/mitsuba.py @@ -20,6 +20,7 @@ ------- Mitsuba rendering module. """ + import os import mitsuba as mi @@ -149,9 +150,9 @@ def render_mesh_objects( mesh.material.vector_properties["base_color"] = material.base_color mesh.material.scalar_properties["roughness"] = material.base_roughness mesh.material.scalar_properties["metallic"] = material.base_metallic - mesh.material.scalar_properties[ - "reflectance" - ] = material.base_reflectance + mesh.material.scalar_properties["reflectance"] = ( + material.base_reflectance + ) mesh.material.scalar_properties["anisotropy"] = material.base_anisotropy # Convert to Mitsuba mesh diff --git a/znvis/visualizer/visualizer.py b/znvis/visualizer/visualizer.py index 3f2a185..bfa88f3 100644 --- a/znvis/visualizer/visualizer.py +++ b/znvis/visualizer/visualizer.py @@ -20,6 +20,7 @@ ------- Main visualizer class. """ + import os os.environ["OPENCV_IO_ENABLE_OPENEXR"] = "1" From a45584f0cfa81e063de33caaf7750f2ecff5be21 Mon Sep 17 00:00:00 2001 From: SamTov Date: Tue, 14 May 2024 12:41:16 +0200 Subject: [PATCH 07/13] Add mitsuba to requirements --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 5d6ab30..5523ac0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ numpy pytest rich opencv-python +mitsuba From 7208e2c16983422c252681355ddeed94e50e484c Mon Sep 17 00:00:00 2001 From: SamTov Date: Tue, 14 May 2024 13:09:46 +0200 Subject: [PATCH 08/13] Correct bare except --- .github/workflows/publish-to-pypi.yaml | 2 +- .github/workflows/pytest.yaml | 4 +--- znvis/rendering/mitsuba.py | 6 +++++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/publish-to-pypi.yaml b/.github/workflows/publish-to-pypi.yaml index b992088..1b62e73 100644 --- a/.github/workflows/publish-to-pypi.yaml +++ b/.github/workflows/publish-to-pypi.yaml @@ -14,7 +14,7 @@ jobs: - name: Set up Python 3.9 uses: actions/setup-python@v1 with: - python-version: 3.9 + python-version: 3.11 - name: Install pypa/build run: >- python -m diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 842c7b3..db96fc6 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -14,9 +14,7 @@ jobs: fail-fast: false matrix: python-version: - - 3.8 - - 3.9 - - "3.10" + - "3.11" steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} diff --git a/znvis/rendering/mitsuba.py b/znvis/rendering/mitsuba.py index d9f07bc..9825ccd 100644 --- a/znvis/rendering/mitsuba.py +++ b/znvis/rendering/mitsuba.py @@ -27,7 +27,11 @@ import numpy as np import open3d as o3d -mi.set_variant("cuda_ad_rgb") +try: + mi.set_variant("cuda_ad_rgb") +except AttributeError: + mi.set_variant("scalar_rgb") + # mi.set_variant("llvm_ad_rgb") # Default scene dict. From e9bc6cdb865eb4bf061e2f14eb9e09d2bea3b0fe Mon Sep 17 00:00:00 2001 From: SamTov Date: Tue, 14 May 2024 13:20:28 +0200 Subject: [PATCH 09/13] Change dataclass to fields --- znvis/bounding_objects/bounding_box.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/znvis/bounding_objects/bounding_box.py b/znvis/bounding_objects/bounding_box.py index 26814d2..eb75ada 100644 --- a/znvis/bounding_objects/bounding_box.py +++ b/znvis/bounding_objects/bounding_box.py @@ -21,7 +21,7 @@ Create bounding box. """ -from dataclasses import dataclass +from dataclasses import dataclass, field import numpy as np import open3d as o3d @@ -43,10 +43,12 @@ class BoundingBox: colour : np.ndarray shape=(3,) """ - center: np.ndarray = np.array([0, 0, 0]) - box_size: np.ndarray = np.array([1.0, 1.0, 1.0]) - rotation_matrix: np.ndarray = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) - colour: np.ndarray = np.array([0.0, 0.0, 0.0]) + center: np.ndarray = field(default_factory=lambda: np.array([0, 0, 0])) + box_size: np.ndarray = field(default_factory=lambda: np.array([1.0, 1.0, 1.0])) + rotation_matrix: np.ndarray = field( + default_factory=lambda: np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + ) + colour: np.ndarray = field(default_factory=lambda: np.array([0.0, 0.0, 0.0])) def __call__(self) -> o3d.geometry.TriangleMesh: """ From 89fd0f1479c850c3533054c9eabfd060731ca431 Mon Sep 17 00:00:00 2001 From: SamTov Date: Tue, 14 May 2024 13:23:04 +0200 Subject: [PATCH 10/13] More dataclass changes --- znvis/material/material.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/znvis/material/material.py b/znvis/material/material.py index 595f664..0a19393 100644 --- a/znvis/material/material.py +++ b/znvis/material/material.py @@ -22,7 +22,7 @@ Material parent class. """ -from dataclasses import dataclass +from dataclasses import dataclass, field import numpy as np @@ -50,7 +50,9 @@ class Material: Mitsuba bsdf object. """ - colour: np.ndarray = np.array([59.0, 53.0, 97.0]) / 255 + colour: np.ndarray = field( + default_factory=lambda: np.array([59.0, 53.0, 97.0]) / 255 + ) alpha: float = 1.0 roughness: float = 0.5 metallic: float = 0.0 From 0b0c0d46efd7c25c79e1244159ab49e991379130 Mon Sep 17 00:00:00 2001 From: SamTov Date: Tue, 14 May 2024 13:46:11 +0200 Subject: [PATCH 11/13] More dataclass refactoring --- znvis/mesh/mesh.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/znvis/mesh/mesh.py b/znvis/mesh/mesh.py index bf63ae8..c84d16b 100644 --- a/znvis/mesh/mesh.py +++ b/znvis/mesh/mesh.py @@ -20,7 +20,7 @@ Module for the mesh parent class. """ -from dataclasses import dataclass +from dataclasses import dataclass, field import numpy as np import open3d as o3d @@ -41,7 +41,7 @@ class Mesh: """ material: Material = Material() - base_direction = np.array([1, 0, 0]) + base_direction = field(default_factory=lambda: np.array([1, 0, 0])) def __post_init__(self): """ From e22fffa3bc91d2774237fb2be200a2b18972e6b1 Mon Sep 17 00:00:00 2001 From: SamTov Date: Tue, 14 May 2024 13:49:56 +0200 Subject: [PATCH 12/13] More updates --- znvis/mesh/mesh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/znvis/mesh/mesh.py b/znvis/mesh/mesh.py index c84d16b..49bb772 100644 --- a/znvis/mesh/mesh.py +++ b/znvis/mesh/mesh.py @@ -40,7 +40,7 @@ class Mesh: A ZnVis material class. """ - material: Material = Material() + material: Material = field(default_factory=lambda: Material()) base_direction = field(default_factory=lambda: np.array([1, 0, 0])) def __post_init__(self): From da0c7284fabe4c6c4f4a6ec912b214731d5377de Mon Sep 17 00:00:00 2001 From: SamTov Date: Tue, 14 May 2024 13:57:34 +0200 Subject: [PATCH 13/13] Add type to dataclass --- znvis/mesh/mesh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/znvis/mesh/mesh.py b/znvis/mesh/mesh.py index 49bb772..252a4b0 100644 --- a/znvis/mesh/mesh.py +++ b/znvis/mesh/mesh.py @@ -41,7 +41,7 @@ class Mesh: """ material: Material = field(default_factory=lambda: Material()) - base_direction = field(default_factory=lambda: np.array([1, 0, 0])) + base_direction: np.ndarray = field(default_factory=lambda: np.array([1, 0, 0])) def __post_init__(self): """