def publish(self):
        if not self._initialized:
            return
        now = rospy.Time.now()

        g_msg = AprilTagDetectionArray()
        g_msg.header.stamp = now
        g_msg.header.frame_id = 'base_link'

        gv_msg = PoseArray()
        gv_msg.header.stamp = now
        gv_msg.header.frame_id = 'base_link'

        # alignment ...
        q = tx.quaternion_from_euler(np.pi / 2, 0, -np.pi / 2)

        for i in range(self._num_markers):
            txn = self._xyz[i]
            rxn = tx.quaternion_from_euler(*(self._rpy[i]))
            rxn = tx.quaternion_multiply(rxn, q)

            msg = AprilTagDetection()
            msg.id = [i]
            msg.size = [self._tag_size]

            pwcs = msg.pose
            pwcs.header.frame_id = 'base_link'
            pwcs.header.stamp = now
            fill_pose_msg(pwcs.pose.pose, txn, rxn)

            g_msg.detections.append(msg)
            gv_msg.poses.append(pwcs.pose.pose)
            self._tfb.sendTransform(txn,
                                    rxn,
                                    now,
                                    child='gt_tag_{}'.format(i),
                                    parent='base_link')

        self._gpub.publish(g_msg)
        self._gvpub.publish(gv_msg)
Example #2
0
 def make_msg(self, Depth1Image, Depth2Image, detection_data, camera1_param,
              camera2_param):
     bboxes_from_camera1 = BoundingBoxes()
     bboxes_from_camera2 = BoundingBoxes()
     bboxes_from_camera1.header = Depth1Image.header
     bboxes_from_camera2.header = Depth2Image.header
     self.now = rospy.get_rostime()
     self.timebefore = detection_data.header.stamp
     # Add point obstacle
     #self.obstacle_msg = ObstacleArrayMsg()
     self.obstacle_msg.header.stamp = detection_data.header.stamp
     self.obstacle_msg.header.frame_id = "odom"  # CHANGE HERE: odom/map
     #self.marker_data = MarkerArray()
     self.landmark_msg = AprilTagDetectionArray()
     self.landmark_msg.detections.append(AprilTagDetection())
     self.landmark_msg.header.stamp = detection_data.header.stamp
     #opencvに変換
     bridge = CvBridge()
     try:
         Depth1image = bridge.imgmsg_to_cv2(Depth1Image,
                                            desired_encoding="passthrough")
         Depth2image = bridge.imgmsg_to_cv2(Depth2Image,
                                            desired_encoding="passthrough")
     except CvBridgeError as e:
         print(e)
     for bbox in detection_data.bounding_boxes:
         if (bbox.ymin + bbox.ymax) / 2 < Depth1image.shape[0]:
             if bbox.ymax > Depth1image.shape[0]:
                 bbox.ymax = Depth1image.shape[0]
             bboxes_from_camera1.bounding_boxes.append(bbox)
         else:
             bbox.ymin = bbox.ymin - Depth1image.shape[0]
             bbox.ymax = bbox.ymax - Depth1image.shape[0]
             if bbox.ymin < 0:
                 bbox.ymin = 0
             bboxes_from_camera2.bounding_boxes.append(bbox)
     camera1_obstacle_msg, camera2_obstacle_msg = ObstacleArrayMsg(
     ), ObstacleArrayMsg()
     camera1_landmark_msg, camera2_landmark_msg = AprilTagDetectionArray(
     ), AprilTagDetectionArray()
     camera1_marker_data, camera2_marker_data = MarkerArray(), MarkerArray()
     camera1_obstacle_msg, camera1_marker_data = self.bbox_to_position_in_odom(
         bboxes_from_camera1, Depth1image, camera1_param)
     obstacle_msg, marker_data = self.bbox_to_position_in_odom(
         bboxes_from_camera2, Depth2image, camera2_param,
         len(camera1_obstacle_msg.obstacles), camera1_obstacle_msg,
         camera1_marker_data)
