diff --git a/main.py b/main.py
index ebfc03841d7cb233e3fb31b096d5a4d589d5a66e..8e44ad6c1500bd8055abd5dcc7c2de23b387c14e 100644
--- a/main.py
+++ b/main.py
@@ -20,6 +20,7 @@ class SimulatorLoop(Loop):
     """
     Class used to call pybullet at a given frequency
     """
+
     def __init__(self, period, t_max, envID, velID, dt_mpc, k_mpc, t, n_periods, T_gait, N_SIMULATION, type_MPC, pyb_feedback):
         """
         Constructor
@@ -41,14 +42,14 @@ class SimulatorLoop(Loop):
         self.ID_deb_lines = []
 
         # Enable/Disable Gepetto viewer
-        self.enable_gepetto_viewer = True
+        self.enable_gepetto_viewer = False
 
         # Which MPC solver you want to use
         # True to have PA's MPC, to False to have Thomas's MPC
         """type_MPC = True"""
 
         # Create Joystick, FootstepPlanner, Logger and Interface objects
-        self.joystick, self.fstep_planner, self.logger, self.interface = utils.init_objects(
+        self.joystick, self.fstep_planner, self.logger, self.interface, self.estimator = utils.init_objects(
             dt, dt_mpc, N_SIMULATION, k_mpc, n_periods, T_gait, type_MPC)
 
         # Wrapper that makes the link with the solver that you want to use for the MPC
@@ -135,7 +136,7 @@ class SimulatorLoop(Loop):
         # If nothing wrong happened yet in TSID controller
         if not self.myController.error:
             self.jointTorques = proc.process_invdyn(self.solo, self.k, self.f_applied, self.pyb_sim, self.interface,
-                                               self.fstep_planner, self.myController, self.enable_hybrid_control)
+                                                    self.fstep_planner, self.myController, self.enable_hybrid_control)
             # t_list_tsid[k] = time.time() - time_tsid  # Logging the time spent to run this iteration of inverse dynamics
 
             # Process PD+ (feedforward torques and feedback torques)
@@ -219,6 +220,7 @@ def run_scenario(envID, velID, dt_mpc, k_mpc, t, n_periods, T_gait, N_SIMULATION
     time_error = False
 
     # Lists to log the duration of 1 iteration of the MPC/TSID
+    t_list_filter = [0] * int(N_SIMULATION)
     t_list_states = [0] * int(N_SIMULATION)
     t_list_fsteps = [0] * int(N_SIMULATION)
     t_list_mpc = [0] * int(N_SIMULATION)
@@ -239,7 +241,7 @@ def run_scenario(envID, velID, dt_mpc, k_mpc, t, n_periods, T_gait, N_SIMULATION
     """type_MPC = True"""
 
     # Create Joystick, FootstepPlanner, Logger and Interface objects
-    joystick, fstep_planner, logger, interface = utils.init_objects(
+    joystick, fstep_planner, logger, interface, estimator = utils.init_objects(
         dt, dt_mpc, N_SIMULATION, k_mpc, n_periods, T_gait, type_MPC, on_solo8,
         predefined_vel)
 
@@ -283,10 +285,19 @@ def run_scenario(envID, velID, dt_mpc, k_mpc, t, n_periods, T_gait, N_SIMULATION
 
         """if (k % 1000) == 0:
             print("Iteration: ", k)"""
-        #print("###")
+        # print("###")
+
+        # Process estimator
+        if k == 1:
+            estimator.run_filter(k, fstep_planner.gait[0, 1:], pyb_sim.robotId,
+                                 myController.invdyn.data(), myController.model)
+        else:
+            estimator.run_filter(k, fstep_planner.gait[0, 1:], pyb_sim.robotId)
+
+        t_filter = time.time()
 
         # Process states update and joystick
-        proc.process_states(solo, k, k_mpc, velID, pyb_sim, interface, joystick, myController, pyb_feedback)
+        proc.process_states(solo, k, k_mpc, velID, pyb_sim, interface, joystick, myController, estimator, pyb_feedback)
 
         t_states = time.time()
 
@@ -315,7 +326,7 @@ def run_scenario(envID, velID, dt_mpc, k_mpc, t, n_periods, T_gait, N_SIMULATION
 
         t_mpc = time.time()
 
-        #print(f_applied.ravel())
+        # print(f_applied.ravel())
 
         # Process Inverse Dynamics
         # time_tsid = time.time()
@@ -339,22 +350,27 @@ def run_scenario(envID, velID, dt_mpc, k_mpc, t, n_periods, T_gait, N_SIMULATION
             jointTorques[jointTorques > t_max] = t_max
             jointTorques[jointTorques < -t_max] = -t_max
 
-            if np.max(np.abs(pyb_sim.vmes12[6:, 0])) < 0.1:
+            """if np.max(np.abs(pyb_sim.vmes12[6:, 0])) < 0.1:
                 print("Trigger get back to default pos")
                 # pyb_sim.get_to_default_position(pyb_sim.straight_standing)
                 pyb_sim.get_to_default_position(np.array([[0.0, np.pi/2, np.pi,
                                                            0.0, np.pi/2, np.pi,
                                                            0.0, -np.pi/2, np.pi,
-                                                           0.0, -np.pi/2, np.pi]]).transpose())
+                                                           0.0, -np.pi/2, np.pi]]).transpose())"""
+            """pyb_sim.get_to_default_position(np.array([[0, 0.8, -1.6,
+                                                       0, 0.8, -1.6,
+                                                       0, -0.8, 1.6,
+                                                       0, -0.8, 1.6]]).transpose())"""
 
-        #print(jointTorques.ravel())
+        # print(jointTorques.ravel())
         """if myController.error:
             pyb.disconnect()
             quit()"""
 
         t_tsid = time.time()
 
-        t_list_states[k] = t_states - time_loop
+        t_list_filter[k] = t_filter - time_loop
+        t_list_states[k] = t_states - t_filter
         t_list_fsteps[k] = t_fsteps - t_states
         t_list_mpc[k] = t_mpc - t_fsteps
         t_list_tsid[k] = t_tsid - t_mpc
@@ -364,9 +380,12 @@ def run_scenario(envID, velID, dt_mpc, k_mpc, t, n_periods, T_gait, N_SIMULATION
         proc.process_pybullet(pyb_sim, k, envID, jointTorques)
 
         # Call logger object to log various parameters
-        logger.call_log_functions(k, pyb_sim, joystick, fstep_planner, interface, mpc_wrapper, myController,
-                                  False, pyb_sim.robotId, pyb_sim.planeId, solo)
-
+        # logger.call_log_functions(k, pyb_sim, joystick, fstep_planner, interface, mpc_wrapper, myController,
+        #                          False, pyb_sim.robotId, pyb_sim.planeId, solo)
+        # logger.log_state(k, pyb_sim, joystick, interface, mpc_wrapper, solo)
+        # logger.log_footsteps(k, interface, myController)
+        # logger.log_fstep_planner(k, fstep_planner)
+        # logger.log_tracking_foot(k, myController, solo)
         """if k >= 1000:
             time.sleep(0.1)"""
 
@@ -382,9 +401,15 @@ def run_scenario(envID, velID, dt_mpc, k_mpc, t, n_periods, T_gait, N_SIMULATION
     print("Computation duration: ", tac-tic)
     print("Simulated duration: ", N_SIMULATION*dt)
     print("Max loop time: ", np.max(t_list_loop[10:]))
+
+    if enable_multiprocessing:
+        print("Stopping parallel process")
+        mpc_wrapper.stop_parallel_loop()
+
     print("END")
 
     plt.figure()
+    plt.plot(t_list_filter[1:], '+', color="orange")
     plt.plot(t_list_states[1:], 'r+')
     plt.plot(t_list_fsteps[1:], 'g+')
     plt.plot(t_list_mpc[1:], 'b+')
@@ -395,6 +420,37 @@ def run_scenario(envID, velID, dt_mpc, k_mpc, t, n_periods, T_gait, N_SIMULATION
 
     pyb.disconnect()
 
+    """NN = myController.log_v_est.shape[2]
+    avg = np.zeros((3, NN))
+    for m in range(NN):
+        tmp_cpt = 0
+        tmp_sum = np.zeros((3, 1))
+        for j in range(4):
+            if np.any(np.abs(myController.log_v_est[:, j, m]) > 1e-2):
+                tmp_cpt += 1
+                tmp_sum[:, 0] = tmp_sum[:, 0] + myController.log_v_est[:, j, m].ravel()
+        if tmp_cpt > 0:
+            avg[:, m:(m+1)] = tmp_sum / tmp_cpt
+
+    plt.figure()
+    for i in range(3):
+        plt.subplot(3, 1, i+1)
+        for j in range(4):
+            plt.plot(myController.log_v_est[i, j, :], linewidth=3)
+            # plt.plot(-myController.log_Fv1F[i, j, :], linewidth=3, linestyle="--")
+        plt.plot(myController.log_v_truth[i, :], "k", linewidth=3, linestyle="--")
+        plt.plot(avg[i, :], color="rebeccapurple", linewidth=3, linestyle="--")
+        plt.legend(["FL", "FR", "HL", "HR", "Truth"])
+    plt.suptitle("Estimation of the velocity of the base")
+
+    plt.figure()
+    for i in range(3):
+        plt.subplot(3, 1, i+1)
+        for j in range(4):
+            plt.plot(logger.feet_vel[i, j, :], linewidth=3)
+    plt.suptitle("Velocity of feet over time")
+    plt.show(block=True)"""
+
     return logger
 
 
diff --git a/processing.py b/processing.py
index f602bc36cd9f1b0c828c86aec3bbd318298fd02c..63c0abca503fd576bf0af2e4165804e0a8a6d586 100644
--- a/processing.py
+++ b/processing.py
@@ -9,7 +9,7 @@ from matplotlib import pyplot as plt
 from utils import getQuaternion
 
 
-def process_states(solo, k, k_mpc, velID, pyb_sim, interface, joystick, tsid_controller, pyb_feedback):
+def process_states(solo, k, k_mpc, velID, pyb_sim, interface, joystick, tsid_controller, estimator, pyb_feedback):
     """Update states by retrieving information from the simulation and the gamepad
 
     Args:
@@ -21,6 +21,7 @@ def process_states(solo, k, k_mpc, velID, pyb_sim, interface, joystick, tsid_con
         interface (object): Interface object of the control loop
         joystick (object): Interface with the gamepad
         tsid_controller (object): Inverse dynamics controller
+        estimator (object): Estimator object which estimates the state of the robot
         pyb_feedback (bool): Whether PyBullet feedback is enabled or not
     """
 
@@ -28,6 +29,9 @@ def process_states(solo, k, k_mpc, velID, pyb_sim, interface, joystick, tsid_con
         # Retrieve data from the simulation (position/orientation/velocity of the robot)
         # Stored in pyb_sim.qmes12 and pyb_sim.vmes12 (quantities in PyBullet world frame)
         pyb_sim.retrieve_pyb_data()
+        pyb_sim.qmes12[3:7, 0] = estimator.filt_ang_pos
+        pyb_sim.vmes12[0:3, 0] = estimator.filt_lin_vel
+        pyb_sim.vmes12[3:6, 0] = estimator.filt_ang_vel
 
         # Retrieve state desired by TSID (position/orientation/velocity of the robot)
         tsid_controller.qtsid[:, 0] = tsid_controller.qdes.copy()  # in TSID world frame
@@ -113,6 +117,24 @@ def process_footsteps_planner(k, k_mpc, pyb_sim, interface, joystick, fstep_plan
         fstep_planner.update_fsteps(k, k_mpc, interface.l_feet, np.vstack((interface.lV, interface.lW)),
                                     joystick.v_ref, interface.lC[2], interface.oMl, pyb_sim.ftps_Ids, False)
 
+    """import pybullet as pyb
+    for j in range(4):
+        pos_tmp = np.array([interface.o_feet[:, j]]).transpose()
+        pyb.resetBasePositionAndOrientation(pyb_sim.ftps_Ids[j, 0],
+                                            posObj=pos_tmp,
+                                            ornObj=np.array([0.0, 0.0, 0.0, 1.0]))"""
+
+    """for i in range(4):
+        a = interface.o_feet[:, i:(i+1)]
+        b = interface.l_feet[:, i:(i+1)]
+        c = interface.oMl.inverse() * interface.o_feet[:, i:(i+1)]
+        d = interface.oMl * interface.l_feet[:, i:(i+1)]
+        print("### ", i, " ###")
+        print("o_feet: ", a.ravel())
+        print("o_feet: ", b.ravel())
+        print("oMl-1 * o_feet: ", c.ravel())
+        print("oMl * l_feet: ", d.ravel())"""
+
     # Update footsteps desired location once every k_mpc iterations of TSID
     if True:  # (k % k_mpc) == 0:
 
diff --git a/utils.py b/utils.py
index 7e454a7669cc52d81f03274a107bea2694dd53f3..df3938d2d695657e5c9097d140fcc3949cede63b 100644
--- a/utils.py
+++ b/utils.py
@@ -7,6 +7,7 @@ import Joystick
 import FootstepPlanner
 import Logger
 import Interface
+import Estimator
 
 import pybullet as pyb  # Pybullet server
 import pybullet_data
@@ -167,7 +168,10 @@ def init_objects(dt_tsid, dt_mpc, k_max_loop, k_mpc, n_periods, T_gait, type_MPC
     # Create Interface object
     interface = Interface.Interface()
 
-    return joystick, fstep_planner, logger, interface
+    # Create Estimator object
+    estimator = Estimator.Estimator(dt_tsid)
+
+    return joystick, fstep_planner, logger, interface, estimator
 
 
 def display_all(solo, k, sequencer, fstep_planner, ftraj_gen, mpc):
@@ -207,7 +211,7 @@ class pybullet_simulator:
     def __init__(self, envID, use_flat_plane, dt=0.001):
 
         # Start the client for PyBullet
-        physicsClient = pyb.connect(pyb.GUI)
+        physicsClient = pyb.connect(pyb.DIRECT)
         # p.GUI for graphical version
         # p.DIRECT for non-graphical version
 
@@ -218,20 +222,23 @@ class pybullet_simulator:
             self.planeId = pyb.loadURDF("plane.urdf")  # Flat plane
         else:
             import random
-            random.seed(42)
+            random.seed(41)
             # p.configureDebugVisualizer(p.COV_ENABLE_RENDERING,0)
             heightPerturbationRange = 0.05
 
             numHeightfieldRows = 256*2
             numHeightfieldColumns = 256*2
             heightfieldData = [0]*numHeightfieldRows*numHeightfieldColumns
+            height_prev = 0.0
             for j in range(int(numHeightfieldColumns/2)):
                 for i in range(int(numHeightfieldRows/2)):
                     height = random.uniform(0, heightPerturbationRange)  # uniform distribution
-                    heightfieldData[2*i+2*j*numHeightfieldRows] = height
+                    # height = 0.25*np.sin(2*np.pi*(i-128)/46)
+                    heightfieldData[2*i+2*j*numHeightfieldRows] = (height + height_prev) * 0.5
                     heightfieldData[2*i+1+2*j*numHeightfieldRows] = height
-                    heightfieldData[2*i+(2*j+1)*numHeightfieldRows] = height
+                    heightfieldData[2*i+(2*j+1)*numHeightfieldRows] = (height + height_prev) * 0.5
                     heightfieldData[2*i+1+(2*j+1)*numHeightfieldRows] = height
+                    height_prev = height
 
             terrainShape = pyb.createCollisionShape(shapeType=pyb.GEOM_HEIGHTFIELD, meshScale=[.05, .05, 1],
                                                     heightfieldTextureScaling=(numHeightfieldRows-1)/2,
@@ -242,14 +249,19 @@ class pybullet_simulator:
             pyb.resetBasePositionAndOrientation(self.planeId, [0, 0, 0], [0, 0, 0, 1])
             pyb.changeVisualShape(self.planeId, -1, rgbaColor=[1, 1, 1, 1])
 
-        # Add stairs with platform and bridge
-        # self.stairsId = pyb.loadURDF("../../../../../Documents/Git-Repositories/mpc-tsid/bauzil_stairs.urdf")#,
-        # basePosition=[-1.25, 3.5, -0.1],
-        # baseOrientation=pyb.getQuaternionFromEuler([0.0, 0.0, 3.1415]))
-        # pyb.changeDynamics(self.stairsId, -1, lateralFriction=1.0)
+        """self.stairsId = pyb.loadURDF("../../../../../Documents/Git-Repositories/mpc-tsid/bauzil_stairs.urdf",
+                                     basePosition=[0.0, 0.0, -0.3],
+                                     baseOrientation=pyb.getQuaternionFromEuler([0.0, 0.0, 0.0]))
+        pyb.changeDynamics(self.stairsId, -1, lateralFriction=1.0)"""
 
         if envID == 1:
 
+            # Add stairs with platform and bridge
+            """self.stairsId = pyb.loadURDF("../../../../../Documents/Git-Repositories/mpc-tsid/bauzil_stairs.urdf")  # ,
+            basePosition = [-1.25, 3.5, -0.1],
+            baseOrientation = pyb.getQuaternionFromEuler([0.0, 0.0, 3.1415])
+            pyb.changeDynamics(self.stairsId, -1, lateralFriction=1.0)"""
+
             mesh_scale = [1.0, 0.1, 0.02]
             visualShapeId = pyb.createVisualShape(shapeType=pyb.GEOM_MESH,
                                                   fileName="cube.obj",
@@ -311,7 +323,7 @@ class pybullet_simulator:
                                             useMaximalCoordinates=True)
                 pyb.changeDynamics(tmpId, -1, lateralFriction=1.0)
 
-            mesh_scale = [0.05, 0.05, 0.05]
+            mesh_scale = [0.1, 0.1, 0.1]
             visualShapeId = pyb.createVisualShape(shapeType=pyb.GEOM_MESH,
                                                   fileName="sphere_smooth.obj",
                                                   halfExtents=[0.5, 0.5, 0.1],
@@ -329,14 +341,14 @@ class pybullet_simulator:
                                                  baseInertialFramePosition=[0, 0, 0],
                                                  baseCollisionShapeIndex=collisionShapeId,
                                                  baseVisualShapeIndex=visualShapeId,
-                                                 basePosition=[-0.6, 0.9, 0.05],
+                                                 basePosition=[-0.6, 0.9, 0.1],
                                                  useMaximalCoordinates=True)
 
             self.sphereId2 = pyb.createMultiBody(baseMass=0.3,
                                                  baseInertialFramePosition=[0, 0, 0],
                                                  baseCollisionShapeIndex=collisionShapeId,
                                                  baseVisualShapeIndex=visualShapeId,
-                                                 basePosition=[0.6, 1.1, 0.05],
+                                                 basePosition=[0.6, 1.1, 0.1],
                                                  useMaximalCoordinates=True)
 
             # Flag to launch the two spheres in the environment toward the robot
@@ -430,16 +442,16 @@ class pybullet_simulator:
         if envID == 1:
             # Check if the robot is in front of the first sphere to trigger it
             if self.flag_sphere1 and (qmes12[1, 0] >= 0.9):
-                pyb.resetBaseVelocity(self.sphereId1, linearVelocity=[3.0, 0.0, 2.0])
+                pyb.resetBaseVelocity(self.sphereId1, linearVelocity=[2.5, 0.0, 2.0])
                 self.flag_sphere1 = False
 
             # Check if the robot is in front of the second sphere to trigger it
             if self.flag_sphere2 and (qmes12[1, 0] >= 1.1):
-                pyb.resetBaseVelocity(self.sphereId2, linearVelocity=[-3.0, 0.0, 2.0])
+                pyb.resetBaseVelocity(self.sphereId2, linearVelocity=[-2.5, 0.0, 2.0])
                 self.flag_sphere2 = False
 
         # Apply perturbation
-        self.apply_external_force(k,  1000, 400, np.array([0.0, 6.0, 0.0]), np.zeros((3,)))
+        # self.apply_external_force(k,  1000, 400, np.array([0.0, 6.0, 0.0]), np.zeros((3,)))
         """self.apply_external_force(k,  4000, 400, np.array([0.0, 2.0, 0.0]), np.zeros((3,)))
         self.apply_external_force(k,  8000, 400, np.array([0.0, -2.0, 0.0]), np.zeros((3,)))
         self.apply_external_force(k, 12000, 400, np.array([2.0, 0.0, 0.0]), np.zeros((3,)))
@@ -454,7 +466,7 @@ class pybullet_simulator:
 
         # Update the PyBullet camera on the robot position to do as if it was attached to the robot
         pyb.resetDebugVisualizerCamera(cameraDistance=0.6, cameraYaw=(0.0*RPY[2]*(180/3.1415)+45), cameraPitch=-39.9,
-                                       cameraTargetPosition=[qmes12[0, 0], qmes12[1, 0] + 0.0, 0.0])
+                                       cameraTargetPosition=[qmes12[0, 0], qmes12[1, 0] + 0.0, 0.0])  # qmes12[2, 0]-0.2])
 
         return 0
 
@@ -519,8 +531,8 @@ class pybullet_simulator:
         cpt = 0
 
         # PD settings
-        P = 0.33 * 3.0
-        D = 0.33 * np.array([1.0, 0.3, 0.3, 1.0, 0.3, 0.3, 1.0, 0.3, 0.3, 1.0, 0.3, 0.3])
+        P = 1.0 * 3.0
+        D = 0.05 * np.array([[1.0, 0.3, 0.3, 1.0, 0.3, 0.3, 1.0, 0.3, 0.3, 1.0, 0.3, 0.3]]).transpose()
 
         while True or np.max(np.abs(qtarget - qmes)) > 0.1:
 
@@ -536,8 +548,9 @@ class pybullet_simulator:
                 ev = dt_traj * cpt
                 A3 = 2 * (qmes - qtarget) / t1**3
                 A2 = (-3/2) * t1 * A3
-                qdes = qmes + A2*ev**2 + A3*ev**3
-            jointTorques = P * (qdes - qmes)  # + D * (self.vdes[6:, 0] - self.vmes[6:, 0])
+                qdes = qmes + A2*(ev**2) + A3*(ev**3)
+                vdes = 2*A2*ev + 3*A3*(ev**2)
+            jointTorques = P * (qdes - qmes) + D * (vdes - vmes)
 
             # Saturation to limit the maximal torque
             t_max = 2.5