diff --git a/src/multitaper.jl b/src/multitaper.jl index afc32378..a016e747 100644 --- a/src/multitaper.jl +++ b/src/multitaper.jl @@ -436,9 +436,20 @@ struct MTCrossSpectraConfig{T,T1,T2,T3,T4,F,T5,T6,C<:MTConfig{T}} function MTCrossSpectraConfig{T,T1,T2,T3,T4,F,T5,T6,C}( n_channels::Int, normalization_weights::T1, x_mt::T2, demean::Bool, mean_per_channel::T3, demeaned_signal::T4, freq::F, freq_range::T5, - freq_inds::T6, ensure_aligned::Bool, mt_config::C + freq_inds::T6, ensure_aligned::Bool, mt_config::C, override_warn::Bool ) where {T,T1,T2,T3,T4,F,T5,T6,C<:MTConfig{T}} check_onesided_real(mt_config) # this restriction is artificial; the code needs to be generalized + if n_channels > mt_config.n_samples && !override_warn + Base.depwarn( + """ + n_channels > n_samples; this is likely a mistake. + From v0.9 onwards, each column is interpreted as a separate signal, + whereas in v0.8 and earlier, each row was interpreted as a separate signal. + To suppress this warning, pass `override_warn=true` as a keyword argument to + `MTCoherenceConfig`, `MTCrossSpectraConfig`, `mt_coherence`, or `mt_cross_power_spectra`. + """, + :mt_cross_power_spectra; force=true) + end return new{T,T1,T2,T3,T4,F,T5,T6,C}( n_channels, normalization_weights, x_mt, demean, mean_per_channel, demeaned_signal, freq, freq_range, @@ -447,12 +458,12 @@ struct MTCrossSpectraConfig{T,T1,T2,T3,T4,F,T5,T6,C<:MTConfig{T}} end function MTCrossSpectraConfig(n_channels::Int, normalization_weights::T1, x_mt::T2, demean::Bool, mean_per_channel::T3, demeaned_signal::T4, freq::F, freq_range::T5, - freq_inds::T6, ensure_aligned::Bool, mt_config::C + freq_inds::T6, ensure_aligned::Bool, mt_config::C, override_warn::Bool=false ) where {T,T1,T2,T3,T4,F,T5,T6,C<:MTConfig{T}} MTCrossSpectraConfig{T,T1,T2,T3,T4,F,T5,T6,C}( n_channels, normalization_weights, x_mt, demean, mean_per_channel, demeaned_signal, freq, freq_range, - freq_inds, ensure_aligned, mt_config + freq_inds, ensure_aligned, mt_config, override_warn ) end end @@ -477,22 +488,23 @@ Returns a `CrossPowerSpectra` object. function MTCrossSpectraConfig{T}(n_channels, n_samples; fs=1, demean=false, freq_range=nothing, ensure_aligned = T == Float32 || T == Complex{Float32}, + override_warn=false, kwargs...) where {T} mt_config = MTConfig{T}(n_samples; fs, kwargs...) - return MTCrossSpectraConfig{T}(n_channels, mt_config; demean, freq_range, ensure_aligned) + return MTCrossSpectraConfig{T}(n_channels, mt_config; demean, freq_range, ensure_aligned, override_warn) end # extra method to ensure it's ok to pass the redundant type parameter {T} MTCrossSpectraConfig{T}(n_channels, mt_config::MTConfig{T}; kwargs...) where {T} = MTCrossSpectraConfig(n_channels, mt_config; kwargs...) -function MTCrossSpectraConfig(n_channels, mt_config::MTConfig{T}; demean=false, - freq_range=nothing, ensure_aligned = T == Float32 || T == Complex{Float32}) where {T} +function MTCrossSpectraConfig(n_channels, mt_config::MTConfig{T}; demean=false, freq_range=nothing, + ensure_aligned = T == Float32 || T == Complex{Float32}, override_warn=false) where {T} n_samples = mt_config.n_samples if demean - mean_per_channel = Vector{T}(undef, n_channels) - demeaned_signal = Matrix{T}(undef, n_channels, n_samples) + mean_per_channel = Matrix{T}(undef, 1, n_channels) + demeaned_signal = Matrix{T}(undef, n_samples, n_channels) else mean_per_channel = nothing demeaned_signal = nothing @@ -512,7 +524,7 @@ function MTCrossSpectraConfig(n_channels, mt_config::MTConfig{T}; demean=false, end return MTCrossSpectraConfig(n_channels, normalization_weights, x_mt, demean, mean_per_channel, demeaned_signal, freq, - freq_range, freq_inds, ensure_aligned, mt_config) + freq_range, freq_inds, ensure_aligned, mt_config, override_warn) end function allocate_output(config::MTCrossSpectraConfig{T}) where {T} @@ -529,7 +541,7 @@ end Computes multitapered cross power spectra between channels of a signal. Arguments: * `output`: `n_channels` x `n_channels` x `length(config.freq)`. Can be created by `DSP.allocate_output(config)`. -* `signal`: `n_channels` x `n_samples` +* `signal`: `n_samples` x `n_channels` * `config`: `MTCrossSpectraConfig{T}`: optionally pass a [`MTCrossSpectraConfig`](@ref) to preallocate temporary and choose configuration settings. Otherwise, one may pass any keyword arguments accepted by this object. @@ -542,19 +554,18 @@ See also [`mt_cross_power_spectra`](@ref) and [`MTCrossSpectraConfig`](@ref). mt_cross_power_spectra! function mt_cross_power_spectra!(output, signal::AbstractMatrix{T}; fs=1, kwargs...) where {T} - n_channels, n_samples = size(signal) + n_samples, n_channels = size(signal) config = MTCrossSpectraConfig{T}(n_channels, n_samples; fs, fft_flags=FFTW.ESTIMATE, kwargs...) return mt_cross_power_spectra!(output, signal, config) end -@views function mt_cross_power_spectra!(output, signal::AbstractMatrix, - config::MTCrossSpectraConfig) +function mt_cross_power_spectra!(output, signal::AbstractMatrix, config::MTCrossSpectraConfig) n_chan = config.n_channels n_samples = config.mt_config.n_samples n_freqi = length(config.freq_inds) - if size(signal) != (n_chan, n_samples) + if size(signal) != (n_samples, n_chan) throw(DimensionMismatch(lazy"Size of `signal` does not match `(config.n_channels, config.mt_config.n_samples)`; got `size(signal)`=$(size(signal)) but `(config.n_channels, config.mt_config.n_samples)`=$((n_chan, n_samples))")) end @@ -576,9 +587,9 @@ end else mt_fft_tapered_multichannel!(x_mt, signal, config) end - x_mt[1, :, :] ./= sqrt(2) + @views x_mt[1, :, :] ./= sqrt(2) if iseven(config.mt_config.nfft) - x_mt[end, :, :] ./= sqrt(2) + @views x_mt[end, :, :] ./= sqrt(2) end cs_inner!(output, config.normalization_weights, x_mt, config) return CrossPowerSpectra(output, config.freq) @@ -586,16 +597,17 @@ end function mt_fft_tapered_multichannel_ensure_aligned!(x_mt, signal, config) fft_output = config.mt_config.fft_output_tmp - for k in 1:(config.n_channels), taper in 1:config.mt_config.ntapers - # we do this in two steps so that we are sure `fft_output` has the memory alignment FFTW expects (without needing the `FFTW.UNALIGNED` flag) - mt_fft_tapered!(fft_output, signal[k, :], taper, config.mt_config) + for k in 1:config.n_channels, taper in 1:config.mt_config.ntapers + # we do this in two steps to ensure `fft_output` has the memory alignment FFTW expects + # (without needing the `FFTW.UNALIGNED` flag) + mt_fft_tapered!(fft_output, view(signal, :, k), taper, config.mt_config) x_mt[:, taper, k] .= fft_output end end @views function mt_fft_tapered_multichannel!(x_mt, signal, config) for k in 1:(config.n_channels), taper in 1:config.mt_config.ntapers - mt_fft_tapered!(x_mt[:, taper, k], signal[k, :], taper, config.mt_config) + mt_fft_tapered!(x_mt[:, taper, k], signal[:, k], taper, config.mt_config) end end @@ -626,7 +638,7 @@ end Computes multitapered cross power spectra between channels of a signal. Arguments: -* `signal`: `n_channels` x `n_samples` +* `signal`: `n_samples` x `n_channels` * Optionally pass an [`MTCrossSpectraConfig`](@ref) object to preallocate temporary variables and choose configuration settings. Otherwise, any keyword arguments accepted by [`MTCrossSpectraConfig`](@ref) may be passed here. @@ -638,7 +650,7 @@ See also [`mt_cross_power_spectra!`](@ref) and [`MTCrossSpectraConfig`](@ref). mt_cross_power_spectra function mt_cross_power_spectra(signal::AbstractMatrix{T}; fs=1, kwargs...) where {T} - n_channels, n_samples = size(signal) + n_samples, n_channels = size(signal) config = MTCrossSpectraConfig{T}(n_channels, n_samples; fs, fft_flags=FFTW.ESTIMATE, kwargs...) return mt_cross_power_spectra(signal, config) @@ -768,7 +780,7 @@ function mt_coherence!(output, signal::AbstractMatrix, n_samples = config.cs_config.mt_config.n_samples n_freqs = length(config.cs_config.freq) - if size(signal) != (n_chan, n_samples) + if size(signal) != (n_samples, n_chan) throw(DimensionMismatch(lazy"Size of `signal` does not match `(config.cs_config.n_channels, config.cs_config.mt_config.n_samples)`; got `size(signal)`=$(size(signal)) but `(config.cs_config.n_channels, config.cs_config.mt_config.n_samples)`=$((n_chan, n_samples))")) end @@ -783,7 +795,7 @@ function mt_coherence!(output, signal::AbstractMatrix, end function mt_coherence!(output, signal::AbstractMatrix{T}; kwargs...) where {T} - n_channels, n_samples = size(signal) + n_samples, n_channels = size(signal) config = MTCoherenceConfig{T}(n_channels, n_samples; fft_flags=FFTW.ESTIMATE, kwargs...) return mt_coherence!(output, signal, config) end @@ -795,7 +807,7 @@ end Arguments: -* `signal`: `n_channels` x `n_samples` matrix +* `signal`: `n_samples` x `n_channels` matrix * Optionally pass an `MTCoherenceConfig` to pre-allocate temporary variables and choose configuration settings, otherwise, see [`MTCrossSpectraConfig`](@ref) for the meaning of the keyword arguments. Returns a `Coherence` object. @@ -805,7 +817,7 @@ See also [`mt_coherence`](@ref) and [`MTCoherenceConfig`](@ref). mt_coherence function mt_coherence(signal::AbstractMatrix{T}; kwargs...) where {T} - n_channels, n_samples = size(signal) + n_samples, n_channels = size(signal) config = MTCoherenceConfig{T}(n_channels, n_samples; fft_flags=FFTW.ESTIMATE, kwargs...) return mt_coherence(signal, config) end diff --git a/test/multitaper.jl b/test/multitaper.jl index c7ccb274..c9e1eb82 100644 --- a/test/multitaper.jl +++ b/test/multitaper.jl @@ -103,14 +103,14 @@ avg_coh(x) = dropdims(mean(coherence(x); dims=3); dims=3) noise = rand(1024) * 2 .- 1 T = Float64 - same_signal = Matrix{T}(undef, 2, n_samples) - same_signal[1, :] = sin_1 - same_signal[2, :] = sin_1 + same_signal = Matrix{T}(undef, n_samples, 2) + same_signal[:, 1] = sin_1 + same_signal[:, 2] = sin_1 coh = avg_coh(mt_coherence(same_signal; demean=true, fs, freq_range)) same_signal_coherence = coh[2, 1] # test in-place gets the same result - config = MTCoherenceConfig{T}(size(same_signal)...; demean=true, fs, freq_range) + config = MTCoherenceConfig{T}(2, n_samples; demean=true, fs, freq_range) out = allocate_output(config) coh2 = avg_coh(mt_coherence!(out, same_signal, config)) @test coh ≈ coh2 @@ -123,12 +123,12 @@ avg_coh(x) = dropdims(mean(coherence(x); dims=3); dims=3) for i in 1:2, j in 1:2, f in eachindex(freq(coh_object)) @test coherence(coh_object)[i, j, f] == coherence(coh_object)[j, i, f] if i == j - @test coherence(coh_object)[i,j, f] == 1 + @test coherence(coh_object)[i, j, f] == 1 end end # check that manually demeaning with `demean=false` gives the same result - same_signal_demeaned = same_signal .- mean(same_signal; dims = 2) + same_signal_demeaned = same_signal .- mean(same_signal; dims = 1) coh3 = avg_coh(mt_coherence(same_signal_demeaned; demean = false)) @test coh3 ≈ coh2 @@ -139,23 +139,23 @@ avg_coh(x) = dropdims(mean(coherence(x); dims=3); dims=3) @test abs(same_signal_coherence - 1) < epsilon - phase_shift = Matrix{T}(undef, 2, n_samples) - phase_shift[1, :] = sin_1 - phase_shift[2, :] = sin_2 + phase_shift = Matrix{T}(undef, n_samples, 2) + phase_shift[:, 1] = sin_1 + phase_shift[:, 2] = sin_2 coh = avg_coh(mt_coherence(phase_shift; fs, freq_range)) - phase_shift_coherence = coh[2,1] + phase_shift_coherence = coh[2, 1] @test abs(phase_shift_coherence - 1) < epsilon - @test coh[1,1] ≈ 1 - @test coh[2,2] ≈ 1 + @test coh[1, 1] ≈ 1 + @test coh[2, 2] ≈ 1 # test in-place gets the same result - config = MTCoherenceConfig{T}(size(phase_shift)...; fs, freq_range) + config = MTCoherenceConfig{T}(2, n_samples; fs, freq_range) out = allocate_output(config) coh2 = avg_coh(mt_coherence!(out, phase_shift, config)) @test coh ≈ coh2 # test out-of-place with config the same result - config = MTCoherenceConfig{T}(size(phase_shift)...; fs, freq_range) + config = MTCoherenceConfig{T}(2, n_samples; fs, freq_range) coh3 = avg_coh(mt_coherence(phase_shift, config)) @test coh ≈ coh3 @@ -164,52 +164,52 @@ avg_coh(x) = dropdims(mean(coherence(x); dims=3); dims=3) @test coh ≈ coh4 # Construct the config via an `MTConfig` - mt_config = MTConfig{T}(size(phase_shift, 2); fs) - config = MTCoherenceConfig(size(phase_shift, 1), mt_config; freq_range) + mt_config = MTConfig{T}(size(phase_shift, 1); fs) + config = MTCoherenceConfig(size(phase_shift, 2), mt_config; freq_range) coh5 = avg_coh(mt_coherence(phase_shift, config)) @test coh ≈ coh5 # test that including type parameter produces the same result - config = @test_nowarn MTCoherenceConfig{T}(size(phase_shift, 1), mt_config; freq_range) + config = @test_nowarn MTCoherenceConfig{T}(size(phase_shift, 2), mt_config; freq_range) cohT = avg_coh(mt_coherence(phase_shift, config)) @test coh5 == cohT # Construct the config via an `MTCrossSpectraConfig` - cs_config = MTCrossSpectraConfig(size(phase_shift, 1), mt_config; freq_range) + cs_config = MTCrossSpectraConfig(size(phase_shift, 2), mt_config; freq_range) # also test with type parameter config, configT = MTCoherenceConfig(cs_config), MTCoherenceConfig{T}(cs_config) coh5, cohT = avg_coh.(mt_coherence.((phase_shift,), (config, configT))) @test coh ≈ coh5 == cohT - different_signal = Matrix{T}(undef, 2, n_samples) - different_signal[1, :] = sin_1 - different_signal[2, :] = noise + different_signal = Matrix{T}(undef, n_samples, 2) + different_signal[:, 1] = sin_1 + different_signal[:, 2] = noise coh = avg_coh(mt_coherence(different_signal; fs, freq_range)) - different_signal_coherence = coh[2,1] + different_signal_coherence = coh[2, 1] # .8 is arbitrary, but represents a high coherence, so if a sine wave is # that coherent with noise, there is a problem @test different_signal_coherence < 0.8 # test in-place gets the same result - config = MTCoherenceConfig{T}(size(different_signal)...; fs, freq_range) + config = MTCoherenceConfig{T}(2, n_samples; fs, freq_range) out = allocate_output(config) coh2 = avg_coh(mt_coherence!(out, different_signal, config)) @test coh ≈ coh2 - less_noisy = Matrix{T}(undef, 2, n_samples) - less_noisy[1, :] = sin_1 - less_noisy[2, :] .= sin_1 .+ noise + less_noisy = Matrix{T}(undef, n_samples, 2) + less_noisy[:, 1] = sin_1 + less_noisy[:, 2] .= sin_1 .+ noise coh = avg_coh(mt_coherence(less_noisy; fs, freq_range)) less_noisy_coherence = coh[2, 1] # test in-place gets the same result - config = MTCoherenceConfig{T}(size(less_noisy)...; fs, freq_range) + config = MTCoherenceConfig{T}(2, n_samples; fs, freq_range) out = allocate_output(config) coh2 = avg_coh(mt_coherence!(out, less_noisy, config)) @test coh ≈ coh2 - more_noisy = Matrix{T}(undef, 2, n_samples) - more_noisy[1, :] = sin_1 - more_noisy[2, :] .= sin_1 .+ 3 * noise + more_noisy = Matrix{T}(undef, n_samples, 2) + more_noisy[:, 1] = sin_1 + more_noisy[:, 2] .= sin_1 .+ 3 * noise coh = avg_coh(mt_coherence(more_noisy; fs, freq_range)) more_noisy_coherence = coh[2, 1] @test less_noisy_coherence < same_signal_coherence @@ -217,22 +217,22 @@ avg_coh(x) = dropdims(mean(coherence(x); dims=3); dims=3) @test different_signal_coherence < more_noisy_coherence # test in-place gets the same result - config = MTCoherenceConfig{T}(size(more_noisy)...; fs, freq_range) + config = MTCoherenceConfig{T}(2, n_samples; fs, freq_range) out = allocate_output(config) coh2 = avg_coh(mt_coherence!(out, more_noisy, config)) @test coh ≈ coh2 - several_signals = Matrix{T}(undef, 3, n_samples) - several_signals[1, :] = sin_1 - several_signals[2, :] = sin_2 - several_signals[3, :] = noise + several_signals = Matrix{T}(undef, n_samples, 3) + several_signals[:, 1] = sin_1 + several_signals[:, 2] = sin_2 + several_signals[:, 3] = noise several_signals_coherences = avg_coh(mt_coherence(several_signals; fs, freq_range)) @test length(several_signals_coherences) == 9 @test several_signals_coherences[2, 1] ≈ phase_shift_coherence @test several_signals_coherences[3, 1] ≈ different_signal_coherence # test in-place gets the same result - config = MTCoherenceConfig{T}(size(several_signals)...; fs, freq_range) + config = MTCoherenceConfig{T}(3, n_samples; fs, freq_range) out = allocate_output(config) @test eltype(out) == Float64 coh2 = avg_coh(mt_coherence!(out, several_signals, config)) @@ -240,7 +240,7 @@ avg_coh(x) = dropdims(mean(coherence(x); dims=3); dims=3) T = Float32 # Float32 output: - config = MTCoherenceConfig{T}(size(several_signals)...; fs, freq_range) + config = MTCoherenceConfig{T}(3, n_samples; fs, freq_range) out = allocate_output(config) @test eltype(out) == Float32 coh3 = avg_coh(mt_coherence!(out, several_signals, config)) @@ -258,19 +258,19 @@ end sin_1 = sinpi.(2 * 12.0 * t) # 12 Hz sinusoid signal sin_2 = sinpi.(2 * 12.0 * t .+ 1) noise = vec(read_reference_data("noise.txt", '\n')) # generated by `rand(1024) * 2 .- 1` - more_noisy = Array{Float64,3}(undef, 1, 2, n_samples) - more_noisy[1, 1, :] = sin_1 - more_noisy[1, 2, :] .= sin_1 .+ 3 * noise + more_noisy = Matrix{Float64}(undef, n_samples, 2) + more_noisy[:, 1] = sin_1 + more_noisy[:, 2] .= sin_1 .+ 3 * noise ## to generate the reference result: - # using PyMNE + # using PyMNE; more_noisy = reshape(more_noisy', 1, 2, n_samples) # mne_coherence_matrix, _ = PyMNE.connectivity.spectral_connectivity(more_noisy, method="coh",sfreq=fs,mode="multitaper",fmin=10,fmax=15,verbose=false) # coh = dropdims(mean(mne_coherence_matrix; dims=3); dims=3)[2, 1] coh = 0.982356762670818 mt_config = DSP.Periodograms.dpss_config(Float64, n_samples; fs, keep_only_large_evals=true, weight_by_evals=true) config = MTCoherenceConfig(2, mt_config; freq_range = (10,15), demean=true) - result = avg_coh(mt_coherence(dropdims(more_noisy;dims=1), config)) + result = avg_coh(mt_coherence(more_noisy, config)) @test result[2, 1] ≈ coh end @@ -278,11 +278,11 @@ end fs = 1000.0 n_samples = 1024 t = (0:1023) ./ fs - data = Array{Float64, 3}(undef, 1, 2, n_samples) - data[1, 1, :] = sinpi.(2 * 12.0 * t) # 12 Hz sinusoid signal - data[1, 2, :] = sinpi.(2 * 12.0 * t .+ 1) + signal = Matrix{Float64}(undef, n_samples, 2) + signal[:, 1] = sinpi.(2 * 12.0 * t) # 12 Hz sinusoid signal + signal[:, 2] = sinpi.(2 * 12.0 * t .+ 1) ## Script to generate reference data: - # using PyMNE, DelimitedFiles + # using PyMNE, DelimitedFiles; data = reshape(signal', 1, 2, n_samples) # result = PyMNE.time_frequency.csd_array_multitaper(data, fs, n_fft = nextpow(2, n_samples), low_bias=true, adaptive=false) # csd_array_multitaper_frequencies = result.frequencies # csd_array_multitaper_values = reduce((x,y) -> cat(x,y; dims=3), [ r.get_data() for r in result]) @@ -294,7 +294,6 @@ end csd_array_multitaper_values_im = reshape(read_reference_data("csd_array_multitaper_values_im.txt", '\n'), (2,2,512)) csd_array_multitaper_values = csd_array_multitaper_values_re + im*csd_array_multitaper_values_im - signal = dropdims(data; dims=1) mt_config = DSP.Periodograms.dpss_config(Float64, n_samples; fs, keep_only_large_evals=true, weight_by_evals=true) config = MTCrossSpectraConfig(2, mt_config; demean=true) result = mt_cross_power_spectra(signal, config) @@ -303,7 +302,7 @@ end @test power(result)[:,:,2:end] ≈ csd_array_multitaper_values # Test in-place. Full precision: - config = MTCrossSpectraConfig(size(signal, 1), mt_config; demean=true) + config = MTCrossSpectraConfig(2, mt_config; demean=true) out = allocate_output(config) @test eltype(out) == Complex{Float64} result2 = mt_cross_power_spectra!(out, signal, config) @@ -312,7 +311,7 @@ end # Float32 output: mt_config32 = DSP.Periodograms.dpss_config(Float32, n_samples; fs, keep_only_large_evals=true, weight_by_evals=true) - config = MTCrossSpectraConfig(size(signal, 1), mt_config32; demean=true) + config = MTCrossSpectraConfig(2, mt_config32; demean=true) out = allocate_output(config) @test eltype(out) == Complex{Float32} result2 = mt_cross_power_spectra!(out, signal, config) @@ -327,6 +326,11 @@ end @test_throws DimensionMismatch mt_cross_power_spectra!(similar(out, size(out, 1) + 1, size(out, 2), size(out, 3)), signal, config) @test_throws DimensionMismatch mt_cross_power_spectra!(out, vcat(signal, signal), config) + # test warnings for n_channels > n_samples + @test_warn "n_channels > n_samples" mt_cross_power_spectra(rand(10, 100)) + @test_warn "n_channels > n_samples" mt_coherence(rand(10, 100)) + @test_nowarn mt_cross_power_spectra(rand(10, 100); override_warn=true) + @test_nowarn mt_coherence(rand(10, 100); override_warn=true) end @@ -334,26 +338,26 @@ end fs = 1000.0 n_samples = 1024 t = (0:1023) ./ fs - noise = vec(read_reference_data("noise.txt", '\n')) + noise = read_reference_data("noise.txt", '\n') signal = sinpi.(2 * 12.0 * t) .+ 3 * noise - cs = mt_cross_power_spectra(reshape(signal, 1, :); fs) - p = mt_pgram(signal; fs) + cs = mt_cross_power_spectra(signal; fs) + p = mt_pgram(vec(signal); fs) @test freq(cs) ≈ freq(p) @test dropdims(power(cs); dims=(1,2)) ≈ power(p) # out-of-place with config config = MTCrossSpectraConfig{Float64}(1, length(signal); fs) - cs = mt_cross_power_spectra(reshape(signal, 1, :), config) + cs = mt_cross_power_spectra(signal, config) @test freq(cs) ≈ freq(p) @test dropdims(power(cs); dims=(1,2)) ≈ power(p) # in-place without config out = allocate_output(config) - cs = mt_cross_power_spectra!(out, reshape(signal, 1, :); fs) + cs = mt_cross_power_spectra!(out, signal; fs) @test freq(cs) ≈ freq(p) @test dropdims(power(cs); dims=(1,2)) ≈ power(p) # rm once two-sided FFTs supported - @test_throws ArgumentError mt_cross_power_spectra(reshape(complex.(signal), 1, :); fs) + @test_throws ArgumentError mt_cross_power_spectra(complex.(signal); fs) end