-
Notifications
You must be signed in to change notification settings - Fork 582
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable CI for robot devices with mocked versions #398
base: main
Are you sure you want to change the base?
Conversation
334ddbd
to
96cc243
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First round, very cool!
I'm wondering how to test robots like Stretch that already have a built-in api. We'll probably need to mock the api itself and test that in test_robots.py
and test_control_robot.py
rather than its low level components. Let me know what you think.
Co-authored-by: Simon Alibert <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I addressed all comments!
while self.stop_event is None or not self.stop_event.is_set(): | ||
while not self.stop_event.is_set(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that self.stop_event is None
was not required.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall it is looking good! I think in the future we may want to remove the testing/mock still from the actual classes (like dynamixel.py
, etc). I feel like that should be kept separate eventually but for now I think it will work!
camera_ids = [] | ||
for device in rs.context().query_devices(): | ||
serial_number = int(device.get_info(rs.camera_info(SERIAL_NUMBER_INDEX))) | ||
for device in RSContext().query_devices(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: In the if...else
statement above you say from pyrealsense2 import context as RSContext # noqa: N812
I presume you do this so this line can become for device in context.query_devices():
. To do that you would need to import the mock values as follows:
from tests.mock_intelrealsenseimport camera_info as RSCameraInfo # noqa: N812
from tests.mock_intelrealsenseimport context as RSContext # noqa: N812
However, if you don't want to do that and keep this line as is then I would change the named imports above since they aren't being used anyways, i.e.
from pyrealsense2 import (
RSCameraInfo,
RSContext,
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am sorry I didnt understand.
This works:
from tests.mock_intelrealsense import RSCameraInfo
RSCameraInfo(SERIAL_NUMBER_INDEX)
This works:
from pyrealsense2 import camera_info as RSCameraInfo
RSCameraInfo(SERIAL_NUMBER_INDEX)
This works:
from pyrealsense2 import camera_info
camera_info(SERIAL_NUMBER_INDEX)
But this doesnt work:
from pyrealsense2 import camera_info as RSCameraInfo
camera_info(SERIAL_NUMBER_INDEX)
@@ -243,24 +262,37 @@ def connect(self): | |||
f"IntelRealSenseCamera({self.camera_index}) is already connected." | |||
) | |||
|
|||
config = rs.config() | |||
if self.mock: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Same comment as above, I would either import these values as named variables like you do when it is not mock, i.e., config
, format
, pipeline
, stream
, or leave them all unnamed like you do when it is mock.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am sorry I didnt understand
The original element name is config
but I rename it to RSConfig
to fit python CamelCase format for naming classes:
from pyrealsense2 import config as RSConfig
# Release camera to make it accessible for `find_camera_indices` | ||
del tmp_camera | ||
import cv2 | ||
from cv2 import ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I don't think you need this line... but I could be wrong ;)
import cv2
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed import cv2
and imported setNumThreads
to clarify:
from cv2 import (
CAP_PROP_FPS,
CAP_PROP_FRAME_HEIGHT,
CAP_PROP_FRAME_WIDTH,
VideoCapture,
setNumThreads,
)
# Use 1 thread to avoid blocking the main thread. Especially useful during data collection
# when other threads are used to save the images.
setNumThreads(1)
# will go sequentially through the code logic protected by a lock, instead of | ||
# in parallel. Also, we use Recursive Lock to avoid deadlock by allowing each | ||
# thread to acquire the lock multiple times. | ||
# TODO(rcadene, aliberts): Add RLock on every robot devices where it makes sense? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: By the way, @samzapo is really amazing with the multi-threading stuff. I can help a bit too but he's the expert if you need a second set of eyes.
# valid cameras. | ||
# Use 1 thread to avoid blocking the main thread. Especially useful during data collection | ||
# when other threads are used to save the images. | ||
cv2.setNumThreads(1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: What happens here if this is mock? Isn't cv2 only imported if it is not mock?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before my big change, cv2
was monkeypatched, and this would be an issue.
After my big change, cv2
isn't monkeypatched anymore, but we have this mock
boolean variable that imports it or not. setNumThreads
is only executed when mock=False
.
if self.mock:
from tests.mock_opencv import (
CAP_PROP_FPS,
CAP_PROP_FRAME_HEIGHT,
CAP_PROP_FRAME_WIDTH,
VideoCapture,
)
else:
from cv2 import (
CAP_PROP_FPS,
CAP_PROP_FRAME_HEIGHT,
CAP_PROP_FRAME_WIDTH,
VideoCapture,
setNumThreads,
)
# Use 1 thread to avoid blocking the main thread. Especially useful during data collection
# when other threads are used to save the images.
setNumThreads(1)
num_tries += 1 | ||
if num_tries > self.fps * 2: | ||
raise TimeoutError("Timed out waiting for async_read() to start.") | ||
# if num_tries > self.fps and (self.thread.ident is None or not self.thread.is_alive()): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I think you have some commented out code to delete:
# if num_tries > self.fps and (self.thread.ident is None or not self.thread.is_alive()):
# raise Exception(
# "The thread responsible for `self.async_read()` took too much time to start. There might be an issue. Verify that `self.thread.start()` has been called."
# )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. It was from Simon Alibert
@@ -154,6 +143,8 @@ | |||
NUM_READ_RETRY = 10 | |||
NUM_WRITE_RETRY = 10 | |||
|
|||
COMM_SUCCESS = 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I don't love that COMM_SUCCESS
is hardcoded here instead of being imported from dynamixel_sdk....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! Removed this line. Added dynamic import everywhere
if self.mock:
from tests.mock_dynamixel import GroupSyncWrite, COMM_SUCCESS
else:
from dynamixel_sdk import GroupSyncWrite, COMM_SUCCESS
```
@@ -359,6 +362,11 @@ def connect(self): | |||
f"DynamixelMotorsBus({self.port}) is already connected. Do not call `motors_bus.connect()` twice." | |||
) | |||
|
|||
if self.mock: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: It seems like what is being "mocked" is the dynamixel_sdk
... not the dynamixel
class itself. Should this class be called mock_dynamixel_sdk
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed!
- Renamed
mock_dynamixel.py
tomock_dynamixel_sdk.py
- Renamed
mock_intelrealsense.py
tomock_pyrealsense2.py
- Renamed
mock_opencv.py
tomock_cv2.py
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed Jess comments
camera_ids = [] | ||
for device in rs.context().query_devices(): | ||
serial_number = int(device.get_info(rs.camera_info(SERIAL_NUMBER_INDEX))) | ||
for device in RSContext().query_devices(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am sorry I didnt understand.
This works:
from tests.mock_intelrealsense import RSCameraInfo
RSCameraInfo(SERIAL_NUMBER_INDEX)
This works:
from pyrealsense2 import camera_info as RSCameraInfo
RSCameraInfo(SERIAL_NUMBER_INDEX)
This works:
from pyrealsense2 import camera_info
camera_info(SERIAL_NUMBER_INDEX)
But this doesnt work:
from pyrealsense2 import camera_info as RSCameraInfo
camera_info(SERIAL_NUMBER_INDEX)
@@ -243,24 +262,37 @@ def connect(self): | |||
f"IntelRealSenseCamera({self.camera_index}) is already connected." | |||
) | |||
|
|||
config = rs.config() | |||
if self.mock: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am sorry I didnt understand
The original element name is config
but I rename it to RSConfig
to fit python CamelCase format for naming classes:
from pyrealsense2 import config as RSConfig
# Release camera to make it accessible for `find_camera_indices` | ||
del tmp_camera | ||
import cv2 | ||
from cv2 import ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed import cv2
and imported setNumThreads
to clarify:
from cv2 import (
CAP_PROP_FPS,
CAP_PROP_FRAME_HEIGHT,
CAP_PROP_FRAME_WIDTH,
VideoCapture,
setNumThreads,
)
# Use 1 thread to avoid blocking the main thread. Especially useful during data collection
# when other threads are used to save the images.
setNumThreads(1)
# valid cameras. | ||
# Use 1 thread to avoid blocking the main thread. Especially useful during data collection | ||
# when other threads are used to save the images. | ||
cv2.setNumThreads(1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before my big change, cv2
was monkeypatched, and this would be an issue.
After my big change, cv2
isn't monkeypatched anymore, but we have this mock
boolean variable that imports it or not. setNumThreads
is only executed when mock=False
.
if self.mock:
from tests.mock_opencv import (
CAP_PROP_FPS,
CAP_PROP_FRAME_HEIGHT,
CAP_PROP_FRAME_WIDTH,
VideoCapture,
)
else:
from cv2 import (
CAP_PROP_FPS,
CAP_PROP_FRAME_HEIGHT,
CAP_PROP_FRAME_WIDTH,
VideoCapture,
setNumThreads,
)
# Use 1 thread to avoid blocking the main thread. Especially useful during data collection
# when other threads are used to save the images.
setNumThreads(1)
num_tries += 1 | ||
if num_tries > self.fps * 2: | ||
raise TimeoutError("Timed out waiting for async_read() to start.") | ||
# if num_tries > self.fps and (self.thread.ident is None or not self.thread.is_alive()): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. It was from Simon Alibert
@@ -154,6 +143,8 @@ | |||
NUM_READ_RETRY = 10 | |||
NUM_WRITE_RETRY = 10 | |||
|
|||
COMM_SUCCESS = 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! Removed this line. Added dynamic import everywhere
if self.mock:
from tests.mock_dynamixel import GroupSyncWrite, COMM_SUCCESS
else:
from dynamixel_sdk import GroupSyncWrite, COMM_SUCCESS
```
@@ -359,6 +362,11 @@ def connect(self): | |||
f"DynamixelMotorsBus({self.port}) is already connected. Do not call `motors_bus.connect()` twice." | |||
) | |||
|
|||
if self.mock: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed!
- Renamed
mock_dynamixel.py
tomock_dynamixel_sdk.py
- Renamed
mock_intelrealsense.py
tomock_pyrealsense2.py
- Renamed
mock_opencv.py
tomock_cv2.py
request.getfixturevalue("patch_builtins_input") | ||
|
||
if robot_type == "aloha": | ||
pytest.skip("TODO(rcadene): enable test once aloha_real and act_aloha_real are merged") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO(rcadene)
# Do not use `with self.lock` here, as it reduces fps | ||
if self.color_image is not None: | ||
return self.color_image |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@aliberts with self.lock
causing slow fps here
Before fix:
with self.lock:
if self.color_image is not None:
return self.color_image
After fix:
if self.color_image is not None:
return self.color_image
What this does
make_camera
andmake_motor
to instantiate specific camera and motor@require_camera
and@require_motor
that try to instantiate, and skip test if not possible (before we were using@require_motor
only)test_cameras.py
andtest_motors.py
instead of relying only onmake_robot
and@require_robot
mock=False
argument to classes and functions dynamically import mocked classes, functions, variablesmock
argument to test functionsTODO:
How it was tested
How to checkout & try? (for the reviewer)
Altogether: