Source code for pyctools.components.interp.filtergenerator

#  Pyctools - a picture processing algorithm development kit.
#  http://github.com/jim-easterbrook/pyctools
#  Copyright (C) 2014-20  Pyctools contributors
#
#  This program is free software: you can redistribute it and/or
#  modify it under the terms of the GNU General Public License as
#  published by the Free Software Foundation, either version 3 of the
#  License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#  General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see
#  <http://www.gnu.org/licenses/>.

__all__ = ['FilterGenerator']
__docformat__ = 'restructuredtext en'

import math
import sys

import numpy

from pyctools.core.config import ConfigInt
from pyctools.core.base import Component
from pyctools.core.frame import Frame

[docs] class FilterGenerator(Component): """Classic filter generator component. Create a filter from a Hamming windowed sinc function. The cut frequency is set to the "Nyquist limit" (half the sampling frequency) of the input or output sampling frequency, whichever is the lower. This cut frequency can be adjusted with the ``xcut`` and ``ycut`` configuration. Connecting a :py:class:`FilterGenerator` component's ``output`` to a :py:class:`~.resize.Resize` component's ``filter`` input allows the filter to be updated (while the components are running) by changing the :py:class:`FilterGenerator` config:: filgen = FilterGenerator(xup=2, xaperture=16) resize = Resize(xup=2) filgen.connect('output', resize.filter) ... start(..., filgen, resize, ...) ... filgen.set_config({'xaperture': 8}) ... If you don't need to change the configuration after creating the :py:class:`~.resize.Resize` component then it's simpler to use a :py:class:`FilterGeneratorCore` to create a fixed filter. 2-dimensional filters can be produced by setting both ``xaperture`` and ``yaperture``, but it is usually more efficient to use two :py:class:`~.resize.Resize` components to process the two dimensions independently. Config: ============= === ==== ``xup`` int Horizontal up-conversion factor. ``xdown`` int Horizontal down-conversion factor. ``xaperture`` int Horizontal filter aperture. ``xcut`` int Adjust horizontal cut frequency. Default is 100%. ``yup`` int Vertical up-conversion factor. ``ydown`` int Vertical down-conversion factor. ``yaperture`` int Vertical filter aperture. ``ycut`` int Adjust vertical cut frequency. Default is 100%. ============= === ==== """ inputs = [] with_outframe_pool = False def initialise(self): self.config['xup'] = ConfigInt(min_value=1) self.config['xdown'] = ConfigInt(min_value=1) self.config['xaperture'] = ConfigInt(min_value=1) self.config['xcut'] = ConfigInt(min_value=1, value=100) self.config['yup'] = ConfigInt(min_value=1) self.config['ydown'] = ConfigInt(min_value=1) self.config['yaperture'] = ConfigInt(min_value=1) self.config['ycut'] = ConfigInt(min_value=1, value=100) def on_start(self): # send first filter coefs self.make_filter() def on_set_config(self): # send more coefs if config changes self.make_filter() def make_filter(self): self.update_config() x_up = self.config['xup'] x_down = self.config['xdown'] x_ap = self.config['xaperture'] x_cut = self.config['xcut'] y_up = self.config['yup'] y_down = self.config['ydown'] y_ap = self.config['yaperture'] y_cut = self.config['ycut'] self.send('output', self.core( x_up=x_up, x_down=x_down, x_ap=x_ap, x_cut=x_cut, y_up=y_up, y_down=y_down, y_ap=y_ap, y_cut=y_cut))
[docs] @classmethod def core(cls, x_up=1, x_down=1, x_ap=1, x_cut=100, y_up=1, y_down=1, y_ap=1, y_cut=100): """Classic filter generator core. Alternative to the :py:class:`FilterGenerator` component that can be used to make a non-reconfigurable resizer:: resize = Resize(xup=2) resize.filter(FilterGenerator.core(xup=2, xaperture=16)) ... start(..., resize, ...) ... :keyword int x_up: Horizontal up-conversion factor. :keyword int x_down: Horizontal down-conversion factor. :keyword int x_ap: Horizontal filter aperture. :keyword int x_cut: Horizontal cut frequency adjustment. :keyword int y_up: Vertical up-conversion factor. :keyword int y_down: Vertical down-conversion factor. :keyword int y_ap: Vertical filter aperture. :keyword int y_cut: Vertical cut frequency adjustment. :return: A :py:class:`~pyctools.core.frame.Frame` object containing the filter. """ def filter_1D(up, down, ap, cut_adj): nyquist_freq = float(min(up, down)) / float(2 * up * down) cut_adj = float(cut_adj) / 100.0 coefs = [] n = 1 while True: theta_1 = float(n) * math.pi * 2.0 * nyquist_freq theta_2 = theta_1 * 2.0 / float(ap) if theta_2 >= math.pi: break theta_1 *= cut_adj coef = math.sin(theta_1) / theta_1 win = 0.5 * (1.0 + math.cos(theta_2)) coef = coef * win if abs(coef) < 1.0e-16: coef = 0.0 coefs.append(coef) n += 1 fil_dim = len(coefs) result = numpy.ones(1 + (fil_dim * 2), dtype=numpy.float32) n = 1 for coef in coefs: result[fil_dim - n] = coef result[fil_dim + n] = coef n += 1 # normalise gain of each phase phases = (up * down) // min(up, down) for n in range(phases): result[n::phases] /= result[n::phases].sum() result /= float(phases) return result x_up = max(x_up, 1) x_down = max(x_down, 1) x_ap = max(x_ap, 1) x_cut = max(x_cut, 1) y_up = max(y_up, 1) y_down = max(y_down, 1) y_ap = max(y_ap, 1) y_cut = max(y_cut, 1) x_fil = filter_1D(x_up, x_down, x_ap, x_cut) y_fil = filter_1D(y_up, y_down, y_ap, y_cut) result = numpy.empty( [y_fil.shape[0], x_fil.shape[0], 1], dtype=numpy.float32) for y in range(y_fil.shape[0]): for x in range(x_fil.shape[0]): result[y, x, 0] = x_fil[x] * y_fil[y] out_frame = Frame() out_frame.data = result out_frame.type = 'fil' audit = 'data = FilterGenerator()\n' if x_up != 1 or x_down != 1 or x_ap != 1: audit += ' x_up: %d, x_down: %d, x_ap: %d, x_cut: %d%%\n' % ( x_up, x_down, x_ap, x_cut) if y_up != 1 or y_down != 1 or y_ap != 1: audit += ' y_up: %d, y_down: %d, y_ap: %d, y_cut: %d%%\n' % ( y_up, y_down, y_ap, y_cut) out_frame.set_audit(cls, audit) return out_frame