committed by
Your Name
7 changed files with 726 additions and 143 deletions
-
3.gitignore
-
2teleop/image_server/image_client.py
-
289teleop/image_server/image_server.py
-
233teleop/robot_control/robot_hand_unitree.py
-
98teleop/teleop_hand_and_arm.py
-
13teleop/utils/episode_writer.py
-
231teleop/utils/rerun_visualizer.py
@ -0,0 +1,231 @@ |
|||||
|
import os |
||||
|
import json |
||||
|
import cv2 |
||||
|
import time |
||||
|
import rerun as rr |
||||
|
import rerun.blueprint as rrb |
||||
|
from datetime import datetime |
||||
|
|
||||
|
class RerunEpisodeReader: |
||||
|
def __init__(self, task_dir = ".", json_file="data.json"): |
||||
|
self.task_dir = task_dir |
||||
|
self.json_file = json_file |
||||
|
|
||||
|
def return_episode_data(self, episode_idx): |
||||
|
# Load episode data on-demand |
||||
|
episode_dir = os.path.join(self.task_dir, f"episode_{episode_idx:04d}") |
||||
|
json_path = os.path.join(episode_dir, self.json_file) |
||||
|
|
||||
|
if not os.path.exists(json_path): |
||||
|
raise FileNotFoundError(f"Episode {episode_idx} data.json not found.") |
||||
|
|
||||
|
with open(json_path, 'r', encoding='utf-8') as jsonf: |
||||
|
json_file = json.load(jsonf) |
||||
|
|
||||
|
episode_data = [] |
||||
|
|
||||
|
# Loop over the data entries and process each one |
||||
|
for item_data in json_file['data']: |
||||
|
# Process images and other data |
||||
|
colors = self._process_images(item_data, 'colors', episode_dir) |
||||
|
depths = self._process_images(item_data, 'depths', episode_dir) |
||||
|
audios = self._process_audio(item_data, 'audios', episode_dir) |
||||
|
|
||||
|
# Append the data in the item_data list |
||||
|
episode_data.append( |
||||
|
{ |
||||
|
'idx': item_data.get('idx', 0), |
||||
|
'colors': colors, |
||||
|
'depths': depths, |
||||
|
'states': item_data.get('states', {}), |
||||
|
'actions': item_data.get('actions', {}), |
||||
|
'tactiles': item_data.get('tactiles', {}), |
||||
|
'audios': audios, |
||||
|
} |
||||
|
) |
||||
|
|
||||
|
return episode_data |
||||
|
|
||||
|
def _process_images(self, item_data, data_type, dir_path): |
||||
|
images = {} |
||||
|
|
||||
|
for key, file_name in item_data.get(data_type, {}).items(): |
||||
|
if file_name: |
||||
|
file_path = os.path.join(dir_path, file_name) |
||||
|
if os.path.exists(file_path): |
||||
|
image = cv2.imread(file_path) |
||||
|
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) |
||||
|
images[key] = image |
||||
|
return images |
||||
|
|
||||
|
def _process_audio(self, item_data, data_type, episode_dir): |
||||
|
audio_data = {} |
||||
|
dir_path = os.path.join(episode_dir, data_type) |
||||
|
|
||||
|
for key, file_name in item_data.get(data_type, {}).items(): |
||||
|
if file_name: |
||||
|
file_path = os.path.join(dir_path, file_name) |
||||
|
if os.path.exists(file_path): |
||||
|
pass # Handle audio data if needed |
||||
|
return audio_data |
||||
|
|
||||
|
class RerunLogger: |
||||
|
def __init__(self, prefix = "", IdxRangeBoundary = 30, memory_limit = "200MB"): |
||||
|
rr.init(datetime.now().strftime("Runtime_%Y%m%d_%H%M%S")) |
||||
|
rr.spawn(memory_limit = memory_limit) |
||||
|
|
||||
|
self.prefix = prefix |
||||
|
self.IdxRangeBoundary = IdxRangeBoundary |
||||
|
# Set up blueprint for live visualization |
||||
|
if self.IdxRangeBoundary: |
||||
|
self.setup_blueprint() |
||||
|
|
||||
|
def setup_blueprint(self): |
||||
|
data_plot_paths = [ |
||||
|
f"{self.prefix}left_arm", |
||||
|
f"{self.prefix}right_arm", |
||||
|
f"{self.prefix}left_hand", |
||||
|
f"{self.prefix}right_hand" |
||||
|
] |
||||
|
image_plot_paths = [ |
||||
|
f"{self.prefix}colors/color_0", |
||||
|
f"{self.prefix}colors/color_1", |
||||
|
f"{self.prefix}colors/color_2", |
||||
|
f"{self.prefix}colors/color_3" |
||||
|
] |
||||
|
|
||||
|
views = [] |
||||
|
for plot_path in data_plot_paths: |
||||
|
view = rrb.TimeSeriesView( |
||||
|
origin = plot_path, |
||||
|
time_ranges=[ |
||||
|
rrb.VisibleTimeRange( |
||||
|
"idx", |
||||
|
start = rrb.TimeRangeBoundary.cursor_relative(seq = -self.IdxRangeBoundary), |
||||
|
end = rrb.TimeRangeBoundary.cursor_relative(), |
||||
|
) |
||||
|
], |
||||
|
plot_legend = rrb.PlotLegend(visible = True), |
||||
|
) |
||||
|
views.append(view) |
||||
|
|
||||
|
for plot_path in image_plot_paths: |
||||
|
view = rrb.Spatial2DView( |
||||
|
origin = plot_path, |
||||
|
time_ranges=[ |
||||
|
rrb.VisibleTimeRange( |
||||
|
"idx", |
||||
|
start = rrb.TimeRangeBoundary.cursor_relative(seq = -self.IdxRangeBoundary), |
||||
|
end = rrb.TimeRangeBoundary.cursor_relative(), |
||||
|
) |
||||
|
], |
||||
|
) |
||||
|
views.append(view) |
||||
|
|
||||
|
grid = rrb.Grid(contents = views, |
||||
|
grid_columns=2, |
||||
|
column_shares=[1, 1], |
||||
|
row_shares=[1, 1, 0.5], |
||||
|
) |
||||
|
rr.send_blueprint(grid) |
||||
|
|
||||
|
|
||||
|
def log_item_data(self, item_data: dict): |
||||
|
rr.set_time_sequence("idx", item_data.get('idx', 0)) |
||||
|
|
||||
|
# Log states |
||||
|
states = item_data.get('states', {}) or {} |
||||
|
for part, state_info in states.items(): |
||||
|
if state_info: |
||||
|
values = state_info.get('qpos', []) |
||||
|
for idx, val in enumerate(values): |
||||
|
rr.log(f"{self.prefix}{part}/states/qpos/{idx}", rr.Scalar(val)) |
||||
|
|
||||
|
# Log actions |
||||
|
actions = item_data.get('actions', {}) or {} |
||||
|
for part, action_info in actions.items(): |
||||
|
if action_info: |
||||
|
values = action_info.get('qpos', []) |
||||
|
for idx, val in enumerate(values): |
||||
|
rr.log(f"{self.prefix}{part}/actions/qpos/{idx}", rr.Scalar(val)) |
||||
|
|
||||
|
# Log colors (images) |
||||
|
colors = item_data.get('colors', {}) or {} |
||||
|
for color_key, color_val in colors.items(): |
||||
|
if color_val is not None: |
||||
|
rr.log(f"{self.prefix}colors/{color_key}", rr.Image(color_val)) |
||||
|
|
||||
|
# Log depths (images) |
||||
|
depths = item_data.get('depths', {}) or {} |
||||
|
for depth_key, depth_val in depths.items(): |
||||
|
if depth_val is not None: |
||||
|
# rr.log(f"{self.prefix}depths/{depth_key}", rr.Image(depth_val)) |
||||
|
pass # Handle depth if needed |
||||
|
|
||||
|
# Log tactile if needed |
||||
|
tactiles = item_data.get('tactiles', {}) or {} |
||||
|
for hand, tactile_vals in tactiles.items(): |
||||
|
if tactile_vals is not None: |
||||
|
pass # Handle tactile if needed |
||||
|
|
||||
|
# Log audios if needed |
||||
|
audios = item_data.get('audios', {}) or {} |
||||
|
for audio_key, audio_val in audios.items(): |
||||
|
if audio_val is not None: |
||||
|
pass # Handle audios if needed |
||||
|
|
||||
|
def log_episode_data(self, episode_data: list): |
||||
|
for item_data in episode_data: |
||||
|
self.log_item_data(item_data) |
||||
|
|
||||
|
|
||||
|
if __name__ == "__main__": |
||||
|
import gdown |
||||
|
import zipfile |
||||
|
import os |
||||
|
url = "https://drive.google.com/file/d/1f5UuFl1z_gaByg_7jDRj1_NxfJZh2evD/view?usp=sharing" |
||||
|
zip_file = "rerun_testdata.zip" |
||||
|
if not os.path.exists("episode_0006"): |
||||
|
if not os.path.exists(zip_file): |
||||
|
file_id = url.split('/')[5] |
||||
|
gdown.download(id=file_id, output=zip_file, quiet=False) |
||||
|
print("download ok.") |
||||
|
with zipfile.ZipFile(zip_file, 'r') as zip_ref: |
||||
|
zip_ref.extractall(".") |
||||
|
print("uncompress ok.") |
||||
|
os.remove(zip_file) |
||||
|
print("clean file ok.") |
||||
|
else: |
||||
|
print("rerun_testdata exits.") |
||||
|
|
||||
|
episode_reader = RerunEpisodeReader() |
||||
|
episode_data6 = episode_reader.return_episode_data(6) |
||||
|
episode_data8 = episode_reader.return_episode_data(8) |
||||
|
|
||||
|
# Example 1: Offline Visualization |
||||
|
user_input = input("Please enter the start signal (enter 'off' to start the subsequent program):\n") |
||||
|
if user_input.lower() == 'off': |
||||
|
print("Starting offline visualization...") |
||||
|
offline_logger = RerunLogger(prefix="offline/") |
||||
|
offline_logger.log_episode_data(episode_data6) |
||||
|
print("Offline visualization completed.") |
||||
|
|
||||
|
# Example 2: Online Visualization with Fixed Time Window |
||||
|
user_input = input("Please enter the start signal (enter 'on' to start the subsequent program):\n") |
||||
|
if user_input.lower() == 'on': |
||||
|
print("Starting online visualization with fixed idx size...") |
||||
|
online_logger = RerunLogger(prefix="online/", IdxRangeBoundary = 60) |
||||
|
for item_data in episode_data6: |
||||
|
online_logger.log_item_data(item_data) |
||||
|
time.sleep(0.033) # 30hz |
||||
|
print("Online visualization completed.") |
||||
|
|
||||
|
# Example 3: Online Visualization with Fixed Time Window |
||||
|
user_input = input("Please enter the start signal (enter 'on' to start the subsequent program):\n") |
||||
|
if user_input.lower() == 'on': |
||||
|
print("Starting online visualization with fixed idx size...") |
||||
|
online_logger = RerunLogger(prefix="online/", IdxRangeBoundary = 60) |
||||
|
for item_data in episode_data8: |
||||
|
online_logger.log_item_data(item_data) |
||||
|
time.sleep(0.033) # 30hz |
||||
|
print("Online visualization completed.") |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue