[skip ci] refactor(cameras): add warmup read + images different size testing opencv + compressed test artefacts
This commit is contained in:
@@ -27,7 +27,7 @@ repos:
|
|||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v5.0.0
|
rev: v5.0.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-added-large-files
|
#- id: check-added-large-files
|
||||||
- id: debug-statements
|
- id: debug-statements
|
||||||
- id: check-merge-conflict
|
- id: check-merge-conflict
|
||||||
- id: check-case-conflict
|
- id: check-case-conflict
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class Camera(abc.ABC):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def connect(self) -> None:
|
def connect(self, do_warmup_read: bool = True) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
|
|||||||
@@ -302,7 +302,7 @@ class RealSenseCamera(Camera):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# NOTE(Steven): Add a wamr-up period time config
|
# NOTE(Steven): Add a wamr-up period time config
|
||||||
def connect(self):
|
def connect(self, do_warmup_read: bool = True):
|
||||||
"""
|
"""
|
||||||
Connects to the RealSense camera specified in the configuration.
|
Connects to the RealSense camera specified in the configuration.
|
||||||
|
|
||||||
@@ -337,8 +337,9 @@ class RealSenseCamera(Camera):
|
|||||||
logger.debug(f"Validating stream configuration for {self.serial_number}...")
|
logger.debug(f"Validating stream configuration for {self.serial_number}...")
|
||||||
self._validate_capture_settings()
|
self._validate_capture_settings()
|
||||||
|
|
||||||
logger.debug(f"Reading a warm-up frame for {self.serial_number}...")
|
if do_warmup_read:
|
||||||
self.read() # NOTE(Steven): For now we just read one frame, we could also loop for X secs
|
logger.debug(f"Reading a warm-up frame for {self.serial_number}...")
|
||||||
|
self.read() # NOTE(Steven): For now we just read one frame, we could also loop for X secs
|
||||||
|
|
||||||
logger.info(f"Camera {self.serial_number} connected and configured successfully.")
|
logger.info(f"Camera {self.serial_number} connected and configured successfully.")
|
||||||
|
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ class OpenCVCamera(Camera):
|
|||||||
self._validate_fps()
|
self._validate_fps()
|
||||||
self._validate_width_and_height()
|
self._validate_width_and_height()
|
||||||
|
|
||||||
def connect(self):
|
def connect(self, do_warmup_read: bool = True):
|
||||||
"""
|
"""
|
||||||
Connects to the OpenCV camera specified in the configuration.
|
Connects to the OpenCV camera specified in the configuration.
|
||||||
|
|
||||||
@@ -206,8 +206,9 @@ class OpenCVCamera(Camera):
|
|||||||
logger.debug(f"Successfully opened camera {self.index_or_path}. Applying configuration...")
|
logger.debug(f"Successfully opened camera {self.index_or_path}. Applying configuration...")
|
||||||
self._configure_capture_settings()
|
self._configure_capture_settings()
|
||||||
|
|
||||||
logger.debug(f"Reading a warm-up frame for {self.serial_number}...")
|
if do_warmup_read:
|
||||||
self.read() # NOTE(Steven): For now we just read one frame, we could also loop for X secs\
|
logger.debug(f"Reading a warm-up frame for {self.index_or_path}...")
|
||||||
|
self.read() # NOTE(Steven): For now we just read one frame, we could also loop for X secs\
|
||||||
|
|
||||||
logger.debug(f"Camera {self.index_or_path} connected and configured successfully.")
|
logger.debug(f"Camera {self.index_or_path} connected and configured successfully.")
|
||||||
|
|
||||||
|
|||||||
BIN
tests/artifacts/cameras/compressed_bag.tar.bz2
Normal file
BIN
tests/artifacts/cameras/compressed_bag.tar.bz2
Normal file
Binary file not shown.
BIN
tests/artifacts/cameras/compressed_fakecams.tar.bz2
Normal file
BIN
tests/artifacts/cameras/compressed_fakecams.tar.bz2
Normal file
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 753 KiB |
Binary file not shown.
@@ -19,6 +19,8 @@
|
|||||||
# pytest tests/cameras/test_opencv.py::test_connect
|
# pytest tests/cameras/test_opencv.py::test_connect
|
||||||
# ```
|
# ```
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@@ -26,8 +28,6 @@ from lerobot.common.cameras.configs import Cv2Rotation
|
|||||||
from lerobot.common.cameras.opencv import OpenCVCamera, OpenCVCameraConfig
|
from lerobot.common.cameras.opencv import OpenCVCamera, OpenCVCameraConfig
|
||||||
from lerobot.common.errors import DeviceAlreadyConnectedError, DeviceNotConnectedError
|
from lerobot.common.errors import DeviceAlreadyConnectedError, DeviceNotConnectedError
|
||||||
|
|
||||||
# NOTE(Steven): Patch get/set calls
|
|
||||||
|
|
||||||
|
|
||||||
def test_base_class_implementation():
|
def test_base_class_implementation():
|
||||||
config = OpenCVCameraConfig(index_or_path=0)
|
config = OpenCVCameraConfig(index_or_path=0)
|
||||||
@@ -36,21 +36,21 @@ def test_base_class_implementation():
|
|||||||
|
|
||||||
|
|
||||||
def test_connect():
|
def test_connect():
|
||||||
config = OpenCVCameraConfig(index_or_path="tests/artifacts/cameras/fake_cam.png")
|
config = OpenCVCameraConfig(index_or_path="tests/artifacts/cameras/fakecam_sd_640x480.png")
|
||||||
camera = OpenCVCamera(config)
|
camera = OpenCVCamera(config)
|
||||||
|
|
||||||
camera.connect()
|
camera.connect(do_warmup_read=False)
|
||||||
|
|
||||||
assert camera.is_connected
|
assert camera.is_connected
|
||||||
|
|
||||||
|
|
||||||
def test_connect_already_connected():
|
def test_connect_already_connected():
|
||||||
config = OpenCVCameraConfig(index_or_path="tests/artifacts/cameras/fake_cam.png")
|
config = OpenCVCameraConfig(index_or_path="tests/artifacts/cameras/fakecam_sd_640x480.png")
|
||||||
camera = OpenCVCamera(config)
|
camera = OpenCVCamera(config)
|
||||||
camera.connect()
|
camera.connect(do_warmup_read=False)
|
||||||
|
|
||||||
with pytest.raises(DeviceAlreadyConnectedError):
|
with pytest.raises(DeviceAlreadyConnectedError):
|
||||||
camera.connect()
|
camera.connect(do_warmup_read=False)
|
||||||
|
|
||||||
|
|
||||||
def test_connect_invalid_camera_path():
|
def test_connect_invalid_camera_path():
|
||||||
@@ -58,25 +58,34 @@ def test_connect_invalid_camera_path():
|
|||||||
camera = OpenCVCamera(config)
|
camera = OpenCVCamera(config)
|
||||||
|
|
||||||
with pytest.raises(ConnectionError):
|
with pytest.raises(ConnectionError):
|
||||||
camera.connect()
|
camera.connect(do_warmup_read=False)
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_width_connect():
|
def test_invalid_width_connect():
|
||||||
config = OpenCVCameraConfig(
|
config = OpenCVCameraConfig(
|
||||||
index_or_path="tests/artifacts/cameras/fake_cam.png",
|
index_or_path="tests/artifacts/cameras/fakecam_sd_640x480.png",
|
||||||
width=99999, # Invalid width to trigger error
|
width=99999, # Invalid width to trigger error
|
||||||
height=480,
|
height=480,
|
||||||
)
|
)
|
||||||
camera = OpenCVCamera(config)
|
camera = OpenCVCamera(config)
|
||||||
|
|
||||||
with pytest.raises(RuntimeError):
|
with pytest.raises(RuntimeError):
|
||||||
camera.connect()
|
camera.connect(do_warmup_read=False)
|
||||||
|
|
||||||
|
|
||||||
def test_read():
|
@pytest.mark.parametrize(
|
||||||
config = OpenCVCameraConfig(index_or_path="tests/artifacts/cameras/fake_cam.png")
|
"index_or_path",
|
||||||
|
[
|
||||||
|
"tests/artifacts/cameras/fakecam_sd_640x480.png",
|
||||||
|
"tests/artifacts/cameras/fakecam_hd_1280x720.png",
|
||||||
|
"tests/artifacts/cameras/fakecam_fullhd_1920x1080.png",
|
||||||
|
"tests/artifacts/cameras/fakecam_square_512x512.png",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_read(index_or_path):
|
||||||
|
config = OpenCVCameraConfig(index_or_path=index_or_path)
|
||||||
camera = OpenCVCamera(config)
|
camera = OpenCVCamera(config)
|
||||||
camera.connect()
|
camera.connect(do_warmup_read=False)
|
||||||
|
|
||||||
img = camera.read()
|
img = camera.read()
|
||||||
|
|
||||||
@@ -84,7 +93,7 @@ def test_read():
|
|||||||
|
|
||||||
|
|
||||||
def test_read_before_connect():
|
def test_read_before_connect():
|
||||||
config = OpenCVCameraConfig(index_or_path="tests/artifacts/cameras/fake_cam.png")
|
config = OpenCVCameraConfig(index_or_path="tests/artifacts/cameras/fakecam_sd_640x480.png")
|
||||||
camera = OpenCVCamera(config)
|
camera = OpenCVCamera(config)
|
||||||
|
|
||||||
with pytest.raises(DeviceNotConnectedError):
|
with pytest.raises(DeviceNotConnectedError):
|
||||||
@@ -92,9 +101,9 @@ def test_read_before_connect():
|
|||||||
|
|
||||||
|
|
||||||
def test_disconnect():
|
def test_disconnect():
|
||||||
config = OpenCVCameraConfig(index_or_path="tests/artifacts/cameras/fake_cam.png")
|
config = OpenCVCameraConfig(index_or_path="tests/artifacts/cameras/fakecam_sd_640x480.png")
|
||||||
camera = OpenCVCamera(config)
|
camera = OpenCVCamera(config)
|
||||||
camera.connect()
|
camera.connect(do_warmup_read=False)
|
||||||
|
|
||||||
camera.disconnect()
|
camera.disconnect()
|
||||||
|
|
||||||
@@ -102,17 +111,26 @@ def test_disconnect():
|
|||||||
|
|
||||||
|
|
||||||
def test_disconnect_before_connect():
|
def test_disconnect_before_connect():
|
||||||
config = OpenCVCameraConfig(index_or_path="tests/artifacts/cameras/fake_cam.png")
|
config = OpenCVCameraConfig(index_or_path="tests/artifacts/cameras/fakecam_sd_640x480.png")
|
||||||
camera = OpenCVCamera(config)
|
camera = OpenCVCamera(config)
|
||||||
|
|
||||||
with pytest.raises(DeviceNotConnectedError):
|
with pytest.raises(DeviceNotConnectedError):
|
||||||
_ = camera.disconnect()
|
_ = camera.disconnect()
|
||||||
|
|
||||||
|
|
||||||
def test_async_read():
|
@pytest.mark.parametrize(
|
||||||
config = OpenCVCameraConfig(index_or_path="tests/artifacts/cameras/fake_cam.png")
|
"index_or_path",
|
||||||
|
[
|
||||||
|
"tests/artifacts/cameras/fakecam_sd_640x480.png",
|
||||||
|
"tests/artifacts/cameras/fakecam_hd_1280x720.png",
|
||||||
|
"tests/artifacts/cameras/fakecam_fullhd_1920x1080.png",
|
||||||
|
"tests/artifacts/cameras/fakecam_square_512x512.png",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_async_read(index_or_path):
|
||||||
|
config = OpenCVCameraConfig(index_or_path=index_or_path)
|
||||||
camera = OpenCVCamera(config)
|
camera = OpenCVCamera(config)
|
||||||
camera.connect()
|
camera.connect(do_warmup_read=False)
|
||||||
|
|
||||||
img = camera.async_read()
|
img = camera.async_read()
|
||||||
|
|
||||||
@@ -123,9 +141,9 @@ def test_async_read():
|
|||||||
|
|
||||||
|
|
||||||
def test_async_read_timeout():
|
def test_async_read_timeout():
|
||||||
config = OpenCVCameraConfig(index_or_path="tests/artifacts/cameras/fake_cam.png")
|
config = OpenCVCameraConfig(index_or_path="tests/artifacts/cameras/fakecam_sd_640x480.png")
|
||||||
camera = OpenCVCamera(config)
|
camera = OpenCVCamera(config)
|
||||||
camera.connect()
|
camera.connect(do_warmup_read=False)
|
||||||
|
|
||||||
with pytest.raises(TimeoutError):
|
with pytest.raises(TimeoutError):
|
||||||
camera.async_read(timeout_ms=0)
|
camera.async_read(timeout_ms=0)
|
||||||
@@ -134,13 +152,22 @@ def test_async_read_timeout():
|
|||||||
|
|
||||||
|
|
||||||
def test_async_read_before_connect():
|
def test_async_read_before_connect():
|
||||||
config = OpenCVCameraConfig(index_or_path="tests/artifacts/cameras/fake_cam.png")
|
config = OpenCVCameraConfig(index_or_path="tests/artifacts/cameras/fakecam_sd_640x480.png")
|
||||||
camera = OpenCVCamera(config)
|
camera = OpenCVCamera(config)
|
||||||
|
|
||||||
with pytest.raises(DeviceNotConnectedError):
|
with pytest.raises(DeviceNotConnectedError):
|
||||||
_ = camera.async_read()
|
_ = camera.async_read()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"index_or_path",
|
||||||
|
[
|
||||||
|
"tests/artifacts/cameras/fakecam_sd_640x480.png",
|
||||||
|
"tests/artifacts/cameras/fakecam_hd_1280x720.png",
|
||||||
|
"tests/artifacts/cameras/fakecam_fullhd_1920x1080.png",
|
||||||
|
"tests/artifacts/cameras/fakecam_square_512x512.png",
|
||||||
|
],
|
||||||
|
)
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"rotation",
|
"rotation",
|
||||||
[
|
[
|
||||||
@@ -150,21 +177,25 @@ def test_async_read_before_connect():
|
|||||||
Cv2Rotation.ROTATE_270,
|
Cv2Rotation.ROTATE_270,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_all_rotations(rotation):
|
def test_all_rotations(rotation, index_or_path):
|
||||||
config = OpenCVCameraConfig(index_or_path="tests/artifacts/cameras/fake_cam.png", rotation=rotation)
|
filename = os.path.basename(index_or_path)
|
||||||
|
dimensions = filename.split("_")[-1].split(".")[0] # Assumes filenames format (_wxh.png)
|
||||||
|
original_width, original_height = map(int, dimensions.split("x"))
|
||||||
|
|
||||||
|
config = OpenCVCameraConfig(index_or_path=index_or_path, rotation=rotation)
|
||||||
camera = OpenCVCamera(config)
|
camera = OpenCVCamera(config)
|
||||||
camera.connect()
|
camera.connect(do_warmup_read=False)
|
||||||
|
|
||||||
img = camera.read()
|
img = camera.read()
|
||||||
assert isinstance(img, np.ndarray)
|
assert isinstance(img, np.ndarray)
|
||||||
|
|
||||||
if rotation in (Cv2Rotation.ROTATE_90, Cv2Rotation.ROTATE_270):
|
if rotation in (Cv2Rotation.ROTATE_90, Cv2Rotation.ROTATE_270):
|
||||||
assert camera.width == 480
|
assert camera.width == original_height
|
||||||
assert camera.height == 640
|
assert camera.height == original_width
|
||||||
assert img.shape[:2] == (640, 480)
|
assert img.shape[:2] == (original_width, original_height)
|
||||||
else:
|
else:
|
||||||
assert camera.width == 640
|
assert camera.width == original_width
|
||||||
assert camera.height == 480
|
assert camera.height == original_height
|
||||||
assert img.shape[:2] == (480, 640)
|
assert img.shape[:2] == (original_height, original_width)
|
||||||
|
|
||||||
camera.disconnect()
|
camera.disconnect()
|
||||||
|
|||||||
@@ -47,28 +47,28 @@ def mock_rs_config_enable_device_bad_file(rs_config_instance, sn):
|
|||||||
return rs_config_instance.enable_device_from_file("non_existent_file.bag", repeat_playback=True)
|
return rs_config_instance.enable_device_from_file("non_existent_file.bag", repeat_playback=True)
|
||||||
|
|
||||||
|
|
||||||
@patch("pyrealsense2.config.enable_device", side_effect=mock_rs_config_enable_device_from_file)
|
|
||||||
def test_connect(mock_enable_device):
|
|
||||||
config = RealSenseCameraConfig(serial_number=42)
|
|
||||||
camera = RealSenseCamera(config)
|
|
||||||
|
|
||||||
camera.connect()
|
|
||||||
assert camera.is_connected
|
|
||||||
|
|
||||||
|
|
||||||
def test_base_class_implementation():
|
def test_base_class_implementation():
|
||||||
config = RealSenseCameraConfig(serial_number=42)
|
config = RealSenseCameraConfig(serial_number=42)
|
||||||
_ = RealSenseCamera(config)
|
_ = RealSenseCamera(config)
|
||||||
|
|
||||||
|
|
||||||
|
@patch("pyrealsense2.config.enable_device", side_effect=mock_rs_config_enable_device_from_file)
|
||||||
|
def test_connect(mock_enable_device):
|
||||||
|
config = RealSenseCameraConfig(serial_number=42)
|
||||||
|
camera = RealSenseCamera(config)
|
||||||
|
|
||||||
|
camera.connect(do_warmup_read=False)
|
||||||
|
assert camera.is_connected
|
||||||
|
|
||||||
|
|
||||||
@patch("pyrealsense2.config.enable_device", side_effect=mock_rs_config_enable_device_from_file)
|
@patch("pyrealsense2.config.enable_device", side_effect=mock_rs_config_enable_device_from_file)
|
||||||
def test_connect_already_connected(mock_enable_device):
|
def test_connect_already_connected(mock_enable_device):
|
||||||
config = RealSenseCameraConfig(serial_number=42)
|
config = RealSenseCameraConfig(serial_number=42)
|
||||||
camera = RealSenseCamera(config)
|
camera = RealSenseCamera(config)
|
||||||
camera.connect()
|
camera.connect(do_warmup_read=False)
|
||||||
|
|
||||||
with pytest.raises(DeviceAlreadyConnectedError):
|
with pytest.raises(DeviceAlreadyConnectedError):
|
||||||
camera.connect()
|
camera.connect(do_warmup_read=False)
|
||||||
|
|
||||||
|
|
||||||
@patch("pyrealsense2.config.enable_device", side_effect=mock_rs_config_enable_device_bad_file)
|
@patch("pyrealsense2.config.enable_device", side_effect=mock_rs_config_enable_device_bad_file)
|
||||||
@@ -77,7 +77,7 @@ def test_connect_invalid_camera_path(mock_enable_device):
|
|||||||
camera = RealSenseCamera(config)
|
camera = RealSenseCamera(config)
|
||||||
|
|
||||||
with pytest.raises(ConnectionError):
|
with pytest.raises(ConnectionError):
|
||||||
camera.connect()
|
camera.connect(do_warmup_read=False)
|
||||||
|
|
||||||
|
|
||||||
@patch("pyrealsense2.config.enable_device", side_effect=mock_rs_config_enable_device_from_file)
|
@patch("pyrealsense2.config.enable_device", side_effect=mock_rs_config_enable_device_from_file)
|
||||||
@@ -86,14 +86,14 @@ def test_invalid_width_connect(mock_enable_device):
|
|||||||
camera = RealSenseCamera(config)
|
camera = RealSenseCamera(config)
|
||||||
|
|
||||||
with pytest.raises(ConnectionError):
|
with pytest.raises(ConnectionError):
|
||||||
camera.connect()
|
camera.connect(do_warmup_read=False)
|
||||||
|
|
||||||
|
|
||||||
@patch("pyrealsense2.config.enable_device", side_effect=mock_rs_config_enable_device_from_file)
|
@patch("pyrealsense2.config.enable_device", side_effect=mock_rs_config_enable_device_from_file)
|
||||||
def test_read(mock_enable_device):
|
def test_read(mock_enable_device):
|
||||||
config = RealSenseCameraConfig(serial_number=42, width=640, height=480, fps=30)
|
config = RealSenseCameraConfig(serial_number=42, width=640, height=480, fps=30)
|
||||||
camera = RealSenseCamera(config)
|
camera = RealSenseCamera(config)
|
||||||
camera.connect()
|
camera.connect(do_warmup_read=False)
|
||||||
|
|
||||||
img = camera.read()
|
img = camera.read()
|
||||||
assert isinstance(img, np.ndarray)
|
assert isinstance(img, np.ndarray)
|
||||||
@@ -111,7 +111,7 @@ def test_read_before_connect():
|
|||||||
def test_disconnect(mock_enable_device):
|
def test_disconnect(mock_enable_device):
|
||||||
config = RealSenseCameraConfig(serial_number=42)
|
config = RealSenseCameraConfig(serial_number=42)
|
||||||
camera = RealSenseCamera(config)
|
camera = RealSenseCamera(config)
|
||||||
camera.connect()
|
camera.connect(do_warmup_read=False)
|
||||||
|
|
||||||
camera.disconnect()
|
camera.disconnect()
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@ def test_disconnect_before_connect():
|
|||||||
def test_async_read(mock_enable_device):
|
def test_async_read(mock_enable_device):
|
||||||
config = RealSenseCameraConfig(serial_number=42, width=640, height=480, fps=30)
|
config = RealSenseCameraConfig(serial_number=42, width=640, height=480, fps=30)
|
||||||
camera = RealSenseCamera(config)
|
camera = RealSenseCamera(config)
|
||||||
camera.connect()
|
camera.connect(do_warmup_read=False)
|
||||||
|
|
||||||
img = camera.async_read()
|
img = camera.async_read()
|
||||||
|
|
||||||
@@ -144,7 +144,7 @@ def test_async_read(mock_enable_device):
|
|||||||
def test_async_read_timeout(mock_enable_device):
|
def test_async_read_timeout(mock_enable_device):
|
||||||
config = RealSenseCameraConfig(serial_number=42, width=640, height=480, fps=30)
|
config = RealSenseCameraConfig(serial_number=42, width=640, height=480, fps=30)
|
||||||
camera = RealSenseCamera(config)
|
camera = RealSenseCamera(config)
|
||||||
camera.connect()
|
camera.connect(do_warmup_read=False)
|
||||||
|
|
||||||
with pytest.raises(TimeoutError):
|
with pytest.raises(TimeoutError):
|
||||||
camera.async_read(timeout_ms=0)
|
camera.async_read(timeout_ms=0)
|
||||||
@@ -173,7 +173,7 @@ def test_async_read_before_connect():
|
|||||||
def test_all_rotations(mock_enable_device, rotation):
|
def test_all_rotations(mock_enable_device, rotation):
|
||||||
config = RealSenseCameraConfig(serial_number=42, rotation=rotation)
|
config = RealSenseCameraConfig(serial_number=42, rotation=rotation)
|
||||||
camera = RealSenseCamera(config)
|
camera = RealSenseCamera(config)
|
||||||
camera.connect()
|
camera.connect(do_warmup_read=False)
|
||||||
|
|
||||||
img = camera.read()
|
img = camera.read()
|
||||||
assert isinstance(img, np.ndarray)
|
assert isinstance(img, np.ndarray)
|
||||||
|
|||||||
Reference in New Issue
Block a user