Merge pull request #518 from DenisKochetov/dev

remove pyrender code
This commit is contained in:
Mr. For Example
2025-08-01 10:24:20 +04:00
committed by GitHub
@@ -5,14 +5,14 @@ import numpy as np
from PIL import Image
import trimesh
from trimesh.transformations import rotation_matrix
import pyrender
# import pyrender # Commented out - not needed for core functionality
from diffusers.utils import export_to_video
from diffusers.utils.loading_utils import load_video
import torch
from torchvision.utils import make_grid
import math
os.environ['PYOPENGL_PLATFORM'] = 'egl'
# os.environ['PYOPENGL_PLATFORM'] = 'egl' # Commented out - not needed without pyrender
def explode_mesh(mesh, explosion_scale=0.4):
# ensure we have a Scene
@@ -63,409 +63,411 @@ def explode_mesh(mesh, explosion_scale=0.4):
return exploded
# All render functions below use pyrender and are commented out
# They are not needed for core PartCrafter functionality
# def render(
# scene: pyrender.Scene,
# renderer: pyrender.Renderer,
# camera: pyrender.Camera,
# pose: np.ndarray,
# light: Optional[pyrender.Light] = None,
# normalize_depth: bool = False,
# flags: int = pyrender.constants.RenderFlags.NONE,
# return_type: Literal['pil', 'ndarray'] = 'pil'
# ) -> Union[Tuple[np.ndarray, np.ndarray], Tuple[Image.Image, Image.Image]]:
# camera_node = scene.add(camera, pose=pose)
# if light is not None:
# light_node = scene.add(light, pose=pose)
# image, depth = renderer.render(
# scene,
# flags=flags
# )
# scene.remove_node(camera_node)
# if light is not None:
# scene.remove_node(light_node)
# if normalize_depth or return_type == 'pil':
# depth = (depth - depth.min()) / (depth.max() - depth.min()) * 255.0
# if return_type == 'pil':
# image = Image.fromarray(image)
# depth = Image.fromarray(depth.astype(np.uint8))
# return image, depth
# def rotation_matrix_from_vectors(vec1, vec2):
# a, b = vec1 / np.linalg.norm(vec1), vec2 / np.linalg.norm(vec2)
# v = np.cross(a, b)
# c = np.dot(a, b)
# s = np.linalg.norm(v)
# if s == 0:
# return np.eye(3) if c > 0 else -np.eye(3)
# kmat = np.array([
# [0, -v[2], v[1]],
# [v[2], 0, -v[0]],
# [-v[1], v[0], 0]
# ])
# return np.eye(3) + kmat + kmat @ kmat * ((1 - c) / (s ** 2))
# def create_circular_camera_positions(
# num_views: int,
# radius: float,
# axis: np.ndarray = np.array([0.0, 1.0, 0.0])
# ) -> List[np.ndarray]:
# # Create a list of positions for a circular camera trajectory
# # around the given axis with the given radius.
# positions = []
# axis = axis / np.linalg.norm(axis)
# for i in range(num_views):
# theta = 2 * np.pi * i / num_views
# position = np.array([
# np.sin(theta) * radius,
# 0.0,
# np.cos(theta) * radius
# ])
# if not np.allclose(axis, np.array([0.0, 1.0, 0.0])):
# R = rotation_matrix_from_vectors(np.array([0.0, 1.0, 0.0]), axis)
# position = R @ position
# positions.append(position)
# return positions
# def create_circular_camera_poses(
# num_views: int,
# radius: float,
# axis: np.ndarray = np.array([0.0, 1.0, 0.0])
# ) -> List[np.ndarray]:
# # Create a list of poses for a circular camera trajectory
# # around the given axis with the given radius.
# # The camera always looks at the origin.
# # The up vector is always [0, 1, 0].
# canonical_pose = np.array([
# [1.0, 0.0, 0.0, 0.0],
# [0.0, 1.0, 0.0, 0.0],
# [0.0, 0.0, 1.0, radius],
# [0.0, 0.0, 0.0, 1.0]
# ])
# poses = []
# for i in range(num_views):
# theta = 2 * np.pi * i / num_views
# R = rotation_matrix(
# angle=theta,
# direction=axis,
# point=[0, 0, 0]
# )
# pose = R @ canonical_pose
# poses.append(pose)
# return poses
# def render_views_around_mesh(
# mesh: Union[trimesh.Trimesh, trimesh.Scene],
# num_views: int = 36,
# radius: float = 3.5,
# axis: np.ndarray = np.array([0.0, 1.0, 0.0]),
# image_size: tuple = (512, 512),
# fov: float = 40.0,
# light_intensity: Optional[float] = 5.0,
# znear: float = 0.1,
# zfar: float = 10.0,
# normalize_depth: bool = False,
# flags: int = pyrender.constants.RenderFlags.NONE,
# return_depth: bool = False,
# return_type: Literal['pil', 'ndarray'] = 'pil'
# ) -> Union[
# List[Image.Image],
# List[np.ndarray],
# Tuple[List[Image.Image], List[Image.Image]],
# Tuple[List[np.ndarray], List[np.ndarray]]
# ]:
# meshes = []
# scenes = []
def render(
scene: pyrender.Scene,
renderer: pyrender.Renderer,
camera: pyrender.Camera,
pose: np.ndarray,
light: Optional[pyrender.Light] = None,
normalize_depth: bool = False,
flags: int = pyrender.constants.RenderFlags.NONE,
return_type: Literal['pil', 'ndarray'] = 'pil'
) -> Union[Tuple[np.ndarray, np.ndarray], Tuple[Image.Image, Image.Image]]:
camera_node = scene.add(camera, pose=pose)
if light is not None:
light_node = scene.add(light, pose=pose)
image, depth = renderer.render(
scene,
flags=flags
)
scene.remove_node(camera_node)
if light is not None:
scene.remove_node(light_node)
if normalize_depth or return_type == 'pil':
depth = (depth - depth.min()) / (depth.max() - depth.min()) * 255.0
if return_type == 'pil':
image = Image.fromarray(image)
depth = Image.fromarray(depth.astype(np.uint8))
return image, depth
def rotation_matrix_from_vectors(vec1, vec2):
a, b = vec1 / np.linalg.norm(vec1), vec2 / np.linalg.norm(vec2)
v = np.cross(a, b)
c = np.dot(a, b)
s = np.linalg.norm(v)
if s == 0:
return np.eye(3) if c > 0 else -np.eye(3)
kmat = np.array([
[0, -v[2], v[1]],
[v[2], 0, -v[0]],
[-v[1], v[0], 0]
])
return np.eye(3) + kmat + kmat @ kmat * ((1 - c) / (s ** 2))
def create_circular_camera_positions(
num_views: int,
radius: float,
axis: np.ndarray = np.array([0.0, 1.0, 0.0])
) -> List[np.ndarray]:
# Create a list of positions for a circular camera trajectory
# around the given axis with the given radius.
positions = []
axis = axis / np.linalg.norm(axis)
for i in range(num_views):
theta = 2 * np.pi * i / num_views
position = np.array([
np.sin(theta) * radius,
0.0,
np.cos(theta) * radius
])
if not np.allclose(axis, np.array([0.0, 1.0, 0.0])):
R = rotation_matrix_from_vectors(np.array([0.0, 1.0, 0.0]), axis)
position = R @ position
positions.append(position)
return positions
def create_circular_camera_poses(
num_views: int,
radius: float,
axis: np.ndarray = np.array([0.0, 1.0, 0.0])
) -> List[np.ndarray]:
# Create a list of poses for a circular camera trajectory
# around the given axis with the given radius.
# The camera always looks at the origin.
# The up vector is always [0, 1, 0].
canonical_pose = np.array([
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, radius],
[0.0, 0.0, 0.0, 1.0]
])
poses = []
for i in range(num_views):
theta = 2 * np.pi * i / num_views
R = rotation_matrix(
angle=theta,
direction=axis,
point=[0, 0, 0]
)
pose = R @ canonical_pose
poses.append(pose)
return poses
def render_views_around_mesh(
mesh: Union[trimesh.Trimesh, trimesh.Scene],
num_views: int = 36,
radius: float = 3.5,
axis: np.ndarray = np.array([0.0, 1.0, 0.0]),
image_size: tuple = (512, 512),
fov: float = 40.0,
light_intensity: Optional[float] = 5.0,
znear: float = 0.1,
zfar: float = 10.0,
normalize_depth: bool = False,
flags: int = pyrender.constants.RenderFlags.NONE,
return_depth: bool = False,
return_type: Literal['pil', 'ndarray'] = 'pil'
) -> Union[
List[Image.Image],
List[np.ndarray],
Tuple[List[Image.Image], List[Image.Image]],
Tuple[List[np.ndarray], List[np.ndarray]]
]:
meshes = []
scenes = []
if not isinstance(mesh, (trimesh.Trimesh, trimesh.Scene)):
raise ValueError("mesh must be a trimesh.Trimesh or trimesh.Scene object")
if isinstance(mesh, trimesh.Trimesh):
for i in range(num_views):
scenes.append(pyrender.Scene.from_trimesh_scene(trimesh.Scene(mesh)))
else:
for i in range(num_views):
value = math.sin(math.pi * (i - 1) / num_views)
scenes.append(pyrender.Scene.from_trimesh_scene(explode_mesh(mesh, 0.2 * value),
ambient_light=[0.02, 0.02, 0.02],
bg_color=[0.0, 0.0, 0.0, 1.0]))
# if not isinstance(mesh, (trimesh.Trimesh, trimesh.Scene)):
# raise ValueError("mesh must be a trimesh.Trimesh or trimesh.Scene object")
# if isinstance(mesh, trimesh.Trimesh):
# for i in range(num_views):
# scenes.append(pyrender.Scene.from_trimesh_scene(trimesh.Scene(mesh)))
# else:
# for i in range(num_views):
# value = math.sin(math.pi * (i - 1) / num_views)
# scenes.append(pyrender.Scene.from_trimesh_scene(explode_mesh(mesh, 0.2 * value),
# ambient_light=[0.02, 0.02, 0.02],
# bg_color=[0.0, 0.0, 0.0, 1.0]))
light = pyrender.DirectionalLight(
color=np.ones(3),
intensity=light_intensity
) if light_intensity is not None else None
camera = pyrender.PerspectiveCamera(
yfov=np.deg2rad(fov),
aspectRatio=image_size[0]/image_size[1],
znear=znear,
zfar=zfar
)
renderer = pyrender.OffscreenRenderer(*image_size)
# light = pyrender.DirectionalLight(
# color=np.ones(3),
# intensity=light_intensity
# ) if light_intensity is not None else None
# camera = pyrender.PerspectiveCamera(
# yfov=np.deg2rad(fov),
# aspectRatio=image_size[0]/image_size[1],
# znear=znear,
# zfar=zfar
# )
# renderer = pyrender.OffscreenRenderer(*image_size)
camera_poses = create_circular_camera_poses(
num_views,
radius,
axis = axis
)
# camera_poses = create_circular_camera_poses(
# num_views,
# radius,
# axis = axis
# )
images, depths = [], []
for i, pose in enumerate(camera_poses):
image, depth = render(
scenes[i], renderer, camera, pose, light,
normalize_depth=normalize_depth,
flags=flags,
return_type=return_type
)
images.append(image)
depths.append(depth)
# images, depths = [], []
# for i, pose in enumerate(camera_poses):
# image, depth = render(
# scenes[i], renderer, camera, pose, light,
# normalize_depth=normalize_depth,
# flags=flags,
# return_type=return_type
# )
# images.append(image)
# depths.append(depth)
renderer.delete()
# renderer.delete()
if return_depth:
return images, depths
return images
# if return_depth:
# return images, depths
# return images
def render_normal_views_around_mesh(
mesh: Union[trimesh.Trimesh, trimesh.Scene],
num_views: int = 36,
radius: float = 3.5,
axis: np.ndarray = np.array([0.0, 1.0, 0.0]),
image_size: tuple = (512, 512),
fov: float = 40.0,
light_intensity: Optional[float] = 5.0,
znear: float = 0.1,
zfar: float = 10.0,
normalize_depth: bool = False,
flags: int = pyrender.constants.RenderFlags.NONE,
return_depth: bool = False,
return_type: Literal['pil', 'ndarray'] = 'pil'
) -> Union[
List[Image.Image],
List[np.ndarray],
Tuple[List[Image.Image], List[Image.Image]],
Tuple[List[np.ndarray], List[np.ndarray]]
]:
# def render_normal_views_around_mesh(
# mesh: Union[trimesh.Trimesh, trimesh.Scene],
# num_views: int = 36,
# radius: float = 3.5,
# axis: np.ndarray = np.array([0.0, 1.0, 0.0]),
# image_size: tuple = (512, 512),
# fov: float = 40.0,
# light_intensity: Optional[float] = 5.0,
# znear: float = 0.1,
# zfar: float = 10.0,
# normalize_depth: bool = False,
# flags: int = pyrender.constants.RenderFlags.NONE,
# return_depth: bool = False,
# return_type: Literal['pil', 'ndarray'] = 'pil'
# ) -> Union[
# List[Image.Image],
# List[np.ndarray],
# Tuple[List[Image.Image], List[Image.Image]],
# Tuple[List[np.ndarray], List[np.ndarray]]
# ]:
if not isinstance(mesh, (trimesh.Trimesh, trimesh.Scene)):
raise ValueError("mesh must be a trimesh.Trimesh or trimesh.Scene object")
if isinstance(mesh, trimesh.Scene):
mesh = mesh.to_geometry()
normals = mesh.vertex_normals
colors = ((normals + 1.0) / 2.0 * 255).astype(np.uint8)
mesh.visual = trimesh.visual.ColorVisuals(
mesh=mesh,
vertex_colors=colors
)
mesh = trimesh.Scene(mesh)
return render_views_around_mesh(
mesh, num_views, radius, axis,
image_size, fov, light_intensity, znear, zfar,
normalize_depth, flags,
return_depth, return_type
)
# if not isinstance(mesh, (trimesh.Trimesh, trimesh.Scene)):
# raise ValueError("mesh must be a trimesh.Trimesh or trimesh.Scene object")
# if isinstance(mesh, trimesh.Scene):
# mesh = mesh.to_geometry()
# normals = mesh.vertex_normals
# colors = ((normals + 1.0) / 2.0 * 255).astype(np.uint8)
# mesh.visual = trimesh.visual.ColorVisuals(
# mesh=mesh,
# vertex_colors=colors
# )
# mesh = trimesh.Scene(mesh)
# return render_views_around_mesh(
# mesh, num_views, radius, axis,
# image_size, fov, light_intensity, znear, zfar,
# normalize_depth, flags,
# return_depth, return_type
# )
def create_camera_pose_on_sphere(
azimuth: float = 0.0, # in degrees
elevation: float = 0.0, # in degrees
radius: float = 3.5,
) -> np.ndarray:
# Create a camera pose for a given azimuth and elevation
# with the given radius.
# The camera always looks at the origin.
# The up vector is always [0, 1, 0].
canonical_pose = np.array([
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, radius],
[0.0, 0.0, 0.0, 1.0]
])
azimuth = np.deg2rad(azimuth)
elevation = np.deg2rad(elevation)
position = np.array([
np.cos(elevation) * np.sin(azimuth),
np.sin(elevation),
np.cos(elevation) * np.cos(azimuth),
])
R = np.eye(4)
R[:3, :3] = rotation_matrix_from_vectors(
np.array([0.0, 0.0, 1.0]),
position
)
pose = R @ canonical_pose
return pose
# def create_camera_pose_on_sphere(
# azimuth: float = 0.0, # in degrees
# elevation: float = 0.0, # in degrees
# radius: float = 3.5,
# ) -> np.ndarray:
# # Create a camera pose for a given azimuth and elevation
# # with the given radius.
# # The camera always looks at the origin.
# # The up vector is always [0, 1, 0].
# canonical_pose = np.array([
# [1.0, 0.0, 0.0, 0.0],
# [0.0, 1.0, 0.0, 0.0],
# [0.0, 0.0, 1.0, radius],
# [0.0, 0.0, 0.0, 1.0]
# ])
# azimuth = np.deg2rad(azimuth)
# elevation = np.deg2rad(elevation)
# position = np.array([
# np.cos(elevation) * np.sin(azimuth),
# np.sin(elevation),
# np.cos(elevation) * np.cos(azimuth),
# ])
# R = np.eye(4)
# R[:3, :3] = rotation_matrix_from_vectors(
# np.array([0.0, 0.0, 1.0]),
# position
# )
# pose = R @ canonical_pose
# return pose
def render_single_view(
mesh: Union[trimesh.Trimesh, trimesh.Scene],
azimuth: float = 0.0, # in degrees
elevation: float = 0.0, # in degrees
radius: float = 3.5,
image_size: tuple = (512, 512),
fov: float = 40.0,
light_intensity: Optional[float] = 5.0,
num_env_lights: int = 0,
znear: float = 0.1,
zfar: float = 10.0,
normalize_depth: bool = False,
flags: int = pyrender.constants.RenderFlags.NONE,
return_depth: bool = False,
return_type: Literal['pil', 'ndarray'] = 'pil'
) -> Union[
Image.Image,
np.ndarray,
Tuple[Image.Image, Image.Image],
Tuple[np.ndarray, np.ndarray]
]:
# def render_single_view(
# mesh: Union[trimesh.Trimesh, trimesh.Scene],
# azimuth: float = 0.0, # in degrees
# elevation: float = 0.0, # in degrees
# radius: float = 3.5,
# image_size: tuple = (512, 512),
# fov: float = 40.0,
# light_intensity: Optional[float] = 5.0,
# num_env_lights: int = 0,
# znear: float = 0.1,
# zfar: float = 10.0,
# normalize_depth: bool = False,
# flags: int = pyrender.constants.RenderFlags.NONE,
# return_depth: bool = False,
# return_type: Literal['pil', 'ndarray'] = 'pil'
# ) -> Union[
# Image.Image,
# np.ndarray,
# Tuple[Image.Image, Image.Image],
# Tuple[np.ndarray, np.ndarray]
# ]:
if not isinstance(mesh, (trimesh.Trimesh, trimesh.Scene)):
raise ValueError("mesh must be a trimesh.Trimesh or trimesh.Scene object")
if isinstance(mesh, trimesh.Trimesh):
mesh = trimesh.Scene(mesh)
# if not isinstance(mesh, (trimesh.Trimesh, trimesh.Scene)):
# raise ValueError("mesh must be a trimesh.Trimesh or trimesh.Scene object")
# if isinstance(mesh, trimesh.Trimesh):
# mesh = trimesh.Scene(mesh)
scene = pyrender.Scene.from_trimesh_scene(mesh)
light = pyrender.DirectionalLight(
color=np.ones(3),
intensity=light_intensity
) if light_intensity is not None else None
camera = pyrender.PerspectiveCamera(
yfov=np.deg2rad(fov),
aspectRatio=image_size[0]/image_size[1],
znear=znear,
zfar=zfar
)
renderer = pyrender.OffscreenRenderer(*image_size)
# scene = pyrender.Scene.from_trimesh_scene(mesh)
# light = pyrender.DirectionalLight(
# color=np.ones(3),
# intensity=light_intensity
# ) if light_intensity is not None else None
# camera = pyrender.PerspectiveCamera(
# yfov=np.deg2rad(fov),
# aspectRatio=image_size[0]/image_size[1],
# znear=znear,
# zfar=zfar
# )
# renderer = pyrender.OffscreenRenderer(*image_size)
camera_pose = create_camera_pose_on_sphere(
azimuth,
elevation,
radius
)
# camera_pose = create_camera_pose_on_sphere(
# azimuth,
# elevation,
# radius
# )
if num_env_lights > 0:
env_light_poses = create_circular_camera_poses(
num_env_lights,
radius,
axis = np.array([0.0, 1.0, 0.0])
)
for pose in env_light_poses:
scene.add(pyrender.DirectionalLight(
color=np.ones(3),
intensity=light_intensity
), pose=pose)
# set light to None
light = None
# if num_env_lights > 0:
# env_light_poses = create_circular_camera_poses(
# num_env_lights,
# radius,
# axis = np.array([0.0, 1.0, 0.0])
# )
# for pose in env_light_poses:
# scene.add(pyrender.DirectionalLight(
# color=np.ones(3),
# intensity=light_intensity
# ), pose=pose)
# # set light to None
# light = None
image, depth = render(
scene, renderer, camera, camera_pose, light,
normalize_depth=normalize_depth,
flags=flags,
return_type=return_type
)
renderer.delete()
# image, depth = render(
# scene, renderer, camera, camera_pose, light,
# normalize_depth=normalize_depth,
# flags=flags,
# return_type=return_type
# )
# renderer.delete()
if return_depth:
return image, depth
return image
# if return_depth:
# return image, depth
# return image
def render_normal_single_view(
mesh: Union[trimesh.Trimesh, trimesh.Scene],
azimuth: float = 0.0, # in degrees
elevation: float = 0.0, # in degrees
radius: float = 3.5,
image_size: tuple = (512, 512),
fov: float = 40.0,
light_intensity: Optional[float] = 5.0,
znear: float = 0.1,
zfar: float = 10.0,
normalize_depth: bool = False,
flags: int = pyrender.constants.RenderFlags.NONE,
return_depth: bool = False,
return_type: Literal['pil', 'ndarray'] = 'pil'
) -> Union[
Image.Image,
np.ndarray,
Tuple[Image.Image, Image.Image],
Tuple[np.ndarray, np.ndarray]
]:
# def render_normal_single_view(
# mesh: Union[trimesh.Trimesh, trimesh.Scene],
# azimuth: float = 0.0, # in degrees
# elevation: float = 0.0, # in degrees
# radius: float = 3.5,
# image_size: tuple = (512, 512),
# fov: float = 40.0,
# light_intensity: Optional[float] = 5.0,
# znear: float = 0.1,
# zfar: float = 10.0,
# normalize_depth: bool = False,
# flags: int = pyrender.constants.RenderFlags.NONE,
# return_depth: bool = False,
# return_type: Literal['pil', 'ndarray'] = 'pil'
# ) -> Union[
# Image.Image,
# np.ndarray,
# Tuple[Image.Image, Image.Image],
# Tuple[np.ndarray, np.ndarray]
# ]:
if not isinstance(mesh, (trimesh.Trimesh, trimesh.Scene)):
raise ValueError("mesh must be a trimesh.Trimesh or trimesh.Scene object")
if isinstance(mesh, trimesh.Scene):
mesh = mesh.to_geometry()
normals = mesh.vertex_normals
colors = ((normals + 1.0) / 2.0 * 255).astype(np.uint8)
mesh.visual = trimesh.visual.ColorVisuals(
mesh=mesh,
vertex_colors=colors
)
mesh = trimesh.Scene(mesh)
return render_single_view(
mesh, azimuth, elevation, radius,
image_size, fov, light_intensity, znear, zfar,
normalize_depth, flags,
return_depth, return_type
)
# if not isinstance(mesh, (trimesh.Trimesh, trimesh.Scene)):
# raise ValueError("mesh must be a trimesh.Trimesh or trimesh.Scene object")
# if isinstance(mesh, trimesh.Scene):
# mesh = mesh.to_geometry()
# normals = mesh.vertex_normals
# colors = ((normals + 1.0) / 2.0 * 255).astype(np.uint8)
# mesh.visual = trimesh.visual.ColorVisuals(
# mesh=mesh,
# vertex_colors=colors
# )
# mesh = trimesh.Scene(mesh)
# return render_single_view(
# mesh, azimuth, elevation, radius,
# image_size, fov, light_intensity, znear, zfar,
# normalize_depth, flags,
# return_depth, return_type
# )
def export_renderings(
images: List[Image.Image],
export_path: str,
fps: int = 36,
loop: int = 0
):
export_type = export_path.split('.')[-1]
if export_type == 'mp4':
export_to_video(
images,
export_path,
fps=fps,
)
elif export_type == 'gif':
duration = 1000 / fps
images[0].save(
export_path,
save_all=True,
append_images=images[1:],
duration=duration,
loop=loop
)
else:
raise ValueError(f'Unknown export type: {export_type}')
# def export_renderings(
# images: List[Image.Image],
# export_path: str,
# fps: int = 36,
# loop: int = 0
# ):
# export_type = export_path.split('.')[-1]
# if export_type == 'mp4':
# export_to_video(
# images,
# export_path,
# fps=fps,
# )
# elif export_type == 'gif':
# duration = 1000 / fps
# images[0].save(
# export_path,
# save_all=True,
# append_images=images[1:],
# duration=duration,
# loop=loop
# )
# else:
# raise ValueError(f'Unknown export type: {export_type}')
def make_grid_for_images_or_videos(
images_or_videos: Union[List[Image.Image], List[List[Image.Image]]],
nrow: int = 4,
padding: int = 0,
pad_value: int = 0,
image_size: tuple = (512, 512),
return_type: Literal['pil', 'ndarray'] = 'pil'
) -> Union[Image.Image, List[Image.Image], np.ndarray]:
if isinstance(images_or_videos[0], Image.Image):
images = [np.array(image.resize(image_size).convert('RGB')) for image in images_or_videos]
images = np.stack(images, axis=0).transpose(0, 3, 1, 2) # [N, C, H, W]
images = torch.from_numpy(images)
image_grid = make_grid(
images,
nrow=nrow,
padding=padding,
pad_value=pad_value,
normalize=False
) # [C, H', W']
image_grid = image_grid.cpu().numpy()
if return_type == 'pil':
image_grid = Image.fromarray(image_grid.transpose(1, 2, 0))
return image_grid
elif isinstance(images_or_videos[0], list) and isinstance(images_or_videos[0][0], Image.Image):
image_grids = []
for i in range(len(images_or_videos[0])):
images = [video[i] for video in images_or_videos]
image_grid = make_grid_for_images_or_videos(
images,
nrow=nrow,
padding=padding,
return_type=return_type
)
image_grids.append(image_grid)
if return_type == 'ndarray':
image_grids = np.stack(image_grids, axis=0)
return image_grids
else:
raise ValueError(f'Unknown input type: {type(images_or_videos[0])}')
# def make_grid_for_images_or_videos(
# images_or_videos: Union[List[Image.Image], List[List[Image.Image]]],
# nrow: int = 4,
# padding: int = 0,
# pad_value: int = 0,
# image_size: tuple = (512, 512),
# return_type: Literal['pil', 'ndarray'] = 'pil'
# ) -> Union[Image.Image, List[Image.Image], np.ndarray]:
# if isinstance(images_or_videos[0], Image.Image):
# images = [np.array(image.resize(image_size).convert('RGB')) for image in images_or_videos]
# images = np.stack(images, axis=0).transpose(0, 3, 1, 2) # [N, C, H, W]
# images = torch.from_numpy(images)
# image_grid = make_grid(
# images,
# nrow=nrow,
# padding=padding,
# pad_value=pad_value,
# normalize=False
# ) # [C, H', W']
# image_grid = image_grid.cpu().numpy()
# if return_type == 'pil':
# image_grid = Image.fromarray(image_grid.transpose(1, 2, 0))
# return image_grid
# elif isinstance(images_or_videos[0], list) and isinstance(images_or_videos[0][0], Image.Image):
# image_grids = []
# for i in range(len(images_or_videos[0])):
# images = [video[i] for video in images_or_videos]
# image_grid = make_grid_for_images_or_videos(
# images,
# nrow=nrow,
# padding=padding,
# return_type=return_type
# )
# image_grids.append(image_grid)
# if return_type == 'ndarray':
# image_grids = np.stack(image_grids, axis=0)
# return image_grids
# else:
# raise ValueError(f'Unknown input type: {type(images_or_videos[0])}')