Skip to content

Conversation

@youssefelzedy
Copy link

@youssefelzedy youssefelzedy commented Sep 19, 2025

This PR adds a new dedicated median filter node to the Raster: Filter category, providing users with an effective tool for noise reduction while preserving edges.

Changes Made

  • Added new median_filter node function with its own dedicated interface
  • Implemented median_filter_algorithm() function that processes each color channel independently
  • Added median_quickselect() helper function using select_nth_unstable_by() with total_cmp() for safe NaN handling
  • Uses O(n) quickselect algorithm instead of O(n log n) full sorting for better performance

Why a Separate Node?

Median filtering serves a fundamentally different purpose than blur operations:

  • Primary use case: Noise reduction (especially salt-and-pepper noise) while preserving sharp edges
  • Different behavior: Does not create smooth blur effects like Gaussian/box blur
  • Distinct parameters: Optimal radius ranges and use cases differ from blur filters
  • UI clarity: Users can easily distinguish between blur effects and noise reduction

Use Cases

  • Removing salt-and-pepper noise from scanned images
  • Cleaning up digital photography noise without edge destruction
  • Pre-processing images before applying other effects
  • Situations where traditional blur would be too destructive to image details

Technical Implementation

  • Uses quickselect algorithm (O(n) average case) for efficient median calculation
  • Operates on square neighborhoods defined by radius parameter
  • Processes RGBA channels independently to maintain color fidelity
  • Safe NaN handling with f32::total_cmp() to prevent runtime panics
  • Consistent gamma space processing with existing filter nodes

The implementation follows established patterns from other filter nodes with proper error handling and performance optimization.

Closes feature in Issue No. #912

- Add median parameter to blur function for noise reduction
- Implement median_filter_algorithm with efficient quickselect
- Support gamma space calculations for median filtering
- Preserve edges while removing noise, complementing existing blur options

Feature in Issue: GraphiteEditor#912
@youssefelzedy youssefelzedy changed the title Add median filter option to blur node Feature: Add median filter option to blur node Sep 19, 2025
@Keavon Keavon requested a review from TrueDoctor September 22, 2025 08:09
Copy link
Member

@TrueDoctor TrueDoctor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Keavon the code looks fine, but I have not checked if the functionality behaves as you would expect

- Add median parameter to blur function for noise reduction
- Implement median_filter_algorithm with efficient quickselect
- Support gamma space calculations for median filtering
- Preserve edges while removing noise, complementing existing blur options

Feature in Issue: GraphiteEditor#912
@Keavon Keavon force-pushed the add-median-filter-blur branch from 2e1e713 to d79af07 Compare November 20, 2025 13:47
@Keavon
Copy link
Member

Keavon commented Nov 20, 2025

Please make this a separate node.

@youssefelzedy
Copy link
Author

youssefelzedy commented Nov 20, 2025

Please make this a separate node.

@Keavon Could you explain more? What do you mean by that?

You mean separating the node for the median filter functionality instead of adding it as an option to the existing blur node?

@Keavon
Copy link
Member

Keavon commented Nov 20, 2025

You mean separating the node for the median filter functionality instead of adding it as an option to the existing blur node?

Yes

- 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
@youssefelzedy
Copy link
Author

@Keavon Could you review it?

}

/// Finds the median of a slice using quickselect for efficiency.
/// This avoids the cost of full sorting (O(n log n)).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By putting it at the end in parentheses, this comment is unclear to me whether the (O(n log n)) refers to this function's runtime or if it refers to the cost of full sorting.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're absolutely right about the comment ambiguity. I've updated it to be clearer:

/// Finds the median of a slice using quickselect for O(n) average case performance.
/// This is more efficient than sorting the entire slice which would be O(n log n).

The original comment was confusing because the (O(n log n)) at the end could be interpreted as either this function's complexity or the complexity we're avoiding. This new version explicitly states:

This function's performance: O(n) average case

#[range((0., 50.))]
#[hard_min(0.)]
radius: PixelLength,
/// Opt to incorrectly apply the filter with color calculations in gamma space for compatibility with the results from other software.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may need to test if other graphics tools use gamma or linear space for their implementations.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, we might not need to test this for the Median filter. Since gamma correction is a monotonic function, the sort order of pixel values doesn't change between Linear and Gamma space. Therefore, the median pixel selected will be mathematically identical in both, unlike arithmetic filters (e.g., Gaussian blur), where space matters.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, please remove it then.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will differ if the median is calculated by taking the average of the middle two elements, but I guess that should usually not be an issue

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, you’re right, but does it really affect the output?

@Keavon
Copy link
Member

Keavon commented Nov 21, 2025

!build

@github-actions
Copy link

📦 Build Complete for a6b538a
https://fb96ac02.graphite.pages.dev

@youssefelzedy youssefelzedy requested a review from Keavon November 21, 2025 09:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants