TorchDSP / torchsig

TorchSig is an open-source signal processing machine learning toolkit based on the PyTorch data handling pipeline.
MIT License
155 stars 37 forks source link

Freq Shift Avoid Aliasing() Transform needs a rework #199

Closed MattCarrickPL closed 11 months ago

MattCarrickPL commented 1 year ago

Frequency shift avoid aliasing transform performs a frequency shift and applies filtering to avoid aliasing such that no energy wraps around the -fs/2 to +fs/2 boundary. Currently it is implemented with an interpolate by 2 using resample_poly(), a LPF, a frequency shift, another LPF, and then decimate by 2 using resample_poly().

  1. The first improvement is that the two LPF's are not needed since there is an implicit LPF within both the interpolate and decimate resample_poly() calls.

    up = 2 down = 1 tensor = sp.resample_poly(tensor, up, down)

    taps = low_pass(cutoff=1 / 4, transition_bandwidth=(0.5 - 1 / 4) / 4) tensor = sp.convolve(tensor, taps, mode="same")

    # Freq shift to desired center freq time_vector = np.arange(tensor.shape[0], dtype=np.float64) tensor = tensor np.exp(2j np.pi f_shift / up time_vector)

    # Filter to remove out-of-band regions taps = low_pass(cutoff=1 / 4, transition_bandwidth=(0.5 - 1 / 4) / 4) tensor = sp.convolve(tensor, taps, mode="same") tensor = tensor[: int(num_iq_samples * up)] # prune to be correct size out of filter

    # Decimate back down to correct sample rate tensor = sp.resample_poly(tensor, down, up)

  2. It's not clear if the interpolate by 2 and decimate by 2 are needed at all. This function needs more analysis, and I am working on the assumption that the transform is used to mimic the effect when a receiver is off-tuned to a signal such that part of the signal's energy is lost because it is outside the receiver's bandwidth:

image

2 (cont) It's possible that just a frequency shift and LPF are needed in order to implement it. I believe it depends on when the transform is applied. If the transform is applied before noise is added then the resampling is not needed. If the transform is applied after the noise is added, then the LPF would impact the spectral response of the noise which may not model the impairment in way we intend.

image

image

MattCarrickPL commented 12 months ago

Doing a deep dive into the frequency shift avoid aliasing transform. The first part is to take the input signal and interpolate by 2 using the scipy resample_poly() function, which looks good: image

MattCarrickPL commented 12 months ago

As listed in the comment above, the LPF after the interpolation is not needed since there is already an LPF implemented by the anti-aliasing filter in the interpolation stage, which has been disabled in the new version of the transform. The LPF before the decimation stage has also been removed for the same reason.

MattCarrickPL commented 12 months ago

The frequency shift is applied to the signal before it is decimated. The challenge is that the frequency shift pushes the signal's energy into the +fs/4 boundary which will alias back into band after downsampling. See the following example: image

Notice in the second figure how the energy aliases from +fs/2 to -fs/2, such that the energy is only about -12 dB down from peak. There is significant aliased energy as a result. A better anti-aliasing filter is needed.

MattCarrickPL commented 12 months ago

Presumably the resample_poly() filter uses a filter cutoff of (normalized) fs/4. The problem with this is that an ideal LPF filter (infinitely long and infinitely small transition bandwidth) is needed which cannot be built. So instead, the cutoff must be reduced in order to create a scenario in which a filter with a non-zero transition bandwidth can be created.

A new filter is proposed which uses a cutoff of 75% of the aliasing edge fs/4. You'll notice on the left image that the proposed coefs (red) have a lower cutoff frequency than that of resample_poly() (black). On the right image the roll-off from the filter is at a lower frequency for the new coefs (red) than resample-poly (black). The improvement is that the aliased energy is now at -65 dB, a +53 dB improvement in dynamic range

image

The trade-off is: the existing resample_poly() retains the the mainlobe of the signal at the cost of having aliased energy, to the point where very little (if any) LPF effect is applied. It is as if the anti-aliasing filter is never applied due to how the energy wraps around from +fs/2 to -fs/2. The proposed filter coefs introduce distortion across the mainlobe of the signal at the benefit to reducing aliasing energy.

MattCarrickPL commented 11 months ago

Merged via https://github.com/TorchDSP/torchsig/pull/210