Skip to content

Commit e6ab4b0

Browse files
authored
Reintroduce vision cycler (HULKs#2293)
* Reintroduce vision cycler * Fix map panel lines overlay * fmt * Fix required inputs breaking in replay * use width instead of step * fix nv12 to YCbCr422 conversion * Rename `RawOrJpeg` -> `ImageBuffer`
1 parent bc07a6e commit e6ab4b0

24 files changed

Lines changed: 2734 additions & 28 deletions

File tree

Cargo.lock

Lines changed: 31 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ members = [
4747
"crates/simulation_message",
4848
"crates/source_analyzer",
4949
"crates/types",
50+
"crates/vision",
5051
"crates/walking_inference",
5152
"crates/world_state",
5253
"crates/zed",
@@ -195,6 +196,7 @@ quote = "1.0.38"
195196
rand = "0.9.0"
196197
rand_chacha = { version = "0.9.0", features = ["serde"] }
197198
rand_distr = "0.5.0"
199+
ransac = { path = "crates/ransac" }
198200
regex = "1.11.1"
199201
repository = { path = "crates/repository" }
200202
reqwest = { version = "0.12.12", features = ["blocking"] }
@@ -229,6 +231,7 @@ tracing = "0.1.41"
229231
tracing-subscriber = "0.3.19"
230232
types = { path = "crates/types" }
231233
uuid = { version = "1.12.1", features = ["v4"] }
234+
vision = { path = "crates/vision" }
232235
walking_inference = { path = "crates/walking_inference" }
233236
wgpu = "27.0.0"
234237
world_state = { path = "crates/world_state" }

crates/code_generation/src/cyclers.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1370,7 +1370,7 @@ fn generate_context_initializers(node: &Node, cycler: &Cycler, mode: CyclerMode)
13701370
CyclerMode::Replay => {
13711371
let name = path_to_extraction_variable_name(cycler_instance, path, "required_input");
13721372
quote! {
1373-
&#name .unwrap()
1373+
#name .as_ref().unwrap()
13741374
}
13751375
},
13761376
}

crates/hulk_booster/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ tokio = { workspace = true }
5959
tokio-tungstenite = { workspace = true }
6060
tokio-util = { workspace = true }
6161
types = { workspace = true }
62+
vision = { workspace = true }
6263
walking_inference = { workspace = true }
6364
world_state = { workspace = true }
6465
zenoh = { workspace = true }

crates/hulk_imagine/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ serde = { workspace = true }
4242
serde_json = { workspace = true }
4343
tokio = { workspace = true }
4444
types = { workspace = true }
45+
vision = { workspace = true }
4546
walking_inference = { workspace = true }
4647
world_state = { workspace = true }
4748
zed = { workspace = true }

crates/hulk_manifest/src/lib.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,19 @@ use source_analyzer::{
99
pub fn collect_hulk_cyclers(root: impl AsRef<Path>) -> Result<Cyclers, Error> {
1010
let manifest = FrameworkManifest {
1111
cyclers: vec![
12+
CyclerManifest {
13+
name: "Vision",
14+
kind: CyclerKind::Perception,
15+
instances: vec![""],
16+
setup_nodes: vec!["vision::image_receiver"],
17+
nodes: vec![
18+
"vision::field_border_detection",
19+
"vision::image_segmenter",
20+
"vision::line_detection",
21+
"vision::segment_filter",
22+
],
23+
execution_time_warning_threshold: Some(Duration::from_secs_f32(1.0 / 30.0)),
24+
},
1225
CyclerManifest {
1326
name: "ObjectDetection",
1427
kind: CyclerKind::Perception,

crates/hulk_mujoco/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ tokio = { workspace = true }
5353
tokio-tungstenite = { workspace = true }
5454
tokio-util = { workspace = true }
5555
types = { workspace = true }
56+
vision = { workspace = true }
5657
walking_inference = { workspace = true }
5758
world_state = { workspace = true }
5859

crates/hulk_replayer/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ serde_json = { workspace = true }
4949
tokio = { workspace = true }
5050
tokio-util = { workspace = true }
5151
types = { workspace = true }
52+
vision = { workspace = true }
5253
walking_inference = { workspace = true }
5354
world_state = { workspace = true }
5455

crates/types/src/ycbcr422_image.rs

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ use std::{
77

88
use color_eyre::eyre::{self, WrapErr};
99
use geometry::circle::Circle;
10-
use image::{ImageReader, RgbImage};
10+
use image::{ImageError, ImageReader, RgbImage, error::DecodingError};
11+
use num_traits::Euclid;
1112
use serde::{Deserialize, Serialize};
1213

1314
use coordinate_systems::Pixel;
@@ -33,12 +34,12 @@ pub struct YCbCr422Image {
3334
buffer: Arc<Vec<YCbCr422>>,
3435
}
3536

36-
impl From<RgbImage> for YCbCr422Image {
37-
fn from(rgb_image: RgbImage) -> Self {
37+
impl From<&RgbImage> for YCbCr422Image {
38+
fn from(rgb_image: &RgbImage) -> Self {
3839
let width_422 = rgb_image.width() / 2;
3940
let height = rgb_image.height();
4041
let data = rgb_image
41-
.into_vec()
42+
.to_vec()
4243
.chunks(6)
4344
.map(|pixel| {
4445
let left_color: YCbCr444 = Rgb {
@@ -110,15 +111,17 @@ impl From<YCbCr422Image> for RgbImage {
110111
}
111112
}
112113

113-
impl From<&Ros2Image> for YCbCr422Image {
114-
fn from(ros2_image: &Ros2Image) -> Self {
114+
impl TryFrom<&Ros2Image> for YCbCr422Image {
115+
type Error = ImageError;
116+
117+
fn try_from(ros2_image: &Ros2Image) -> Result<Self, ImageError> {
115118
let width_422 = ros2_image.width / 2;
116119
let height = ros2_image.height;
117120

118-
let data = match ros2_image.encoding.as_str() {
121+
let data: Vec<YCbCr422> = match ros2_image.encoding.as_str() {
119122
"rgb8" => ros2_image
120123
.data
121-
.chunks(6)
124+
.chunks_exact(6)
122125
.map(|pixel| {
123126
let left_color: YCbCr444 = Rgb {
124127
red: pixel[0],
@@ -135,14 +138,61 @@ impl From<&Ros2Image> for YCbCr422Image {
135138
[left_color, right_color].into()
136139
})
137140
.collect(),
138-
_ => unimplemented!("image encoding not supported"),
141+
"nv12" => {
142+
let y_plane_size = (ros2_image.width * ros2_image.height) as usize;
143+
let uv_plane_size = (ros2_image.width * ros2_image.height / 2) as usize;
144+
145+
if ros2_image.data.len() < y_plane_size + uv_plane_size {
146+
return Err(ImageError::Decoding(DecodingError::from_format_hint(
147+
image::error::ImageFormatHint::Name(
148+
"NV12: Source buffer is too small for the given dimensions".to_string(),
149+
),
150+
)));
151+
}
152+
153+
let y_stride = ros2_image.width;
154+
let chunked_y_stride = y_stride / 2;
155+
156+
let (y_plane, uv_plane) = ros2_image.data.split_at(y_plane_size);
157+
158+
assert_eq!(uv_plane.len(), uv_plane_size);
159+
160+
y_plane
161+
.chunks_exact(2)
162+
.enumerate()
163+
.map(|(i, y_chunk)| {
164+
let (y_row, column) = (i as u32).div_rem_euclid(&chunked_y_stride);
165+
let uv_row = y_row / 2;
166+
167+
assert!(uv_row <= ros2_image.height / 2);
168+
assert!(column <= ros2_image.width / 2);
169+
170+
// don't forget sunscreen
171+
let uv_index = uv_row * chunked_y_stride + column;
172+
let uv_byte_index = uv_index as usize * 2;
173+
174+
assert!(uv_byte_index < uv_plane_size);
175+
176+
let cb = uv_plane[uv_byte_index];
177+
let cr = uv_plane[uv_byte_index + 1];
178+
179+
YCbCr422 {
180+
y1: y_chunk[0],
181+
cb,
182+
y2: y_chunk[1],
183+
cr,
184+
}
185+
})
186+
.collect()
187+
}
188+
encoding => unimplemented!(r#"image encoding "{encoding}" not supported"#),
139189
};
140190

141-
Self {
191+
Ok(Self {
142192
width_422,
143193
height,
144194
buffer: Arc::new(data),
145-
}
195+
})
146196
}
147197
}
148198

@@ -225,7 +275,7 @@ impl YCbCr422Image {
225275

226276
pub fn load_from_rgb_file(path: impl AsRef<Path>) -> eyre::Result<Self> {
227277
let rgb_image = ImageReader::open(path)?.decode()?.into_rgb8();
228-
Ok(Self::from(rgb_image))
278+
Ok(Self::from(&rgb_image))
229279
}
230280

231281
pub fn save_to_rgb_file(&self, file: impl AsRef<Path> + Debug) -> eyre::Result<()> {

crates/vision/Cargo.toml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
[package]
2+
name = "vision"
3+
version.workspace = true
4+
edition.workspace = true
5+
license.workspace = true
6+
homepage.workspace = true
7+
8+
[dependencies]
9+
approx = { workspace = true }
10+
calibration = { workspace = true }
11+
color-eyre = { workspace = true }
12+
context_attribute = { workspace = true }
13+
coordinate_systems = { workspace = true }
14+
filtering = { workspace = true }
15+
framework = { workspace = true }
16+
geometry = { workspace = true }
17+
hardware = { workspace = true }
18+
image = { workspace = true }
19+
itertools = { workspace = true }
20+
linear_algebra = { workspace = true }
21+
nalgebra = { workspace = true }
22+
ordered-float = { workspace = true }
23+
projection = { workspace = true }
24+
rand = { workspace = true }
25+
rand_chacha = { workspace = true }
26+
ransac = { workspace = true }
27+
ros2 = { workspace = true }
28+
serde = { workspace = true }
29+
types = { workspace = true }

0 commit comments

Comments
 (0)