はじめに
Python で1次元の numpy.ndarray
型で時間波形を処理していて、波形を時間方向にシフトさせるコードを書こうとしたのですが、サンプリング時間からインデックスを求めてスライスしてとかやるのが面倒だったので、いい方法がないか Chat GPT に聞いて実装しました。
波形を時間方向にシフトさせるコード
前提としては、シフトさせたときに、 np.roll
のように配列ではみ出した部分が逆側に回るということがないようにしたいと思っていました。
実装の方法としては4つありそうです。以下にPythonのコードを記載します。
shift_waveform_interpolation
numpy.interp
を利用する方法
shift_waveform_ndimage
scipy.ndimage.shift
を利用する方法
shift_waveform
numpy
のスライスを利用する方法
shift_waveform_concatenation
numpy.concatenate
とnumpy
のスライスを組み合わせる方法
どれも波形から見ると同じように見えましたが、実際に同じ値になっているのは、shift_waveform
と shift_waveform_concatenation
の結果だけでした。
これらはスライスで取ってきてるので元のデータと同じになります。一方で、他の処理は内部で補間などがされていると考えられますが、詳細は確認できていません。
import numpy as np
import matplotlib.pyplot as plt
from scipy.ndimage import shift
from typing import List
def shift_waveform_interpolation(
original_waveform: np.ndarray, shift_time: float, sampling_time: float
) -> np.ndarray:
"""
Shift a waveform using interpolation.
Parameters
----------
original_waveform : np.ndarray
The original waveform.
shift_time : float
The amount of time by which the waveform should be shifted.
sampling_time : float
The sampling time of the waveform.
Returns
-------
out : np.ndarray
The shifted waveform.
"""
t = np.arange(0, len(original_waveform) * sampling_time, sampling_time)
t_shifted = t + shift_time
return np.interp(t, t_shifted, original_waveform, left=0, right=0)
def shift_waveform_ndimage(
waveform: np.ndarray, shift_time: float, sampling_time: float
) -> np.ndarray:
"""
Shift a waveform using scipy.ndimage's shift function.
Parameters
----------
waveform : np.ndarray
The original waveform.
shift_time : float
The amount of time by which the waveform should be shifted.
sampling_time : float
The sampling time of the waveform.
Returns
-------
out : np.ndarray
The shifted waveform.
"""
shift_amount = int(shift_time / sampling_time)
return shift(waveform, shift_amount, cval=0)
def shift_waveform(
waveform: np.ndarray, shift_time: float, sampling_time: float
) -> np.ndarray:
"""
Shift a waveform by a specified time.
Parameters
----------
waveform : np.ndarray
The original waveform.
shift_time : float
The amount of time by which the waveform should be shifted.
sampling_time : float
The sampling time of the waveform.
Returns
-------
out : np.ndarray
The shifted waveform.
"""
# シフト量をサンプル数に変換
shift_amount = int(shift_time / sampling_time)
# 0で埋められた配列を用意
shifted_waveform = np.zeros_like(waveform)
# シフト量が正なら右に、負なら左にシフト
if shift_amount > 0:
shifted_waveform[shift_amount:] = waveform[:-shift_amount]
elif shift_amount < 0:
shifted_waveform[:shift_amount] = waveform[-shift_amount:]
return shifted_waveform
def shift_waveform_concatenation(
original_waveform: np.ndarray, shift_time: float, sampling_time: float
) -> np.ndarray:
"""
Shift a waveform by concatenating zeros.
Parameters
----------
original_waveform : np.ndarray
The original waveform.
shift_time : float
The amount of time by which the waveform should be shifted.
sampling_time : float
The sampling time of the waveform.
Returns
-------
out : np.ndarray
The shifted waveform.
"""
# シフト量をサンプル数に変換
shift_amount = int(shift_time / sampling_time)
# 0で埋められた配列を用意し、元の波形を連結
shifted_waveform = np.concatenate(
[np.zeros(shift_amount), original_waveform[:-shift_amount]]
)
return shifted_waveform
def plot_shifted_waveforms(
original_waveform: np.ndarray,
shifted_waveforms: List[np.ndarray],
shift_times: List[float],
method_names: List[str],
) -> None:
"""
Plot the original and shifted waveforms obtained using different methods.
Parameters
----------
original_waveform : np.ndarray
The original waveform.
shifted_waveforms : List of np.ndarray
List of shifted waveforms obtained using different methods.
shift_times : List of float
List of shift times for each method.
method_names : List of str
List of names for each method.
"""
t = np.arange(0, len(original_waveform) * 0.1, 0.1)
plt.plot(t, original_waveform, label="Original Waveform")
for shifted_waveform, shift_time, method_name in zip(
shifted_waveforms, shift_times, method_names
):
plt.plot(
t,
shifted_waveform,
label=f"{method_name} Shift by {shift_time}s",
)
plt.xlabel("Time")
plt.ylabel("Amplitude")
plt.legend()
plt.show()
# オリジナルのガウス波形を生成
t = np.arange(0, 100, 0.1)
original_gaussian = np.exp(-((t - 50) ** 2) / (2 * 10**2))
# サンプリング時間
sampling_time = 0.1
# シフトする時間 T[s]
shift_time = 50
# 各関数で波形をシフト
shifted_waveform_interp = shift_waveform_interpolation(
original_gaussian, shift_time, sampling_time
)
shifted_waveform_ndimage = shift_waveform_ndimage(
original_gaussian, shift_time, sampling_time
)
shifted_waveform_custom = shift_waveform(original_gaussian, shift_time, sampling_time)
shifted_gaussian_concatenation = shift_waveform_concatenation(
original_gaussian, shift_time, sampling_time
)
# 波形を比較してプロット
plot_shifted_waveforms(
original_gaussian,
[
shifted_waveform_interp,
shifted_waveform_ndimage,
shifted_waveform_custom,
shifted_gaussian_concatenation,
],
[shift_time, shift_time, shift_time, shift_time],
["Interpolation", "ndimage", "Custom", "Concatennation"],
)
print(np.all(shifted_waveform_interp == shifted_waveform_ndimage)) # False
print(np.all(shifted_waveform_interp == shifted_waveform_custom)) # False
print(np.all(shifted_waveform_interp == shifted_gaussian_concatenation)) # False
print(np.all(shifted_waveform_ndimage == shifted_waveform_custom)) # False
print(np.all(shifted_waveform_ndimage == shifted_gaussian_concatenation)) # False
print(np.all(shifted_waveform_custom == shifted_gaussian_concatenation)) # True
おわりに
とりあえず、波形を時間方向にシフトさせるコードを作りたかったので、書きました。
複数の実装方法があることを Chat GPT から学べたのと、計算方法が違うときは内部までしっかり理解することが必要だなと感じました。
コメント