{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n# Butterworth Filters\n\nThe Butterworth filter is implemented in the frequency domain and is designed\nto have no passband or stopband ripple. It can be used in either a lowpass or\nhighpass variant. The ``cutoff_frequency_ratio`` parameter is used to set the\ncutoff frequency as a fraction of the sampling frequency. Given that the\nNyquist frequency is half the sampling frequency, this means that this\nparameter should be a positive floating point value < 0.5. The ``order`` of the\nfilter can be adjusted to control the transition width, with higher values\nleading to a sharper transition between the passband and stopband.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Butterworth filtering example\nHere we define a `get_filtered` helper function to repeat lowpass and\nhighpass filtering at a specified series of cutoff frequencies.\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt\n\nfrom skimage import data, filters\n\nimage = data.camera()\n\n# cutoff frequencies as a fraction of the maximum frequency\ncutoffs = [0.02, 0.08, 0.16]\n\n\ndef get_filtered(image, cutoffs, squared_butterworth=True, order=3.0, npad=0):\n \"\"\"Lowpass and highpass butterworth filtering at all specified cutoffs.\n\n Parameters\n ----------\n image : ndarray\n The image to be filtered.\n cutoffs : sequence of int\n Both lowpass and highpass filtering will be performed for each cutoff\n frequency in `cutoffs`.\n squared_butterworth : bool, optional\n Whether the traditional Butterworth filter or its square is used.\n order : float, optional\n The order of the Butterworth filter\n\n Returns\n -------\n lowpass_filtered : list of ndarray\n List of images lowpass filtered at the frequencies in `cutoffs`.\n highpass_filtered : list of ndarray\n List of images highpass filtered at the frequencies in `cutoffs`.\n \"\"\"\n\n lowpass_filtered = []\n highpass_filtered = []\n for cutoff in cutoffs:\n lowpass_filtered.append(\n filters.butterworth(\n image,\n cutoff_frequency_ratio=cutoff,\n order=order,\n high_pass=False,\n squared_butterworth=squared_butterworth,\n npad=npad,\n )\n )\n highpass_filtered.append(\n filters.butterworth(\n image,\n cutoff_frequency_ratio=cutoff,\n order=order,\n high_pass=True,\n squared_butterworth=squared_butterworth,\n npad=npad,\n )\n )\n return lowpass_filtered, highpass_filtered\n\n\ndef plot_filtered(lowpass_filtered, highpass_filtered, cutoffs):\n \"\"\"Generate plots for paired lists of lowpass and highpass images.\"\"\"\n fig, axes = plt.subplots(2, 1 + len(cutoffs), figsize=(12, 8))\n fontdict = dict(fontsize=14, fontweight='bold')\n\n axes[0, 0].imshow(image, cmap='gray')\n axes[0, 0].set_title('original', fontdict=fontdict)\n axes[1, 0].set_axis_off()\n\n for i, c in enumerate(cutoffs):\n axes[0, i + 1].imshow(lowpass_filtered[i], cmap='gray')\n axes[0, i + 1].set_title(f'lowpass, c={c}', fontdict=fontdict)\n axes[1, i + 1].imshow(highpass_filtered[i], cmap='gray')\n axes[1, i + 1].set_title(f'highpass, c={c}', fontdict=fontdict)\n\n for ax in axes.ravel():\n ax.set_xticks([])\n ax.set_yticks([])\n plt.tight_layout()\n return fig, axes\n\n\n# Perform filtering with the (squared) Butterworth filter at a range of\n# cutoffs.\nlowpasses, highpasses = get_filtered(image, cutoffs, squared_butterworth=True)\n\nfig, axes = plot_filtered(lowpasses, highpasses, cutoffs)\ntitledict = dict(fontsize=18, fontweight='bold')\nfig.text(\n 0.5,\n 0.95,\n '(squared) Butterworth filtering (order=3.0, npad=0)',\n fontdict=titledict,\n horizontalalignment='center',\n)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Avoiding boundary artifacts\n\nIt can be seen in the images above that there are artifacts near the edge of\nthe images (particularly for the smaller cutoff values). This is due to the\nperiodic nature of the DFT and can be reduced by applying some amount of\npadding to the edges prior to filtering so that there are not sharp eges in\nthe periodic extension of the image. This can be done via the ``npad``\nargument to ``butterworth``.\n\nNote that with padding, the undesired shading at the image edges is\nsubstantially reduced.\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"lowpasses, highpasses = get_filtered(image, cutoffs, squared_butterworth=True, npad=32)\n\nfig, axes = plot_filtered(lowpasses, highpasses, cutoffs)\nfig.text(\n 0.5,\n 0.95,\n '(squared) Butterworth filtering (order=3.0, npad=32)',\n fontdict=titledict,\n horizontalalignment='center',\n)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## True Butterworth filter\n\nTo use the traditional signal processing definition of the Butterworth filter,\nset ``squared_butterworth=False``. This variant has an amplitude profile in\nthe frequency domain that is the square root of the default case. This causes\nthe transition from the passband to the stopband to be more gradual at any\ngiven `order`. This can be seen in the following images which appear a bit\nsharper in the lowpass case than their squared Butterworth counterparts\nabove.\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"lowpasses, highpasses = get_filtered(image, cutoffs, squared_butterworth=False, npad=32)\n\nfig, axes = plot_filtered(lowpasses, highpasses, cutoffs)\nfig.text(\n 0.5,\n 0.95,\n 'Butterworth filtering (order=3.0, npad=32)',\n fontdict=titledict,\n horizontalalignment='center',\n)\n\nplt.show()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.3"
}
},
"nbformat": 4,
"nbformat_minor": 0
}