def fourier(B, dx, dt): from scipy.fft import rfft2, rfftfreq Bwk = rfft2(B) w = rfftfreq(B.shape[1], d=dt) k = rfftfreq(B.shape[0], d=dx) k = k * 2 * np.pi w = w * 2 * np.pi return Bwk, w, k
def inverse_psf_rfft(psf, shape=None, l=20, mode='laplacian'): """ Computes the real FFT of a regularized inversed 2D PSF (or projected 3D) This follows the convention of fft.rfft: only half the spectrum is computed. Parameters ---------- psf : array [ZXY] or [XY] The 2D PSF (if 3D, will be projected on Z axis). shape : tuple (int, int), optional Shape of the full-sized desired PSF (if None, will be the same as the PSF), by default None. l : int, optional Regularization lambda, by default 20 mode : str, optional The regularizer used, by default laplacian. One of: ['laplacian', 'constant'] Returns ------- array [XY] The real FFT of the inverse PSF. Raises ------ ValueError If the PSF has incorrect number of dimensions. If the regularizer is unknown. """ if psf.ndim == 3: psf = psf.sum(0) elif psf.ndim != 2: raise ValueError("Invalid dimensions for PSF: {}".format(psf.ndim)) if shape is None: shape = psf.shape psf_fft = fft.rfft2(psf, s=shape) # We need to shift the PSF so that the center is located at the (0, 0) pixel # otherwise deconvolving will shift every pixel freq = fft.rfftfreq(shape[1]) phase_shift = freq * 2 * np.pi * ((psf.shape[1] - 1) // 2) psf_fft *= np.exp(1j * phase_shift[None, :]) freq = fft.fftfreq(shape[0]) phase_shift = freq * 2 * np.pi * ((psf.shape[0] - 1) // 2) psf_fft *= np.exp(1j * phase_shift[:, None]) if mode == 'laplacian': # Laplacian regularization filter to avoid NaN filt = [[0, -0.25, 0], [-0.25, 1, -0.25], [0, -0.25, 0]] reg = np.abs(fft.rfft2(filt, s=shape))**2 elif mode == 'constant': reg = 1 else: raise ValueError('Unknown regularizer: {}'.format(mode)) return psf_fft.conjugate() / (np.abs(psf_fft)**2 + l * reg)
def time_fourier( x, *ys, sampleFactor=1, interpKind='linear', minFreq='auto', maxFreq='auto', minAmps=None, maxAmps=None, ): if minAmps is None: minAmps = [None for y in ys] if maxAmps is None: maxAmps = [None for y in ys] x, *ys = time_smooth(x, *ys, sampleFactor=sampleFactor, kind=interpKind) N = len(x) T = np.diff(x).mean() freqs = rfftfreq(N, T)[:N // 2] if minFreq is None: minFreq = min(freqs) elif minFreq == 'auto': minFreq = (1 / np.ptp(x)) * 10 if maxFreq is None: maxFreq = max(freqs) elif maxFreq == 'auto': maxFreq = (1 / T) / 10 mask = np.logical_and(freqs >= minFreq, freqs <= maxFreq) freqs = freqs[mask] w = blackman(N) amps = [] for y, minAmp, maxAmp in zip(ys, minAmps, maxAmps): y = y - y.mean() amp = np.abs(rfft(y * w))[:N // 2] if not minAmp is None: amp = np.where(amp < minAmp, 0, amp) if not maxAmp is None: amp = np.where(amp > maxAmp, maxAmp, amp) amps.append(amp[mask]) return (freqs, *amps)
def _set_freqs(self): # Set freqs indexes if self._sides == "onesided": self._freqs = rfftfreq(self._nfft, 1 / self._fs) else: self._freqs = fftfreq(self._nfft, 1 / self._fs)
def spectral_plots(signal, sample_rate, pitch=None): N = len(signal) X = rfft(signal, N) Xmag = np.abs(X) freq = rfftfreq(N, 1 / sample_rate) fig, ax = plt.subplots(3, 1, sharex=True) fig.set_figheight(5) if pitch: for ax_ in ax: ax_.axvline(pitch, linestyle='--', color='black') ax[0].plot(freq, Xmag) ax[0].yaxis.set_ticklabels([]) ax[0].set_ylabel('spectrum') ax[1].plot(freq, np.abs(func['ACF'](Xmag))) ax[1].yaxis.set_ticklabels([]) ax[1].set_ylabel('ACF') Nh = 10 # for example hps = 20 * np.log10( [np.sum(Xmag[k:Nh * k:k]) for k in range(1, len(freq))]) hss = np.array( [20 * np.sum(np.log(Xmag[k:Nh * k:k])) for k in range(1, len(freq))]) ax[2].plot(freq[1:], normalize(hps), label='product') ax[2].plot(freq[1:], normalize(hss), label='sum') ax[2].legend() ax[2].yaxis.set_ticklabels([]) ax[2].set_ylabel('harmonic sum/product') ax[-1].set_xlim(0, 5000) ax[-1].set_xlabel('frequency (Hz)') plt.show()
def fft_plot(yf: np.ndarray, fname: str): ''' Plots the FFT params: --- yf (np.ndarray): input data to plot fname (str): save file name ''' N = int((config.SAMPLE_RATE / config.RESAMPLE_RATE) * config.DURATION) xf = rfftfreq(N - 1, 1 / int(config.SAMPLE_RATE / config.RESAMPLE_RATE)) _, axs = plt.subplots(nrows=1, figsize=(11, 9)) plt.rcParams['font.size'] = '14' for label in (axs.get_xticklabels() + axs.get_yticklabels()): label.set_fontsize(14) plt.plot(xf, yf) axs.set_title('Frequency spectra') axs.set_ylabel('Signal strength', fontsize=14) axs.set_xlabel('Frequency (Hz)', fontsize=14) file_location = eda_config.OUTPUT_DATA_DIR / Path(f'{fname}.png') plt.savefig(file_location)
def actualizarGraficoLectura(self): #preparar datos a plotear self.ploteando = True tamano = len(self.tarea.datosLeidos[0]) if tamano > self.maximoPuntos: liminferior = tamano - self.maximoPuntos - 1 elif tamano: liminferior = 0 else: self.ploteando = False return datos = [] for c in enumerate(self.tarea.datosLeidos): datos.append(self.tarea.datosLeidos[c[0]][liminferior:]) for p in enumerate(self.lineas): # print(p) p[1].setData(datos[0], datos[self.indicesPlot[p[0]] + 1]) yf = rfft(datos[self.indicesPlot[p[0]] + 1]) xf = rfftfreq(len(datos[0]), 1 / self.tarea.freq) self.lineasFreq[p[0]].setData(xf, np.abs(yf)) #plotear ffts self.ploteando = False
def detect_coughs( file='sounds/samples/vi95kMQ65UeU7K1wae12D1GUeXd2/sample-1613658921823.m4a' ): # Replace the below random code with something meaningful which # generates a one-column dataframe with a column named "peak_start" y, sr = librosa.load(file) # sr is the sampling rate N = y.shape[0] # N is number of samples yf = rfft(y) xf = rfftfreq(N, 1 / sr) # Going to frequency domain points_per_freq = len(xf1) / (sr / 2) target_idx_100 = int(points_per_freq * 100) target_idx_2000 = int(points_per_freq * 2000) # Removing all frequencies except 100-2000 Hz, as this is the typical range of frequencies for a cough from an adult. # Reference: https://pubmed.ncbi.nlm.nih.gov/12666872/ yf[:target_idx_100 + 1] = 0 yf[target_idx_2000 - 1:] = 0 new_sig = irfft(yf) # Going back to time domain with filtered signal peaks_array = find_peaks( new_sig, prominence=0.02 )[0] #Finding start of peaks in filtered signal, should correspond to coughs peaks = peaks_array / sr # The time instant of starting of peak is arrived at by dividing by sampling rate out = pd.DataFrame({'peak_start': peaks}) return (out)
def stftfreq(wsize, sfreq=None): # noqa: D401 """Compute frequencies of stft transformation. Parameters ---------- wsize : int Size of stft window. sfreq : float Sampling frequency. If None the frequencies are given between 0 and pi otherwise it's given in Hz. Returns ------- freqs : array The positive frequencies returned by stft. See Also -------- stft istft """ from scipy.fft import rfftfreq freqs = rfftfreq(wsize) if sfreq is not None: freqs *= float(sfreq) return freqs
def _preprocess_spectrum(self, spectrum, low_cutoff_res, high_cutoff_res): """Cuts off low and high frequencies and then subtracts the background by calculating a lower envelope to the min peaks (ctf zero crossings). Also finds an upper envelope to be used as a damping function for computing forward models. """ low_cutoff_freq = 1 / low_cutoff_res high_cutoff_freq = 1 / high_cutoff_res freqencies = fft.rfftfreq(n=self.full_spectrum.shape[0], d=self.pixelsize) # dft indices of low and high cutoffs self._low_idx = np.argmax(freqencies > low_cutoff_freq) self._high_idx = np.argmax(freqencies > high_cutoff_freq) # get envelopes bottom_envelope, self._upper_envelope = self._get_envelopes( spectrum, self._low_idx, self._high_idx) # cut off high frequencies spectrum = spectrum[:, :self._high_idx] spectrum = np.vstack( (spectrum[:self._high_idx, :], spectrum[-self._high_idx:, :])) spectrum = fft.fftshift(spectrum, axes=0) # subtract bottom envelope x = range(spectrum.shape[1]) y = range(-spectrum.shape[0] // 2, spectrum.shape[0] // 2) X, Y = np.meshgrid(x, y) r = np.sqrt(X**2 + Y**2) spectrum -= bottom_envelope(r - self._low_idx) # zero out distances not between low_idx and high_idx w = np.where((r < self._low_idx) | (r > self._high_idx)) spectrum[w[0], w[1]] = 0 return spectrum
def _ctf_array(self, z1, z2, major_semiaxis_angle, phase_shift): """Returns a 2D array of CTF values in the same shape as the reduced spectrum""" # calculate defocus matrix minor_semiaxis_angle = 90 + major_semiaxis_angle minor_semiaxis = np.array([ np.cos(minor_semiaxis_angle * np.pi / 180), np.sin(minor_semiaxis_angle * np.pi / 180), ]).reshape(2, 1) semiaxes, _ = np.linalg.qr(np.hstack((minor_semiaxis, np.identity(2)))) defocus_matrix = semiaxes @ [[z1, 0], [0, z2]] @ semiaxes.transpose() # vectorize ctf calculation kx = fft.rfftfreq(n=self.full_spectrum.shape[0], d=self.pixelsize) kx = kx[:self._high_idx] ky = fft.fftfreq(n=self.full_spectrum.shape[0], d=self.pixelsize) ky = np.hstack((ky[:self._high_idx], ky[-self._high_idx:])) ky = fft.fftshift(ky) KX, KY = np.meshgrid(kx, ky) chi_args = ( defocus_matrix, KX, KY, self.electron_wavelength, self.spherical_abberation, phase_shift, ) return ctf(self.amplitude_contrast, chi_args)
def binned_fft(self, data: np.ndarray, target_sample_rate: int, original_sample_rate: int = 48000) -> np.ndarray: # # Get the "normalized" FFT based on the sample rates # " The height is a reflection of power density, so if you double the sampling frequency, # and hence half the width of each frequency bin, you'll double the amplitude of the FFT result." # >> https://wiki.analytica.com/FFT # # 2^(new_sample_rate/original_sample_rate) = power gain # Divide by that but offset by 1 since if new fs = old fs we multiply by 1/2^0 = 1 # try: raw_fft = self.fft(data) * (2**math.log2( original_sample_rate / target_sample_rate)) except TypeError as e: print(e) sys.exit() # raw_fft = self.fft(data) # # TODO: wont work for old fs and new fs equal # if original_sample_rate != target_sample_rate: # raw_fft *= (math.log10(original_sample_rate / target_sample_rate) / math.log10(2)) # # Get the frequencies that FFT represent based on the sample rate # Get the frequencies that the indexes determine fftf = rfftfreq(raw_fft.shape[0], 1 / target_sample_rate) # # Return pairs of [[freq], [fft]] array return np.array([fftf, raw_fft], dtype=object)
def spectrum_contour(self, comp, axis_vals, axis_mode='half_channel', fig=None, ax=None, pcolor_kw=None, **kwargs): if not comp in ('x', 'z'): raise ValueError(" Variable `comp' must eiher be 'x' or 'z'\n") axis_vals = misc_utils.check_list_vals(axis_vals) y_index_axis_vals = indexing.y_coord_index_norm( self._avg_data, axis_vals, None, axis_mode) Ruu_all = self.autocorrDF[comp] #[:,axis_vals,:] shape = Ruu_all.shape Ruu = np.zeros((shape[0], len(axis_vals), shape[2])) for i, vals in zip(range(shape[2]), y_index_axis_vals): Ruu[:, :, i] = Ruu_all[:, vals, i] kwargs = cplt.update_subplots_kw(kwargs, figsize=[10, 4 * len(axis_vals)]) fig, ax = cplt.create_fig_ax_without_squeeze(len(axis_vals), fig=fig, ax=ax, **kwargs) coord = self._meta_data.CoordDF[comp].copy()[:shape[0]] x_axis = self._avg_data._return_xaxis() pcolor_kw = cplt.update_pcolor_kw(pcolor_kw) for i in range(len(axis_vals)): wavenumber_spectra = np.zeros((int(0.5 * shape[0]) + 1, shape[2]), dtype=np.complex128) for j in range(shape[2]): wavenumber_spectra[:, j] = fft.rfft(Ruu[:, i, j]) comp_size = shape[0] wavenumber_comp = 2 * np.pi * fft.rfftfreq(comp_size, coord[1] - coord[0]) X, Y = np.meshgrid(x_axis, wavenumber_comp) ax[i] = ax[i].pcolormesh(X, Y, np.abs(wavenumber_spectra), **pcolor_kw) ax[i].axes.set_ylabel(r"$\kappa_%s$" % comp) title = r"$%s=%.3g$"%("y" if axis_mode=='half_channel' \ else r"\delta_u" if axis_mode=='disp_thickness' \ else r"\theta" if axis_mode=='mom_thickness' else "y^+", axis_vals[i] ) ax[i].axes.set_ylim( [np.amin(wavenumber_comp[1:]), np.amax(wavenumber_comp)]) ax[i].axes.set_title(title) # ,fontsize=15,loc='left') fig.colorbar(ax[i], ax=ax[i].axes) return fig, ax
def calculate_peak(waves, chunksize, sampling_rate, start, cycles): yf = rfft(waves) xf = rfftfreq(waves.size, 1 / sampling_rate) peak = np.where(np.abs(yf) == np.abs(yf).max())[0][0] peak = round((peak / ((chunksize) / sampling_rate)), 2) return peak
def fourier_transform(self,df, name_column, show = False): myarray = np.asarray((df[name_column])) N = len(myarray) yf = rfft(myarray) xf = rfftfreq(N, 1 / analyzer.simple_rate) if (show == True): plt.plot(xf, np.abs(yf)) plt.show() return yf[1]
def FourierTransformPower(Powers, Times): # Do FFT (real) on the input powers # Returns the frequency values and the corresponding amplitude FFTPowers = fft.rfft(Powers) nPoints = len(Times) SampleSpacing = (Times.max() - Times.min()) / nPoints FFTFreqs = fft.rfftfreq(nPoints, d=SampleSpacing) FFTPowers = numpy.abs(FFTPowers) return FFTFreqs, FFTPowers
def fourier_transform(arr, name): arr = arr - np.mean(arr) N = len(arr) yf = rfft(arr) xf = rfftfreq(N, 1) plt.plot(xf, np.abs(yf), label='{}'.format(name)) return yf, xf
def makeFilter(shape: List[int], alpha: float, dtype=float) -> np.ndarray: assert 1 <= len(shape) <= 2, "shape must be one or two elements" if len(shape) == 1: shape = [shape[0], shape[0]] # Generate filter in the frequency domain fy = sf.fftfreq(shape[0])[:, np.newaxis].astype(dtype) fx = sf.rfftfreq(shape[1])[np.newaxis, :].astype(dtype) H2 = (fx**2 + fy**2)**(alpha / 2.0) return sf.fftshift(sf.irfft2(H2))
def plot_fourier(frame_rate, np_frames, output_file): """Plot Fourier Transformation of audio sample.""" plt_fourier = pyplot n = len(np_frames) #norm_frames = np.int16((byte_frames / byte_frames.max()) * 32767) yf = rfft(np_frames) xf = rfftfreq(n, 1 / frame_rate) fig = plt_fourier.figure(num=None, figsize=(12, 7.5), dpi=100) plt_fourier.plot(xf, np.abs(yf)) plt_fourier.savefig(output_file)
def get_1d_average(self, spectrum, horizontal_res=50, sigma_ratio=1 / 60): """Returns a 1D signal of the vertical spectrum inside the cutoff range""" freqencies = fft.rfftfreq(n=self.full_spectrum.shape[0], d=self.pixelsize) width = np.argmax(freqencies > 1 / horizontal_res) vertical_strip = spectrum[self._low_idx:self._high_idx, :width] vertical_1d = np.average(vertical_strip, axis=1) sigma = sigma_ratio * len(vertical_1d) vertical_1d = ndimage.gaussian_filter1d(vertical_1d, sigma=sigma, mode="nearest") return vertical_1d
def calculate(): self.sample_rate, self.audio = wavfile.read(self.audiofile) self.audio = np.transpose(self.audio) self.duration = len(self.audio[:][0]) / float(self.sample_rate) self.audio_fft = [None] * self.audio.ndim self.audio_fft[0] = rfft(self.audio[0]) self.audio_fft[1] = rfft(self.audio[1]) self.freq = rfftfreq(self.audio[0].size, 1. / self.sample_rate) print( f"Sample Rate: {self.sample_rate} with {self.audio.ndim} channels" )
def fft(x, fs): """ FFT function :param x: input data for fft :param fs: sample frequency [Hz] :return: frequency [Hz], magnitude """ n = len(x) y = np.abs(rfft(x)) f = rfftfreq(n, 1/fs) return f, y
def _get_approx_defocus(self, first_zero_crossing_idx): # get approximate defocus from first zero crossing freqencies = fft.rfftfreq(n=self.full_spectrum.shape[0], d=self.pixelsize) k_first_zero_crossing = freqencies[self._low_idx + first_zero_crossing_idx] chi_first_zero_crossing = -0.5 approximate_defocus = ( chi_first_zero_crossing - 10 * (1 / 4) * self.spherical_abberation * self.electron_wavelength**3 * k_first_zero_crossing**4) * ( -2 / (100 * self.electron_wavelength * k_first_zero_crossing**2)) return approximate_defocus
def spectrum(a, delta=1, **kwargs): """ Return frequencies, amplitude and phase of FFT. """ nfreq = kwargs.get('nfreq') if nfreq is None: nfreq = next_fast_len(a.size) spectrum = rfft(a, nfreq) freqs = rfftfreq(n=nfreq, d=delta) am = np.abs(spectrum) ph = np.angle(spectrum) return freqs, am, ph
def analyze_audio(audio): # Just extract one column (mono) since it's stereo audio = audio[:,0] yf = rfft(audio) # Return in abs because we want the module from a complex number, despite having only real part (because of the rrft) yf_s = Series(abs(yf)) # The band where we want to detect our signal 17-21kHz yf_s = yf_s[17000:21000] print(yf_s.nlargest(20)) # Show on graph the correspondant point xf = rfftfreq(POINTS, 1 / FS) plt.plot(xf, np.abs(yf)) plt.show()
def fourier_tf(data, fs=128, viz=False, tmp_signal=None, tmp_channel=None): if isinstance(data, pd.DataFrame): tmp = np.absolute( rfftn(data.values.reshape(data.shape[0], 14, 512), axes=2)) else: tmp = np.absolute(rfftn(data.reshape(data.shape[0], 14, 512), axes=2)) fft_freq = rfftfreq(512, 1.0 / fs) if viz == True: plt.plot(fft_freq, t[tmp_signal, tmp_channel, :]) return tmp
def Peak_amplitude(a, cutoff): ################################################################ # calculates peak amplitude from a signal and a frequency band # # # # a : a signal (time series) # # cutoff : frequency band # ################################################################ fs = 20 #Hertz yf = rfft(a) #power spectra ps = 2*(np.abs(yf)**2.0) M = len(ps) xf = rfftfreq(M, 1/fs) idx = np.logical_and(xf >= cutoff[0], xf <= cutoff[1]) cutoff_idx = np.where(idx)[0] #peak amplitude pa = max(ps[cutoff_idx]/max(ps)) return pa
def _mt_spectra(x, dpss, sfreq, n_fft=None): """Compute tapered spectra. Parameters ---------- x : array, shape=(..., n_times) Input signal dpss : array, shape=(n_tapers, n_times) The tapers sfreq : float The sampling frequency n_fft : int | None Length of the FFT. If None, the number of samples in the input signal will be used. Returns ------- x_mt : array, shape=(..., n_tapers, n_times) The tapered spectra freqs : array The frequency points in Hz of the spectra """ from scipy.fft import rfft, rfftfreq if n_fft is None: n_fft = x.shape[-1] # remove mean (do not use in-place subtraction as it may modify input x) x = x - np.mean(x, axis=-1, keepdims=True) # only keep positive frequencies freqs = rfftfreq(n_fft, 1. / sfreq) # The following is equivalent to this, but uses less memory: # x_mt = fftpack.fft(x[:, np.newaxis, :] * dpss, n=n_fft) n_tapers = dpss.shape[0] if dpss.ndim > 1 else 1 x_mt = np.zeros(x.shape[:-1] + (n_tapers, len(freqs)), dtype=np.complex128) for idx, sig in enumerate(x): x_mt[idx] = rfft(sig[..., np.newaxis, :] * dpss, n=n_fft) # Adjust DC and maybe Nyquist, depending on one-sided transform x_mt[..., 0] /= np.sqrt(2.) if x.shape[1] % 2 == 0: x_mt[..., -1] /= np.sqrt(2.) return x_mt, freqs
def run(self, win_data: WindowedData) -> SpectraData: """ Perform the FFT Data is padded to the next fast length before performing the FFT to speed up processing. Therefore, the output length may not be as expected. Parameters ---------- win_data : WindowedData The input windowed data Returns ------- SpectraData The Fourier transformed output """ from scipy.fft import next_fast_len, rfftfreq metadata_dict = win_data.metadata.dict() data = {} spectra_levels_metadata = [] messages = [] logger.info("Performing fourier transforms of windowed decimated data") for ilevel in range(win_data.metadata.n_levels): logger.info(f"Transforming level {ilevel}") level_metadata = win_data.metadata.levels_metadata[ilevel] win_size = level_metadata.win_size n_transform = next_fast_len(win_size, real=True) logger.debug( f"Padding size {win_size} to next fast len {n_transform}") freqs = rfftfreq(n=n_transform, d=1.0 / level_metadata.fs).tolist() data[ilevel] = self._get_level_data(level_metadata, win_data.get_level(ilevel), n_transform) spectra_levels_metadata.append( self._get_level_metadata(level_metadata, freqs)) messages.append(f"Calculated spectra for level {ilevel}") metadata = self._get_metadata(metadata_dict, spectra_levels_metadata) metadata.history.add_record(self._get_record(messages)) logger.info("Fourier transforms completed") return SpectraData(metadata, data)
def texshadeFFT(x: np.ndarray, alpha: float) -> np.ndarray: """FFT-based texture shading elevation Given an array `x` of elevation data and an `alpha` > 0, apply the texture-shading algorithm using the full (real-only) FFT: the entire `x` array will be FFT'd. `alpha` is the shading detail factor, i.e., the power of the fractional-Laplacian operator. `alpha=0` means no detail (output is the input). `alpha=2.0` is the full (non-fractional) Laplacian operator and is probably too high. `alpha <= 1.0` seem aesthetically pleasing. Returns an array the same dimensions as `x` that contains the texture-shaded version of the input array. If `x` is memory-mapped and/or your system doesn't have 5x `x`'s memory available, consider using `texshade.texshadeSpatial`, which implements a low-memory version of the algorithm by approximating the frequency response of the fractional-Laplacian filter with a finite impulse response filter applied in the spatial-domain. Implementation note: this function uses Scipy's FFTPACK routines (in `scipy.fftpack`) instead of Numpy's FFT (`numpy.fft`) because the former can return single-precision float32. In newer versions of Numpy/Scipy, this advantage may have evaporated [1], [2]. [1] https://github.com/numpy/numpy/issues/6012 [2] https://github.com/scipy/scipy/issues/2487 """ Nyx = [nextprod([2, 3, 5, 7], x) for x in x.shape] # Generate filter in the frequency domain fy = sf.fftfreq(Nyx[0])[:, np.newaxis].astype(x.dtype) fx = sf.rfftfreq(Nyx[1])[np.newaxis, :].astype(x.dtype) H2 = (fx**2 + fy**2)**(alpha / 2.0) # Compute the FFT of the input and apply the filter xr = sf.rfft2(x, s=Nyx) * H2 H2 = None # potentially trigger GC here to reclaim H2's memory xr = sf.irfft2(xr) # Return the same size as input return xr[:x.shape[0], :x.shape[1]]