"""Conversions to and from Numpy objects; makes numerical computations much
more convenient.
"""
import numpy as np
from klampt.math import so3,se3
from ..model import types
SUPPORTED_TYPES = set(['Vector3','Point','Matrix3','Rotation','RigidTransform',
'Config','Configs','Trajectory',
'TriangleMesh','PointCloud','ImplicitSurface','OccupancyGrid','Geometry3D' ])
"""set of supported types for numpy I/O"""
[docs]
def to_numpy(obj,type='auto'):
"""Converts a Klamp't object to a numpy array or multiple numpy arrays.
Supports:
* lists and tuples
* RigidTransform: returned as 4x4 homogeneous coordinate transform
* Matrix3, Rotation: returned as 3x3 matrix. Can't be determined
with 'auto', need to specify type='Matrix3' or 'Rotation'.
* Configs
* Trajectory: returns a pair (times,milestones)
* TriangleMesh: returns a pair (verts,indices)
* PointCloud: returns a n x (3+k) array, where k is the # of properties
* ImplicitSurface , OccupancyGrid: returns a triple (bmin,bmax,array)
* Geometry3D: returns a pair (T,geomdata)
If you want to get a transformed point cloud or mesh, you can pass in a
Geometry3D as the obj, and its geometry data type as the type.
"""
global SUPPORTED_TYPES
if type == 'auto':
otype = types.object_to_type(obj,SUPPORTED_TYPES)
if otype is None and type=='auto':
raise ValueError('obj is not a supported type: '+', '.join(otype))
type = otype
if type not in SUPPORTED_TYPES:
raise ValueError(type+' is not a supported type')
if type == 'RigidTransform':
return np.array(se3.homogeneous(obj))
elif type == 'Rotation' or type == 'Matrix3':
return np.array(so3.matrix(obj))
elif type == 'Trajectory':
return np.array(obj.times),np.array(obj.milestones)
elif type == 'TriangleMesh':
from klampt import Geometry3D
if isinstance(obj,Geometry3D):
tm = obj.getTriangleMesh()
res = to_numpy(tm,type)
res = (res[0],res[1].copy())
R = to_numpy(obj.getCurrentTransform()[0],'Matrix3')
t = to_numpy(obj.getCurrentTransform()[1],'Vector3')
return (np.dot(R,res[0])+t,res[1])
return (obj.vertices,obj.indices)
elif type == 'PointCloud':
from klampt import Geometry3D
if isinstance(obj,Geometry3D):
pc = obj.getPointCloud()
res = to_numpy(pc,type)
R = to_numpy(obj.getCurrentTransform()[0],'Matrix3')
t = to_numpy(obj.getCurrentTransform()[1],'Vector3')
res[:,:3] = np.dot(R,res[:,:3])+t
return res
points = obj.points
properties = obj.properties
if len(properties) == 0:
return points
return np.hstack((points,properties))
elif type in ['ImplicitSurface','VoluemGrid','OccupancyGrid']:
bmin = np.array(obj.bmin)
bmax = np.array(obj.bmax)
values = obj.values
return (bmin,bmax,values)
elif type == 'Geometry3D':
if obj.type() == 'PointCloud':
pc = obj.getPointCloud()
pcdata = to_numpy(pc,obj.type())
return to_numpy(obj.getCurrentTransform(),'RigidTransform'),pcdata
elif obj.type() == 'TriangleMesh':
mesh = obj.getTriangleMesh()
meshdata = to_numpy(mesh,obj.type())
meshdata = (meshdata[0].copy(),meshdata[1].copy())
return to_numpy(obj.getCurrentTransform(),'RigidTransform'),meshdata
elif obj.type() == 'ImplicitSurface':
grid = obj.getImplicitSurface()
griddata = to_numpy(grid,obj.type())
griddata = (griddata[0],griddata[1],griddata[2].copy())
return to_numpy(obj.getCurrentTransform(),'RigidTransform'),griddata
elif obj.type() == 'OccupancyGrid':
grid = obj.getOccupancyGrid()
griddata = to_numpy(grid,obj.type())
griddata = (griddata[0],griddata[1],griddata[2].copy())
return to_numpy(obj.getCurrentTransform(),'RigidTransform'),griddata
elif obj.type() == 'Group':
arrays = []
for i in range(obj.numElements()):
arrays.append(to_numpy(obj.getElement(i),'Geometry3D'))
return to_numpy(obj.getCurrentTransform(),'RigidTransform'),arrays
else:
raise ValueError("Can't convert Geometry3D of type "+obj.type()+" yet")
else:
return np.array(obj)
[docs]
def from_numpy(obj,type='auto',template=None):
"""Converts a numpy array or multiple numpy arrays to a Klamp't object.
Supports:
* lists and tuples
* RigidTransform: accepts a 4x4 homogeneous coordinate transform
* Matrix3, Rotation: accepts a 3x3 matrix.
* Configs
* Trajectory: accepts a pair (times,milestones)
* TriangleMesh: accepts a pair (verts,indices)
* PointCloud: accepts a n x (3+k) array, where k is the # of properties
* ImplicitSurface, OccupancyGrid: accepts a triple (bmin,bmax,array)
* Geometry3D: accepts a pair (T,geomdata)
"""
global SUPPORTED_TYPES
if type == 'auto' and template is not None:
otype = types.object_to_types(template)
if isinstance(otype,(list,tuple)):
for t in otype:
if t in SUPPORTED_TYPES:
type = t
break
if type == 'auto':
raise ValueError('obj is not a supported type: '+', '.join(otype))
else:
type = otype
if type == 'auto':
if isinstance(obj,(tuple,list)):
if all(isinstance(v,np.ndarray) for v in obj):
if len(obj)==2:
if len(obj[0].shape) == 1 and len(obj[1].shape) == 2:
type = 'Trajectory'
elif len(obj[0].shape) == 2 and len(obj[1].shape) == 2 and obj[0].shape[1] == 3 and obj[1].shape[1] == 3:
type = 'TriangleMesh'
if len(obj)==3:
if obj[0].shape == (3,) and obj[1].shape == (3,):
type = 'ImplicitSurface'
if type == 'auto':
raise ValueError("Can't auto-detect type of list of shapes"+', '.join(str(v.shape) for v in obj))
else:
if isinstance(obj[0],np.ndarray) and obj[0].shape == (4,4):
type = 'Geometry3D'
else:
raise ValueError("Can't auto-detect type of irregular list")
else:
assert isinstance(obj,np.ndarray),"Can only convert lists, tuples, and arrays from numpy"
if obj.shape == (3,3):
type = 'Matrix3'
elif obj.shape == (4,4):
type = 'RigidTransform'
elif len(obj.shape) == 1:
type = 'Config'
else:
raise ValueError("Can't auto-detect type of matrix of shape "+str(obj.shape))
if type not in SUPPORTED_TYPES:
raise ValueError(type+' is not a supported type')
if type == 'RigidTransform':
return se3.from_homogeneous(obj)
elif type == 'Rotation' or type == 'Matrix3':
return so3.from_matrix(obj)
elif type == 'Trajectory':
assert len(obj)==2,"Trajectory format is (times,milestones)"
times = obj[0].tolist()
milestones = obj[1].tolist()
if template is not None:
return template.constructor()(times,milestones)
from klampt.model.trajectory import Trajectory
return Trajectory(times,milestones)
elif type == 'TriangleMesh':
from klampt import TriangleMesh
res = TriangleMesh()
res.setVertices(obj[0].astype(float))
res.setIndices(obj[1].astype(np.int32))
return res
elif type == 'PointCloud':
from klampt import PointCloud
assert len(obj.shape) == 2,"PointCloud array must be a 2D array"
assert obj.shape[1] >= 3,"PointCloud array must have at least 3 values"
#points = obj[:,:3]
#properties = obj[:,3:]
numproperties = obj.shape[1]-3
res = PointCloud()
#res.setPoints(points)
res.setPointsAndProperties(obj.astype(float))
if template is not None:
if template.numProperties() != numproperties:
raise ValueError("Template object doesn't have the same properties as the numpy object")
for i in range(template.numProperties()):
res.setPropertyName(i,template.getPropertyName(i))
else:
#properties are already indicated with setPointsAndProperties
pass
return res
elif type in ['ImplicitSurface','VolumeGrid','OccupancyGrid']:
from klampt import ImplicitSurface,OccupancyGrid
assert len(obj) == 3,"ImplicitSurface format is (bmin,bmax,values)"
assert len(obj[2].shape) == 3,"ImplicitSurface values must be a 3D array"
bmin = obj[0]
bmax = obj[1]
values = obj[2]
if type == 'OccupancyGrid':
res = OccupancyGrid()
else:
res = ImplicitSurface()
res.bbox.append(bmin[0])
res.bbox.append(bmin[1])
res.bbox.append(bmin[2])
res.bbox.append(bmax[0])
res.bbox.append(bmax[1])
res.bbox.append(bmax[2])
res.dims.append(values.shape[0])
res.dims.append(values.shape[1])
res.dims.append(values.shape[2])
res.setValues(values.astype(float))
return res
elif type == 'Group':
from klampt import Geometry3D
res = Geometry3D()
assert isinstance(obj,(list,tuple)),"Group format is a list or tuple of Geometry3D's"
for i in range(len(obj)):
res.setElement(i,from_numpy(obj[i],'Geometry3D'))
return res
elif type == 'Geometry3D':
from klampt import Geometry3D
if not isinstance(obj,(list,tuple)) or len(obj) != 2:
raise ValueError("Geometry3D must be a (transform,geometry) tuple")
T = from_numpy(obj[0],'RigidTransform')
geomdata = obj[1]
subtype = None
if template is not None:
subtype = template.type()
if subtype == 'PointCloud':
g = Geometry3D(from_numpy(geomdata,subtype,template.getPointCloud()))
else:
g = Geometry3D(from_numpy(geomdata,subtype))
g.setCurrentTransform(*T)
return g
subtype = 'Group'
if all(isinstance(v,np.ndarray) for v in geomdata):
if len(geomdata)==2:
if len(geomdata[0].shape) == 1 and len(geomdata[1].shape) == 2:
subtype = 'Trajectory'
elif len(geomdata[0].shape) == 2 and len(geomdata[1].shape) == 2 and geomdata[0].shape[1] == 3 and geomdata[1].shape[1] == 3:
subtype = 'TriangleMesh'
if len(geomdata)==3:
if geomdata[0].shape == (3,) and geomdata[1].shape == (3,):
subtype = 'ImplicitSurface'
g = Geometry3D(from_numpy(geomdata,subtype))
g.setCurrentTransform(*T)
return g
else:
return obj.flatten()