Skip to content

Commit 064cb70

Browse files
committed
Add MedianFilter (window median) over a kW x kH neighbourhood
Per-channel median: collects window values, insertion-sorts, returns the middle element. Selectable border (REPLICATE | CONSTANT_ZERO). Matches nppiFilterMedianBorder. Builds on linear_filter.h params/enums. Wired into image_processing.h. Verified bit-exact vs NPP FilterMedianBorder (8u C1, 3x3 & 5x5, NPP_BORDER_REPLICATE).
1 parent 38f85ad commit 064cb70

2 files changed

Lines changed: 103 additions & 0 deletions

File tree

include/fused_kernel/algorithms/image_processing/image_processing.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <fused_kernel/algorithms/image_processing/deinterlace.h>
2222
#include <fused_kernel/algorithms/image_processing/interpolation.h>
2323
#include <fused_kernel/algorithms/image_processing/linear_filter.h>
24+
#include <fused_kernel/algorithms/image_processing/median_filter.h>
2425
#include <fused_kernel/algorithms/image_processing/resize.h>
2526
#include <fused_kernel/algorithms/image_processing/saturate.h>
2627
#include <fused_kernel/algorithms/image_processing/warping.h>
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/* Copyright 2023-2026 Oscar Amoros Huguet
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License. */
14+
15+
#ifndef FK_MEDIAN_FILTER
16+
#define FK_MEDIAN_FILTER
17+
18+
#include <fused_kernel/core/execution_model/operation_model/operation_model.h>
19+
#include <fused_kernel/core/data/ptr_nd.h>
20+
#include <fused_kernel/core/data/rawptr.h>
21+
#include <fused_kernel/algorithms/image_processing/linear_filter.h> // FilterBorder enum
22+
23+
namespace fk {
24+
25+
// Median filter over a kW x kH window. Per channel, collects the window values,
26+
// sorts them, and returns the middle element — matching nppiFilterMedianBorder.
27+
// MAX_WINDOW caps the per-thread scratch array (compile-time); kW*kH must be <= it.
28+
template <ND D, typename T, int MAX_WINDOW = 49, FilterBorder BORDER = FilterBorder::REPLICATE>
29+
struct MedianFilter {
30+
private:
31+
using Parent = ReadOperation<T, LinearFilterParams<D, T>, T, TF::DISABLED, MedianFilter<D, T, MAX_WINDOW, BORDER>>;
32+
using SelfType = MedianFilter<D, T, MAX_WINDOW, BORDER>;
33+
using Base = VBase<T>;
34+
static constexpr int CH = cn<T>;
35+
public:
36+
FK_STATIC_STRUCT(MedianFilter, SelfType)
37+
DECLARE_READ_PARENT
38+
39+
FK_HOST_DEVICE_FUSE auto exec(const Point thread, const ParamsType& params) -> T {
40+
const int W = static_cast<int>(params.src.dims.width);
41+
const int H = static_cast<int>(params.src.dims.height);
42+
const int count = params.kW * params.kH;
43+
Base buf[CH][MAX_WINDOW];
44+
int n = 0;
45+
for (int ky = 0; ky < params.kH; ++ky) {
46+
for (int kx = 0; kx < params.kW; ++kx) {
47+
int sx = static_cast<int>(thread.x) + kx - params.anchorX;
48+
int sy = static_cast<int>(thread.y) + ky - params.anchorY;
49+
T v;
50+
if constexpr (BORDER == FilterBorder::REPLICATE) {
51+
sx = sx < 0 ? 0 : (sx >= W ? W - 1 : sx);
52+
sy = sy < 0 ? 0 : (sy >= H ? H - 1 : sy);
53+
v = *PtrAccessor<D>::template cr_point<T, T>(Point{sx, sy, thread.z}, params.src);
54+
} else {
55+
if (sx < 0 || sx >= W || sy < 0 || sy >= H) v = make_set<T>(static_cast<Base>(0));
56+
else v = *PtrAccessor<D>::template cr_point<T, T>(Point{sx, sy, thread.z}, params.src);
57+
}
58+
storeChannels(buf, n, v);
59+
++n;
60+
}
61+
}
62+
// Insertion sort each channel, then pick the middle element.
63+
T out;
64+
const int mid = count / 2;
65+
for (int c = 0; c < CH; ++c) {
66+
for (int i = 1; i < count; ++i) {
67+
Base key = buf[c][i];
68+
int j = i - 1;
69+
while (j >= 0 && buf[c][j] > key) { buf[c][j + 1] = buf[c][j]; --j; }
70+
buf[c][j + 1] = key;
71+
}
72+
setChannel(out, c, buf[c][mid]);
73+
}
74+
return out;
75+
}
76+
77+
FK_HOST_DEVICE_FUSE uint num_elems_x(const Point thread, const OperationDataType& opData) { return opData.params.src.dims.width; }
78+
FK_HOST_DEVICE_FUSE uint num_elems_y(const Point thread, const OperationDataType& opData) { return opData.params.src.dims.height; }
79+
FK_HOST_DEVICE_FUSE uint num_elems_z(const Point thread, const OperationDataType& opData) { return 1; }
80+
FK_HOST_DEVICE_FUSE uint pitch(const Point thread, const OperationDataType& opData) { return opData.params.src.dims.pitch; }
81+
FK_HOST_DEVICE_FUSE ActiveThreads getActiveThreads(const OperationDataType& opData) {
82+
return { num_elems_x(Point{0,0,0}, opData), num_elems_y(Point{0,0,0}, opData), 1u };
83+
}
84+
FK_HOST_FUSE InstantiableType build(const Ptr<D, T>& src, int kW, int kH, int anchorX, int anchorY) {
85+
return { { LinearFilterParams<D, T>{ src.ptr(), RawPtr<ND::_2D, float>{}, kW, kH, anchorX, anchorY } } };
86+
}
87+
private:
88+
FK_HOST_DEVICE_FUSE void storeChannels(Base buf[CH][MAX_WINDOW], int n, const T& v) {
89+
if constexpr (CH == 1) { buf[0][n] = v; }
90+
else if constexpr (CH == 2) { buf[0][n] = v.x; buf[1][n] = v.y; }
91+
else if constexpr (CH == 3) { buf[0][n] = v.x; buf[1][n] = v.y; buf[2][n] = v.z; }
92+
else { buf[0][n] = v.x; buf[1][n] = v.y; buf[2][n] = v.z; buf[3][n] = v.w; }
93+
}
94+
FK_HOST_DEVICE_FUSE void setChannel(T& out, int c, Base val) {
95+
if constexpr (CH == 1) { out = val; }
96+
else { (&out.x)[c] = val; }
97+
}
98+
};
99+
100+
} // namespace fk
101+
102+
#endif

0 commit comments

Comments
 (0)