Source code for fury.material

"""Module for creating various materials used in 3D rendering."""

import numpy as np

from fury.colormap import normalize_colors
from fury.lib import (
    ImageBasicMaterial,
    LineArrowMaterial,
    LineMaterial,
    LineSegmentMaterial,
    LineThinMaterial,
    LineThinSegmentMaterial,
    MeshBasicMaterial,
    MeshPhongMaterial,
    MeshPhysicalMaterial,
    MeshStandardMaterial,
    PointsGaussianBlobMaterial,
    PointsMarkerMaterial,
    PointsMaterial,
    TextMaterial,
)


[docs] def validate_opacity(opacity): """ Ensure opacity is between 0 and 1. Parameters ---------- opacity : float Opacity value to validate. Returns ------- float Validated opacity value. Raises ------ ValueError If opacity is not between 0 and 1. """ if opacity is None: return 1.0 if not (0 <= opacity <= 1): raise ValueError("Opacity must be between 0 and 1.") return opacity
[docs] def validate_color(color, opacity, mode): """ Validate and modify color based on opacity and mode. Parameters ---------- color : tuple or None RGB or RGBA color tuple. opacity : float Opacity value between 0 and 1. mode : str Color mode, either 'auto' or 'vertex'. Returns ------- tuple or None Modified color tuple with opacity applied. Raises ------ ValueError If color is None when mode is 'auto' or if color has invalid length. """ if color is None and mode == "auto": return (1, 1, 1, opacity) if mode == "vertex": return (1, 1, 1) if color is not None: if len(color) == 3: return (*color, opacity) elif len(color) == 4: return (*color[:3], color[3] * opacity) else: raise ValueError("Color must be a tuple of length 3 or 4.") return color
PBR_MATERIALS = ("standard", "physical") DEFAULT_PBR_ROUGHNESS = 0.5 def _create_mesh_material( *, material="phong", enable_picking=True, color=None, opacity=1.0, mode="vertex", flat_shading=True, texture=None, wireframe=False, wireframe_thickness=-1.0, alpha_mode="blend", depth_write=True, metalness=None, roughness=None, emissive=None, emissive_intensity=None, env_map=None, env_map_intensity=None, metalness_map=None, roughness_map=None, emissive_map=None, normal_map=None, normal_scale=None, **kwargs, ): """ Create a mesh material. Parameters ---------- material : str, optional The type of material to create. Options are 'phong' (default), 'basic', 'standard' and 'physical'. The last two are physically based (PBR) materials: 'standard' implements the metallic-roughness workflow and 'physical' extends it with clearcoat, sheen, anisotropy, iridescence and transmission. enable_picking : bool, optional Whether the material should be pickable in a scene. color : tuple or None, optional The color of the material, represented as an RGB or RGBA tuple. If None, the default color is used. opacity : float, optional The opacity of the material, from 0 (transparent) to 1 (opaque). If RGBA is provided, the final alpha will be: final_alpha = alpha_in_RGBA * opacity. mode : str, optional The color mode of the material. Options are 'auto' and 'vertex'. flat_shading : bool, optional Whether to use flat shading (True) or smooth shading (False). texture : Texture or TextureMap, optional The texture map specifying the color for each texture coordinate. wireframe : bool, optional Whether to render the mesh as a wireframe. wireframe_thickness : float, optional The thickness of the wireframe lines. alpha_mode : str, optional The alpha mode for the material. Please see the below link for details: https://docs.pygfx.org/stable/_autosummary/materials/pygfx.materials.Material.html#pygfx.materials.Material.alpha_mode. depth_write : bool, optional Whether to write depth information for the material. metalness : float, optional How metallic the surface looks, from 0 (dielectric) to 1 (metal). PBR materials only. If None, the pygfx default (0.0) is used. A metal has no diffuse response, so it needs either an ``env_map`` or a directional light to be visible at all. roughness : float, optional How rough the surface is, from 0 (mirror-like) to 1 (fully diffuse). PBR materials only. Defaults to ``DEFAULT_PBR_ROUGHNESS`` (0.5) rather than the pygfx default of 1.0, at which a metal renders as a flat disc. emissive : str or tuple, optional The emissive (light emitting) color of the material. PBR materials only. emissive_intensity : float, optional The intensity applied to ``emissive``. PBR materials only. env_map : Texture, optional The environment cube map used for image based lighting. Without it, a PBR material is lit only by the lights present in the scene. PBR materials only. env_map_intensity : float, optional The intensity of the environment map contribution. PBR materials only. metalness_map : TextureMap or Texture, optional Texture whose blue channel modulates ``metalness``. PBR materials only. roughness_map : TextureMap or Texture, optional Texture whose green channel modulates ``roughness``. PBR materials only. emissive_map : TextureMap or Texture, optional Texture modulating ``emissive``. PBR materials only. normal_map : TextureMap or Texture, optional Tangent space normal map. PBR materials only. normal_scale : tuple, optional How much ``normal_map`` affects the surface, as an (x, y) pair. PBR materials only. **kwargs : dict, optional Extra properties forwarded to the underlying pygfx material. Only accepted when ``material='physical'``, where they cover the additional ``MeshPhysicalMaterial`` properties such as ``ior``, ``clearcoat``, ``clearcoat_roughness``, ``anisotropy``, ``anisotropy_rotation``, ``sheen``, ``sheen_color``, ``sheen_roughness``, ``specular``, ``specular_intensity`` and ``iridescence``. Returns ------- MeshMaterial A mesh material object of the specified type with the given properties. Raises ------ ValueError If an unsupported material type is specified, or if PBR properties are given for a material that is not physically based. Notes ----- The FURY 0.x helpers ``manifest_pbr`` and ``manifest_principled`` map onto ``material='physical'`` as follows: ``metallic`` -> ``metalness``, ``coat_strength`` -> ``clearcoat``, ``coat_roughness`` -> ``clearcoat_roughness``, ``base_ior`` -> ``ior``, ``sheen_tint`` -> ``sheen_color``; ``roughness``, ``anisotropy``, ``anisotropy_rotation`` and ``sheen`` keep their names. """ opacity = validate_opacity(opacity) color = validate_color(color, opacity, mode) args = { "pick_write": enable_picking, "color_mode": mode, "color": color, "opacity": opacity, "flat_shading": flat_shading, "map": texture, "wireframe": wireframe, "wireframe_thickness": wireframe_thickness, "alpha_mode": alpha_mode, "depth_write": depth_write, } if roughness is None and material in PBR_MATERIALS: roughness = 0.5 pbr_args = { name: value for name, value in { "metalness": metalness, "roughness": roughness, "emissive": emissive, "emissive_intensity": emissive_intensity, "env_map": env_map, "env_map_intensity": env_map_intensity, "metalness_map": metalness_map, "roughness_map": roughness_map, "emissive_map": emissive_map, "normal_map": normal_map, "normal_scale": normal_scale, }.items() if value is not None } pbr_args.update(kwargs) if material not in PBR_MATERIALS and pbr_args: raise ValueError( f"PBR properties {sorted(pbr_args)} are not supported by material " f"'{material}'. Use one of {PBR_MATERIALS} instead." ) if material == "standard" and kwargs: raise ValueError( f"Properties {sorted(kwargs)} are not supported by material " "'standard'. Use material='physical' instead." ) if material == "phong": return MeshPhongMaterial(**args) elif material == "basic": return MeshBasicMaterial(**args) elif material == "standard": return MeshStandardMaterial(**args, **pbr_args) elif material == "physical": return MeshPhysicalMaterial(**args, **pbr_args) else: raise ValueError(f"Unsupported material type: {material}") def _create_line_material( *, material="basic", enable_picking=True, color=None, opacity=1.0, mode="auto", thickness=2.0, thickness_space="screen", dash_pattern=(), dash_offset=0.0, anti_aliasing=True, alpha_mode="blend", depth_write=True, ): """ Create a line material. Parameters ---------- material : str, optional The type of line material to create. Options are 'line' (default), 'segment', 'arrow', 'thin', and 'thin_segment'. enable_picking : bool, optional Whether the material should be pickable in a scene. color : tuple or None, optional The color of the material, represented as an RGBA tuple. If None, the default color is used. opacity : float, optional The opacity of the material, from 0 (transparent) to 1 (opaque). If RGBA is provided, the final alpha will be: final_alpha = alpha_in_RGBA * opacity. mode : str, optional The color mode of the material. Options are 'auto' and 'vertex'. thickness : float, optional The line thickness expressed in logical pixels. thickness_space : str, optional The coordinate space in which the thickness is expressed ('screen', 'world', 'model'). dash_pattern : tuple, optional The pattern of the dash, e.g., [2, 3]. meaning no dashing. dash_offset : float, optional The offset into the dash cycle to start drawing at. anti_aliasing : bool, optional Whether or not the line is anti-aliased in the shader. alpha_mode : str, optional The alpha mode for the material. Please see the below link for details: https://docs.pygfx.org/stable/_autosummary/materials/pygfx.materials.Material.html#pygfx.materials.Material.alpha_mode. depth_write : bool, optional Whether to write depth information for the material. Returns ------- LineMaterial A line material object of the specified type with the given properties. """ opacity = validate_opacity(opacity) color = validate_color(color, opacity, mode) args = { "pick_write": enable_picking, "color_mode": mode, "color": color, "thickness": thickness, "thickness_space": thickness_space, "dash_pattern": dash_pattern, "dash_offset": dash_offset, "aa": anti_aliasing, "alpha_mode": alpha_mode, "depth_write": depth_write, } if material == "basic": return LineMaterial(**args) elif material == "segment": return LineSegmentMaterial(**args) elif material == "arrow": return LineArrowMaterial(**args) elif material == "thin": return LineThinMaterial(**args) elif material == "thin_segment": return LineThinSegmentMaterial(**args) else: raise ValueError(f"Unsupported material type: {material}") def _create_vector_field_material( cross_section, *, visibility=None, material="thin_line", enable_picking=True, opacity=1.0, thickness=1.0, thickness_space="screen", anti_aliasing=True, alpha_mode="blend", depth_write=True, ): """ Create a line material. Parameters ---------- cross_section : list or tuple, shape (3,), optional A list or tuple representing the cross section dimensions. If None, the cross section will be ignored and complete field will be shown. visibility : list or tuple, shape (3,), optional A list or tuple representing the visibility in the x, y, and z dimensions. If None, the visibility will be set to (-1, -1, -1) to show the complete field. material : str, optional The type of vector field material to create. Options are 'thin_line' (default), 'line', 'arrow'. enable_picking : bool, optional Whether the material should be pickable in a scene. opacity : float, optional The opacity of the material, from 0 (transparent) to 1 (opaque). If RGBA is provided, the final alpha will be: final_alpha = alpha_in_RGBA * opacity. thickness : float, optional The line thickness expressed in logical pixels. thickness_space : str, optional The coordinate space in which the thickness is expressed ('screen', 'world', 'model'). anti_aliasing : bool, optional Whether or not the line is anti-aliased in the shader. alpha_mode : str, optional The alpha mode for the material. Please see the below link for details: https://docs.pygfx.org/stable/_autosummary/materials/pygfx.materials.Material.html#pygfx.materials.Material.alpha_mode. depth_write : bool, optional Whether to write depth information for the material. Returns ------- LineMaterial A line material object of the specified type with the given properties. """ opacity = validate_opacity(opacity) args = { "pick_write": enable_picking, "opacity": opacity, "thickness": thickness, "thickness_space": thickness_space, "aa": anti_aliasing, "alpha_mode": alpha_mode, "depth_write": depth_write, } if material == "thin_line": return VectorFieldThinLineMaterial(cross_section, visibility=visibility, **args) elif material == "line": return VectorFieldLineMaterial(cross_section, visibility=visibility, **args) elif material == "arrow": return VectorFieldArrowMaterial(cross_section, visibility=visibility, **args) else: raise ValueError(f"Unsupported material type: {material}") def _create_points_material( *, material="basic", color=(1.0, 1.0, 1.0), size=4.0, map=None, aa=True, marker="circle", edge_color="black", edge_width=1.0, mode="vertex", opacity=1.0, enable_picking=True, alpha_mode="blend", depth_write=True, ): """ Create a points material. Parameters ---------- material : str, optional The type of material to create. Options are 'basic' (default), 'gaussian', and 'marker'. color : tuple, optional RGB or RGBA values in the range [0, 1]. size : float, optional The size (diameter) of the points in logical pixels. map : TextureMap or Texture, optional The texture map specifying the color for each texture coordinate. aa : bool, optional Whether or not the points are anti-aliased in the shader. marker : str or MarkerShape, optional The shape of the marker. Options are "●": "circle", "+": "plus", "x": "cross", "♥": "heart", "✳": "asterix". edge_color : str or tuple or Color, optional The color of line marking the edge of the markers. edge_width : float, optional The width of the edge of the markers. mode : str, optional The color mode of the material. Options are 'auto' and 'vertex'. opacity : float, optional The opacity of the material, from 0 (transparent) to 1 (opaque). If RGBA is provided, the final alpha will be: final_alpha = alpha_in_RGBA * opacity. enable_picking : bool, optional Whether the material should be pickable in a scene. alpha_mode : str, optional The alpha mode for the material. Please see the below link for details: https://docs.pygfx.org/stable/_autosummary/materials/pygfx.materials.Material.html#pygfx.materials.Material.alpha_mode. depth_write : bool, optional Whether to write depth information for the material. Returns ------- PointsMaterial A point material object of the specified type with the given properties. Raises ------ ValueError If an unsupported material type is specified. """ opacity = validate_opacity(opacity) color = validate_color(color, 1.0, mode) args = { "color": color, "size": size, "color_mode": mode, "map": map, "aa": aa, "pick_write": enable_picking, "opacity": opacity, "alpha_mode": alpha_mode, "depth_write": depth_write, } if material == "basic": return PointsMaterial(**args) elif material == "gaussian": return PointsGaussianBlobMaterial(**args) elif material == "marker": return PointsMarkerMaterial( marker=marker, edge_color=edge_color, edge_width=edge_width, **args ) else: raise ValueError(f"Unsupported material type: {material}") def _create_text_material( *, color=(0, 0, 0), opacity=1.0, outline_color=(0, 0, 0), outline_thickness=0.0, weight_offset=1.0, aliasing=True, alpha_mode="blend", depth_write=True, ): """ Create a text material. Parameters ---------- color : str, tuple, list or ndarray, optional The color of the text. Accepts a hex string, RGB(A) in [0, 1], or RGB(A) in [0, 255]. opacity : float, optional The opacity of the material, from 0 (transparent) to 1 (opaque). If RGBA is provided, the final alpha will be: final_alpha = alpha_in_RGBA * opacity. outline_color : tuple, optional The color of the outline of the text as RGB or RGBA tuple. outline_thickness : float, optional A value indicating the relative width of the outline. Valid values are between 0.0 and 0.5. weight_offset : float, optional A value representing an offset to the font weight. Font weights are in the range 100-900, so this value should be in the same order of magnitude. Can be negative to make text thinner. aliasing : bool, optional If True, use anti-aliasing while rendering glyphs. Aliasing gives prettier results, but may affect performance for very large texts. alpha_mode : str, optional The alpha mode for the material. Please see the below link for details: https://docs.pygfx.org/stable/_autosummary/materials/pygfx.materials.Material.html#pygfx.materials.Material.alpha_mode. depth_write : bool, optional Whether to write depth information for the material. Returns ------- TextMaterial A text material object with the specified properties. """ opacity = validate_opacity(opacity) if color is not None: color = normalize_colors(color)[0] if len(color) == 3: color = (*color, opacity) else: color = (*color[:3], color[3] * opacity) return TextMaterial( color=color, outline_color=outline_color, outline_thickness=outline_thickness, weight_offset=weight_offset, aa=aliasing, alpha_mode=alpha_mode, depth_write=depth_write, ) def _create_image_material( *, clim=None, map=None, gamma=1.0, interpolation="nearest", alpha_mode="blend", depth_write=True, ): """ Rasterized image material. Parameters ---------- clim : tuple, optional The contrast limits to scale the data values with. map : Texture or TextureMap, optional The texture map to turn the image values into its final color. gamma : float, optional The gamma correction to apply to the image data. Must be greater than 0.0. interpolation : str, optional The method to interpolate the image data. Either 'nearest' or 'linear'. alpha_mode : str, optional The alpha mode for the material. Please see the below link for details: https://docs.pygfx.org/stable/_autosummary/materials/pygfx.materials.Material.html#pygfx.materials.Material.alpha_mode. depth_write : bool, optional Whether to write depth information for the material. Returns ------- ImageMaterial A rasterized image material object with the specified properties. """ return ImageBasicMaterial( clim=clim, map=map, gamma=gamma, interpolation=interpolation, alpha_mode=alpha_mode, depth_write=depth_write, )
[docs] class VectorFieldThinLineMaterial(LineMaterial): """ Material for VectorFieldActor. Parameters ---------- cross_section : {list, tuple, ndarray} A list or tuple or ndarray representing the cross section dimensions. visibility : {list, tuple, ndarray}, optional A list or tuple or ndarray representing the visibility in the 3D. If None, the visibility will be set to (-1, -1, -1) to show the complete field. **kwargs : dict Additional keyword arguments for the material. """ uniform_type = dict( LineThinSegmentMaterial.uniform_type, cross_section="4xf4", # vec4<i32> visibility="4xi4", # vec4<i32> )
[docs] def __init__(self, cross_section, *, visibility=None, **kwargs): """ Initialize the VectorFieldMaterial. Parameters ---------- cross_section : {list, tuple, ndarray} A list or tuple or ndarray representing the cross section dimensions. visibility : {list, tuple, ndarray}, optional A list or tuple or ndarray representing the visibility in the 3D. If None, the visibility will be set to (-1, -1, -1) to show the complete field. **kwargs : dict Additional keyword arguments for the material. """ super().__init__(color_mode="vertex", **kwargs) self.cross_section = cross_section self.visibility = visibility
@property def visibility(self): """ Get the visibility of the vector field in each dimension. Returns ------- list A list representing the visibility in the x, y, and z dimensions. """ vis = self.uniform_buffer.data["visibility"][:3] if all(vis == (-1, -1, -1)): return None return [bool(i) for i in vis] @visibility.setter def visibility(self, visibility): """ Set the visibility of the vector field in each dimension. Parameters ---------- visibility : list or tuple A list or tuple representing the visibility in the x, y, and z dimensions. """ if visibility is None: self.uniform_buffer.data["visibility"] = np.asarray( [-1, -1, -1, 0], dtype=np.int32 ) else: if len(visibility) != 3: raise ValueError("visibility must have exactly 3 dimensions.") if not all( isinstance(i, bool) or (hasattr(i, "item") and isinstance(i.item(), bool)) for i in visibility ): raise ValueError("visibility must contain only booleans.") self.uniform_buffer.data["visibility"] = np.asarray( [*visibility, 0], dtype=np.int32 ) self.uniform_buffer.update_full() @property def cross_section(self): """ Get the cross section of the vector field. Returns ------- list A list representing the cross section dimensions. """ return self.uniform_buffer.data["cross_section"][:3] @cross_section.setter def cross_section(self, cross_section): """ Set the cross section of the vector field. Parameters ---------- cross_section : list or tuple A list or tuple representing the cross section dimensions. """ if len(cross_section) != 3: raise ValueError("cross_section must have exactly 3 dimensions.") self.uniform_buffer.data["cross_section"] = np.asarray( [*cross_section, 0], dtype=np.int32 ) self.uniform_buffer.update_full()
[docs] class VectorFieldLineMaterial(VectorFieldThinLineMaterial): """ Material for VectorFieldActor. This class provides a way to distinguish the usage of right shader for creating a vector field. """
[docs] class VectorFieldArrowMaterial(VectorFieldThinLineMaterial): """ Material for VectorFieldActor. This class provides a way to distinguish the usage of right shader for creating a vector field. """
[docs] class SphGlyphMaterial(MeshPhongMaterial): """ Initialize the Spherical Glyph Material. Parameters ---------- n_coeffs : int, optional The maximum spherical harmonic degree. scale : int, optional The scale factor. shininess : int, optional The shininess factor. emissive : str, optional The emissive color. specular : str, optional The specular color. **kwargs : dict Additional keyword arguments for the material. """ uniform_type = dict( MeshPhongMaterial.uniform_type, n_coeffs="i4", scale="f4", )
[docs] def __init__( self, n_coeffs=-1, scale=1, shininess=30, emissive="#000", specular="#494949", **kwargs, ): """ Initialize the Spherical Glyph Material. Parameters ---------- n_coeffs : int, optional The maximum spherical harmonic degree. This value will limit the number of spherical harmonic coefficients that can be used from the data. If -1, no limit is applied. scale : int, optional The scale factor. shininess : int, optional The shininess factor. emissive : str, optional The emissive color. specular : str, optional The specular color. **kwargs : dict Additional keyword arguments for the material. """ super().__init__(shininess, emissive, specular, **kwargs) self.n_coeffs = n_coeffs self.scale = scale
@property def n_coeffs(self): """ Get the maximum number of spherical harmonic coefficients. Returns ------- int The maximum number of spherical harmonic coefficients. """ return self.uniform_buffer.data["n_coeffs"] @n_coeffs.setter def n_coeffs(self, value): """ Set the maximum number of spherical harmonic coefficients. Parameters ---------- value : int The maximum number of spherical harmonic coefficients. """ if not isinstance(value, int): raise ValueError("n_coeffs must be an integer.") self.uniform_buffer.data["n_coeffs"] = value self.uniform_buffer.update_full() @property def scale(self): """ Get the scale factor. Returns ------- float The scale factor. """ return self.uniform_buffer.data["scale"] @scale.setter def scale(self, value): """ Set the scale factor. Parameters ---------- value : float The scale factor. """ if not isinstance(value, (int, float)): raise ValueError("scale must be a number.") self.uniform_buffer.data["scale"] = float(value) self.uniform_buffer.update_full()
[docs] class StreamlinesMaterial(LineMaterial): """ Initialize the Streamlines Material. Parameters ---------- outline_thickness : float, optional The thickness of the outline. outline_color : tuple, optional The color of the outline as an RGBA tuple. roi_enabled : bool, optional Whether ROI culling is active. This is typically driven by the presence of an ROI mask on the actor. roi_dim : tuple, optional 3D integer dimensions (nx, ny, nz) of a mask grid when a volumetric ROI is used in the shader. **kwargs : dict Additional keyword arguments for the material. """ uniform_type = dict( LineMaterial.uniform_type, outline_thickness="f4", outline_color="4xf4", roi_enabled="i4", roi_dim="4xi4", )
[docs] def __init__( self, outline_thickness=0.0, outline_color=(0, 0, 0), roi_enabled=None, roi_dim=(0, 0, 0), **kwargs, ): """ Initialize the Streamline Material. Parameters ---------- outline_thickness : float, optional The thickness of the outline. outline_color : tuple, optional The color of the outline as an RGBA tuple. roi_enabled : bool, optional Whether ROI culling is active. If None, ROI culling defaults to False until an ROI mask is attached by the actor. roi_dim : tuple, optional 3D integer dimensions (nx, ny, nz) of a mask grid when a volumetric ROI is used in the shader. **kwargs : dict Additional keyword arguments for the material. """ super().__init__(**kwargs) self.outline_thickness = outline_thickness self.outline_color = outline_color self.roi_dim = roi_dim self.roi_enabled = roi_enabled
@property def outline_thickness(self): """ Get the outline thickness. Returns ------- float The thickness of the outline. """ return float(self.uniform_buffer.data["outline_thickness"]) @outline_thickness.setter def outline_thickness(self, value): """ Set the outline thickness. Parameters ---------- value : float The thickness of the outline. """ self.uniform_buffer.data["outline_thickness"] = float(value) self.uniform_buffer.update_full() @property def outline_color(self): """ Get the outline color. Returns ------- tuple The color of the outline as an RGBA tuple. """ return self.uniform_buffer.data["outline_color"][:3] @outline_color.setter def outline_color(self, value): """ Set the outline color. Parameters ---------- value : tuple The color of the outline as an RGB or RGBA tuple. """ if len(value) == 3: value = (*value, 1.0) self.uniform_buffer.data["outline_color"] = value self.uniform_buffer.update_full() @property def roi_enabled(self): """ Return True when ROI-based culling is active. Returns ------- bool True if ROI-based culling is enabled, False otherwise. """ return bool(self.uniform_buffer.data["roi_enabled"]) @roi_enabled.setter def roi_enabled(self, value): """ Enable/disable ROI-based culling. Parameters ---------- value : bool True to enable ROI-based culling, False to disable. """ self.uniform_buffer.data["roi_enabled"] = int(bool(value)) self.uniform_buffer.update_full() @property def roi_dim(self): """ ROI grid dimensions as (nx, ny, nz). Returns ------- tuple A tuple of three integers representing the ROI grid dimensions. """ return tuple(int(x) for x in self.uniform_buffer.data["roi_dim"][:3]) @roi_dim.setter def roi_dim(self, value): """ Set the ROI grid dimensions. Parameters ---------- value : tuple A tuple of three integers representing the ROI grid dimensions. """ dims = np.asarray(value, dtype=np.int32).reshape(-1) if dims.size != 3: raise ValueError("roi_dim must contain exactly three integers.") self.uniform_buffer.data["roi_dim"] = ( int(dims[0]), int(dims[1]), int(dims[2]), 0, ) self.uniform_buffer.update_full()
class _StreamlineBakedMaterial(StreamlinesMaterial): """ Initialize the internal baked streamline material. Parameters ---------- auto_detach : bool, optional If True, automatically switch to render-only material after baking. **kwargs : dict Additional keyword arguments for the material. """ def __init__(self, *, auto_detach=True, **kwargs): """ Initialize the internal baked streamline material. Parameters ---------- auto_detach : bool, optional If True, automatically switch to render-only material after baking. **kwargs : dict Additional keyword arguments for the material. """ super().__init__(**kwargs) self.auto_detach = bool(auto_detach)
[docs] class RoundedRectangleMaterial(MeshBasicMaterial): """ Material for rendering rounded rectangles using signed distance fields. Parameters ---------- size : tuple Width and height of the rectangle in pixels or local units. corner_radius : float Radius of the rounded corners in the same units. **kwargs : dict Additional keyword arguments forwarded to ``MeshBasicMaterial``. """ uniform_type = dict( MeshBasicMaterial.uniform_type, size="2xf4", corner_radius="f4", )
[docs] def __init__(self, size=(100.0, 100.0), corner_radius=10.0, **kwargs): """ Initialize the material and forward arguments to the base class. Parameters ---------- size : tuple Width and height of the rectangle in pixels or local units. corner_radius : float Radius of the rounded corners in the same units. **kwargs : dict Additional keyword arguments forwarded to ``MeshBasicMaterial``. """ kwargs.setdefault("pick_write", True) super().__init__(**kwargs) self.size = size self.corner_radius = corner_radius
@property def size(self): """ Get the physical size of the rectangle. Returns ------- tuple Width and height of the rectangle. """ return self.uniform_buffer.data["size"] @size.setter def size(self, value): """ Set the physical size of the rectangle. Parameters ---------- value : tuple Width and height of the rectangle. """ self.uniform_buffer.data["size"] = value self.uniform_buffer.update_full() @property def corner_radius(self): """ Get the corner radius of the rectangle. Returns ------- float Corner radius of the rectangle. """ return float(self.uniform_buffer.data["corner_radius"]) @corner_radius.setter def corner_radius(self, value): """ Set the corner radius of the rectangle. Parameters ---------- value : float Corner radius of the rectangle. """ self.uniform_buffer.data["corner_radius"] = float(value) self.uniform_buffer.update_full()
[docs] class BillboardMaterial(MeshBasicMaterial): """ Billboard material for creating quads that always face the camera. This material is designed to work with the BillboardShader to create rectangles that automatically rotate to face the camera while maintaining their 3D position. Parameters ---------- **kwargs : dict Additional keyword arguments forwarded to :class:`~fury.material.MeshBasicMaterial`. """
[docs] def __init__(self, **kwargs): """ Initialize the material and forward arguments to the base class. Parameters ---------- **kwargs : dict Additional keyword arguments forwarded to ``MeshBasicMaterial``. """ super().__init__(**kwargs)
[docs] class StreamtubeMaterial(MeshPhongMaterial): """ Material for GPU-generated streamtubes. Parameters ---------- **kwargs : dict, optional Arguments forwarded to :class:`MeshPhongMaterial`. """
[docs] def __init__(self, **kwargs): """ Initialise the material with MeshPhongMaterial keyword arguments. Parameters ---------- **kwargs : dict, optional Arguments forwarded to :class:`MeshPhongMaterial`. """ super().__init__(**kwargs)
class _StreamtubeBakedMaterial(MeshPhongMaterial): """ Internal material for compute-shader-based streamtubes with auto-baking. This material is used internally by the streamtube actor when GPU compute shaders are available. Users should not instantiate this directly; instead use the ``streamtube()`` function which will automatically select the appropriate implementation. Parameters ---------- radius : float, optional Tube radius used by the compute shader. segments : int, optional Number of radial segments forming the tube. end_caps : bool, optional Whether flat caps are generated for each tube. auto_detach : bool, optional If True, automatically switch to render-only material after baking. **kwargs : dict, optional Additional arguments forwarded to :class:`MeshPhongMaterial`. Notes ----- This is an internal class marked with a leading underscore. It should not be used directly by end users. """ uniform_type = dict( MeshPhongMaterial.uniform_type, tube_radius="f4", tube_segments="u4", tube_end_caps="i4", line_count="u4", ) def __init__( self, *, radius=0.2, segments=8, end_caps=True, auto_detach=True, **kwargs, ): """ Initialise uniforms controlling the compute-driven streamtube. Parameters ---------- radius : float, optional Tube radius used by the compute shader. segments : int, optional Number of radial segments forming the tube. end_caps : bool, optional Whether flat caps are generated for each tube. auto_detach : bool, optional If True, automatically switch to render-only material after baking. **kwargs : dict, optional Additional arguments forwarded to :class:`MeshPhongMaterial`. """ super().__init__(**kwargs) self.radius = radius self.segments = segments self.end_caps = end_caps self.line_count = 0 self.auto_detach = bool(auto_detach) @property def radius(self): """ Get the tube radius used by the compute shader. Returns ------- float Current tube radius. """ return float(self.uniform_buffer.data["tube_radius"]) @radius.setter def radius(self, value): """ Set the tube radius used in the compute shader. Parameters ---------- value : float New tube radius value. """ self.uniform_buffer.data["tube_radius"] = float(value) self.uniform_buffer.update_full() @property def segments(self): """ Get the number of radial segments per tube. Returns ------- int Number of radial segments. """ return int(self.uniform_buffer.data["tube_segments"]) @segments.setter def segments(self, value): """ Set the number of radial segments per tube. Parameters ---------- value : int New segment count. """ self.uniform_buffer.data["tube_segments"] = int(value) self.uniform_buffer.update_full() @property def end_caps(self): """ Check whether end caps are rendered. Returns ------- bool ``True`` when end caps are enabled. """ return bool(self.uniform_buffer.data["tube_end_caps"]) @end_caps.setter def end_caps(self, value): """ Enable or disable end caps on the tubes. Parameters ---------- value : bool Flag indicating whether to render end caps. """ self.uniform_buffer.data["tube_end_caps"] = int(bool(value)) self.uniform_buffer.update_full() @property def line_count(self): """ Get the number of lines processed by the compute shader. Returns ------- int Number of lines passed to the GPU compute stage. """ return int(self.uniform_buffer.data["line_count"]) @line_count.setter def line_count(self, value): """ Update the number of lines handled by the compute shader. Parameters ---------- value : int Number of lines to process. """ self.uniform_buffer.data["line_count"] = int(value) self.uniform_buffer.update_full() def _setup_compute_shader(self, line_count, max_line_length, tube_segments): """ Record metadata used by the GPU compute pass. Parameters ---------- line_count : int Total number of lines to process. max_line_length : int Maximum length of any line in the batch. tube_segments : int Number of radial tube segments. """ self.line_count = line_count self.segments = tube_segments self._max_line_length = max_line_length
[docs] class BillboardSphereMaterial(MeshPhongMaterial): """ Phong-lit material for billboard-based impostor spheres. Parameters ---------- **material_kwargs : dict Keyword arguments propagated to :class:`~fury.material.MeshPhongMaterial`. """
[docs] def __init__(self, **material_kwargs): """ Initialize the sphere impostor material. Parameters ---------- **material_kwargs : dict Keyword arguments propagated to :class:`~fury.material.MeshPhongMaterial`. """ material_kwargs.setdefault("flat_shading", False) super().__init__(**material_kwargs)
[docs] class BillboardEllipsoidMaterial(BillboardSphereMaterial): """ Phong-lit ellipsoid impostors with optional world-space slice selection. Parameters ---------- cross_section : array-like, shape (3,), optional World-space slice positions, converted to integers. visibility : array-like of bool, shape (3,), optional Enabled X, Y, and Z slice planes. None disables slice selection. orientation_colors : bool, optional Multiply RGB by the selected model-space semi-axis direction color. **material_kwargs : dict Keyword arguments propagated to :class:`~fury.material.BillboardSphereMaterial`. """ uniform_type = dict( BillboardSphereMaterial.uniform_type, cross_section="4xf4", visibility="4xi4", orientation_colors="i4", )
[docs] def __init__( self, *, cross_section=(0, 0, 0), visibility=None, orientation_colors=False, **material_kwargs, ): """ Initialize the ellipsoid impostor material. Parameters ---------- cross_section : array-like, shape (3,), optional World-space slice positions, converted to integers. visibility : array-like of bool, shape (3,), optional Enabled X, Y, and Z slice planes. None disables slice selection. orientation_colors : bool, optional Multiply RGB by the selected model-space semi-axis direction color. **material_kwargs : dict Keyword arguments propagated to :class:`~fury.material.BillboardSphereMaterial`. """ super().__init__(**material_kwargs) self.cross_section = cross_section self.visibility = visibility self.orientation_colors = orientation_colors
@property def orientation_colors(self): """ Get whether model-space directional coloring is enabled. Returns ------- bool Whether the selected semi-axis direction multiplies RGB. """ return bool(self.uniform_buffer.data["orientation_colors"]) @orientation_colors.setter def orientation_colors(self, orientation_colors): """ Set whether model-space directional coloring is enabled. Parameters ---------- orientation_colors : bool Whether the selected semi-axis direction multiplies RGB. """ self.uniform_buffer.data["orientation_colors"] = bool(orientation_colors) self.uniform_buffer.update_full() @property def cross_section(self): """ Get the world-space slice positions. Returns ------- ndarray, shape (3,) Integer-converted slice positions stored in the material uniform. """ return self.uniform_buffer.data["cross_section"][:3] @cross_section.setter def cross_section(self, cross_section): """ Set the world-space slice positions. Parameters ---------- cross_section : array-like, shape (3,) Slice positions, converted to integers. """ if len(cross_section) != 3: raise ValueError("cross_section must have exactly 3 dimensions.") self.uniform_buffer.data["cross_section"] = np.asarray( [*cross_section, 0], dtype=np.int32 ) self.uniform_buffer.update_full() @property def visibility(self): """ Get the enabled slice planes. Returns ------- list of bool or None Enabled X, Y, and Z planes, or None when slice selection is disabled. """ visibility = self.uniform_buffer.data["visibility"][:3] if all(visibility == (-1, -1, -1)): return None return [bool(value) for value in visibility] @visibility.setter def visibility(self, visibility): """ Set the enabled slice planes. Parameters ---------- visibility : array-like of bool, shape (3,), or None Enabled X, Y, and Z planes. None disables slice selection. """ if visibility is None: self.uniform_buffer.data["visibility"] = np.asarray( [-1, -1, -1, 0], dtype=np.int32 ) else: if len(visibility) != 3: raise ValueError("visibility must have exactly 3 dimensions.") if not all( isinstance(value, bool) or (hasattr(value, "item") and isinstance(value.item(), bool)) for value in visibility ): raise ValueError("visibility must contain only booleans.") self.uniform_buffer.data["visibility"] = np.asarray( [*visibility, 0], dtype=np.int32 ) self.uniform_buffer.update_full()
[docs] class NetworkMaterial(PointsMaterial): """ Material handling simulation parameters for the Network. Parameters ---------- k : float, optional Optimal distance constant. damping : float, optional Damping factor for velocity. speed : float, optional Simulation speed factor. repulsion_strength : float, optional Multiplier for repulsive forces. size : float, optional Size of the rendered nodes. **kwargs : dict Additional parameters passed to PointsMaterial. """ uniform_type = dict( PointsMaterial.uniform_type, k="f4", damping="f4", speed="f4", repulsion_strength="f4", )
[docs] def __init__( self, k=10.0, damping=0.9, speed=1.0, repulsion_strength=1.0, size=10.0, **kwargs, ): """ Initialize the material instance. Parameters ---------- k : float, optional Optimal distance constant. damping : float, optional Damping factor for velocity. speed : float, optional Simulation speed factor. repulsion_strength : float, optional Multiplier for repulsive forces. size : float, optional Size of the rendered nodes. **kwargs : dict Additional parameters passed to PointsMaterial. """ super().__init__(size=size, **kwargs) self.k = k self.damping = damping self.speed = speed self.repulsion_strength = repulsion_strength
@property def k(self): """ Get the optimal distance constant. Returns ------- float The optimal distance constant. """ return self.uniform_buffer.data["k"] @k.setter def k(self, v): """ Set the optimal distance constant. Parameters ---------- v : float The new optimal distance value. """ self.uniform_buffer.data["k"] = v self.uniform_buffer.update_full() @property def damping(self): """ Get the velocity damping factor. Returns ------- float The damping factor. """ return self.uniform_buffer.data["damping"] @damping.setter def damping(self, v): """ Set the velocity damping factor. Parameters ---------- v : float The new damping factor value. """ self.uniform_buffer.data["damping"] = v self.uniform_buffer.update_full() @property def speed(self): """ Get the simulation speed factor. Returns ------- float The simulation speed factor. """ return self.uniform_buffer.data["speed"] @speed.setter def speed(self, v): """ Set the simulation speed factor. Parameters ---------- v : float The new simulation speed value. """ self.uniform_buffer.data["speed"] = v self.uniform_buffer.update_full() @property def repulsion_strength(self): """ Get the repulsive force multiplier. Returns ------- float The repulsion strength multiplier. """ return self.uniform_buffer.data["repulsion_strength"] @repulsion_strength.setter def repulsion_strength(self, v): """ Set the repulsive force multiplier. Parameters ---------- v : float The new repulsion strength multiplier. """ self.uniform_buffer.data["repulsion_strength"] = v self.uniform_buffer.update_full()