Skip to content

synthetic

Module for synthetic data generation.

Mesh

Bases: Object

Object with associated mesh.

This class maintains two meshes, the original mesh and the scaled mesh.

Updating the scale will always be relative to the original mesh. I.e., two times setting the relative scale by 0.1 will not yield a final scale of 0.01 as it will always be relative to the original mesh.

Source code in sdfest/estimation/synthetic.py
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
class Mesh(Object):
    """Object with associated mesh.

    This class maintains two meshes, the original mesh and the scaled mesh.

    Updating the scale will always be relative to the original mesh. I.e., two times
    setting the relative scale by 0.1 will not yield a final scale of 0.01 as it will
    always be relative to the original mesh.
    """

    def __init__(
        self,
        mesh: Optional[o3d.geometry.TriangleMesh] = None,
        path: Optional[str] = None,
        scale: float = 1,
        rel_scale: bool = False,
        center: bool = False,
        position: Optional[np.array] = None,
        orientation: Optional[np.array] = None,
    ):
        """Initialize mesh.

        Must provide either mesh or path. If path is given, mesh will be loaded from
        specified file. If mesh is given, it will be used as the original mesh.

        Args:
            mesh: The original (i.e., unscaled mesh).
            path: The path of the mesh to load.
            scale: See Mesh.update_scale.
            rel_scale: See Mesh.update_scale.
            center: whether to center the mesh upon loading.
            position: See Object.__init__.
            orientation:  See Object.__init__.
        """
        super().__init__(position=position, orientation=orientation)
        if mesh is not None and path is not None:
            raise ValueError("Only one of mesh or path can be specified")
        if mesh is not None:
            self._original_mesh = mesh
        if path is not None:
            self._original_mesh = o3d.io.read_triangle_mesh(path)
        if center:
            self._original_mesh.translate([0, 0, 0], relative=False)

        self.update_scale(scale, rel_scale)

    def load_mesh_from_file(
        self, path: str, scale: float = 1, rel_scale: bool = False
    ) -> None:
        """Load mesh from file.

        Args:
            path: Path of the obj file.
            scale: See Mesh.update_scale.
            rel_scale: See Mesh.update_scale.
        """
        self._original_mesh = o3d.io.read_triangle_mesh(path)
        self.update_scale(scale, rel_scale)

    def update_scale(self, scale: float = 1, rel_scale: bool = False) -> None:
        """Update relative or absolute scale of mesh.

        Absolute scale represents half the largest extent in x, y, or z direction.
        Relative scale represents the scale factor from original mesh.

        Args:
            scale: The desired absolute or relative scale of the object.
            rel_scale:
                If true, scale will be relative to original mesh.
                Otherwise, scale will be the resulting absolute scale.
        """
        # self._scale will always be absolute scale
        if rel_scale:
            # copy construct mesh
            self._scaled_mesh = o3d.geometry.TriangleMesh(self._original_mesh)

            original_scale = self._get_original_scale()
            self._scaled_mesh.scale(scale, [0, 0, 0])

            self._scale = original_scale * scale
        else:
            # copy construct mesh
            self._scaled_mesh = o3d.geometry.TriangleMesh(self._original_mesh)

            # scale original mesh s.t. output has the provided scale
            original_scale = self._get_original_scale()
            scale_factor = scale / original_scale
            self._scaled_mesh.scale(scale_factor, [0, 0, 0])

            self._scale = scale

    def _get_original_scale(self) -> float:
        """Compute current scale of original mesh.

        Scale is largest x/y/z extent over 2.
        """
        mins = np.amin(self._original_mesh.vertices, axis=0)
        maxs = np.amax(self._original_mesh.vertices, axis=0)
        ranges = maxs - mins
        return np.max(ranges) / 2

    def get_transformed_o3d_geometry(self) -> o3d.geometry.TriangleMesh:
        """Get o3d mesh at current pose."""
        transformed_mesh = o3d.geometry.TriangleMesh(self._scaled_mesh)
        R = Rotation.from_quat(self.orientation).as_matrix()
        transformed_mesh.rotate(R, center=np.array([0, 0, 0]))
        transformed_mesh.translate(self.position)
        transformed_mesh.compute_vertex_normals()
        return transformed_mesh

__init__(mesh=None, path=None, scale=1, rel_scale=False, center=False, position=None, orientation=None)

Initialize mesh.

Must provide either mesh or path. If path is given, mesh will be loaded from specified file. If mesh is given, it will be used as the original mesh.

Parameters:

Name Type Description Default
mesh Optional[TriangleMesh]

The original (i.e., unscaled mesh).

None
path Optional[str]

The path of the mesh to load.

None
scale float

See Mesh.update_scale.

1
rel_scale bool

See Mesh.update_scale.

False
center bool

whether to center the mesh upon loading.

False
position Optional[array]

See Object.init.

None
orientation Optional[array]

See Object.init.

None
Source code in sdfest/estimation/synthetic.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
def __init__(
    self,
    mesh: Optional[o3d.geometry.TriangleMesh] = None,
    path: Optional[str] = None,
    scale: float = 1,
    rel_scale: bool = False,
    center: bool = False,
    position: Optional[np.array] = None,
    orientation: Optional[np.array] = None,
):
    """Initialize mesh.

    Must provide either mesh or path. If path is given, mesh will be loaded from
    specified file. If mesh is given, it will be used as the original mesh.

    Args:
        mesh: The original (i.e., unscaled mesh).
        path: The path of the mesh to load.
        scale: See Mesh.update_scale.
        rel_scale: See Mesh.update_scale.
        center: whether to center the mesh upon loading.
        position: See Object.__init__.
        orientation:  See Object.__init__.
    """
    super().__init__(position=position, orientation=orientation)
    if mesh is not None and path is not None:
        raise ValueError("Only one of mesh or path can be specified")
    if mesh is not None:
        self._original_mesh = mesh
    if path is not None:
        self._original_mesh = o3d.io.read_triangle_mesh(path)
    if center:
        self._original_mesh.translate([0, 0, 0], relative=False)

    self.update_scale(scale, rel_scale)

