Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions examples/image-3d.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import os
import matplotlib.image as mpl_img

import gsp
from gsp_matplotlib import glm
from common.launcher import parse_args

# import gsp
__dirname__ = os.path.dirname(os.path.abspath(__file__))

# Parse command line arguments
core, visual, render = parse_args()

# Create a canvas and a viewport

canvas = core.Canvas(256, 256, 100.0)
viewport = core.Viewport(canvas, 0, 0, 256, 256, [1, 1, 1, 1])

# Create a cube with paths

cube_path_positions = glm.vec3(8)
cube_path_positions[...] = [
(-1.0, -1.0, +1.0),
(+1.0, -1.0, +1.0),
(-1.0, +1.0, +1.0),
(+1.0, +1.0, +1.0),
(-1.0, -1.0, -1.0),
(+1.0, -1.0, -1.0),
(-1.0, +1.0, -1.0),
(+1.0, +1.0, -1.0),
]
cube_path_face_indices = [
[0, 1],
[1, 3],
[3, 2],
[2, 0],
[4, 5],
[5, 7],
[7, 6],
[6, 4],
[0, 4],
[1, 5],
[2, 6],
[3, 7],
]

colormap = gsp.transform.Colormap("gray", vmin=0.0, vmax=0.75)
depth = gsp.transform.Out("screen[paths].z")
paths_visual = visual.Paths(
cube_path_positions,
cube_path_face_indices,
line_colors=colormap(depth),
line_widths=5.0 * (1 - 1.25 * depth),
line_styles=gsp.core.LineStyle.solid,
line_joins=gsp.core.LineJoin.round,
line_caps=gsp.core.LineCap.round,
)
paths_visual.render(viewport)

# Read the image_data numpy array from a file and create a texture

image_path = f"{__dirname__}/images/UV_Grid_Sm.jpg"
image_data = mpl_img.imread(image_path)
texture = core.Texture(image_data, image_data.shape)

# Create an image visual
image_visual = visual.Image(
positions=[[-1, 1, -1]],
texture_2d=texture,
image_extent=(-1, 1, -1, 1),
)
image_visual.render(viewport)

# Show or save the result

render(canvas, [viewport], [paths_visual, image_visual])
Binary file added examples/images/UV_Grid_Sm.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 9 additions & 8 deletions gsp/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
# Authors: Nicolas P .Rougier <[email protected]>
# License: BSD 3 clause

from . data import Data
from . list import List
from . buffer import Buffer
from . canvas import Canvas
from . viewport import Viewport
from . types import Color, Marker, Measure
from . types import Matrix, Vec2, Vec3, Vec4
from . types import LineCap, LineStyle, LineJoin
from .data import Data
from .list import List
from .buffer import Buffer
from .canvas import Canvas
from .viewport import Viewport
from .texture import Texture
from .types import Color, Marker, Measure
from .types import Matrix, Vec2, Vec3, Vec4
from .types import LineCap, LineStyle, LineJoin
30 changes: 30 additions & 0 deletions gsp/core/texture.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Package: Graphic Server Protocol
# Authors: Nicolas P .Rougier <[email protected]>
# License: BSD 3 clause
from __future__ import annotations

from gsp import Object
from gsp.io.command import command
import numpy as np


class Texture(Object):
"""
A texture is a rectangular two-dimensional image that can be
applied to a surface in 3D space.
"""

@command("core.Texture")
def __init__(self, texture_data: np.ndarray, shape: tuple):
"""
A texture is a rectangular two-dimensional image.

Parameters
----------

texture_data:
The image data of the texture.
shape:
The shape of the texture (height, width, channels).
"""
Object.__init__(self)
17 changes: 9 additions & 8 deletions gsp/visual/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
# Authors: Nicolas P .Rougier <[email protected]>
# License: BSD 3 clause

from . visual import Visual
from . pixels import Pixels
from . points import Points
from . markers import Markers
from . segments import Segments
from . paths import Paths
from . polygons import Polygons
# from . image import Image
from .visual import Visual
from .pixels import Pixels
from .points import Points
from .markers import Markers
from .segments import Segments
from .paths import Paths
from .polygons import Polygons
from .image import Image

# from . mesh import Mesh
33 changes: 33 additions & 0 deletions gsp/visual/image.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Package: Graphic Server Protocol
# Authors: Nicolas P .Rougier <[email protected]>
# License: BSD 3 clause

import numpy as np
from gsp.visual import Visual
from gsp.core import Buffer, Color, Texture
from gsp.transform import Transform
from gsp.io.command import command


class Image(Visual):

@command("visual.Image")
def __init__(self, positions: Transform | Buffer, texture_2d: Texture, image_extent: tuple):

super().__init__()

# These variables are available prior to rendering
self._in_variables = {
"positions": positions,
"texture_2d": texture_2d,
"image_extent": image_extent,
"viewport": None,
}

# These variables exists only during rendering and are
# available on server side only. We have thus to make
# sure they are not tracked.
n = len(positions)
self._out_variables = {
"screen[positions]": np.empty((n, 3), np.float32),
}
14 changes: 8 additions & 6 deletions gsp_matplotlib/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
# Authors: Nicolas P .Rougier <[email protected]>
# License: BSD 3 clause

