Shortcuts

Source code for pypose.metric.ape_rpe

import torch, warnings
from ..function.geometry import svdstf
from ..lietensor import mat2SO3, SE3, Sim3, identity_Sim3
from ..lietensor.lietensor import SE3Type, Sim3Type


class StampedSE3(object):
    def __init__(self, timestamps=None, poses_SE3=None, dtype=torch.float64):
        r"""
        Internal class for represent the trajectory with timestamps.

        Args:
            timestamps (array-like of ``float`` or ``None``):
                The timestamps of the trajectory.
                Must have same length with poses.
                For example, `torch.tensor(...)` or `None`.
            poses_SE3 (array-like of ``SE3``):
                The trajectory poses. Must be SE3.
                For example, `pypose.SE3(torch.rand(10, 7))`.
            dtype (`torch.float64` or higher):
                The data type for poses to calculate.
                Defaults to torch.float64.

        Returns:
            None.

        Error:
            1. ValueError: The poses have shape problem.
            2. ValueError: The timestamps have shape problem.
        """
        assert (poses_SE3 is not None), {"The pose must be not None"}
        assert (poses_SE3.numel() != 0),  {"The pose must be not empty"}
        assert len(poses_SE3.lshape) == 1, {"Only one trajectory estimation is support,\
                The shape of the trajectory must be 2"}
        self.poses = poses_SE3.to(dtype)

        if timestamps is None:
            self.timestamps = torch.arange(poses_SE3.lshape[0], dtype=torch.float64,
                                           device=poses_SE3.device)
        else:
            self.timestamps = timestamps.type(torch.float64).to(poses_SE3.device)

        assert len(self.timestamps.shape) == 1, {"The timestamp should be one array"}
        assert self.timestamps.shape[0] == self.poses.lshape[0], \
            {"timestamps and poses must have same length"}
        assert torch.all(torch.sort(self.timestamps)[0] == self.timestamps), \
            {"timestamps must be accending"}

    def __getitem__(self, index):
        return StampedSE3(self.timestamps[index], self.poses[index], self.poses.dtype)

    def reduce_to_ids(self, ids) -> None:
        if isinstance(ids, torch.Tensor):
            ids = ids.long().tolist()
        self.timestamps = self.timestamps[ids]
        self.poses = self.poses[ids]

    def align(self, trans):
        if isinstance(trans.ltype, SE3Type):
            self.poses = trans @ self.poses

        elif isinstance(trans.ltype, Sim3Type):
            ones = torch.ones_like(self.poses.data[..., 0:1])
            poses_sim = Sim3(torch.cat((self.poses.data, ones), dim=-1))
            traned_pose = trans @ poses_sim
            self.poses = SE3(traned_pose.data[..., 0:7])

    def translation(self):
        return self.poses.translation()

    def rotation(self):
        return self.poses.rotation()

    def type(self, dtype=torch.float64):
        self.poses = self.poses.to(dtype)

    def cuda(self):
        self.poses = self.poses.cuda()

    def cpu(self):
        self.poses = self.poses.cpu()

    @property
    def num_poses(self):
        return self.poses.shape[0]

    @property
    def first_pose(self):
        return self.poses[0]

    @property
    def dtype(self):
        return self.poses.dtype

    @property
    def device(self):
        return self.poses.device

    @property
    def accumulated_distances(self) -> torch.tensor:
        trans = self.translation()
        zeros = torch.zeros(1, dtype=trans.dtype)
        norm = torch.linalg.norm(trans[:-1] - trans[1:], dim=-1, dtype=trans.dtype)
        return torch.cat((zeros, torch.cumsum(norm, dim=0)))


def matching_time_indices(stamps_1, stamps_2, max_diff=0.01, offset_2=0.0):
    r"""
    Search for the best matching timestamps of two lists of timestamps.

    Args:
        stamps_1 (array-like of ``float``):
            First list of timestamps.
            For example, `torch.tensor(...)`.
        stamps_2 (array-like of ``float``):
            Second list of timestamps.
            For example, `torch.tensor(...)`.
        max_diff (``float``, optional):
            The maximum allowed absolute time difference (in seconds)
            for associating poses. Defaults to 0.01.
        offset_2 (``float``, optional):
            The aligned offset (in seconds) for the second timestamps.
            Defaults to 0.0.

    Returns:
        matching_indices_1: ``list[int]``
            Indices of timestamps_1 that match with timestamps_1.
        matching_indices_2: ``list[int]``
            Indices of timestamps_2 that match with timestamps_2.
    """
    stamps_2 += offset_2
    diff_mat = (stamps_1[..., None] - stamps_2[None]).abs()
    indices_1 = torch.arange(len(stamps_1), device=stamps_1.device)
    value, indices_2 = diff_mat.min(dim=-1)

    matching_indices_1 = indices_1[value < max_diff].tolist()
    matching_indices_2 = indices_2[value < max_diff].tolist()

    return matching_indices_1, matching_indices_2


def associate_traj(rtraj, etraj, max_diff=0.01, offset_2=0.0, threshold=0.3):
    r"""
    Associate two trajectories by matching their timestamps.

    Args:
        rtraj (``StampedSE3``):
            The trajectory for reference.
        etraj (``StampedSE3``):
            The trajectory for estimation.
        max_diff (``float``, optional):
            The maximum allowed absolute time difference (in seconds)
            for associating poses. Defaults to 0.01.
        offset_2 (``float``, optional):
            The aligned offset (in seconds) for the second timestamps.
            Defaults to 0.0.
        threshold (``float``, optional):
            The threshold for valid matching pairs. If the ratio of
            matching pairs is below this threshold, a warning is issued.
            Defaults to 0.3.

    Returns:
        etraj_aligned: ``StampedSE3``
            The aligned estimation trajectory.
        rtraj_aligned: ``StampedSE3``
            The aligned reference trajectory.

    Warning:
        The matched stamps are under the threshold.
    """
    snd_longer = len(etraj.timestamps) > len(rtraj.timestamps)
    traj_long = etraj if snd_longer else rtraj
    traj_short = rtraj if snd_longer else etraj
    max_pairs = len(traj_short.timestamps)

    matching_indices_short, matching_indices_long = matching_time_indices(
        traj_short.timestamps, traj_long.timestamps, max_diff,
        offset_2 if snd_longer else -offset_2)

    assert len(matching_indices_short) == len(matching_indices_long), \
        {r"matching_time_indices returned unequal number of indices"}

    num_matches = len(matching_indices_long)
    traj_short = traj_short[matching_indices_short]
    traj_long = traj_long[matching_indices_long]

    rtraj_aligned = traj_short if snd_longer else traj_long
    etraj_aligned = traj_long if snd_longer else traj_short

    assert num_matches != 0, \
        {f"found no matching timestamps between estimation and reference with max time "
            "diff {max_diff} (s) and time offset {offset_2} (s)"}

    if num_matches < threshold * max_pairs:
        warnings.warn("Alert !!!!!!!!!!!!!!!!!!!!!!! \
                       The estimated trajectory has not enough \
                       timestamps within the GT timestamps. \
                       May be not be enough for aligned and not accurate results.",
                      category=Warning, stacklevel=2)

    return rtraj_aligned, etraj_aligned


def compute_error(rtraj, etraj, output: str = 'translation', mtype: str = 'ape', otype:str = 'All'):
    r'''
    Get the error of the pose based on the output type.

    Args:
        rtraj (``StampedSE3``):
            The reference trajectory.
        etraj (``StampedSE3``):
            The estimated trajectory.
        output: (``str``, optional):
            The type of pose error.
            Including: 'translation', 'rotation', 'pose', 'rotation', 'radian', 'degree'.
        mtype: (``str``, optional):
            The type of metrics.
            Including: 'ape', 'rpe'.

    Returns:
        error: The all errors of the pose.

    Error:
        ValueError: The output type is not supported.

    Remarks:
        if mtype == 'ape'
            'translation': || t_{e} - t_{r} ||_2
            'rotation': || R_{e}^T * R_{r} - I_3 ||_2
            'pose':  || T_{e}^{-1} * T_{r} - I_4 ||_2
            'radian': :math:`|| \mathrm{Log}(R_{e}^T * R_{r}) ||_2`
            'degree': :math:`\mathrm{Degree}(|| \mathrm{Log}(R_{e}^T * R_{r}) ||_2)`
        if mtype == 'rpe':
            'translation': :math:`|| -{R_{r}^{rel}}^T * t_{r}^{rel} + {R_{e}^{rel}}^T * t_{e}^{rel} ||_2`
            'rotation': :math:`|| {R_{r}^{rel}}^T * R_{e}^{rel} - I_3 ||_2`
            'pose': :math:`|| {T_{r}^{rel}}^{-1} * T_{e}^{rel} - I_4 ||_2`
            'radian': :math:`|| \mathrm{Log}({R_{r}^{rel}}^T * R_{e}^{rel}) ||_2)`
            'degree': :math:`\mathrm{Degree}(|| \mathrm{Log}({R_{r}^{rel}}^T * R_{e}^{rel}) ||_2))`
    '''
    if mtype == 'ape':
        if output == 'translation':
            E = etraj.translation() - rtraj.translation()
        else:
            E = (etraj.poses.Inv() @ rtraj.poses).matrix()
    elif mtype == 'rpe':
        E = (rtraj.poses.Inv() @ etraj.poses).matrix()

    if output == 'translation':
        if mtype == 'ape':
            error = torch.linalg.norm(E, dim=-1)
        elif mtype == 'rpe':
            error = E[..., :3, 3].norm(dim=-1)
    elif output == 'rotation':
        I = torch.eye(3, device=E.device, dtype=E.dtype).expand_as(E[:,:3,:3])
        error = torch.linalg.norm((E[:,:3,:3] - I), dim=(-2, -1))
    elif output == 'pose':
        I = torch.eye(4, device=E.device, dtype=E.dtype).expand_as(E)
        error = torch.linalg.norm((E - I), dim=(-2, -1))
    elif output == 'radian':
        error = mat2SO3(E[:,:3,:3], check=False).Log().norm(dim=-1)
    elif output == 'degree':
        error = mat2SO3(E[:,:3,:3], check=False).Log().norm(dim=-1).rad2deg()
    else:
        raise ValueError(f"Unknown output type: {output}")

    options = ['All', 'Max', 'Min', 'Mean', 'Median', 'RMSE', 'SSE', 'STD']
    if otype not in options:
        raise ValueError(f"Unknown output metric type, select one in {options}")
    results = {}
    if otype == 'Max' or 'All':
        results['Max'] = torch.max(error.abs())
    if otype == 'Min' or 'All':
        results['Min'] = torch.min(error.abs())
    if otype == 'Mean' or 'All':
        results['Mean'] = torch.mean(error.abs())
    if otype == 'Median' or 'All':
        results['Median'] = torch.median(error.abs())
    if otype == 'RMSE' or 'All':
        results['RMSE'] = torch.sqrt(torch.mean(torch.pow(error, 2)))
    if otype == 'SSE' or 'All':
        results['SSE'] = torch.sum(torch.pow(error, 2))
    if otype == 'STD' or 'All':
        results['STD']  = torch.std(error.abs())

    if otype == 'All':
        return results # return a dict
    else:
        return results[otype] # return a tensor


def pairs_by_frames(traj, delta, all=False):
    r'''
    Get index of pairs in the trajectory by its index distance.

    Args:
        traj (``StampedSE3``):
            The trajectory.
        delta (``float``):
            The interval step used to select the pair.
        all (``bool``, optional):
            If True, all associated pairs are used for evaluation.
            Defaults to False.

    Returns: list
        id_pairs: list of index pairs.
    '''
    traj_len = traj.num_poses
    delta = int(delta)
    assert delta >= 1, "delta must >= 1"
    if all:
        ids_1 = torch.arange(traj_len, device=traj.device, dtype=torch.long)
        ids_2 = ids_1 + delta
        id_pairs = (ids_1[ids_2<traj_len].tolist(),
                    ids_2[ids_2<traj_len].tolist())
    else:
        ids = torch.arange(0, traj_len, delta, device=traj.device, dtype=torch.long)
        id_pairs = (ids[:-1].tolist(), ids[1:].tolist())

    return id_pairs


def pairs_by_dist(traj, delta, tol=0.0, all=False):
    r'''
    Get index of pairs in the trajectory by its path distance.

    Args:
        traj (``StampedSE3``):
            The trajectory
        delta (``float``):
            The interval step used to select the pair.
        tol (``float``, optional):
            The absolute path tolerance for accepting or rejecting pairs in all mode.
            Defaults to 0.0.
        all (``bool``, optional):
            If True, all associated pairs are used for evaluation.
            Defaults to False.

    Returns: list
        id_pairs: list of index pairs.
    '''
    if all:
        idx_0 = []
        idx_1 = []
        distances = traj.accumulated_distances
        for i in range(distances.size(0) - 1):
            offset = i + 1
            distance_from_here = distances[offset:] - distances[i]
            candidate_index = torch.argmin(torch.abs(distance_from_here - delta)).item()
            if (torch.abs(distance_from_here[candidate_index] - delta) > tol):
                continue
            idx_0.append(i)
            idx_1.append(candidate_index + offset)
        id_pairs = (idx_0, idx_1)
    else:
        idx = []
        previous_translation = traj.translation()[0]
        current_path = 0.0
        for i, current_translation in enumerate(traj.translation()):
            current_path += float(torch.norm(current_translation - previous_translation))
            previous_translation = current_translation
            if current_path >= delta:
                idx.append(i)
                current_path = 0.0
        id_pairs = (idx[:-1], idx[1:])
    return id_pairs


def pair_id(traj, delta=1.0, associate: str='frame', rtol=0.1, all= False):
    r'''
    Get index of pairs with distance==delta from a trajectory.

    Args:
        traj (``StampedSE3``):
            The trajectory.
        delta (``float``, optional):
            The interval step used to select the pair. For example, when
            `associate='distance'`, it can represent the distance
            step in meters. Defaults to 1.0.
        associate (``str``, optional):
            The method used to associate pairs between the two trajectories.
            Supported options: 'frame', 'distance'. Defaults to 'frame'.
        rtol (``float``, optional):
            The relative tolerance for accepting or rejecting deltas.
            Defaults to 0.1.
        all (``bool``, optional):
            If True, all associated pairs are used for evaluation.
            Defaults to False.

    Returns:
        list: list of index pairs.
    '''
    if associate == 'frame':
        id_pairs = pairs_by_frames(traj, int(delta), all)
    elif associate == 'distance':
        id_pairs = pairs_by_dist(traj, delta, delta * rtol, all)
    else:
        raise ValueError(f"unsupported delta unit: {associate}")

    if len(id_pairs) == 0:
        raise ValueError(
            f"delta = {delta} ({associate}) produced an empty index list - "
            "try lower values or a less strict tolerance")

    return id_pairs