Example #3
0
def tag_callback(data, listener):
    """ Callback when we receive new tag information """

    tags = data
    global curr_pose
    
    # No detected tags
    if len(tags.detections) == 0:
        return
    
    detections = tags.detections
    min_dist = MAX_WORLD_SIZE
    min_detection = AprilTagDetection()

    # Use minimum distance to calculate position.
    # The closer we are, the more accurate the estimate
    for detection in detections:
        dist = distance(detection.pose)
        if dist < min_dist:
            min_dist = dist
            min_detection = detection

    tag_name = 'tag_' + str(min_detection.id[0])

    # Block if our transforms don't exist yet
    listener.waitForTransform(tag_name, "camera", rospy.Time(0), rospy.Duration(3))
    listener.waitForTransform("map", tag_name, rospy.Time(0), rospy.Duration(3))

    # In the camera frame, camera is at (0,0,0)
    camera_pose = PoseStamped()
    camera_pose.header.frame_id = "camera"
    camera_pose.pose.position.x = 0
    camera_pose.pose.position.y = 0
    camera_pose.pose.position.z = 0

    p_in_tag = listener.transformPose(tag_name, camera_pose)
    p_in_map = listener.transformPose("map", p_in_tag)

    curr_pose.pose.position = p_in_map.pose.position
    curr_pose.header.frame_id = "map"
    curr_pose.header.stamp = rospy.Time.now()
def xbee_callback(msg_pre):

    msg = msg_pre.data
    case = msg[0]
    # case 0 => Testing case
    # case 1 => Apriltags Message Receive

    if case == "0":
        rospy.loginfo("This is a test message.")
        rospy.loginfo((msg[1:]))
    elif case == "1":
        rospy.loginfo("This is an Apriltags message.")

        at_array = AprilTagDetectionArray()

        secs = int(msg[1:10])
        nsecs = int(msg[10:19])
        at_array.header.stamp.secs = secs
        at_array.header.stamp.nsecs = nsecs
        time_offset = 18
        # Offset of each parameter
        id_offset = 0
        id_len = 3
        # px
        px_left_offset = id_offset + id_len
        px_left_len = 2
        px_right_offset = px_left_offset + px_left_len
        px_right_len = 3
        # py
        py_left_offset = px_right_offset + px_right_len
        py_left_len = 2
        py_right_offset = py_left_offset + py_left_len
        py_right_len = 3
        # pz
        pz_left_offset = py_right_offset + py_right_len
        pz_left_len = 2
        pz_right_offset = pz_left_offset + pz_left_len
        pz_right_len = 3
        # ox
        ox_left_offset = pz_right_offset + pz_right_len
        ox_left_len = 1
        ox_right_offset = ox_left_offset + ox_left_len
        ox_right_len = 12
        # oy
        oy_left_offset = ox_right_offset + ox_right_len
        oy_left_len = 1
        oy_right_offset = oy_left_offset + oy_left_len
        oy_right_len = 12
        # oz
        oz_left_offset = oy_right_offset + oy_right_len
        oz_left_len = 1
        oz_right_offset = oz_left_offset + oz_left_len
        oz_right_len = 12
        # ow
        ow_left_offset = oz_right_offset + oz_right_len
        ow_left_len = 1
        ow_right_offset = ow_left_offset + ow_left_len
        ow_right_len = 12
        # Watchtower id
        wat_id_offset = ow_right_offset + ow_right_len

        AT_OFFSET = ow_right_offset + ow_right_len

        position_offset = 50  # Assume the range of pose -49~49
        orientation_offset = 1  # Quaternion has range -1~1
        print "Len: ", len(msg[1 + time_offset:])
        print "Offset: ", AT_OFFSET
        for i in range(len(msg[1 + time_offset:]) / AT_OFFSET):
            detection = AprilTagDetection()

            base_offset = i + 1 + time_offset
            detection.id.append(int(msg[base_offset:base_offset + id_len]))
            detection.pose.pose.pose.position.x = int(
                msg[base_offset + px_left_offset:base_offset + px_left_offset +
                    px_left_len]) + float(
                        msg[base_offset + px_right_offset:base_offset +
                            px_right_offset +
                            px_right_len]) / 1000 - position_offset
            detection.pose.pose.pose.position.y = int(
                msg[base_offset + py_left_offset:base_offset + py_left_offset +
                    py_left_len]) + float(
                        msg[base_offset + py_right_offset:base_offset +
                            py_right_offset +
                            py_right_len]) / 1000 - position_offset
            detection.pose.pose.pose.position.z = int(
                msg[base_offset + pz_left_offset:base_offset + pz_left_offset +
                    pz_left_len]) + float(
                        msg[base_offset + pz_right_offset:base_offset +
                            pz_right_offset +
                            pz_right_len]) / 1000 - position_offset
            detection.pose.pose.pose.orientation.x = int(
                msg[base_offset + ox_left_offset:base_offset + ox_left_offset +
                    ox_left_len]) + float(
                        msg[base_offset + ox_right_offset:base_offset +
                            ox_right_offset +
                            ox_right_len]) / 1000000000000 - orientation_offset
            detection.pose.pose.pose.orientation.y = int(
                msg[base_offset + oy_left_offset:base_offset + oy_left_offset +
                    oy_left_len]) + float(
                        msg[base_offset + oy_right_offset:base_offset +
                            oy_right_offset +
                            oy_right_len]) / 1000000000000 - orientation_offset
            detection.pose.pose.pose.orientation.z = int(
                msg[base_offset + oz_left_offset:base_offset + oz_left_offset +
                    oz_left_len]) + float(
                        msg[base_offset + oz_right_offset:base_offset +
                            oz_right_offset +
                            oz_right_len]) / 1000000000000 - orientation_offset
            detection.pose.pose.pose.orientation.w = int(
                msg[base_offset + ow_left_offset:base_offset + ow_left_offset +
                    ow_left_len]) + float(
                        msg[base_offset + ow_right_offset:base_offset +
                            ow_right_offset +
                            ow_right_len]) / 1000000000000 - orientation_offset
            detection.pose.header.frame_id = msg[base_offset + wat_id_offset:]

            print "The Watchtower is: ", detection.pose.header.frame_id

            at_array.detections.append(detection)

        pub_apriltags.publish(at_array)
