Skip to content

Commit 03900f7

Browse files
committed
update
1 parent 9485c48 commit 03900f7

23 files changed

Lines changed: 1390 additions & 328 deletions

.DS_Store

0 Bytes
Binary file not shown.

README.md

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
VisionProTeleop
22
===========
33

4+
> **🎉 UPDATE: Now supporting Low-Latency Video Streaming!** You can now stream back your robot's camera feed back to Vision Pro via webRTC protocol, alongside the original hand tracking data stream.
5+
No complicated network setting required. Download the app, `pip install avp_stream`, and you're done!
6+
47
![CleanShot 2024-03-03 at 13 55 11@2x](https://github.com/Improbable-AI/VisionProTeleop/assets/68195716/d87a906c-ccf3-4e2d-bd25-a66dc0df803b)
58

69

710

8-
Wanna use your new Apple Vision Pro to control your robot? Wanna record how you navigate and manipulate the world to train your robot?
9-
This VisionOS app and python library streams your Head + Wrist + Hand Tracking result via gRPC over a WiFi network, so any robots connected to the same wifi network can subscribe and use.
11+
Wanna use your new Apple Vision Pro to control your robot? Wanna record how you navigate and manipulate the world?
12+
13+
This VisionOS app and python library streams your Head + Wrist + Hand Tracking result via gRPC over a WiFi network, so any robots connected to the same wifi network can subscribe and use. **It can also stream stereo (or mono) camera feeds from your robot, back to the Vision Pro.**
1014

1115
> **For a more detailed explanation, check out this short [paper](./assets/short_paper_new.pdf).**
1216
@@ -51,13 +55,57 @@ Then, add this code snippet to any of your projects you were developing:
5155
```python
5256
from avp_stream import VisionProStreamer
5357
avp_ip = "10.31.181.201" # example IP
54-
s = VisionProStreamer(ip = avp_ip, record = True)
58+
s = VisionProStreamer(ip = avp_ip)
59+
60+
while True:
61+
r = s.latest
62+
print(r['head'], r['right_wrist'], r['right_fingers'])
63+
```
64+
65+
### Step 4. [🎉NEW FEATURE] Stream video feeds from your robot back to Vision Pro!
66+
67+
Streaming your robot's video feed back to Vision Pro requires one additional line: `start_video_streaming`.
68+
69+
```python
70+
from avp_stream import VisionProStreamer
71+
avp_ip = "10.31.181.201" # example IP
72+
s = VisionProStreamer(ip = avp_ip)
73+
74+
# you can simply start a video stream
75+
# by defining which video device you want to use
76+
s.start_video_streaming(device="/dev/video0", format="v4l2", \
77+
size="640x480", fps=30)
5578

5679
while True:
5780
r = s.latest
5881
print(r['head'], r['right_wrist'], r['right_fingers'])
5982
```
6083

84+
You can also:
85+
86+
- image-process the camera frames before you send it to Vision Pro
87+
```python
88+
s = VisionProStreamer(ip = avp_ip)
89+
# define your own image processing function, and register
90+
s.register_frame_callback(my_own_processor)
91+
s.start_video_streaming(device="/dev/video0", format="v4l2", \
92+
size="640x480", fps=30)
93+
```
94+
- work without a physical camera and send over synthetically generated frames (i.e., rendered from simulation)
95+
```python
96+
s = VisionProStreamer(ip = avp_ip)
97+
# define your own image generating function, and register
98+
s.register_frame_callback(synthetic_frame_generator)
99+
s.start_video_streaming(device = None, format = None, \
100+
size="1280x720", fps=60)
101+
```
102+
103+
which is explained in detail in [examples](examples) folder.
104+
105+
> **NOTE**: Finding the right combination of `device`, `format`, `size`, and `fps` can sometime be tricky, since camera models only support certain combination. Also, depending on your operation system and how you plugged the camera, the `device` might have been mounted on a different directory.
106+
107+
108+
61109

62110

63111
## Available Data
@@ -90,6 +138,8 @@ r['left_wrist_roll']: float
90138
```
91139

92140

141+
142+
93143
### Axis Convention
94144

95145
Refer to the image below to see how the axis are defined for your head, wrist, and fingers.
@@ -104,6 +154,19 @@ Refer to the image below to see how the axis are defined for your head, wrist, a
104154
Refer to the image above to see what order the joints are represented in each hand's skeleton.
105155

106156

157+
## App Features
158+
159+
### Status Window
160+
161+
162+
163+
### Modifying the Video Viewport
164+
165+
166+
167+
### Temporarily Hiding Video Stream
168+
169+
107170
## Acknowledgements
108171

109172
We acknowledge support from Hyundai Motor Company and ARO MURI grant number W911NF-23-1-0277.

Tracking Streamer.xcodeproj/project.pbxproj

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
C1893C762B93C1AC00F2269D /* handtracking.grpc.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1893C742B93C1AC00F2269D /* handtracking.grpc.swift */; };
3131
C1893C772B93C1AC00F2269D /* handtracking.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1893C752B93C1AC00F2269D /* handtracking.pb.swift */; };
3232
C1DA80C42ECE377600B55CC2 /* StatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1DA80C32ECE377600B55CC2 /* StatusView.swift */; };
33+
C1DA80C72ECE8C6F00B55CC2 /* ViewControlSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1DA80C62ECE8C6F00B55CC2 /* ViewControlSettings.swift */; };
3334
C1E05F782ECD84DF0018C506 /* LiveKitWebRTC in Frameworks */ = {isa = PBXBuildFile; productRef = C1E05F772ECD84DF0018C506 /* LiveKitWebRTC */; };
3435
C1E05F7C2ECD850B0018C506 /* ImmersiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1E05F7A2ECD850B0018C506 /* ImmersiveView.swift */; };
3536
C1E05F7D2ECD850B0018C506 /* ImageData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1E05F792ECD850B0018C506 /* ImageData.swift */; };
@@ -65,8 +66,9 @@
6566
16EE5E9A2B576DB000D354ED /* 🧩Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "🧩Model.swift"; sourceTree = "<group>"; };
6667
C1893C742B93C1AC00F2269D /* handtracking.grpc.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = handtracking.grpc.swift; path = avp_stream/grpc_msg/handtracking.grpc.swift; sourceTree = SOURCE_ROOT; };
6768
C1893C752B93C1AC00F2269D /* handtracking.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = handtracking.pb.swift; path = avp_stream/grpc_msg/handtracking.pb.swift; sourceTree = SOURCE_ROOT; };
68-
C19896F42B7D5099003BAF99 /* VisionProTeleop.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = VisionProTeleop.entitlements; sourceTree = "<group>"; };
6969
C1DA80C32ECE377600B55CC2 /* StatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusView.swift; sourceTree = "<group>"; };
70+
C1DA80C52ECE821800B55CC2 /* VisionProTeleop.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = VisionProTeleop.entitlements; sourceTree = "<group>"; };
71+
C1DA80C62ECE8C6F00B55CC2 /* ViewControlSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewControlSettings.swift; sourceTree = "<group>"; };
7072
C1E05F792ECD850B0018C506 /* ImageData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageData.swift; sourceTree = "<group>"; };
7173
C1E05F7A2ECD850B0018C506 /* ImmersiveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImmersiveView.swift; sourceTree = "<group>"; };
7274
C1E05F7B2ECD850B0018C506 /* WebRTCClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRTCClient.swift; sourceTree = "<group>"; };
@@ -142,9 +144,9 @@
142144
165ADB492B4B71B0008A756F /* Tracking Streamer */ = {
143145
isa = PBXGroup;
144146
children = (
147+
C1DA80C52ECE821800B55CC2 /* VisionProTeleop.entitlements */,
145148
C1893C742B93C1AC00F2269D /* handtracking.grpc.swift */,
146149
C1893C752B93C1AC00F2269D /* handtracking.pb.swift */,
147-
C19896F42B7D5099003BAF99 /* VisionProTeleop.entitlements */,
148150
165ADB4E2B4B71B0008A756F /* App.swift */,
149151
165ADB502B4B71B0008A756F /* ContentView.swift */,
150152
C1E05F792ECD850B0018C506 /* ImageData.swift */,
@@ -153,6 +155,7 @@
153155
16688DE52B59F35F004CE12B /* 🥽AppModel.swift */,
154156
1642FAB82B4D6CAA0084F9ED /* 🌐RealityView.swift */,
155157
C1DA80C32ECE377600B55CC2 /* StatusView.swift */,
158+
C1DA80C62ECE8C6F00B55CC2 /* ViewControlSettings.swift */,
156159
16EE5E982B576DA800D354ED /* 🧩Name.swift */,
157160
16EE5E9A2B576DB000D354ED /* 🧩Model.swift */,
158161
160ED7972B4B74C5002AD987 /* 🧑HeadTrackingComponent&System.swift */,
@@ -261,6 +264,7 @@
261264
16EE5E992B576DA800D354ED /* 🧩Name.swift in Sources */,
262265
160ED7982B4B74C5002AD987 /* 🧑HeadTrackingComponent&System.swift in Sources */,
263266
1642FAB92B4D6CAA0084F9ED /* 🌐RealityView.swift in Sources */,
267+
C1DA80C72ECE8C6F00B55CC2 /* ViewControlSettings.swift in Sources */,
264268
1642FAB52B4D54A60084F9ED /* 🛠️SettingPanel.swift in Sources */,
265269
16253CFB2B4E2FCB0028F0E2 /* 📏Unit.swift in Sources */,
266270
165ADB512B4B71B0008A756F /* ContentView.swift in Sources */,
@@ -411,19 +415,23 @@
411415
CODE_SIGN_ENTITLEMENTS = "Tracking Streamer/VisionProTeleop.entitlements";
412416
CODE_SIGN_IDENTITY = "Apple Development";
413417
CODE_SIGN_STYLE = Automatic;
414-
CURRENT_PROJECT_VERSION = 1;
418+
CURRENT_PROJECT_VERSION = 2;
415419
DEVELOPMENT_ASSET_PATHS = "";
416420
DEVELOPMENT_TEAM = ATTMC2WVK2;
417421
ENABLE_PREVIEWS = YES;
418422
GENERATE_INFOPLIST_FILE = YES;
419423
INFOPLIST_FILE = "$(TARGET_NAME)/Supporting files/Info.plist";
420424
INFOPLIST_KEY_CFBundleDisplayName = "Tracking Streamer";
425+
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
421426
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
427+
INFOPLIST_KEY_NSCameraUsageDescription = "This app might use your cameras to sense the surrounding environments. ";
428+
INFOPLIST_KEY_NSHandsTrackingUsageDescription = "This app tracks your hand to teleoperate a robot. ";
429+
INFOPLIST_KEY_NSWorldSensingUsageDescription = "This app senses your surronuding world to reconstruct a simulation.";
422430
LD_RUNPATH_SEARCH_PATHS = (
423431
"$(inherited)",
424432
"@executable_path/Frameworks",
425433
);
426-
MARKETING_VERSION = 1.0;
434+
MARKETING_VERSION = 2.0;
427435
PRODUCT_BUNDLE_IDENTIFIER = improbable.younghyo.DexTeleop2;
428436
PRODUCT_NAME = "$(TARGET_NAME)";
429437
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -443,19 +451,23 @@
443451
CODE_SIGN_ENTITLEMENTS = "Tracking Streamer/VisionProTeleop.entitlements";
444452
CODE_SIGN_IDENTITY = "Apple Development";
445453
CODE_SIGN_STYLE = Automatic;
446-
CURRENT_PROJECT_VERSION = 1;
454+
CURRENT_PROJECT_VERSION = 2;
447455
DEVELOPMENT_ASSET_PATHS = "";
448456
DEVELOPMENT_TEAM = ATTMC2WVK2;
449457
ENABLE_PREVIEWS = YES;
450458
GENERATE_INFOPLIST_FILE = YES;
451459
INFOPLIST_FILE = "$(TARGET_NAME)/Supporting files/Info.plist";
452460
INFOPLIST_KEY_CFBundleDisplayName = "Tracking Streamer";
461+
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
453462
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
463+
INFOPLIST_KEY_NSCameraUsageDescription = "This app might use your cameras to sense the surrounding environments. ";
464+
INFOPLIST_KEY_NSHandsTrackingUsageDescription = "This app tracks your hand to teleoperate a robot. ";
465+
INFOPLIST_KEY_NSWorldSensingUsageDescription = "This app senses your surronuding world to reconstruct a simulation.";
454466
LD_RUNPATH_SEARCH_PATHS = (
455467
"$(inherited)",
456468
"@executable_path/Frameworks",
457469
);
458-
MARKETING_VERSION = 1.0;
470+
MARKETING_VERSION = 2.0;
459471
PRODUCT_BUNDLE_IDENTIFIER = improbable.younghyo.DexTeleop2;
460472
PRODUCT_NAME = "$(TARGET_NAME)";
461473
PROVISIONING_PROFILE_SPECIFIER = "";

Tracking Streamer/ContentView.swift

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -149,17 +149,14 @@ func getIPAddress() -> String {
149149

150150
guard let interface = ptr?.pointee else { return "" }
151151
let addrFamily = interface.ifa_addr.pointee.sa_family
152-
if addrFamily == UInt8(AF_INET) || addrFamily == UInt8(AF_INET6) {
153-
154-
// wifi = ["en0"]
155-
// wired = ["en2", "en3", "en4"]
156-
// cellular = ["pdp_ip0","pdp_ip1","pdp_ip2","pdp_ip3"]
157-
152+
if addrFamily == UInt8(AF_INET) {
153+
// Only check en0 (WiFi interface)
158154
let name: String = String(cString: (interface.ifa_name))
159-
if name == "en0" || name == "en2" || name == "en3" || name == "en4" || name == "pdp_ip0" || name == "pdp_ip1" || name == "pdp_ip2" || name == "pdp_ip3" {
155+
if name == "en0" {
160156
var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
161157
getnameinfo(interface.ifa_addr, socklen_t((interface.ifa_addr.pointee.sa_len)), &hostname, socklen_t(hostname.count), nil, socklen_t(0), NI_NUMERICHOST)
162158
address = String(cString: hostname)
159+
break // Found en0, no need to continue
163160
}
164161
}
165162
}

0 commit comments

Comments
 (0)