Skip to content

Commit a6b538a

Browse files
committed
Add median filter node for noise reduction
- Create dedicated median_filter node separate from blur functionality - Implement median_filter_algorithm with efficient quickselect - Support gamma space calculations for consistency - Use safe NaN handling with f32::total_cmp to prevent panics - Optimize performance with memory reuse and O(n) median selection
1 parent 9df9c10 commit a6b538a

File tree

1 file changed

+33
-5
lines changed

1 file changed

+33
-5
lines changed

node-graph/nodes/raster/src/filter.rs

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use raster_types::Image;
66
use raster_types::{Bitmap, BitmapMut};
77
use raster_types::{CPU, Raster};
88

9-
/// Blurs the image with a Gaussian, blur kernel or Median filter.
9+
/// Blurs the image with a Gaussian or box blur kernel filter.
1010
#[node_macro::node(category("Raster: Filter"))]
1111
async fn blur(
1212
_: impl Ctx,
@@ -18,8 +18,6 @@ async fn blur(
1818
radius: PixelLength,
1919
/// Use a lower-quality box kernel instead of a circular Gaussian kernel. This is faster but produces boxy artifacts.
2020
box_blur: bool,
21-
/// Use a median filter instead of a blur. This is good for removing noise while preserving edges, but does not produce a smooth blur effect.
22-
median: bool,
2321
/// Opt to incorrectly apply the filter with color calculations in gamma space for compatibility with the results from other software.
2422
gamma: bool,
2523
) -> Table<Raster<CPU>> {
@@ -34,8 +32,6 @@ async fn blur(
3432
image.clone()
3533
} else if box_blur {
3634
Raster::new_cpu(box_blur_algorithm(image.into_data(), radius, gamma))
37-
} else if median {
38-
Raster::new_cpu(median_filter_algorithm(image.into_data(), radius as u32, gamma))
3935
} else {
4036
Raster::new_cpu(gaussian_blur_algorithm(image.into_data(), radius, gamma))
4137
};
@@ -46,6 +42,38 @@ async fn blur(
4642
.collect()
4743
}
4844

45+
/// Applies a median filter to reduce noise while preserving edges.
46+
#[node_macro::node(category("Raster: Filter"))]
47+
async fn median_filter(
48+
_: impl Ctx,
49+
/// The image to be filtered.
50+
image_frame: Table<Raster<CPU>>,
51+
/// The radius of the filter kernel. Larger values remove more noise but may blur fine details.
52+
#[range((0., 50.))]
53+
#[hard_min(0.)]
54+
radius: PixelLength,
55+
/// Opt to incorrectly apply the filter with color calculations in gamma space for compatibility with the results from other software.
56+
gamma: bool,
57+
) -> Table<Raster<CPU>> {
58+
image_frame
59+
.into_iter()
60+
.map(|mut row| {
61+
let image = row.element.clone();
62+
63+
// Apply median filter
64+
let filtered_image = if radius < 0.5 {
65+
// Minimum filter radius
66+
image.clone()
67+
} else {
68+
Raster::new_cpu(median_filter_algorithm(image.into_data(), radius as u32, gamma))
69+
};
70+
71+
row.element = filtered_image;
72+
row
73+
})
74+
.collect()
75+
}
76+
4977
// 1D gaussian kernel
5078
fn gaussian_kernel(radius: f64) -> Vec<f64> {
5179
// Given radius, compute the size of the kernel that's approximately three times the radius

0 commit comments

Comments
 (0)