Example #5
0
    def data_cb(self, joint_msg, detection_msgs):
        # set info-mat param
        # reorder joint information
        try:
            jorder = ['waist', 'shoulder', 'elbow', 'hand', 'wrist']
            j_idx = [joint_msg.name.index(j) for j in jorder]
            j = [joint_msg.position[i] for i in j_idx]
            #j = np.random.normal(loc=j, scale=0.005)
            j = np.concatenate((j, [0]))  # assume final joint is fixed
        except Exception as e:
            rospy.logerr_throttle(
                1.0, 'Joint Positions Failed : {} \n {}'.format(e, joint_msg))

        if len(detection_msgs.detections) <= 0:
            # nothing detected, stop
            return

        # form pose ...
        p, q = fk(self._dh0, j)

        # testing ; add motion noise
        # p = np.random.normal(p, scale=0.01)
        # dq = qmath.rq(s=0.01)
        # q = qmath.qmul(dq, q)

        if not self._initialized:
            # save previous pose and don't proceed
            self._p = p
            self._q = q
            #self._slam.initialize(np.concatenate([p,q], axis=0))
            self._initialized = True
            return

        # form observations ...
        # nodes organized as:
        # [base_link, landmarks{0..m-1}, poses{0..p-1}]

        zs = []
        try:
            for pm in detection_msgs.detections:
                m_id = pm.id[0]
                ps = PoseStamped(header=pm.pose.header, pose=pm.pose.pose.pose)
                # TODO : compute+apply static transform?
                ps = self._tfl.transformPose('stereo_optical_link', ps)
                zp, zq = pmsg2pq(ps.pose)

                # testing; add observation noise
                # zp = np.random.normal(zp, scale=0.05)
                # dzq = qmath.rq(s=0.05)
                # zq = qmath.qmul(dzq, zq)

                dz = np.concatenate([zp, zq], axis=0)
                zi = self._m2i[m_id]
                if zi >= self._num_markers:
                    continue
                self._i2m[zi] = m_id
                zs.append([zi, dz, self._omega.copy()])
        except Exception as e:
            rospy.logerr_throttle(1.0, 'TF Failed : {}'.format(e))
            return

        ### semi-offline batch optimization
        ox = 1.0 * self._omega
        self._graph.append([p, q, zs])
        if len(self._graph) >= self._batch_size:
            n_poses = 1 + len(self._graph)  # add 1 for 0-node constraint
            nodes = []
            edges = []
            p_nodes = []
            z_nodes = [[] for _ in range(self._num_markers)]

            # add 0-node
            nodes.append(np.asarray([0, 0, 0, 0, 0, 0, 1]))

            for gi, g in enumerate(self._graph):
                xi = 1 + self._num_markers + gi  # current pose index

                # pose info
                gp, gq, gz = g
                gx = np.concatenate([gp, gq], axis=0)
                p_nodes.append(gx)

                edges.append([0, xi, gx, ox])
                # TODO : handle scenarios when a landmark didn't appear in 200 observations

                for zi, dz, zo in gz:
                    # edge from motion index to re-computed landmark index
                    edges.append([xi, 1 + zi, dz, zo])
                    zp_a, zq_a = qmath.x2pq(qmath.xadd_rel(gx, dz, T=False))
                    z_nodes[zi].append([zp_a, zq_a])

            # add landmark nodes
            if self._zinit:
                # use the previous initialization for landmarks
                for zp, zq in self._z_nodes:
                    zx = np.concatenate([zp, zq], axis=0)
                    nodes.append(zx)
            else:
                #initialize landmarks with average over 200 samples
                for zi, zn in enumerate(z_nodes):
                    zp, zq = zip(*zn)
                    zp = np.mean(zp, axis=0)
                    zq = qmath.qmean(zq)
                    zx = np.concatenate([zp, zq], axis=0)
                    nodes.append(zx)
                lms = nodes[1:1 + self._num_markers]
                lms = [qmath.x2pq(x) for x in lms]
                lms_re = [lms[self._m2i[i]] for i in range(self._num_markers)]
                self._pub_ze.publish(msgn(lms_re, rospy.Time.now()))
                self._zinit = True

            # add motion nodes
            for zx in p_nodes:
                nodes.append(zx)

            #print "PRE:"
            #print nodes[-self._num_markers:]
            nodes = self._slam.optimize(nodes,
                                        edges,
                                        max_iter=100,
                                        tol=1e-12,
                                        min_iter=100)
            #print "POST:"
            #print nodes[-self._num_markers:]

            lms = nodes[1:1 + self._num_markers]  # landmarks
            lms = [qmath.x2pq(x) for x in lms]
            self._z_nodes = lms
            lms_re = [lms[self._m2i[i]] for i in range(self._num_markers)]
            self._pub_ze.publish(msgn(lms_re, rospy.Time.now()))
            self._graph.clear()

            stamp = rospy.Time.now()
            g_msg = AprilTagDetectionArray()
            g_msg.header.stamp = stamp
            g_msg.header.frame_id = 'base_link'

            for i in range(self._num_markers):
                msg = AprilTagDetection()
                m_id = self._i2m[i]
                msg.id = [m_id]
                msg.size = [0.0]  # not really a thing
                msg.pose.pose.pose = pose(lms[i][0], lms[i][1])
                msg.pose.header.frame_id = 'base_link'
                msg.pose.header.stamp = stamp
                g_msg.detections.append(msg)
            self._gt_pub.publish(g_msg)
        return
        ### END: semi-offline batch optimization

        ### online slam with "reasonable" initialization?
        #if not self._zinit:
        #    # add entry ...
        #    gx = np.concatenate([p,q], axis=0)
        #    for z in zs:
        #        _, zi, dz, _ = z
        #        zi = zi - 2
        #        zp_a, zq_a = qmath.x2pq(qmath.xadd_rel(gx, dz, T=False))
        #        self._z_nodes[zi].append( (zp_a, zq_a) )

        #    # check entries ...
        #    ls = [len(e) for e in self._z_nodes]
        #    if np.all(np.greater_equal(ls, 100)):
        #        znodes = []
        #        for zi, zn in enumerate(self._z_nodes):
        #            zp, zq = zip(*zn)
        #            zp   = np.mean(zp, axis=0)
        #            zq   = qmath.qmean(zq)
        #            zx   = np.concatenate([zp,zq], axis=0)
        #            znodes.append(zx)
        #            self._slam._nodes[2+zi] = zx
        #            self._slam.initialize(gx)
        #            self._p = p
        #            self._q = q
        #        self._zinit = True
        #    return
        ### END : online slam with reasonable initialization

        #dp, dq = qmath.xrel(self._p, self._q, p, q)
        #dx     = np.concatenate([dp,dq], axis=0)
        dx = np.concatenate([p, q], axis=0)
        self._slam._nodes[0] = np.asarray([0, 0, 0, 0, 0, 0, 1],
                                          dtype=np.float32)

        # save p-q
        #self._p = p
        #self._q = q

        #        for zp, zq in self._z_nodes:
        #            zx = np.concatenate([zp,zq], axis=0)
        #            nodes.append(zx)

        # form dz ...
        mu = self._slam.step(x=dx, zs=zs)
        mu = np.reshape(mu, [-1, 7])
        ep, eq = qmath.x2pq(mu[1])
        gtp, gtq = fk(self._dh, j)

        #perr = np.subtract(ep,  gtp)
        #perr = np.linalg.norm(perr)
        #qerr = qmath.T(qmath.qmul(qmath.qinv(gtq), eq))
        #qerr = ((qerr + np.pi) % (2*np.pi)) - np.pi
        #qerr = np.linalg.norm(qerr)
        #epe, eqe = perr, qerr

        #perr = np.subtract(p,  gtp)
        #perr = np.linalg.norm(perr)
        #qerr = qmath.T(qmath.qmul(qmath.qinv(gtq), q))
        #qerr = ((qerr + np.pi) % (2*np.pi)) - np.pi
        #qerr = np.linalg.norm(qerr)
        #pe, qe = perr, qerr
        #print pe-epe, qe-eqe

        ez = [qmath.x2pq(e) for e in mu[2:]]

        stamp = rospy.Time.now()
        g_msg = AprilTagDetectionArray()
        g_msg.header.stamp = stamp
        g_msg.header.frame_id = 'base_link'

        for i in range(self._num_markers):
            msg = AprilTagDetection()
            m_id = self._i2m[i]
            msg.id = [m_id]
            msg.size = [0.0]  # not really a thing
            msg.pose.pose.pose = pose(ez[i][0], ez[i][1])
            msg.pose.header.frame_id = 'base_link'
            msg.pose.header.stamp = stamp
            g_msg.detections.append(msg)
        self._gt_pub.publish(g_msg)

        #ez     = [unparametrize(e) for e in mu[1:]]

        self._p, self._q = ep.copy(), eq.copy()
        #self._p, self._q = p, q

        t = joint_msg.header.stamp
        self._pub_ee.publish(msg1(ep, eq, t))
        self._pub_ze.publish(msgn(ez, t))
    def data_cb(self, joint_msg, detection_msgs):
        try:
            jorder = ['waist', 'shoulder', 'elbow', 'hand', 'wrist']
            j_idx = [joint_msg.name.index(j) for j in jorder]
            j = [joint_msg.position[i] for i in j_idx]
            j = np.concatenate((j, [0]))  # assume final joint is fixed
        except Exception as e:
            rospy.logerr_throttle(
                1.0, 'Joint Positions Failed : {} \n {}'.format(e, joint_msg))

        Xs = [np.eye(4) for _ in range(self._num_markers)]
        vis = [False for _ in range(self._num_markers)]

        if len(detection_msgs.detections) <= 0:
            return

        try:
            for pm in detection_msgs.detections:
                # pm.pose = pwcs
                # pm.pose.pose = pwc
                # pm.pose.pose.pose = p

                # TODO : technically requires tf.transformPose(...) for robustness.
                #pose = tf.transformPose(

                ps = PoseStamped(header=pm.pose.header, pose=pm.pose.pose.pose)
                # this transform should theoretically be painless + static
                # Optionally, store the transform beforehand and apply it
                ps = self._tfl.transformPose('stereo_optical_link', ps)

                q = ps.pose.orientation
                p = ps.pose.position
                m_id = pm.id[0]
                X = tx.compose_matrix(angles=tx.euler_from_quaternion(
                    [q.x, q.y, q.z, q.w]),
                                      translate=[p.x, p.y, p.z])
                i = self._m2i[m_id]  # transform tag id to index
                if i >= self._num_markers:
                    continue
                self._i2m[i] = m_id
                Xs[i] = X
                vis[i] = True
                self._seen[i] = True
        except Exception as e:
            rospy.logerr_throttle(1.0, 'TF Failed : {}'.format(e))

        Xs = np.float32(Xs)

        self._data.append((j, Xs, vis))

        self._step += 1
        rospy.loginfo_throttle(
            1.0, 'Current Step : {}; vis : {}'.format(self._step, vis))

        T = self._calib.eval_1(j, Xs, vis)  # == (1, M, 4, 4)

        now = rospy.Time.now()

        m_msg = AprilTagDetectionArray()
        m_msg.header.frame_id = 'base_link'
        m_msg.header.stamp = now

        pv_msg = PoseArray()
        pv_msg.header.stamp = now
        pv_msg.header.frame_id = 'base_link'

        for i in range(self._num_markers):
            if vis[i]:
                txn = tx.translation_from_matrix(T[i])
                rxn = tx.quaternion_from_matrix(T[i])

                msg = AprilTagDetection()
                msg.id = [self._i2m[i]]
                msg.size = [0.0]  # not really a thing

                pwcs = msg.pose
                pwcs.header.frame_id = 'base_link'
                pwcs.header.stamp = now
                fill_pose_msg(pwcs.pose.pose, txn, rxn)

                m_msg.detections.append(msg)
                pv_msg.poses.append(pwcs.pose.pose)
        self._pub.publish(m_msg)
        self._vpub.publish(pv_msg)
