Skip to content

Commit 3a0afe8

Browse files
authored
Merge pull request JdeRobot#3529 from JdeRobot/add-image-viewer-machine-vision
Add image viewer in machine vision exercise
2 parents 02ce335 + 3f926ef commit 3a0afe8

6 files changed

Lines changed: 164 additions & 30 deletions

File tree

database/exercises/db.sql

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -289,10 +289,11 @@ COPY public.exercises_tools (id, exercise_id, tool_id) FROM stdin;
289289
72 25 console
290290
73 25 simulator
291291
74 25 rviz
292-
75 26 console
293-
76 26 simulator
294-
77 26 web_gui
295-
78 15 video
292+
75 25 web_gui
293+
76 26 console
294+
77 26 simulator
295+
78 26 web_gui
296+
79 15 video
296297
\.
297298

298299
--
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import React, { useState } from "react";
2+
import { useExercise } from "Contexts/ExerciseContext";
3+
import WebGUIContainer, {
4+
connectApplication,
5+
} from "Components/exercise/WebGUIContainer";
6+
import WebGUIImage from "Components/exercise/WebGUIImage";
7+
8+
const WebGUI = () => {
9+
const exerciseContext = useExercise();
10+
const manager = exerciseContext.manager;
11+
12+
const [image, setImage] = useState<string | undefined>(undefined);
13+
14+
const updateCallback = (updateData: unknown) => {
15+
const data = updateData as any;
16+
17+
if (data.update?.image_right) {
18+
const img = JSON.parse(data.update.image_right);
19+
20+
if (img.image_right !== "") {
21+
setImage(`data:image/jpeg;base64,${img.image_right}`);
22+
}
23+
}
24+
};
25+
26+
const stateCallback = () => {};
27+
28+
connectApplication(manager, updateCallback, stateCallback);
29+
30+
return (
31+
<WebGUIContainer>
32+
<WebGUIImage style={{ width: "100%" }} src={image} />
33+
</WebGUIContainer>
34+
);
35+
};
36+
37+
export default WebGUI;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import js from "@eslint/js";
2+
import globals from "globals";
3+
import tseslint from "typescript-eslint";
4+
import pluginReact from "eslint-plugin-react";
5+
import { defineConfig } from "eslint/config";
6+
7+
export default defineConfig([
8+
{
9+
files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
10+
plugins: { js },
11+
extends: ["js/recommended"],
12+
languageOptions: {
13+
globals: globals.browser,
14+
parserOptions: {
15+
project: './tsconfig.json',
16+
tsconfigRootDir: import.meta.dirname
17+
},
18+
},
19+
},
20+
tseslint.configs.recommended,
21+
pluginReact.configs.flat.recommended,
22+
{
23+
settings: {
24+
react: {
25+
version: "detect",
26+
},
27+
},
28+
},
29+
]);
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
{
2+
"compilerOptions": {
3+
"target": "es5",
4+
"lib": [
5+
"dom",
6+
"dom.iterable",
7+
"esnext"
8+
],
9+
"types": [
10+
"../../../react_frontend/svg.d.ts",
11+
"../../../react_frontend/png.d.ts",
12+
"../../../react_frontend/jpg.d.ts",
13+
"../../../react_frontend/zip.d.ts"
14+
],
15+
"paths": {
16+
"Assets/*": [ "../../../react_frontend/src/assets/*"],
17+
"Components/*": [ "../../../react_frontend/src/components/*"],
18+
"Utils/*": [ "../../../react_frontend/src/utils/*"],
19+
"Contexts/*": [ "../../../react_frontend/src/contexts/*"],
20+
"Helpers/*": [ "../../../react_frontend/src/helpers/*"],
21+
"Icons/*": [ "../../../react_frontend/src/icons/*"],
22+
"Styles/*": [ "../../../react_frontend/src/styles/*"],
23+
"Types/*": [ "../../../react_frontend/src/types/*"],
24+
"Constants/*": [ "../../../react_frontend/src/constants/*"],
25+
"Api": [ "../../../react_frontend/src/api/index.ts"],
26+
"Routes": [ "../../../react_frontend/src/routes/index.ts"],
27+
"*": [ "../../../react_frontend/node_modules/*"],
28+
},
29+
"baseUrl": "./",
30+
"rootDirs": [
31+
"./",
32+
"../../../react_frontend/src"
33+
],
34+
"typeRoots": ["../../../react_frontend/node_modules/@types"],
35+
"declaration": true,
36+
"allowJs": true,
37+
"skipLibCheck": true,
38+
"esModuleInterop": true,
39+
"allowSyntheticDefaultImports": true,
40+
"strict": true,
41+
"noImplicitAny": true,
42+
"forceConsistentCasingInFileNames": true,
43+
"noFallthroughCasesInSwitch": true,
44+
"module": "esnext",
45+
"moduleResolution": "node",
46+
"resolveJsonModule": true,
47+
"isolatedModules": true,
48+
"noEmit": true,
49+
"jsx": "react-jsx"
50+
},
51+
"include": [
52+
"./",
53+
"eslint.config.mts",
54+
"../../../react_frontend/src",
55+
"../../../react_frontend/png.d.ts",
56+
"../../../react_frontend/jpg.d.ts",
57+
"../../../react_frontend/svg.d.ts",
58+
"../../../react_frontend/zip.d.ts"
59+
]
60+
}