[docs]def ape(rstamp, rpose, estamp, epose, etype: str = "translation", diff: float = 0.01, offset: float = 0.0, align: bool = False, scale: bool = False, nposes: int = -1, origin: bool = False, thresh: float = 0.3, otype: str = 'All'): r''' Compute the Absolute Pose Error (APE) between two trajectories. Args: rstamp (array-like of ``float`` or ``None``): The timestamps of the reference trajectory. Must have the same length as `rpose`. For example, `torch.tensor([...])` or `None`. rpose (array-like of ``SE3``): The poses of the reference trajectory. Must have the same length as `rstamp`. For example, `pypose.SE3(torch.rand(10, 7))`. estamp (array-like of ``float`` or ``None``): The timestamps of the estimated trajectory. Must have the same length as `epose`. For example, `torch.tensor([...])` or `None`. epose (array-like of ``SE3``): The poses of the estimated trajectory. Must have the same length as `estamp`. For example, `pypose.SE3(torch.rand(10, 7))`. etype (``str``, optional): The type of pose error. Supported options include: ``'translation'``: :math:`|| t_{e} - t_{r} ||_2` ``'rotation'``: :math:`|| R_{e}^T * R_{r} - I_3 ||_2` ``'pose'``: :math:`|| T_{e}^{-1} * T_{r} - I_4 ||_2` ``'radian'``: :math:`|| \mathrm{Log}(R_{e}^T * R_{r}) ||_2` ``'degree'``: :math:`\mathrm{Degree}(|| \mathrm{Log}(R_{e}^T * R_{r}) ||_2)` Default: ``'translation'`` diff (``float``, optional): The maximum allowed absolute time difference (in seconds) for associating poses. Defaults to 0.01. offset (``float``, optional): The aligned offset (in seconds) for the second timestamps. Defaults to 0.0. align (``bool``, optional): If True, the trajectory is aligned using a scaled SVD method. Defaults to False. scale (``bool``, optional): If True, the scale is corrected using the SVD method. Defaults to False. nposes (``int``, optional): The number of poses to use for alignment. If -1, all poses are used. Defaults to -1. origin (``bool``, optional): If True, the trajectory is aligned by the first pose. Defaults to False. thresh (``float``, optional): The threshold for valid matching pairs. If the ratio of matching pairs is below this threshold, a warning is issued. Defaults to 0.3. otype (``str``, optional): The output type for the metric. Supported options include: ``'All'``: All metrics will be computed and returned. ``'Max'``: The Max error is computed and returned. ``'Min'``: The Min error is computed and returned. ``'Mean'``: The Mean error is computed and returned. ``'Median'``: The Median error is computed and returned. ``'RMSE'``: The root mean square error (RMSE) is computed and returned. ``'SSE'``: The sum of square error (SSE) is computed and returned. ``'STD'``: The standard deviation (STD) is computed and returned. Defaults to ``'All'``. Returns: ``dict`` or ``Tensor``: The computed statistics of the APE (Absolute Pose Error). The return is a ``dict`` if ``otype`` is not ``'all'``, otherwise a ``Tensor``. Examples: >>> import torch >>> import pypose as pp >>> rstamp = torch.tensor([1311868163.8696999550, 1311868163.8731000423, ... 1311868163.8763999939]) >>> rpose = pp.SE3([[-0.1357000023, -1.4217000008, 1.4764000177, ... 0.6452999711, -0.5497999787, 0.3362999856, -0.4101000130], ... [-0.1357000023, -1.4218000174, 1.4764000177, ... 0.6453999877, -0.5497000217, 0.3361000121, -0.4101999998], ... [-0.1358000040, -1.4219000340, 1.4764000177, ... 0.6455000043, -0.5498999953, 0.3357999921, -0.4101000130]]) >>> estamp = torch.tensor([1311868164.3631811142, 1311868164.3990259171, ... 1311868164.4309399128]) >>> epose = pp.SE3([[0.0000000000, 0.0000000000, 0.0000000000, ... 0.0000000000, 0.0000000000, 0.0000000000, 1.0000000000], ... [-0.0005019300, 0.0010138600, -0.0020097860, ... -0.0020761820,-0.0010706080, -0.0007627490, 0.9999969602], ... [0.0004298200, 0.0019603260, -0.0048985220, ... -0.0043526068,-0.0036625920, -0.0023494449, 0.9999810457]]) >>> pp.metric.ape(rstamp, rpose, estamp, epose) {'Max': tensor(2.0590, dtype=torch.float64), 'Min': tensor(2.0541, dtype=torch.float64), 'Mean': tensor(2.0565, dtype=torch.float64), 'Median': tensor(2.0562, dtype=torch.float64), 'RMSE': tensor(2.0565, dtype=torch.float64), 'SSE': tensor(12.6871, dtype=torch.float64), 'STD': tensor(0.0025, dtype=torch.float64)} >>> pp.metric.ape(rstamp, rpose, estamp, epose, otype='Mean') tensor(2.0565, dtype=torch.float64) ''' rtraj, etraj = StampedSE3(rstamp, rpose), StampedSE3(estamp, epose) rtraj, etraj = associate_traj(rtraj, etraj, diff, offset, thresh) trans_mat = identity_Sim3(1, dtype=etraj.dtype, device=etraj.device) if align or scale: nposes = etraj.num_poses if nposes == -1 else nposes est_trans = etraj.translation()[..., :nposes] ref_trans = rtraj.translation()[..., :nposes] trans_mat = svdstf(est_trans, ref_trans, scale) elif origin: trans_mat[..., :7] = (rtraj.first_pose @ etraj.first_pose.Inv()).data etraj.align(trans_mat) return compute_error(rtraj, etraj, etype, mtype = 'ape', otype=otype)
[docs]def rpe(rstamp, rpose, estamp, epose, etype: str = "translation", diff: float = 0.01, offset: float = 0.0, align: bool = False, scale: bool = False, nposes: int = -1, origin: bool = False, associate: str = 'frame', delta: float = 1.0, rtol: float = 0.1, all: bool = False, thresh: float = 0.3, rpair: bool = False, otype: str = 'All'): r''' Compute the Relative Pose Error (RPE) between two trajectories. Args: rstamp (array-like of ``float`` or ``None``): The timestamps of the reference trajectory. Must have the same length as `rpose`. For example, `torch.tensor([...])` or `None`. rpose (array-like of ``SE3``): The poses of the reference trajectory. Must have the same length as `rstamp`. For example, `pypose.SE3(torch.rand(10, 7))`. estamp (array-like of ``float`` or ``None``): The timestamps of the estimated trajectory. Must have the same length as `epose`. For example, `torch.tensor([...])` or `None`. epose (array-like of ``SE3``): The poses of the estimated trajectory. Must have the same length as `estamp`. For example, `pypose.SE3(torch.rand(10, 7))`. etype (``str``, optional): The type of pose error. Supported options include: ``'translation'``: :math:`||{R_{r}^{rel}}^T * t_{r}^{rel} - {R_{e}^{rel}}^T * t_{e}^{rel}||_2` ``'rotation'``: :math:`|| {R_{r}^{rel}}^T * R_{e}^{rel} - I_3 ||_2` ``'pose'``: :math:`|| {T_{r}^{rel}}^{-1} * T_{e}^{rel} - I_4 ||_2` ``'radian'``: :math:`|| \mathrm{Log}({R_{r}^{rel}}^T * R_{e}^{rel}) ||_2` ``'degree'``: :math:`\mathrm{Degree}(|| \mathrm{Log}({R_{r}^{rel}}^T * R_{e}^{rel}) ||_2))` Default: ``'translation'`` diff (``float``, optional): The maximum allowed absolute time difference (in seconds) for associating poses. Defaults to 0.01. offset (``float``, optional): The aligned offset (in seconds) for the second timestamps. Defaults to 0.0. align (``bool``, optional): If True, the trajectory is aligned using a scaled SVD method. Defaults to False. scale (``bool``, optional): If True, the scale is corrected using the SVD method. Defaults to False. nposes (``int``, optional): The number of poses to use for alignment. If -1, all poses are used. Defaults to -1. origin (``bool``, optional): If True, the trajectory is aligned by the first pose. Defaults to False. associate (``str``, optional): The method used to associate pairs between the two trajectories. Supported options: 'frame', 'distance'. Defaults to 'frame'. delta (``float``, optional): The interval step used to select the pair. For example, when `associate='distance'`, it can represent the distance step in meters. Defaults to 1.0. rtol (``float``, optional): The relative tolerance for accepting or rejecting deltas. Defaults to 0.1. all (``bool``, optional): If True, all associated pairs are used for evaluation. Defaults to False. thresh (``float``, optional): The threshold for valid matching pairs. If the ratio of matching pairs is below this threshold, a warning is issued. Defaults to 0.3. rpair (``bool``, optional): Use reference trajectory to compute the pairing indices or not. Defaults to False. otype (``str``, optional): The output type for the metric. Supported options include: ``'All'``: All metrics will be computed and returned. ``'Max'``: The Max error is computed and returned. ``'Min'``: The Min error is computed and returned. ``'Mean'``: The Mean error is computed and returned. ``'Median'``: The Median error is computed and returned. ``'RMSE'``: The root mean square error (RMSE) is computed and returned. ``'SSE'``: The sum of square error (SSE) is computed and returned. ``'STD'``: The standard deviation (STD) is computed and returned. Defaults to ``'All'``. Returns: ``dict`` or ``Tensor``: The computed statistics of the RPE (Relative Pose Error). The return is a ``dict`` if ``otype`` is not ``'all'``, otherwise a ``Tensor``. Examples: >>> import torch >>> import pypose as pp >>> rstamp = torch.tensor([1311868163.8696999550, 1311868163.8731000423, ... 1311868163.8763999939]) >>> rpose = pp.SE3([[-0.1357000023, -1.4217000008, 1.4764000177, ... 0.6452999711, -0.5497999787, 0.3362999856, -0.4101000130], ... [-0.1357000023, -1.4218000174, 1.4764000177, ... 0.6453999877, -0.5497000217, 0.3361000121, -0.4101999998], ... [-0.1358000040, -1.4219000340, 1.4764000177, ... 0.6455000043, -0.5498999953, 0.3357999921, -0.4101000130]]) >>> estamp = torch.tensor([1311868164.3631811142, 1311868164.3990259171, ... 1311868164.4309399128]) >>> epose = pp.SE3([[0.0000000000, 0.0000000000, 0.0000000000, ... 0.0000000000, 0.0000000000, 0.0000000000, 1.0000000000], ... [-0.0005019300, 0.0010138600, -0.0020097860, ... -0.0020761820,-0.0010706080, -0.0007627490, 0.9999969602], ... [0.0004298200, 0.0019603260, -0.0048985220, ... -0.0043526068, -0.0036625920, -0.0023494449, 0.9999810457]]) >>> pp.metric.rpe(rstamp, rpose, estamp, epose) {'Max': tensor(0.0032, dtype=torch.float64), 'Min': tensor(0.0023, dtype=torch.float64), 'Mean': tensor(0.0027, dtype=torch.float64), 'Median': tensor(0.0023, dtype=torch.float64), 'RMSE': tensor(0.0028, dtype=torch.float64), 'SSE': tensor(1.5428e-05, dtype=torch.float64), 'STD': tensor(0.0006, dtype=torch.float64)} >>> pp.metric.rpe(rstamp, rpose, estamp, epose, otype='Mean') tensor(0.0027, dtype=torch.float64) ''' rtraj, etraj = StampedSE3(rstamp, rpose), StampedSE3(estamp, epose) rtraj, etraj = associate_traj(rtraj, etraj, diff, offset, thresh) trans_mat = identity_Sim3(1, dtype=etraj.dtype, device=etraj.device) if align or scale: nposes = etraj.num_poses if nposes == -1 else nposes est_trans = etraj.translation()[:,:nposes] ref_trans = rtraj.translation()[:,:nposes] trans_mat = svdstf(est_trans, ref_trans, scale) elif origin: trans_mat[...,:7] = (rtraj.first_pose @ etraj.first_pose.Inv()).data etraj.align(trans_mat) sour_id, tar_id = pair_id((rtraj if rpair else etraj), delta, associate, rtol, all) rpose_rela = rtraj[sour_id].poses.Inv() @ rtraj[tar_id].poses epose_rela = etraj[sour_id].poses.Inv() @ etraj[tar_id].poses rtraj_rela = StampedSE3(rtraj[sour_id].timestamps, rpose_rela) etraj_rela = StampedSE3(etraj[sour_id].timestamps, epose_rela) return compute_error(rtraj_rela, etraj_rela, etype, mtype = 'rpe', otype = otype)

Docs

Access documentation for PyPose

View Docs

Tutorials

Get started with tutorials and examples

View Tutorials

Get Started

Find resources and how to start using pypose

View Resources