Example #7
0
    def publish(self):
        now = rospy.Time.now()



        if self._smooth:
            # smooth change over time ...
            if self._j_0 is None:
                self._j_0 = np.random.uniform(low=-np.pi, high=np.pi, size=5)
                self._j_1 = np.random.uniform(low=-np.pi, high=np.pi, size=5)
                jpos = np.copy(self._j_0)
            else:
                self._m_i += 1
                jpos = alerp(self._j_0, self._j_1, float(self._m_i) / self._m_n)
                if self._m_i >= self._m_n:
                    self._j_0 = np.copy(self._j_1)
                    self._j_1 = np.random.uniform(low=-np.pi, high=np.pi, size=5)
                    self._m_i = 0
        else:
            # completely random
            jpos = np.random.uniform(low=-np.pi, high=np.pi, size=5)

        if self._zero:
            jpos *= 0
        txn, rxn = fk(self._dh, np.concatenate( [jpos, [0]] ))

        ## base_link --> object
        M07 = [tx.compose_matrix(translate = xyz, angles=rpy) for (xyz, rpy) in zip(self._xyz, self._rpy)]

        ## base_link --> stereo_optical_link
        M06 = tx.compose_matrix(
                angles = tx.euler_from_quaternion(rxn),
                translate = txn) # T_0_6

        M06R = tx.quaternion_matrix(rxn)
        Rt =  M06R[:3,:3].T
        T  = -np.matmul(Rt, np.reshape(txn, [3,1]))
        M06i = np.zeros((4,4), dtype=np.float32)
        M06i[:3,:3] = Rt
        M06i[:3, 3] = T[:,0]
        M06i[ 3, 3] = 1.0

        m_msg = AprilTagDetectionArray()
        m_msg.header.stamp = now
        m_msg.header.frame_id = 'stereo_optical_link'

        pv_msg = PoseArray()
        pv_msg.header.stamp = now
        pv_msg.header.frame_id = 'stereo_optical_link'

        M67 = [np.matmul(M06i, M) for M in M07]

        for i in range(self._num_markers):
            if np.random.random() > self._p:
                continue
            M = M67[i]
            txn = tx.translation_from_matrix(M)
            rxn = tx.quaternion_from_matrix(M)
            if self._noise > 0.0:
                txn = np.random.normal(loc=txn, scale=self._noise)
                h = np.random.uniform(-self._noise, self._noise)
                ax = np.random.normal(size=3)
                ax /= np.linalg.norm(ax)
                d_rxn = tx.quaternion_about_axis(h, ax)
                rxn = tx.quaternion_multiply(d_rxn, rxn)
            
            msg = AprilTagDetection()
            msg.id = [i]
            msg.size = [0.0] # not really a thing

            pwcs = msg.pose
            pwcs.header.frame_id = 'stereo_optical_link'
            pwcs.header.stamp = now
            fill_pose_msg(pwcs.pose.pose, txn, rxn)

            m_msg.detections.append(msg)
            pv_msg.poses.append(pwcs.pose.pose)

        self._ppub.publish(m_msg)
        self._pvpub.publish(pv_msg)

        g_msg = AprilTagDetectionArray()
        g_msg.header.stamp = now
        g_msg.header.frame_id = 'base_link'

        gv_msg = PoseArray()
        gv_msg.header.stamp = now
        gv_msg.header.frame_id = 'base_link'

        for i in range(self._num_markers):
            M = M07[i]
            txn = tx.translation_from_matrix(M)
            rxn = tx.quaternion_from_matrix(M)

            msg = AprilTagDetection()
            msg.id = [i]
            msg.size = [0.0] # not really a thing

            pwcs = msg.pose
            pwcs.header.frame_id = 'base_link'
            pwcs.header.stamp = now
            fill_pose_msg(pwcs.pose.pose, txn, rxn)
            
            g_msg.detections.append(msg)
            gv_msg.poses.append(pwcs.pose.pose)

        if self._ground_truth:
            self._gpub.publish(g_msg)
            
        # visualizations are always published
        self._gvpub.publish(gv_msg)

        #gmsg = PoseStamped()
        #gmsg.header.frame_id = 'base_link'
        #txn = tx.translation_from_matrix(M07)
        #rxn = tx.quaternion_from_matrix(M07)
        #gmsg.header.stamp = now
        #fill_pose_msg(gmsg.pose, txn, rxn)
        #self._gpub.publish(gmsg)

        jmsg = JointState()
        jmsg.header.stamp = now
        jmsg.header.frame_id = 'map' #??

        jmsg.name = ['waist', 'shoulder', 'elbow', 'hand', 'wrist']
        jmsg.position = jpos
        jmsg.velocity = np.zeros_like(jpos)
        jmsg.effort = np.zeros_like(jpos)
        self._jpub.publish(jmsg)