diff --git a/Cargo.lock b/Cargo.lock
index 1973fbbeb88622cb01fe3ddfc191aae796a00f54..ebe621e801437e1d3512943a39df7382fa578f72 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1338,6 +1338,7 @@ version = "0.1.0"
 dependencies = [
  "async-std",
  "clap",
+ "criterion",
  "hercules_rt",
  "juno_build",
  "opencv",
diff --git a/juno_samples/edge_detection/Cargo.toml b/juno_samples/edge_detection/Cargo.toml
index a3825eed9b6601ca4d06d8e81d332ed5986b3124..fc5fb451f277897cd3fa5004f953380ee2ed2f46 100644
--- a/juno_samples/edge_detection/Cargo.toml
+++ b/juno_samples/edge_detection/Cargo.toml
@@ -13,6 +13,10 @@ name = "juno_edge_detection"
 path = "src/main.rs"
 required-features = ["opencv"]
 
+[lib]
+path = "src/lib.rs"
+required-features = ["opencv"]
+
 [build-dependencies]
 juno_build = { path = "../../juno_build" }
 
@@ -23,3 +27,10 @@ async-std = "*"
 clap = { version = "*", features = ["derive"] }
 with_builtin_macros = "0.1.0"
 opencv = { version = "*", features = ["clang-runtime"], optional = true }
+
+[dev-dependencies]
+criterion = { version = "0.5", features = ["html_reports"] }
+
+[[bench]]
+name = "edge_detection_bench"
+harness = false
diff --git a/juno_samples/edge_detection/benches/edge_detection_bench.rs b/juno_samples/edge_detection/benches/edge_detection_bench.rs
new file mode 100644
index 0000000000000000000000000000000000000000..806a886510666f471335227b201daa74c000f4e1
--- /dev/null
+++ b/juno_samples/edge_detection/benches/edge_detection_bench.rs
@@ -0,0 +1,104 @@
+#![feature(concat_idents)]
+use std::slice::from_raw_parts;
+
+use criterion::{criterion_group, criterion_main, Criterion};
+
+use opencv::core::{Mat, Size, CV_32F, CV_8U};
+use opencv::imgproc::{cvt_color_def, ColorConversionCodes};
+use opencv::prelude::{MatTraitConst, VideoCaptureTrait, VideoCaptureTraitConst};
+use opencv::videoio::{VideoCapture, VideoCaptureProperties, VideoWriter, VideoWriterTrait};
+
+use hercules_rt::{runner, HerculesImmBox, HerculesImmBoxTo};
+
+use juno_edge_detection::*;
+
+juno_build::juno!("edge_detection");
+
+fn edge_detection_bench(c: &mut Criterion) {
+    let mut group = c.benchmark_group("edge detection bench");
+    group.sample_size(10);
+
+    let input = "examples/formula1_scaled.mp4";
+
+    let gs: usize = 7;
+    let gaussian_filter: Vec<f32> = vec![
+        0.000036, 0.000363, 0.001446, 0.002291, 0.001446, 0.000363, 0.000036, 0.000363, 0.003676,
+        0.014662, 0.023226, 0.014662, 0.003676, 0.000363, 0.001446, 0.014662, 0.058488, 0.092651,
+        0.058488, 0.014662, 0.001446, 0.002291, 0.023226, 0.092651, 0.146768, 0.092651, 0.023226,
+        0.002291, 0.001446, 0.014662, 0.058488, 0.092651, 0.058488, 0.014662, 0.001446, 0.000363,
+        0.003676, 0.014662, 0.023226, 0.014662, 0.003676, 0.000363, 0.000036, 0.000363, 0.001446,
+        0.002291, 0.001446, 0.000363, 0.000036,
+    ];
+    let gaussian_filter_h = HerculesImmBox::from(gaussian_filter.as_slice());
+
+    let sz: usize = 3;
+    let structure: Vec<f32> = vec![1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0];
+    let structure_h = HerculesImmBox::from(structure.as_slice());
+
+    let sb: usize = 3;
+    let sx: Vec<f32> = vec![-1.0, 0.0, 1.0, -2.0, 0.0, 2.0, -1.0, 0.0, 1.0];
+    let sx_h = HerculesImmBox::from(sx.as_slice());
+
+    let sy: Vec<f32> = vec![-1.0, -2.0, -1.0, 0.0, 0.0, 0.0, 1.0, 2.0, 1.0];
+    let sy_h = HerculesImmBox::from(sy.as_slice());
+
+    let theta: f32 = 0.1;
+
+    let mut video = VideoCapture::from_file_def(&input).expect("Error loading video");
+    assert!(video.is_opened().unwrap());
+
+    let fps = video
+        .get(VideoCaptureProperties::CAP_PROP_FPS.into())
+        .expect("Error getting fps");
+
+    let _num_frames = video
+        .get(VideoCaptureProperties::CAP_PROP_FRAME_COUNT.into())
+        .expect("Error getting number of frames") as usize;
+    let width = video
+        .get(VideoCaptureProperties::CAP_PROP_FRAME_WIDTH.into())
+        .expect("Error getting width") as usize;
+    let height = video
+        .get(VideoCaptureProperties::CAP_PROP_FRAME_HEIGHT.into())
+        .expect("Error getting height") as usize;
+    let num_frames = 5;
+
+    let mut r = runner!(edge_detection);
+
+    let frames: Vec<_> = (0..num_frames).map(|_| load_frame(&mut video)).collect();
+
+    group.bench_function("edge detection bench", |b| {
+        b.iter(|| {
+            for i in 0..num_frames {
+                let frame = &frames[i];
+                let ptr = frame.ptr_def().unwrap() as *const f32;
+
+                assert!(frame.rows() as usize == height);
+                assert!(frame.cols() as usize == width);
+
+                let input = unsafe { from_raw_parts(ptr, height * width) };
+
+                let input_h = HerculesImmBox::from(input);
+
+                let result = async_std::task::block_on(async {
+                    r.run(
+                        height as u64,
+                        width as u64,
+                        gs as u64,
+                        sz as u64,
+                        sb as u64,
+                        input_h.to(),
+                        gaussian_filter_h.to(),
+                        structure_h.to(),
+                        sx_h.to(),
+                        sy_h.to(),
+                        theta,
+                    )
+                    .await
+                });
+            }
+        })
+    });
+}
+
+criterion_group!(benches, edge_detection_bench);
+criterion_main!(benches);
diff --git a/juno_samples/edge_detection/src/lib.rs b/juno_samples/edge_detection/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..37268b561f2c0104a89f097b88abde5d3a34692a
--- /dev/null
+++ b/juno_samples/edge_detection/src/lib.rs
@@ -0,0 +1,255 @@
+#![feature(concat_idents)]
+
+mod edge_detection_rust;
+
+use hercules_rt::{runner, HerculesImmBox, HerculesImmBoxTo, HerculesMutBox};
+
+use std::slice::from_raw_parts;
+
+use clap::Parser;
+
+use opencv::core::{Mat, Size, CV_32F, CV_8U};
+use opencv::highgui::{imshow, wait_key};
+use opencv::imgproc::{cvt_color_def, ColorConversionCodes};
+use opencv::prelude::{MatTraitConst, VideoCaptureTrait, VideoCaptureTraitConst};
+use opencv::videoio::{VideoCapture, VideoCaptureProperties, VideoWriter, VideoWriterTrait};
+
+juno_build::juno!("edge_detection");
+
+#[derive(Parser)]
+#[clap(author, version, about, long_about = None)]
+pub struct EdgeDetectionInputs {
+    pub input: String,
+    #[clap(short, long)]
+    pub display: bool,
+    #[clap(short, long, value_name = "PATH")]
+    pub output: Option<String>,
+    #[clap(short, long)]
+    pub verify: bool,
+    #[clap(long = "display-verify")]
+    pub display_verify: bool,
+    #[clap(long = "output-verify", value_name = "PATH")]
+    pub output_verify: Option<String>,
+    #[clap(short, long, value_name = "COUNT")]
+    pub frames: Option<usize>,
+}
+
+pub fn load_frame(video: &mut VideoCapture) -> Mat {
+    let mut frame = Mat::default();
+
+    let Ok(true) = video.read(&mut frame) else {
+        panic!("Failed to load frame");
+    };
+    let result = if frame.channels() == 3 {
+        let mut converted = Mat::default();
+        let () = cvt_color_def(
+            &frame,
+            &mut converted,
+            ColorConversionCodes::COLOR_BGR2GRAY.into(),
+        )
+        .expect("Failure in conversion to grayscale");
+        let mut result = Mat::default();
+        let () = converted
+            .convert_to(&mut result, CV_32F, 1.0 / 255.0, 0.0)
+            .expect("Failure in conversion to f32");
+        result
+    } else if frame.channels() == 1 {
+        let mut result = Mat::default();
+        let () = frame
+            .convert_to(&mut result, CV_32F, 1.0 / 255.0, 0.0)
+            .expect("Failure in conversion to f32");
+        result
+    } else {
+        panic!("Expected either RGB or grayscale image");
+    };
+
+    assert!(result.is_continuous());
+    result
+}
+
+pub fn frame_from_slice(frame: &[f32], height: usize, width: usize) -> Mat {
+    let result = Mat::from_slice(frame)
+        .expect("Failed to create matrix from result")
+        .reshape(1, height as i32)
+        .expect("Failed to reshape result matrix")
+        .clone_pointee();
+    assert!(result.cols() == width as i32);
+
+    // Convert to u8 since the VideoWriter seems to require that
+    let mut converted = Mat::default();
+    let () = result
+        .convert_to(&mut converted, CV_8U, 255.0, 0.0)
+        .expect("Failure in conversion to u8");
+
+    converted
+}
+
+pub fn edge_detection_harness(args: EdgeDetectionInputs) {
+    let EdgeDetectionInputs {
+        input,
+        display,
+        output,
+        verify,
+        display_verify,
+        output_verify,
+        frames,
+    } = args;
+
+    let gs: usize = 7;
+    let gaussian_filter: Vec<f32> = vec![
+        0.000036, 0.000363, 0.001446, 0.002291, 0.001446, 0.000363, 0.000036, 0.000363, 0.003676,
+        0.014662, 0.023226, 0.014662, 0.003676, 0.000363, 0.001446, 0.014662, 0.058488, 0.092651,
+        0.058488, 0.014662, 0.001446, 0.002291, 0.023226, 0.092651, 0.146768, 0.092651, 0.023226,
+        0.002291, 0.001446, 0.014662, 0.058488, 0.092651, 0.058488, 0.014662, 0.001446, 0.000363,
+        0.003676, 0.014662, 0.023226, 0.014662, 0.003676, 0.000363, 0.000036, 0.000363, 0.001446,
+        0.002291, 0.001446, 0.000363, 0.000036,
+    ];
+    let gaussian_filter_h = HerculesImmBox::from(gaussian_filter.as_slice());
+
+    let sz: usize = 3;
+    let structure: Vec<f32> = vec![1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0];
+    let structure_h = HerculesImmBox::from(structure.as_slice());
+
+    let sb: usize = 3;
+    let sx: Vec<f32> = vec![-1.0, 0.0, 1.0, -2.0, 0.0, 2.0, -1.0, 0.0, 1.0];
+    let sx_h = HerculesImmBox::from(sx.as_slice());
+
+    let sy: Vec<f32> = vec![-1.0, -2.0, -1.0, 0.0, 0.0, 0.0, 1.0, 2.0, 1.0];
+    let sy_h = HerculesImmBox::from(sy.as_slice());
+
+    let theta: f32 = 0.1;
+
+    let mut video = VideoCapture::from_file_def(&input).expect("Error loading video");
+    assert!(video.is_opened().unwrap());
+
+    let fps = video
+        .get(VideoCaptureProperties::CAP_PROP_FPS.into())
+        .expect("Error getting fps");
+
+    let num_frames = video
+        .get(VideoCaptureProperties::CAP_PROP_FRAME_COUNT.into())
+        .expect("Error getting number of frames") as usize;
+    let width = video
+        .get(VideoCaptureProperties::CAP_PROP_FRAME_WIDTH.into())
+        .expect("Error getting width") as usize;
+    let height = video
+        .get(VideoCaptureProperties::CAP_PROP_FRAME_HEIGHT.into())
+        .expect("Error getting height") as usize;
+
+    let num_frames = if let Some(frames) = frames {
+        usize::min(frames, num_frames)
+    } else {
+        num_frames
+    };
+
+    let mut r = runner!(edge_detection);
+
+    let mut output = output.map(|filename| {
+        VideoWriter::new(
+            &filename,
+            VideoWriter::fourcc('m', 'p', '4', 'v').unwrap(),
+            fps,
+            Size {
+                width: width as i32,
+                height: height as i32,
+            },
+            false,
+        )
+        .expect("Error opening output video")
+    });
+
+    let mut output_verify = output_verify.map(|filename| {
+        VideoWriter::new(
+            &filename,
+            VideoWriter::fourcc('m', 'p', '4', 'v').unwrap(),
+            fps,
+            Size {
+                width: width as i32,
+                height: height as i32,
+            },
+            false,
+        )
+        .expect("Error opening output video")
+    });
+
+    for i in 0..num_frames {
+        let frame = load_frame(&mut video);
+        let ptr = frame.ptr_def().unwrap() as *const f32;
+
+        assert!(frame.rows() as usize == height);
+        assert!(frame.cols() as usize == width);
+
+        let input = unsafe { from_raw_parts(ptr, height * width) };
+
+        let input_h = HerculesImmBox::from(input);
+
+        let result = async_std::task::block_on(async {
+            HerculesMutBox::from(
+                r.run(
+                    height as u64,
+                    width as u64,
+                    gs as u64,
+                    sz as u64,
+                    sb as u64,
+                    input_h.to(),
+                    gaussian_filter_h.to(),
+                    structure_h.to(),
+                    sx_h.to(),
+                    sy_h.to(),
+                    theta,
+                )
+                .await,
+            )
+        })
+        .as_slice()
+        .to_vec();
+
+        if display {
+            let result = frame_from_slice(&result, height, width);
+            let () = imshow("Juno", &result).expect("Failure in displaying image");
+        }
+        if let Some(ref mut output) = output {
+            let result = frame_from_slice(&result, height, width);
+            let () = output.write(&result).expect("Failure in writing frame");
+        }
+
+        if verify {
+            let rust_result = edge_detection_rust::edge_detection(
+                height,
+                width,
+                gs,
+                sz,
+                sb,
+                input,
+                &gaussian_filter,
+                &structure,
+                &sx,
+                &sy,
+                theta,
+            );
+
+            assert_eq!(result, rust_result);
+            println!("Frames {} match", i);
+
+            if display_verify {
+                let rust_result = frame_from_slice(&rust_result, height, width);
+                let () = imshow("Rust", &rust_result).expect("Failure in displaying image");
+            }
+            if let Some(ref mut output) = output_verify {
+                let result = frame_from_slice(&rust_result, height, width);
+                let () = output.write(&result).expect("Failure in writing frame");
+            }
+        }
+
+        if display || (verify && display_verify) {
+            let _ = wait_key(0);
+        }
+    }
+
+    if let Some(mut output) = output {
+        let () = output.release().expect("Failure releasing output video");
+    }
+    if let Some(mut output) = output_verify {
+        let () = output.release().expect("Failure releasing output video");
+    }
+}
diff --git a/juno_samples/edge_detection/src/main.rs b/juno_samples/edge_detection/src/main.rs
index 60ccb51565bdaa6d0f1837385a9de7ac52dc0128..23c4903ac3c4d1743bdabac1ee7f7608f86eb8db 100644
--- a/juno_samples/edge_detection/src/main.rs
+++ b/juno_samples/edge_detection/src/main.rs
@@ -1,290 +1,6 @@
-#![feature(concat_idents)]
-
-mod edge_detection_rust;
-
-use hercules_rt::{runner, HerculesImmBox, HerculesImmBoxTo, HerculesMutBox};
-
-use std::slice::from_raw_parts;
-
 use clap::Parser;
 
-use opencv::core::{Mat, Size, CV_32F, CV_8U};
-use opencv::highgui::{imshow, wait_key};
-use opencv::imgproc::{cvt_color_def, ColorConversionCodes};
-use opencv::prelude::{MatTraitConst, VideoCaptureTrait, VideoCaptureTraitConst};
-use opencv::videoio::{VideoCapture, VideoCaptureProperties, VideoWriter, VideoWriterTrait};
-
-juno_build::juno!("edge_detection");
-
-#[derive(Parser)]
-#[clap(author, version, about, long_about = None)]
-struct EdgeDetectionInputs {
-    input: String,
-    #[clap(short, long)]
-    display: bool,
-    #[clap(short, long, value_name = "PATH")]
-    output: Option<String>,
-    #[clap(short, long)]
-    verify: bool,
-    #[clap(long = "display-verify")]
-    display_verify: bool,
-    #[clap(long = "output-verify", value_name = "PATH")]
-    output_verify: Option<String>,
-    #[clap(short, long, value_name = "COUNT")]
-    frames: Option<usize>,
-}
-
-fn load_frame(video: &mut VideoCapture) -> Mat {
-    let mut frame = Mat::default();
-
-    let Ok(true) = video.read(&mut frame) else {
-        panic!("Failed to load frame");
-    };
-    let result = if frame.channels() == 3 {
-        let mut converted = Mat::default();
-        let () = cvt_color_def(
-            &frame,
-            &mut converted,
-            ColorConversionCodes::COLOR_BGR2GRAY.into(),
-        )
-        .expect("Failure in conversion to grayscale");
-        let mut result = Mat::default();
-        let () = converted
-            .convert_to(&mut result, CV_32F, 1.0 / 255.0, 0.0)
-            .expect("Failure in conversion to f32");
-        result
-    } else if frame.channels() == 1 {
-        let mut result = Mat::default();
-        let () = frame
-            .convert_to(&mut result, CV_32F, 1.0 / 255.0, 0.0)
-            .expect("Failure in conversion to f32");
-        result
-    } else {
-        panic!("Expected either RGB or grayscale image");
-    };
-
-    assert!(result.is_continuous());
-    result
-}
-
-fn frame_from_slice(frame: &[f32], height: usize, width: usize) -> Mat {
-    let result = Mat::from_slice(frame)
-        .expect("Failed to create matrix from result")
-        .reshape(1, height as i32)
-        .expect("Failed to reshape result matrix")
-        .clone_pointee();
-    assert!(result.cols() == width as i32);
-
-    // Convert to u8 since the VideoWriter seems to require that
-    let mut converted = Mat::default();
-    let () = result
-        .convert_to(&mut converted, CV_8U, 255.0, 0.0)
-        .expect("Failure in conversion to u8");
-
-    converted
-}
-
-async fn safe_run<'a, 'b, 'c, 'd, 'e, 'f>(
-    runner: &'a mut HerculesRunner_edge_detection,
-    n: u64,
-    m: u64,
-    gs: u64,
-    sz: u64,
-    sb: u64,
-    input: &'b HerculesImmBox<'b, f32>,
-    gaussian_filter: &'c HerculesImmBox<'c, f32>,
-    structure: &'d HerculesImmBox<'d, f32>,
-    sx: &'e HerculesImmBox<'e, f32>,
-    sy: &'f HerculesImmBox<'f, f32>,
-    theta: f32,
-) -> HerculesMutBox<'a, f32> {
-    HerculesMutBox::from(
-        runner
-            .run(
-                n,
-                m,
-                gs,
-                sz,
-                sb,
-                input.to(),
-                gaussian_filter.to(),
-                structure.to(),
-                sx.to(),
-                sy.to(),
-                theta,
-            )
-            .await,
-    )
-}
-
-fn edge_detection_harness(args: EdgeDetectionInputs) {
-    let EdgeDetectionInputs {
-        input,
-        display,
-        output,
-        verify,
-        display_verify,
-        output_verify,
-        frames,
-    } = args;
-
-    let gs: usize = 7;
-    let gaussian_filter: Vec<f32> = vec![
-        0.000036, 0.000363, 0.001446, 0.002291, 0.001446, 0.000363, 0.000036, 0.000363, 0.003676,
-        0.014662, 0.023226, 0.014662, 0.003676, 0.000363, 0.001446, 0.014662, 0.058488, 0.092651,
-        0.058488, 0.014662, 0.001446, 0.002291, 0.023226, 0.092651, 0.146768, 0.092651, 0.023226,
-        0.002291, 0.001446, 0.014662, 0.058488, 0.092651, 0.058488, 0.014662, 0.001446, 0.000363,
-        0.003676, 0.014662, 0.023226, 0.014662, 0.003676, 0.000363, 0.000036, 0.000363, 0.001446,
-        0.002291, 0.001446, 0.000363, 0.000036,
-    ];
-    let gaussian_filter_h = HerculesImmBox::from(gaussian_filter.as_slice());
-
-    let sz: usize = 3;
-    let structure: Vec<f32> = vec![1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0];
-    let structure_h = HerculesImmBox::from(structure.as_slice());
-
-    let sb: usize = 3;
-    let sx: Vec<f32> = vec![-1.0, 0.0, 1.0, -2.0, 0.0, 2.0, -1.0, 0.0, 1.0];
-    let sx_h = HerculesImmBox::from(sx.as_slice());
-
-    let sy: Vec<f32> = vec![-1.0, -2.0, -1.0, 0.0, 0.0, 0.0, 1.0, 2.0, 1.0];
-    let sy_h = HerculesImmBox::from(sy.as_slice());
-
-    let theta: f32 = 0.1;
-
-    let mut video = VideoCapture::from_file_def(&input).expect("Error loading video");
-    assert!(video.is_opened().unwrap());
-
-    let fps = video
-        .get(VideoCaptureProperties::CAP_PROP_FPS.into())
-        .expect("Error getting fps");
-
-    let num_frames = video
-        .get(VideoCaptureProperties::CAP_PROP_FRAME_COUNT.into())
-        .expect("Error getting number of frames") as usize;
-    let width = video
-        .get(VideoCaptureProperties::CAP_PROP_FRAME_WIDTH.into())
-        .expect("Error getting width") as usize;
-    let height = video
-        .get(VideoCaptureProperties::CAP_PROP_FRAME_HEIGHT.into())
-        .expect("Error getting height") as usize;
-
-    let num_frames = if let Some(frames) = frames {
-        usize::min(frames, num_frames)
-    } else {
-        num_frames
-    };
-
-    let mut r = runner!(edge_detection);
-
-    let mut output = output.map(|filename| {
-        VideoWriter::new(
-            &filename,
-            VideoWriter::fourcc('m', 'p', '4', 'v').unwrap(),
-            fps,
-            Size {
-                width: width as i32,
-                height: height as i32,
-            },
-            false,
-        )
-        .expect("Error opening output video")
-    });
-
-    let mut output_verify = output_verify.map(|filename| {
-        VideoWriter::new(
-            &filename,
-            VideoWriter::fourcc('m', 'p', '4', 'v').unwrap(),
-            fps,
-            Size {
-                width: width as i32,
-                height: height as i32,
-            },
-            false,
-        )
-        .expect("Error opening output video")
-    });
-
-    for i in 0..num_frames {
-        let frame = load_frame(&mut video);
-        let ptr = frame.ptr_def().unwrap() as *const f32;
-
-        assert!(frame.rows() as usize == height);
-        assert!(frame.cols() as usize == width);
-
-        let input = unsafe { from_raw_parts(ptr, height * width) };
-
-        let input_h = HerculesImmBox::from(input);
-
-        let result = async_std::task::block_on(async {
-            safe_run(
-                &mut r,
-                height as u64,
-                width as u64,
-                gs as u64,
-                sz as u64,
-                sb as u64,
-                &input_h,
-                &gaussian_filter_h,
-                &structure_h,
-                &sx_h,
-                &sy_h,
-                theta,
-            )
-            .await
-        })
-        .as_slice()
-        .to_vec();
-
-        if display {
-            let result = frame_from_slice(&result, height, width);
-            let () = imshow("Juno", &result).expect("Failure in displaying image");
-        }
-        if let Some(ref mut output) = output {
-            let result = frame_from_slice(&result, height, width);
-            let () = output.write(&result).expect("Failure in writing frame");
-        }
-
-        if verify {
-            let rust_result = edge_detection_rust::edge_detection(
-                height,
-                width,
-                gs,
-                sz,
-                sb,
-                input,
-                &gaussian_filter,
-                &structure,
-                &sx,
-                &sy,
-                theta,
-            );
-
-            assert_eq!(result, rust_result);
-            println!("Frames {} match", i);
-
-            if display_verify {
-                let rust_result = frame_from_slice(&rust_result, height, width);
-                let () = imshow("Rust", &rust_result).expect("Failure in displaying image");
-            }
-            if let Some(ref mut output) = output_verify {
-                let result = frame_from_slice(&rust_result, height, width);
-                let () = output.write(&result).expect("Failure in writing frame");
-            }
-        }
-
-        if display || (verify && display_verify) {
-            let _ = wait_key(0);
-        }
-    }
-
-    if let Some(mut output) = output {
-        let () = output.release().expect("Failure releasing output video");
-    }
-    if let Some(mut output) = output_verify {
-        let () = output.release().expect("Failure releasing output video");
-    }
-}
+use juno_edge_detection::{edge_detection_harness, EdgeDetectionInputs};
 
 fn main() {
     let args = EdgeDetectionInputs::parse();