genesis.options.textures 源代码

import os
from typing import Any, Optional

import Imath
import numpy as np
import OpenEXR
from PIL import Image

import genesis as gs

from .options import Options


[文档]class Texture(Options): """ Base class for Genesis's texture objects. Note ---- This class should *not* be instantiated directly. """ def __init__(self, **data): super().__init__(**data) def check_dim(self, dim): raise NotImplementedError
[文档]class ColorTexture(Texture): """ A texture with a single color. Parameters ---------- color : tuple, shape (3,) RGB color value. """ color: tuple = (1.0, 1.0, 1.0) def check_dim(self, dim): if len(self.color) > dim: self.color, res = self.color[:dim], self.color[dim:] return ColorTexture(color=res) return None def apply_cutoff(self, cutoff): if cutoff is None: return self.color = tuple([1.0 if c >= cutoff else 0.0 for c in self.color])
[文档]class ImageTexture(Texture): """ A texture with a texture map (image). Parameters ---------- image_path : str, optional Path to the image file. image_array : np.ndarray, optional Image array. image_color : tuple, optional The base color which will be multiplied with the image color. Default is (1.0, 1.0, 1.0, 1.0). encoding : str The encoding way of the image. Possible values are ['srgb', 'linear']. - 'srgb': Encoding of some RGB images. - 'linear': All generic images, such as opacity, roughness and normal, should be encoded with 'linear'. """ image_path: Optional[str] = None image_array: Optional[Any] = None image_color: Optional[tuple] = (1.0, 1.0, 1.0, 1.0) encoding: str = "srgb" def __init__(self, **data): super().__init__(**data) if not (self.image_path is None) ^ (self.image_array is None): gs.raise_exception("Please set either `image_path` or `image_array`.") if self.image_path is not None: input_image_path = self.image_path if not os.path.exists(self.image_path): self.image_path = os.path.join(gs.utils.get_assets_dir(), self.image_path) if not os.path.exists(self.image_path): gs.raise_exception( f"File not found in either current directory or assets directory: '{input_image_path}'." ) # Load image_path as actual image_array, unless for special texture images (e.g. `.hdr` and `.exr`) that are only supported by raytracers if self.image_path.endswith((".hdr", ".exr")): self.encoding = "linear" # .exr or .hdr images should be encoded with 'linear' if self.image_path.endswith((".exr")): exr_file = OpenEXR.InputFile(self.image_path) exr_header = exr_file.header() if exr_header["compression"].v > Imath.Compression.PIZ_COMPRESSION: new_image_path = f"{self.image_path[:-4]}_ZIP.exr" gs.logger.warning( f"EXR image {self.image_path}'s compression type {exr_header['compression']} is not supported. " f"Converting to compression type ZIP_COMPRESSION and saving to {new_image_path}." ) self.image_path = new_image_path if not os.path.exists(new_image_path): channel_data = {channel: exr_file.channel(channel) for channel in exr_header["channels"]} exr_header["compression"] = Imath.Compression(Imath.Compression.ZIP_COMPRESSION) new_exr_file = OpenEXR.OutputFile(new_image_path, exr_header) new_exr_file.writePixels(channel_data) new_exr_file.close() exr_file.close() else: self.image_array = np.array(Image.open(self.image_path)) self.image_path = None elif self.image_array is not None: if not isinstance(self.image_array, np.ndarray): gs.raise_exception("`image_array` needs to be an numpy array.") if self.image_array is None: self._mean_color = np.array([1.0, 1.0, 1.0], dtype=np.float16) self._channel = 3 else: self._mean_color = (np.mean(self.image_array, axis=(0, 1), dtype=np.float32) / 255.0).astype(np.float16) self._channel = self.image_array.shape[2] if self.image_array.ndim == 3 else 1 if self.image_color is None: self.image_color = tuple([1.0 for _ in range(self._channel)]) else: self.image_color = self.image_color[: self._channel] self.encoding = self.encoding.lower() if self.encoding not in ["srgb", "linear"]: gs.raise_exception(f"Invalid image encoding: {self.encoding}.") assert self.image_array is None or self.image_array.dtype == np.uint8 def check_dim(self, dim): if self.image_array is not None: if self._channel > dim: self.image_array, res = self.image_array[:, :, :dim], self.image_array[:, :, dim:] self.image_color, res_color = self.image_color[:dim], self.image_color[dim:] self._channel = dim return ImageTexture(image_array=res, image_color=res_color, encoding="linear").check_simplify() return None def check_simplify(self): if self.image_array is None: return self max_color = np.max(self.image_array, axis=(0, 1)) min_color = np.min(self.image_array, axis=(0, 1)) if np.all(min_color == max_color): return ColorTexture(color=tuple(max_color.reshape(-1) / 255.0 * self.image_color)) else: return self def mean_color(self): return self._mean_color def channel(self): return self._channel def apply_cutoff(self, cutoff): if cutoff is None or self.image_array is None: # Cutoff does not apply on image file. return self.image_array = np.where(self.image_array >= 255 * cutoff, 255, 0).astype(np.uint8)