def jacobian(self, points): r""" Computes the Jacobian of the transform w.r.t the parameters. The Jacobian generated (for 2D) is of the form:: x -y 1 0 y x 0 1 This maintains a parameter order of:: W(x;p) = [1 + a -b ] [x] + tx [b 1 + a] [y] + ty Parameters ---------- points : (N, D) ndarray The points to calculate the jacobian over Returns ------- dW_dp : (N, P, D) ndarray A (``n_points``, ``n_params``, ``n_dims``) array representing the Jacobian of the transform. Raises ------ DimensionalityError ``points.n_dims != self.n_dims`` or transform is not 2D """ n_points, points_n_dim = points.shape if points_n_dim != self.n_dims: raise DimensionalityError('Trying to sample jacobian in incorrect ' 'dimensions (transform is {0}D, ' 'sampling at {1}D)'.format( self.n_dims, points_n_dim)) elif self.n_dims != 2: # TODO: implement 3D Jacobian raise DimensionalityError("Only the Jacobian of a 2D similarity " "transform is currently supported.") # prealloc the jacobian jac = np.zeros((n_points, self.n_parameters, self.n_dims)) ones = np.ones_like(points) # Build a mask and apply it to the points to build the jacobian # Do this for each parameter - [a, b, tx, ty] respectively self._apply_jacobian_mask(jac, np.array([1, 1]), 0, points) self._apply_jacobian_mask(jac, np.array([-1, 1]), 1, points[:, ::-1]) self._apply_jacobian_mask(jac, np.array([1, 0]), 2, ones) self._apply_jacobian_mask(jac, np.array([0, 1]), 3, ones) return jac
def render(self, **kwargs): r""" Select the correct type of trimesh viewer for the given trimesh dimensionality. Parameters ---------- kwargs : dict Passed through to trimesh viewer. Returns ------- viewer : :class:`Renderer` The rendering object. Raises ------ DimensionalityError Only 2D and 3D viewers are supported. """ if self.points.shape[1] == 2: return TriMeshViewer2d(self.figure_id, self.new_figure, self.points, self.trilist).render(**kwargs) elif self.points.shape[1] == 3: return TriMeshViewer3d(self.figure_id, self.new_figure, self.points, self.trilist).render(**kwargs) else: raise DimensionalityError("Only 2D and 3D TriMeshes are " "currently supported")
def render(self, **kwargs): r""" Select the correct type of image viewer for the given image dimensionality. Parameters ---------- kwargs : dict Passed through to image viewer. Returns ------- viewer : :class:`Renderer` The rendering object. Raises ------ DimensionalityError Only 2D images are supported. """ if self.dimensions == 2: if self.use_subplots: return ImageSubplotsViewer2d(self.figure_id, self.new_figure, self.pixels).render(**kwargs) else: return ImageViewer2d(self.figure_id, self.new_figure, self.pixels).render(**kwargs) else: raise DimensionalityError("Only 2D images are currently supported")
def from_vector_inplace(self, p): r""" Returns an instance of the transform from the given parameters, expected to be in Fortran ordering. Supports rebuilding from 2D parameter sets. 2D Similarity: 4 parameters:: [a, b, tx, ty] Parameters ---------- p : (P,) ndarray The array of parameters. Raises ------ DimensionalityError, NotImplementedError Only 2D transforms are supported. """ if p.shape[0] == 4: homog = np.eye(3) homog[0, 0] += p[0] homog[1, 1] += p[0] homog[0, 1] = -p[1] homog[1, 0] = p[1] homog[:2, 2] = p[2:] Affine.set_h_matrix(self, homog) # use the Affine setter cheekily elif p.shape[0] == 7: raise NotImplementedError("3D similarity transforms cannot be " "vectorized yet.") else: raise DimensionalityError("Only 2D and 3D Similarity transforms " "are currently supported.")
def _view(self, figure_id=None, new_figure=False, textured=True, **kwargs): r""" Visualize the :class:`TexturedTriMesh`. Only 3D objects are currently supported. Parameters ---------- textured : bool, optional If ``True``, render the texture. Default: ``True`` Returns ------- viewer : :class:`menpo.visualize.base.Renderer` The viewer object. Raises ------ DimensionalityError If ``self.n_dims != 3``. """ if textured: if self.n_dims == 3: return TexturedTriMeshViewer3d( figure_id, new_figure, self.points, self.trilist, self.texture, self.tcoords.points).render(**kwargs) else: raise DimensionalityError("Only viewing of 3D textured meshes" "is currently supported.") else: return super(TexturedTriMesh, self)._view(figure_id=figure_id, new_figure=new_figure, **kwargs)
def render(self, **kwargs): if self.dimensions == 2: if self.use_subplots: MultiImageSubplotsViewer2d(self.figure_id, self.new_figure, self.pixels_list).render(**kwargs) else: return MultiImageViewer2d(self.figure_id, self.new_figure, self.pixels_list).render(**kwargs) else: raise DimensionalityError("Only 2D images are currently supported")
def set_rotation_matrix(self, value): shape = value.shape if len(shape) != 2 and shape[0] != shape[1]: raise ValueError("You need to provide a square rotation matrix") # The update better be the same size elif self.n_dims != shape[0]: raise DimensionalityError("Trying to update the rotation " "matrix to a different dimension") # TODO actually check I am a valid rotation # TODO slightly dodgey here accessing _h_matrix self._h_matrix[:-1, :-1] = value
def jacobian(self, points): r""" Computes the Jacobian of the transform w.r.t the parameters. This is constant for affine transforms. The Jacobian generated (for 2D) is of the form:: x 0 y 0 1 0 0 x 0 y 0 1 This maintains a parameter order of:: W(x;p) = [1 + p1 p3 p5] [x] [p2 1 + p4 p6] [y] [1] Parameters ---------- points : (N, D) ndarray The set of points to calculate the jacobian for. Returns ------- dW_dp : (N, P, D) ndarray A (``n_points``, ``n_params``, ``n_dims``) array representing the Jacobian of the transform. """ n_points, points_n_dim = points.shape if points_n_dim != self.n_dims: raise DimensionalityError( "Trying to sample jacobian in incorrect dimensions " "(transform is {0}D, sampling at {1}D)".format( self.n_dims, points_n_dim)) # prealloc the jacobian jac = np.zeros((n_points, self.n_parameters, self.n_dims)) # a mask that we can apply at each iteration dim_mask = np.eye(self.n_dims, dtype=np.bool) for i, s in enumerate(range(0, self.n_dims * self.n_dims, self.n_dims)): # i is current axis # s is slicing offset # make a mask for a single points jacobian full_mask = np.zeros((self.n_parameters, self.n_dims), dtype=bool) # fill the mask in for the ith axis full_mask[slice(s, s + self.n_dims)] = dim_mask # assign the ith axis points to this mask, broadcasting over all # points jac[:, full_mask] = points[:, i][..., None] # finally, just repeat the same but for the ones at the end full_mask = np.zeros((self.n_parameters, self.n_dims), dtype=bool) full_mask[slice(s + self.n_dims, s + 2 * self.n_dims)] = dim_mask jac[:, full_mask] = 1 return jac
def set_h_matrix(self, value): r""" Updates the h_matrix, performing sanity checks. The Affine h_matrix is limited in what values are allowed. Account for them here. """ shape = value.shape if len(shape) != 2 and shape[0] != shape[1]: raise ValueError("You need to provide a square homogeneous matrix") if self.h_matrix is not None: # already have a matrix set! The update better be the same size if self.n_dims != shape[0] - 1: raise DimensionalityError("Trying to update the homogeneous " "matrix to a different dimension") if shape[0] - 1 not in [2, 3]: raise DimensionalityError("Affine Transforms can only be 2D or 3D") if not (np.allclose(value[-1, :-1], 0) and np.allclose(value[-1, -1], 1)): raise ValueError("Bottom row must be [0 0 0 1] or [0, 0, 1]") self._h_matrix = value.copy()
def as_vector(self): r""" Return the parameters of the transform as a 1D array. These parameters are parametrised as deltas from the identity warp. The parameters are output in the order ``[a, b, tx, ty]``, given that ``a = k cos(theta) - 1`` and ``b = k sin(theta)`` where ``k`` is a uniform scale and ``theta`` is a clockwise rotation in radians. **2D** ========= =========================================== parameter definition ========= =========================================== a ``a = k cos(theta) - 1`` b ``b = k sin(theta)`` tx Translation in ``x`` ty Translation in ``y`` ========= =========================================== .. note:: Only 2D transforms are currently supported. Returns ------- params : (P,) ndarray The values that parameterise the transform. Raises ------ DimensionalityError, NotImplementedError If the transform is not 2D """ n_dims = self.n_dims if n_dims == 2: params = self.h_matrix - np.eye(n_dims + 1) # Pick off a, b, tx, ty params = params[:n_dims, :].flatten(order='F') # Pick out a, b, tx, ty return params[[0, 1, 4, 5]] elif n_dims == 3: raise NotImplementedError("3D similarity transforms cannot be " "vectorized yet.") else: raise DimensionalityError("Only 2D and 3D Similarity transforms " "are currently supported.")
def face_normals(self): r""" Normal at each face. :type: (``n_tris``, 3) ndarray Compute the face normals from the current set of points and triangle list. Only valid for 3D dimensional meshes. Raises ------ DimensionalityError If mesh is not 3D """ if self.n_dims != 3: raise DimensionalityError("Normals are only valid for 3D meshes") return compute_normals(self.points, self.trilist)[1]
def __setitem__(self, group_label, value): """ Sets a new landmark group for the given label. This can be set using an existing landmark group, or using a PointCloud. Existing landmark groups will have their target reset. If a PointCloud is provided then all landmarks belong to a single label `all`. Parameters ---------- group_label : String Label of new group. value : LandmarkGroup or PointCloud The new landmark group to set. Raises ------ DimensionalityError If the landmarks and the shape are not of the same dimensionality. """ from menpo.shape import PointCloud # firstly, make sure the dim is correct if value.n_dims != self._target.n_dims: from menpo.exception import DimensionalityError raise DimensionalityError("Trying to set {}D landmarks on a " "{}D shape".format( value.n_dims, self._target.n_dims)) if isinstance(value, PointCloud): lmark_group = LandmarkGroup( None, None, value, {'all': np.ones(value.n_points, dtype=np.bool)}) elif isinstance(value, LandmarkGroup): lmark_group = copy.deepcopy(value) else: raise ValueError('Valid types are PointCloud or LandmarkGroup') self._landmark_groups[group_label] = lmark_group self._landmark_groups[group_label]._group_label = group_label self._landmark_groups[group_label]._target = self._target
def render(self, **kwargs): r""" Select the correct type of landmark viewer for the given parent shape. Parameters ---------- kwargs : dict Passed through to landmark viewer. Returns ------- viewer : :class:`Renderer` The rendering object. Raises ------ DimensionalityError Only 2D and 3D viewers are supported. """ if self.pointcloud.n_dims == 2: from menpo.image.base import Image if isinstance(self.target, Image): return LandmarkViewer2dImage( self.figure_id, self.new_figure, self.group_label, self.pointcloud, self.labels_to_masks).render(**kwargs) else: return LandmarkViewer2d(self.figure_id, self.new_figure, self.group_label, self.pointcloud, self.labels_to_masks).render(**kwargs) elif self.pointcloud.n_dims == 3: return LandmarkViewer3d(self.figure_id, self.new_figure, self.group_label, self.pointcloud, self.labels_to_masks).render(**kwargs) else: raise DimensionalityError("Only 2D and 3D landmarks are " "currently supported")