#from . data import Data
from . list import List
from . buffer import Buffer
from . canvas import Canvas
from . viewport import Viewport
# from . data import Data
from .list import List
from .buffer import Buffer
from .canvas import Canvas
from .viewport import Viewport
from .texture import Texture
from gsp.core import Color, Marker, Measure
#from . types import LineCap, LineStyle, LineJoin

# from . types import LineCap, LineStyle, LineJoin
26 changes: 26 additions & 0 deletions gsp_matplotlib/core/texture.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Package: Graphic Server Protocol / Matplotlib
# Authors: Nicolas P .Rougier <[email protected]>
# License: BSD 3 clause
# from __future__ import annotations
import numpy as np
from gsp import core


class Texture(core.Texture):

__doc__ = core.Texture.__doc__

def __init__(self, texture_data: np.ndarray, shape: tuple):

super().__init__(texture_data=texture_data, shape=shape)

self._texture_data = texture_data.flatten()
self._shape = shape

@property
def data(self) -> np.ndarray:
return self._texture_data

@property
def shape(self) -> tuple:
return self._shape
15 changes: 8 additions & 7 deletions gsp_matplotlib/visual/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
# Authors: Nicolas P .Rougier <[email protected]>
# License: BSD 3 clause

from . pixels import Pixels
from . points import Points
from . markers import Markers
from . segments import Segments
from . paths import Paths
from . polygons import Polygons
# from . image import Image
from .pixels import Pixels
from .points import Points
from .markers import Markers
from .segments import Segments
from .paths import Paths
from .polygons import Polygons
from .image import Image

# from . mesh import Mesh
98 changes: 98 additions & 0 deletions gsp_matplotlib/visual/image.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Package: Graphic Server Protocol / Matplotlib
# Authors: Nicolas P .Rougier <[email protected]>
# License: BSD 3 clause

import numpy as np
from gsp import visual
from gsp.io.command import command
from gsp.transform import Transform
from gsp.core import Buffer, Color, Matrix
import matplotlib.image as mpl_img
from gsp_matplotlib import glm
from gsp_matplotlib.core.viewport import Viewport
from gsp_matplotlib.core.texture import Texture


class Image(visual.Image):
__doc__ = visual.Image.__doc__

@command("visual.Image")
def __init__(
self,
positions: Transform | Buffer,
texture_2d: Texture,
image_extent: tuple = (-1, 1, -1, 1),
) -> None:
"""
Initialize an Image object.

Parameters:
positions (Transform | Buffer): A (N, 3) array of XYZ positions in object space.
texture_2d (Texture): A Texture object containing the image to display.
image_extent (tuple): A tuple (left, right, bottom, top) defining the extent of the image in object space.
"""

super().__init__(positions, texture_2d, image_extent, __no_command__=True)

self._positions = positions
self._texture_2d = texture_2d
self._image_extent = image_extent

def render(
self,
viewport: Viewport,
model: Matrix | None = None,
view: Matrix | None = None,
proj: Matrix | None = None,
):
super().render(viewport, model, view, proj)

model = model if model is not None else self._model
view = view if view is not None else self._view
proj = proj if proj is not None else self._proj

# Disable tracking for newly created glm.ndarray (or else,
# this will create GSP buffers)
tracker = glm.ndarray.tracked.__tracker_class__
glm.ndarray.tracked.__tracker_class__ = None

# Create the collection if necessary
if viewport not in self._viewports:
axe_image = mpl_img.AxesImage(
viewport._axes,
data=self._texture_2d.data.reshape(self._texture_2d.shape),
)
self._viewports[viewport] = axe_image
viewport._axes.add_image(axe_image)

# This is necessary for measure transforms that need to be
# kept up to date with canvas size
canvas = viewport._canvas._figure.canvas
canvas.mpl_connect("resize_event", lambda event: self.render(viewport))

# If render has been called without model/view/proj, we don't
# render Such call is only used to declare that this visual is
# to be rendered on that viewport.
if self._transform is None:
# Restore tracking
glm.ndarray.tracked.__tracker_class__ = tracker
return

axe_image: mpl_img.AxesImage = self._viewports[viewport]
positions4d = glm.to_vec4(self._positions) @ self._transform.T
positions3d = glm.to_vec3(positions4d)
# FIXME here image_extent is divided by W after rotation
# but there is nothing to compensate for the camera z
# - should i divide by the camera's zoom ?
projected_extent = (
positions3d[0, 0] + self._image_extent[0] / positions4d[0, 3],
positions3d[0, 0] + self._image_extent[1] / positions4d[0, 3],
positions3d[0, 1] + self._image_extent[2] / positions4d[0, 3],
positions3d[0, 1] + self._image_extent[3] / positions4d[0, 3],
)
axe_image.set_extent(projected_extent)

self.set_variable("screen[positions]", positions3d)

# Restore tracking
glm.ndarray.tracked.__tracker_class__ = tracker