How to structure my own Python signal processing module?

91 Views Asked by At

I want to take my Python (currently Version 3.9.7) programming skills to a next level. Up to now I just wrote some small scripts for myself, that no one hat to review or reuse. Now, I want to write code that can be considered as "clean" and can be reused by others. For this purpose, I am writing my own signal processing module with which I can generate high- and lowpassfilters in order to filter signals. I have no experience with structuring packages / modules, so I have some questions regarding code structure.

Up to now, I have a class sim_lowpass:

# -*- coding: utf-8 -*-
"""
Created on Wed Jun 22 10:37:19 2022

@author: ilja
"""

from matplotlib import pyplot as plt
import math


class sim_lowpass:
    """ Lowpass Simulation Class """
    
    def __init__(self, cutoff: int, order: int, fs: int) -> None:
        self.fs = fs
        self.nyq = int(0.5 * fs)
        self.cutoff = cutoff
        self.order = order
    
    
    def _transfer_func(self,f: float) -> float:
        """ Transfer function in the z-domain """
        
        if self.order == 1:
            return 1/(1+(f/(2*math.pi*self.cutoff)))
    
        
    def transfer_func(self) -> list[float]:
        """ Transfer function in the z-domain """
        
        if self.order == 1:
            # f = np.linspace(self.df, self.nyq, self.N/2)
            f = list(range(int(self.nyq)))
            return [self._transfer_func(i) for i in f]
    
    
    def propagate(self, x_hat):
        filtered = [i*j for i,j in zip(x_hat, self.impulse_response())]
        return filtered
    
    
    def bode_plot(self, tr_func: list[float]) -> None:
        fig, (ax1, ax2) = plt.subplots(2, 1, constrained_layout=True, 
                                       figsize = (8,5))
        ax1.plot(list(range(self.nyq)), tr_func)
        #ax1.set_title('Magnitude')
        ax1.set_xscale('log')
        ax1.set_yscale('log')
        ax1.set_ylabel('Magnitude (dB)')
        ax1.grid(True)
        # ax2.plot(list(range(self.nyq)), tr_func) # TODO
        # ax2.set_title('Phase')
        ax2.set_xscale('log')
        ax2.set_yscale('log')
        ax2.set_xlabel('Frequency (Hz)')
        ax2.set_ylabel('Phase (deg)')
        ax2.grid(True)
        
        fig.suptitle('Bode Plot', fontsize=16)
    

def main() -> None:
    # define filter params
    cutoff = 100
    order = 1
    fs = 4e6
    
    # create filter
    lp = sim_lowpass(cutoff, order, fs)
    tf = lp.transfer_func()
    lp.bode_plot(tf)    


if __name__ == '__main__':
    main()

Questions:

  • First of all: Is the code up to now well structured (in terms of scalability, testability, ... what else is there?)

  • Second: Now I want to create the class sim_lowpass. How do I continue without copy-pasting the parts I can reuse from the highpass class?

  • Third: Where do I place this file (and what would be a meaningful name) inside the package hierarchy?

  • Last but not least: Any other tips for improvement?

1

There are 1 best solutions below

2
LudvigL On

I usually get inspiration for code-structure from real projects. For example, since you are using matplotlib, their github could be a place to start: https://github.com/matplotlib/matplotlib/tree/main/lib/matplotlib