exercises/machine_vision/python_template/HAL.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
from geometry_msgs.msg import Point, Pose, PoseStamped, Quaternion
4343
from pcl_filter_msgs.msg import ColorFilter, ShapeFilter
4444
from tf_transformations import euler_from_quaternion, quaternion_from_euler
45+
from hal_interfaces.general.camera import CameraNode
4546

4647
# Build PATH and import Python classes from IFRA package:
4748
PATH = os.path.join(get_package_share_directory("ros2srrc_execution"), "python")
@@ -58,11 +59,30 @@
5859
from ros2srrc_data.msg import Action, Joint, Joints, Xyz, Ypr, Robpose
5960

6061
# Global variables
61-
rclpy.init(args=None)
62+
if not rclpy.ok():
63+
rclpy.init(args=None)
6264
UR5 = RBT()
6365
_home_joints = [0.0, -90.0, 0.0, 0.0, -90.0, 0.0]
6466
_perception_manager = None
6567

68+
# Camera node
69+
camera_node = CameraNode("/hand_camera/image_raw")
70+
71+
# Spin nodes so that subscription callbacks load topic data
72+
executor = MultiThreadedExecutor()
73+
executor.add_node(camera_node)
74+
75+
def __auto_spin():
76+
while rclpy.ok():
77+
try:
78+
executor.spin_once(timeout_sec=0)
79+
except Exception:
80+
pass
81+
time.sleep(1/90.0)
82+
83+
executor_thread = threading.Thread(target=__auto_spin, daemon=True)
84+
executor_thread.start()
85+
6686
#################################### UTILITY CLASSES ###################################################
6787

6888

@@ -1039,6 +1059,14 @@ def get_target_position(target_name):
10391059
return perception_mgr.get_target_position(target_name)
10401060

10411061

1062+
def getImage():
1063+
image = camera_node.getImage()
1064+
1065+
while image is None:
1066+
image = camera_node.getImage()
1067+
1068+
return image.data
1069+
10421070
#################################### MAIN FUNCTION ###################################################
10431071

10441072

exercises/machine_vision/python_template/WebGUI.py

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ def __init__(self, host="ws://127.0.0.1:2303", freq=30.0):
1717
# Execution control vars
1818
self.out_period = 1.0 / freq
1919
self.right_image = None
20-
self.left_image = None
2120
self.image_lock = threading.Lock()
2221
self.ack = True
2322
self.ack_frontend = True
@@ -27,7 +26,7 @@ def __init__(self, host="ws://127.0.0.1:2303", freq=30.0):
2726
self.world_name = "empty"
2827

2928
self.host = host
30-
self.msg = {"image_right": "", "image_left": ""}
29+
self.msg = {"image_right": ""}
3130

3231
self.ideal_cycle = 80
3332
self.real_time_factor = 0
@@ -48,7 +47,7 @@ def gui_out_thread(self):
4847
with self.ack_lock:
4948
with self.image_lock:
5049
if self.ack:
51-
if np.any(self.left_image) or np.any(self.right_image):
50+
if np.any(self.right_image):
5251
self.update_gui()
5352
self.ack = False
5453

@@ -60,14 +59,6 @@ def gui_out_thread(self):
6059
# Prepares and send image to the websocket server
6160
def update_gui(self):
6261

63-
if np.any(self.left_image):
64-
_, encoded_left_image = cv2.imencode(".JPEG", self.left_image)
65-
b64_left = base64.b64encode(encoded_left_image).decode("utf-8")
66-
shape_left = self.left_image.shape
67-
else:
68-
b64_left = None
69-
shape_left = 0
70-
7162
if np.any(self.right_image):
7263
_, encoded_right_image = cv2.imencode(".JPEG", self.right_image)
7364
b64_right = base64.b64encode(encoded_right_image).decode("utf-8")
@@ -76,24 +67,16 @@ def update_gui(self):
7667
b64_right = None
7768
shape_right = 0
7869

79-
payload_left = {
80-
"image_left": b64_left,
81-
"shape_left": shape_left,
82-
}
8370
payload_right = {
8471
"image_right": b64_right,
8572
"shape_right": shape_right,
8673
}
87-
self.msg["image_left"] = json.dumps(payload_left)
74+
8875
self.msg["image_right"] = json.dumps(payload_right)
8976
message = json.dumps(self.msg)
9077
self.send_to_client(message)
9178

9279
# Functions to set the next image to be sent
93-
def setLeftImage(self, image):
94-
with self.image_lock:
95-
self.left_image = image
96-
9780
def setRightImage(self, image):
9881
with self.image_lock:
9982
self.right_image = image
@@ -108,8 +91,4 @@ def setRightImage(self, image):
10891

10992
# Expose the user functions
11093
def showImage(image):
111-
gui.setRightImage(image)
112-
113-
114-
def showLeftImage(image):
115-
gui.setLeftImage(image)
94+
gui.setRightImage(image)

0 commit comments

Comments
 (0)