medfilt
result = medfilt(x, [window=3])
Input: x, window · Output: result
What it is
Replaces each sample by the median of window samples centred on it. x is a length-N number vector or an Image (filtered channel by channel); window is a positional integer, odd, default 3.
Overloads
x can be number, vector, or Image.
2 forms. Each carries its own inputs, outputs and worked example — pick the tab for the one you are calling.
| Code | Input | Output | Description |
result = medfilt(x, [window=3]) | x number or vectorx a length-N number vector.window optional (default 3)Slides a window-wide 1-D neighbourhood and keeps the median. | result — | — |
Examples
Written for this form and run by the build -- the output below is what the engine actually produced.
v = [5, 3, 40, 6, 5, 4, 3]
print(medfilt(v, 3)) # [3, 5, 6, 6, 5, 4, 4] -- the spike at index 2 is gone
[3, 5, 6, 6, 5, 4, 4]
| Code | Input | Output | Description |
result = medfilt(x, [window=3]) | x Imagex an Image.window optional (default 3)Runs the true 2-D median over a window×window square, channel by channel — not a row-by-row 1-D median, which would smear vertical structure it never looks at. | result — | Only possible because the Image carries its own width and height. (medfilt2/median_filter are the exact same builtin under other names, so this case applies to them too.). |
Examples
Written for this form and run by the build -- the output below is what the engine actually produced.
m = [128,128,128,128,128; 128,128,255,128,128; 128,128,128,128,128; 128,128,128,128,128; 128,128,128,128,128]
img = image_from_matrix(m)
cleaned = medfilt(img, 3)
print(imhist(img, 4)) # [0, 0, 24, 1] -- one bright pixel
print(imhist(cleaned, 4)) # [0, 0, 25, 0] -- gone, over a 2-D neighbourhood this time
[0, 0, 24, 1] [0, 0, 25, 0]
The paragraph and table above describe the removal tools individually; the example below chains several of them into one cleanup pipeline. First, impulse(n, [index=], [amplitude=]) builds the spike to inject in the first place — a plain length-n zero vector with one sample set to amplitude at position index. n is a required positional integer, the output length; index is a named integer sample position, default 0; amplitude is a named number, default 1. It returns a length-n number vector, and is the natural way to add a single, controlled transient rather than reaching for add_noise's random placement.
A clean tone corrupted by both a single spike and a slow linear drift needs two different tools, applied in the right order — a median filter cannot see through a ramp, and a polynomial detrend cannot see through a spike, so the spike must go first:
fs = 1000
n = 500
t = (0 to n - 1) / fs
clean = sin(2 * pi * 7 * t)
spike = 5 * impulse(n, index = 200)
corrupted = clean + spike + 0.01 * (0 to n - 1) # a spike, plus baseline drift
fixed = medfilt(corrupted, 5) # the spike
flat = detrend(fixed, 1) # the ramp
sg = savgol(flat, 11, 2) # a final polish, shape-preserving
sm = smooth(flat, 11) # same thing, one word (default method is savgol)
print("after cleanup: {measure_snr(clean, sg):.2f} dB (savgol), {measure_snr(clean, sm):.2f} dB (smooth)")
after cleanup: 17.81 dB (savgol), 17.81 dB (smooth)
How it works
medfilt(x, n) on a vector, medfilt(img, n) on an image. The answer to salt-and-pepper, and the reason is worth knowing: an average is pulled by an outlier in proportion to how far out it is, while a median ignores it entirely as long as fewer than half the window is corrupt. Which is also why it keeps an edge that an average smears -- a step is not an outlier to a median.
From the note above this builtin in the interpreter, where the reasoning was written down as it was built.