get_transformed_o3d_geometry()

Get o3d mesh at current pose.

Source code in sdfest/estimation/synthetic.py
132
133
134
135
136
137
138
139
def get_transformed_o3d_geometry(self) -> o3d.geometry.TriangleMesh:
    """Get o3d mesh at current pose."""
    transformed_mesh = o3d.geometry.TriangleMesh(self._scaled_mesh)
    R = Rotation.from_quat(self.orientation).as_matrix()
    transformed_mesh.rotate(R, center=np.array([0, 0, 0]))
    transformed_mesh.translate(self.position)
    transformed_mesh.compute_vertex_normals()
    return transformed_mesh

load_mesh_from_file(path, scale=1, rel_scale=False)

Load mesh from file.

Parameters:

Name Type Description Default
path str

Path of the obj file.

required
scale float

See Mesh.update_scale.

1
rel_scale bool

See Mesh.update_scale.

False
Source code in sdfest/estimation/synthetic.py
77
78
79
80
81
82
83
84
85
86
87
88
def load_mesh_from_file(
    self, path: str, scale: float = 1, rel_scale: bool = False
) -> None:
    """Load mesh from file.

    Args:
        path: Path of the obj file.
        scale: See Mesh.update_scale.
        rel_scale: See Mesh.update_scale.
    """
    self._original_mesh = o3d.io.read_triangle_mesh(path)
    self.update_scale(scale, rel_scale)

update_scale(scale=1, rel_scale=False)

Update relative or absolute scale of mesh.

Absolute scale represents half the largest extent in x, y, or z direction. Relative scale represents the scale factor from original mesh.

Parameters:

Name Type Description Default
scale float

The desired absolute or relative scale of the object.

1
rel_scale bool

If true, scale will be relative to original mesh. Otherwise, scale will be the resulting absolute scale.

False
Source code in sdfest/estimation/synthetic.py
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
def update_scale(self, scale: float = 1, rel_scale: bool = False) -> None:
    """Update relative or absolute scale of mesh.

    Absolute scale represents half the largest extent in x, y, or z direction.
    Relative scale represents the scale factor from original mesh.

    Args:
        scale: The desired absolute or relative scale of the object.
        rel_scale:
            If true, scale will be relative to original mesh.
            Otherwise, scale will be the resulting absolute scale.
    """
    # self._scale will always be absolute scale
    if rel_scale:
        # copy construct mesh
        self._scaled_mesh = o3d.geometry.TriangleMesh(self._original_mesh)

        original_scale = self._get_original_scale()
        self._scaled_mesh.scale(scale, [0, 0, 0])

        self._scale = original_scale * scale
    else:
        # copy construct mesh
        self._scaled_mesh = o3d.geometry.TriangleMesh(self._original_mesh)

        # scale original mesh s.t. output has the provided scale
        original_scale = self._get_original_scale()
        scale_factor = scale / original_scale
        self._scaled_mesh.scale(scale_factor, [0, 0, 0])

        self._scale = scale

Object

Bases: ABC

Generic positioned object representation.

Each object has a 6-DOF pose, stored as a 3D translation vector and a normalized quaternion representing its orientation.

Source code in sdfest/estimation/synthetic.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Object(ABC):
    """Generic positioned object representation.

    Each object has a 6-DOF pose, stored as a 3D translation vector and
    a normalized quaternion representing its orientation.
    """

    def __init__(self, position=None, orientation=None):
        """Initialize object position and orientation."""
        if position is None:
            position = np.array([0, 0, 0])
        if orientation is None:
            orientation = np.array([0, 0, 0, 1])
        self.position = position
        self.orientation = orientation

__init__(position=None, orientation=None)

Initialize object position and orientation.

Source code in sdfest/estimation/synthetic.py
21
22
23
24
25
26
27
28
def __init__(self, position=None, orientation=None):
    """Initialize object position and orientation."""
    if position is None:
        position = np.array([0, 0, 0])
    if orientation is None:
        orientation = np.array([0, 0, 0, 1])
    self.position = position
    self.orientation = orientation

draw_depth_geometry(obj, camera)

Render an object given a camera.

Source code in sdfest/estimation/synthetic.py
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
def draw_depth_geometry(obj: Object, camera: Camera):
    """Render an object given a camera."""
    # see http://www.open3d.org/docs/latest/tutorial/visualization/customized_visualization.html
    # rend = o3d.visualization.rendering.OffscreenRenderer()
    # img = rend.render_to_image()

    # Create visualizer
    vis = o3d.visualization.Visualizer()
    vis.create_window(width=camera.width, height=camera.height, visible=False)

    # Add mesh in correct position
    vis.add_geometry(obj.get_transformed_o3d_geometry(), True)

    options = vis.get_render_option()
    options.mesh_show_back_face = True

    # Set camera at fixed position (i.e., at 0,0,0, looking along z axis)
    view_control = vis.get_view_control()
    o3d_cam = camera.get_o3d_pinhole_camera_parameters()
    o3d_cam.extrinsic = np.array(
        [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]
    )
    view_control.convert_from_pinhole_camera_parameters(o3d_cam, True)

    # Generate the depth image
    vis.poll_events()
    vis.update_renderer()
    depth = np.asarray(vis.capture_depth_float_buffer(do_render=True))

    return depth