Pratik Dash
Pratik Dash

Reputation: 185

How to perform a left shift of discrete signals via convolution operation in python?

A left shift of t0 in a signal can be obtained by convolving it with δ(t+t0). I want to obtain this in python using discrete signals by only using the np.convolve operator in mode='full'. Note that I can't use np.roll or assignment from an index like [shift:]. It has to be obtained purely by convolution. Here's an example:

signal = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9])
shift = 3
filter = ??
shifted_signal = np.convolve(signal, filter, mode='full')
print(shifted_signal)

[4, 5, 6, 7, 8, 9, 0, 0, 0, ...

The number of zeros at the right side, or to say the length of shifted_signal is not important. But I want the signal to start from the correct position. What should the filter be such that I get the desired output? I need it this way because I am trying to obtain the left shift from an FFT analysis. A left shift of t0 in the time domain corresponds to a multiplication of ei2πft0 in the frequency domain. I can obtain t0 by comparing the shifted signal with the original in the frequency domain. But to check if my algorithm works, I just can't think of a filter that performs a left shift in the time domain.

I tried filter = [0, 0, 0, 1] and filter = [1, 0, 0, 0] but they adds zeros to the left and right of the signal. The signal itself doesn't start from the desired index. I have no idea what other filter I can use to obtain the desired result.

Upvotes: 0

Views: 356

Answers (3)

Pratik Dash
Pratik Dash

Reputation: 185

After realizing that it may not be possible to perform a left shift via convolution with a Dirac delta function using only np.convolve(, mode='full') since there is no concept of negative indices in arrays in python, I manually defined a time axis to perform discrete convolution based on its elements rather than the indices of arrays. The execution is crude and runs very slowly for large arrays but it serves as a proof-of-concept. The code below shows an example.

Special mention to @dankal444 who also had the same suggestion in the edit to his answer.

# performing a left shift via convolution in python

import numpy as np
import matplotlib.pyplot as plt

# custom define a convolution function
def convolve(signal, kernel, time):
    # initialize a convolution array
    conv = np.zeros_like(time)
    
    # flip the kernel about time=0
    flipped_kernel = np.flip(kernel)
    inverted_time = np.flip(time)*-1
    
    # now loop through time
    for i, t in enumerate(time):
        # shift the flipped kernel
        shifted_inverted_time = inverted_time + t
        
        # get the times when the flipped kernel overlaps with the signal
        time_overlap_indices = np.where((time >= shifted_inverted_time.min()) & (time <= shifted_inverted_time.max()))[0]
        sit_overlap_indices = np.where((shifted_inverted_time >= time.min()) & (shifted_inverted_time <= time.max()))[0]
        
        # make sure that both have the same times
        if len(time_overlap_indices) > len(sit_overlap_indices):
        
            if time[time_overlap_indices[0]] != shifted_inverted_time[sit_overlap_indices[0]] or \
                    time[time_overlap_indices[-1]] != shifted_inverted_time[sit_overlap_indices[-1]]:
        
                time_overlap_indices = time_overlap_indices[1:] if (time[time_overlap_indices[0]] !=
                                                                    shifted_inverted_time[sit_overlap_indices[0]]) \
                                                                else time_overlap_indices[:-1]
        elif len(time_overlap_indices) < len(sit_overlap_indices):
        
            if shifted_inverted_time[sit_overlap_indices[0]] != time[time_overlap_indices[0]] or \
               shifted_inverted_time[sit_overlap_indices[-1]] != time[time_overlap_indices[-1]]:
        
                sit_overlap_indices = sit_overlap_indices[1:] if (shifted_inverted_time[sit_overlap_indices[0]] !=
                                                                  time[time_overlap_indices[0]]) \
                                                                else sit_overlap_indices[:-1]

        # get the convolution at index 'i'
        conv[i] = np.sum(signal[time_overlap_indices] * flipped_kernel[sit_overlap_indices])

    return conv

# Example 1D signal
time = np.linspace(-10, 8, 10000)
signal = np.exp(-(time-5)**2)

# define a left shift Dirac delta function
shift = -9 # in μs
leftShift = np.zeros_like(time)
leftShift[np.where(time >= shift)[0][0]] = 1

# get the convolved signal
convolved_signal = convolve(signal, leftShift, time)

# plot the signal, kernel and the convolved signal
plt.figure()
# plot the signals in the time domain
plt.plot(time, signal, label='signal', color='C0')
plt.axvline(time[np.argmax(signal)], color='C0', linestyle='--',
            label='signal peak at t = {} μs'.format(np.round(time[np.argmax(signal)], 2)))

plt.axvline(x=shift, ymin=0, ymax=1, color='C2', linestyle='--', 
            label='left shift of {} μs'.format(np.round(shift, 2)))

plt.plot(time, convolved_signal, label='convolved signal', color='C1')

plt.axvline(time[np.argmax(convolved_signal)], color='C1', linestyle='--',
            label='convolved signal \npeak at t = {} μs'.format(np.round(time[np.argmax(convolved_signal)], 2)))

# append new ticks to the current ticks
plt.xticks(np.append(plt.gca().get_xticks(), np.round([shift, time[np.argmax(signal)], time[np.argmax(convolved_signal)]], 2)))
plt.xlabel('Time (in μs)'), plt.ylabel('Amplitude'), plt.grid(), plt.legend()
plt.title('Left shift of {} μs'.format(shift))

Plot showing the left shift of a Gaussian pulse

Upvotes: 0

dankal444
dankal444

Reputation: 4158

You can't shift signal left using standard convolution (without cutting the signal as mode 'same' does). There is no filter kernel that could accomplish that. If you could do that you could also travel back in time or foresee future.

Convolution can represent Linear System that has an input and some changed delayed (or not, but definitely not hastened) output. If it could "hasten" signals it would mean it could read future - give some information on the output about signal that hasn't yet entered input.

EDIT: addressing comment

However, we are in digital domain, and can interpret results of the discrete convolution a bit arbitrarily. We can assume any time domain we want for the convolution kernel.

We can say that kernel (e.g. [1, 0, 0]) has time domain of [-1, 0, 1]. For simplicity signal has time domain [0, 1, 2..]. This way result of the convolution will have time domain shifted, to the beginning of kernel: [-1, 0, 1, 2..]. And we accomplished shifting signal left.

We could use any time domain we wanted for the kernel and have different result, but the one that is symmetrical around 0 seems common sense.

Upvotes: 0

user25884369
user25884369

Reputation: 29

I got your desired result by switching the mode to same. Here is the code

import numpy as np
signal = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9])
shift = 3
filter = [1]
for i in filter:
    if len(filter) < 2 * shift + 1:
        filter.append(0)
    else:
        break
shifted_signal = np.convolve(signal, filter, mode='same')
print(shifted_signal)

Output is

[4 5 6 7 8 9 0 0 0]

This could probably be improved by using list comprehension

Upvotes: 0

Related Questions