File: zero_padding.py

package info (click to toggle)
python-sigima 1.1.1-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 25,608 kB
  • sloc: python: 35,251; makefile: 3
file content (163 lines) | stat: -rw-r--r-- 5,260 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.

"""
Zero Padding for FFT Enhancement
=================================

This example demonstrates how to apply zero-padding to signals, a common technique
used to improve FFT frequency resolution. It shows the proper usage of
:class:`sigima.params.ZeroPadding1DParam`, including the important
``update_from_obj()`` call.

Zero-padding adds zeros to a signal, effectively interpolating the frequency domain
representation. This is particularly useful for:

- Improving frequency resolution in FFT analysis
- Preparing signals for convolution operations
- Matching signal lengths for spectral comparisons
"""

# %%
# Importing the required modules
# ------------------------------

import numpy as np

import sigima.params
import sigima.proc.signal as sips
from sigima import viz
from sigima.objects import create_signal

# %%
# Create a test signal
# --------------------
# We create a simple cosine signal with a specific frequency.

# Signal parameters
freq = 50.0  # Hz
duration = 0.1  # seconds
sample_rate = 1000  # Hz
n_points = int(duration * sample_rate)

# Create time array and signal
t = np.linspace(0, duration, n_points, endpoint=False)
y = np.cos(2 * np.pi * freq * t)

signal = create_signal(
    title=f"Cosine {freq} Hz", x=t, y=y, units=("s", "V"), labels=("Time", "Amplitude")
)

print(f"Original signal: {n_points} points")

viz.view_curves(signal, title="Original Signal")

# %%
# Zero-padding with "next_pow2" strategy
# --------------------------------------
#
# The "next_pow2" strategy pads the signal to the next power of 2, which is
# optimal for FFT computations.
#
# .. important::
#
#     When using strategies other than "custom", you **must call**
#     ``update_from_obj()`` to compute the number of padding points based on
#     the actual signal size.

# Create the parameter with "next_pow2" strategy
param = sigima.params.ZeroPadding1DParam.create(strategy="next_pow2")

# At this point, param.n is still the default value (1)
print(f"Before update_from_obj: n = {param.n}")

# IMPORTANT: Update parameters from the signal to compute the actual 'n'
param.update_from_obj(signal)

# Now param.n has been computed based on the signal size
print(
    f"After update_from_obj: n = {param.n} "
    f"(signal will be padded to {n_points + param.n} points)"
)

# Apply zero-padding
padded_signal = sips.zero_padding(signal, param)

padded_size = padded_signal.y.size
power_of_2 = 2 ** int(np.log2(padded_size))
print(f"Padded signal: {padded_size} points (power of 2: {power_of_2})")

# %%
# Compare original and padded signals
# -----------------------------------
# The padded signal has zeros appended at the end.

viz.view_curves([signal, padded_signal], title="Original vs Zero-Padded Signal")

# %%
# FFT comparison: improved frequency resolution
# ---------------------------------------------
# Zero-padding improves the apparent frequency resolution of the FFT by
# interpolating between frequency bins.

# Compute FFT of original signal
fft_original = sips.fft(signal)
fft_original.title = f"FFT Original ({fft_original.y.size} bins)"

# Compute FFT of padded signal
fft_padded = sips.fft(padded_signal)
fft_padded.title = f"FFT Zero-Padded ({fft_padded.y.size} bins)"

print(f"Original FFT: {fft_original.y.size} frequency bins")
print(f"Padded FFT: {fft_padded.y.size} frequency bins")

viz.view_curves([fft_original, fft_padded], title="FFT: Original vs Zero-Padded")

# %%
# Using different strategies
# --------------------------
# The available strategies are:
#
# - ``"next_pow2"``: Pad to the next power of 2 (optimal for FFT)
# - ``"double"``: Double the signal length
# - ``"triple"``: Triple the signal length
# - ``"custom"``: Specify the exact number of points to add

for strategy in ["next_pow2", "double", "triple"]:
    param = sigima.params.ZeroPadding1DParam.create(strategy=strategy)
    param.update_from_obj(signal)
    print(f"Strategy '{strategy}': adds {param.n} points → total {n_points + param.n}")

# %%
# Using "custom" strategy
# -----------------------
# With the "custom" strategy, you specify the exact number of points.
# In this case, ``update_from_obj()`` is not strictly necessary (but harmless).

param_custom = sigima.params.ZeroPadding1DParam.create(strategy="custom", n=500)
print(f"Custom strategy: adds {param_custom.n} points")

padded_custom = sips.zero_padding(signal, param_custom)
print(f"Result: {padded_custom.y.size} points")

# %%
# Choosing padding location
# -------------------------
# Zero-padding can be applied at different locations:
#
# - ``"append"``: Add zeros at the end (default)
# - ``"prepend"``: Add zeros at the beginning
# - ``"both"``: Split zeros between beginning and end

from sigima.enums import PadLocation1D

results = []
for location in PadLocation1D:
    param = sigima.params.ZeroPadding1DParam.create(strategy="double")
    param.location = location
    param.update_from_obj(signal)
    result = sips.zero_padding(signal, param)
    result.title = f"Padded ({location.value})"
    results.append(result)
    print(f"Location '{location.value}': x=[{result.x[0]:.4f}, {result.x[-1]:.4f}]")

viz.view_curves(results, title="Padding Location Comparison")