diff --git a/optitrack_streamer/optitrack_streamer_node.py b/optitrack_streamer/optitrack_streamer_node.py
index 2a6120cc2a16dfaa0cddff2b6de9bcc7759f04f7..41ecc83c7a1de2468d1b139a24c9bc35b4a0d6d3 100644
--- a/optitrack_streamer/optitrack_streamer_node.py
+++ b/optitrack_streamer/optitrack_streamer_node.py
@@ -1,13 +1,11 @@
 import rclpy
 from rclpy.node import Node
-from geometry_msgs.msg import Pose, TransformStamped
+from geometry_msgs.msg import PoseStamped
 import threading
 from .NatNetClient import NatNetClient
 from tf2_ros import TransformBroadcaster
 from helper_functions.utility import Key
 from copy import deepcopy as cp
-import numpy as np
-from scipy.spatial.transform import Rotation as R
 
 rigid_body_data = {}
 
@@ -31,10 +29,8 @@ class OptitrackStreamerNode(Node):
         self.rigid_bodies = {}
 
         # Publisher
-        self.right_hand_publisher = self.create_publisher(Pose, '/optitrack/raw_right', rclpy.qos.qos_profile_sensor_data)
-        self.left_hand_publisher = self.create_publisher(Pose, '/optitrack/raw_left', rclpy.qos.qos_profile_sensor_data)
-        
-        self.rb_id_to_publisher = {"1": self.right_hand_publisher, "2": self.left_hand_publisher}
+        self.wrist_raw_publisher = self.create_publisher(PoseStamped, '/wrist/raw', rclpy.qos.qos_profile_sensor_data)
+        self.rb_id_to_frame_id = {"1": "right", "2": "left"}
 
         # Main loop
         self.mainloop = self.create_timer(0.01, self.mainloop_callback) 
@@ -44,23 +40,6 @@ class OptitrackStreamerNode(Node):
 
         natnet_thread = threading.Thread(target=self.start_natnet, daemon=True)
         natnet_thread.start()
-
-    def broadcast_tf(self, base_name, frame_name, position, quaternion):
-        t = TransformStamped()
-        t.header.stamp = self.get_clock().now().to_msg()
-        t.header.frame_id = base_name
-        t.child_frame_id = frame_name
-
-        t.transform.translation.x = position[0]
-        t.transform.translation.y = position[1]
-        t.transform.translation.z = position[2]
-
-        t.transform.rotation.x = quaternion[0]
-        t.transform.rotation.y = quaternion[1]
-        t.transform.rotation.z = quaternion[2]
-        t.transform.rotation.w = quaternion[3]
-
-        self.tf_broadcaster.sendTransform(t)
         
     # Run the client in a separate thread
     def start_natnet(self):
@@ -71,38 +50,28 @@ class OptitrackStreamerNode(Node):
         self.rigid_bodies[str(rigid_body_id)] = {"pos":position, "rot":rotation}
 
     def mainloop_callback(self):
-        msg = "Received: " + str(list(self.rigid_bodies.keys()))
-        self.get_logger().info(msg, throttle_duration_sec=10)
+        self.get_logger().info("Received: " + str(list(self.rigid_bodies.keys())), throttle_duration_sec=10)
         
-        for id, publisher in self.rb_id_to_publisher.items():
+        for id, frame_id in self.rb_id_to_frame_id.items():
             try:
                 data = cp(self.rigid_bodies[id])
             except:
                 continue
 
-            raw = {"p":np.array([-data["pos"][0], data["pos"][2], data["pos"][1]]), 
-                "r":np.array([-data["rot"][0], data["rot"][2], data["rot"][1], data["rot"][3]])}
-            
-            self.broadcast_tf("world", id, raw["p"], raw["r"])
-            self.broadcast_tf("world", "base", [0.1, 0.0, 0.0], np.array([0.0, 0.0, 0.0, 1.0]))
+            pose = PoseStamped()
+            pose.header.frame_id = frame_id
+            pose.header.stamp = self.get_clock().now().to_msg()
 
-            if self.key.keyPress == "r":
-                self.get_logger().info("Reset!", throttle_duration_sec = 1)
+            pose.pose.position.x = -data["pos"][0]
+            pose.pose.position.y = data["pos"][2]
+            pose.pose.position.z = data["pos"][1]
 
-                initial_rotation = R.from_quat(cp(raw["r"]))
-                self.calib_rotation = initial_rotation.inv()
+            pose.pose.orientation.x = -data["rot"][0]
+            pose.pose.orientation.y = data["rot"][2]
+            pose.pose.orientation.z = data["rot"][1]
+            pose.pose.orientation.w = data["rot"][3]
 
-            try:
-                aligned = R.from_quat(raw["r"]) * self.calib_rotation
-
-                rb_to_wrist_r = np.array([0.0, -0.05, -0.12])
-                trans_vec = np.matmul(aligned.as_matrix(), rb_to_wrist_r)
-                aligned_new_disp = cp(raw["p"]) + trans_vec
-    
-                self.broadcast_tf("world", id + "_inv", aligned_new_disp, aligned.as_quat())
-                
-            except:
-                pass
+            self.wrist_raw_publisher.publish(pose)
 
 def main():
     rclpy.init()
diff --git a/optitrack_streamer/wrist_streamer_node.py b/optitrack_streamer/wrist_streamer_node.py
new file mode 100644
index 0000000000000000000000000000000000000000..31f8e4b5f0a4f2b8382ca258ceb5b0cbbec6b375
--- /dev/null
+++ b/optitrack_streamer/wrist_streamer_node.py
@@ -0,0 +1,135 @@
+import rclpy
+from rclpy.node import Node
+from geometry_msgs.msg import PoseStamped, TransformStamped
+from tf2_ros import TransformBroadcaster
+import json, os
+import numpy as np
+from scipy.spatial.transform import Rotation as R
+
+
+class WristStreamerNode(Node):
+
+    def __init__(self):
+        super().__init__('Wrist_Streamer_Node')
+        self.get_logger().info('Starting Wrist Streamer')
+        
+        # Variables
+        self.calib_rotation = {"right":None, "left":None}
+        self.calib_translation = {"right":None, "left":None}
+        self.state = {"right":"No message", "left":"No message"}
+        
+        # Static variables
+        self.rigid_body_two_wirst = {"right":np.array([0.0, -0.05, -0.12]),"left":np.array([0.0, -0.05, -0.12])}
+        self.calib_world_position = {"right":np.array([0.05, 0.0, 0.0]), "left":np.array([-0.05, 0.0, 0.0])}
+
+        # tf related
+        self.tf_broadcaster = TransformBroadcaster(self)
+
+        # Subscriber
+        self.wrist_raw_subscriber = self.create_subscription(PoseStamped, '/wrist/raw', self.wrist_raw_callback, rclpy.qos.qos_profile_sensor_data)
+
+        # Publisher
+        self.wrist_publisher = self.create_publisher(PoseStamped, '/wrist/calibrated', rclpy.qos.qos_profile_sensor_data)
+           
+        # Main loop
+        self.mainloop = self.create_timer(2, self.mainloop_callback) 
+
+        
+    def broadcast_tf(self, base_name, frame_name, position, quaternion):
+        t = TransformStamped()
+        t.header.stamp = self.get_clock().now().to_msg()
+        t.header.frame_id = base_name
+        t.child_frame_id = frame_name
+
+        t.transform.translation.x = position[0]
+        t.transform.translation.y = position[1]
+        t.transform.translation.z = position[2]
+
+        t.transform.rotation.x = quaternion[0]
+        t.transform.rotation.y = quaternion[1]
+        t.transform.rotation.z = quaternion[2]
+        t.transform.rotation.w = quaternion[3]
+
+        self.tf_broadcaster.sendTransform(t)
+
+    def publish_wrist(self, frame_name, position, quaternion):
+        pose = PoseStamped()
+        pose.header.stamp = self.get_clock().now().to_msg()
+        pose.header.frame_id = frame_name
+
+        pose.pose.position.x = position[0]
+        pose.pose.position.y = position[1]
+        pose.pose.position.z = position[2]
+
+        pose.pose.orientation.x = quaternion[0]
+        pose.pose.orientation.y = quaternion[1]
+        pose.pose.orientation.z = quaternion[2]
+        pose.pose.orientation.w = quaternion[3]
+
+        self.wrist_publisher.publish(pose)
+        
+    def get_calibration(self, which_hand):
+        dir_path = os.path.dirname(os.path.realpath(__file__))        
+
+        json_file = dir_path + "/../calibration/" + which_hand + "/" + which_hand + ".calib.json"
+        try:
+            return json.load(open(json_file))
+        except:
+            return None
+        
+    def save_calibration(self, which_hand, trans, quat):
+        json_object = json.dumps({"trans": trans,"quat":quat}, indent=4)
+        with open("/../calibration/" + which_hand + ".calib.json", "w") as outfile:
+            outfile.write(json_object)
+
+    def wrist_raw_callback(self, msg:PoseStamped):
+        for which_hand in ["right", "left"]:
+            if msg.header.frame_id == which_hand:
+
+                if self.calib_rotation[which_hand] is None:
+                    calib = self.get_calibration(which_hand)
+                    
+                    if calib is None:
+                        self.state[which_hand] = "No calibration"
+                        continue
+                
+                    self.calib_rotation[which_hand] = R.from_quat(calib["quat"]).inv()
+                    self.calib_translation[which_hand] = calib["trans"]
+
+                self.state[which_hand] = "Calibrated and streaming"
+
+                raw_trans = np.array([msg.pose.position.x, msg.pose.position.y, msg.pose.position.z])
+                raw_quat = np.array([msg.pose.orientation.x, msg.pose.orientation.y, 
+                                    msg.pose.orientation.z, msg.pose.orientation.w])
+                
+                # Get the scipy rotation representation of the rigid body aligned with the base coordinate
+                aligned = R.from_quat(raw_quat) * self.calib_rotation[which_hand]
+
+                # Get the translation from the 'tree' to the actual wrist of the human
+                trans_vec = np.matmul(aligned.as_matrix(), self.rigid_body_two_wirst)
+                
+                # Zero the translation 
+                aligned_trans = raw_trans - self.calib_translation[which_hand] + trans_vec + self.calib_world_position
+                aligned_quat = aligned.as_quat()
+
+                self.publish_wrist(which_hand, aligned_trans, aligned_quat)
+                self.broadcast_tf("world", which_hand + "_ref", aligned_trans, aligned_quat)
+
+    def mainloop_callback(self):
+        msg = "\n"
+        msg += "Right: " + self.state["right"]
+        msg += "Left: " + self.state["left"] + "\n"
+
+        self.get_logger().info(msg)
+
+def main():
+    rclpy.init()
+    node = WristStreamerNode()
+    try:
+        rclpy.spin(node)
+    except KeyboardInterrupt:
+        pass
+
+    rclpy.shutdown()
+
+
diff --git a/setup.py b/setup.py
index c3cf2132cf0176fc4437924552362e2c365d8664..7b808ec234b983ead6b7567e3c6326197356d575 100644
--- a/setup.py
+++ b/setup.py
@@ -1,3 +1,5 @@
+import os
+from glob import glob
 from setuptools import find_packages, setup
 
 package_name = 'optitrack_streamer'
@@ -10,6 +12,7 @@ setup(
         ('share/ament_index/resource_index/packages',
             ['resource/' + package_name]),
         ('share/' + package_name, ['package.xml']),
+        (os.path.join('share', package_name, 'calibration'), glob('*.calib.json'))
     ],
     install_requires=['setuptools'],
     zip_safe=True,
@@ -20,7 +23,8 @@ setup(
     tests_require=['pytest'],
     entry_points={
         'console_scripts': [
-            'optitrack_streamer_node = optitrack_streamer.optitrack_streamer_node:main'
+            'optitrack_streamer_node = optitrack_streamer.optitrack_streamer_node:main',
+            'wrist_streamer_node = optitrack_streamer.wrist_streamer_node:main'
         ],
     },
 )