class SPCore::Signal
Store signal data and provide some useful methods for working with (testing and analyzing) the data.
@author James Tunnell
Constants
- ARG_SPECS
Used to process hashed arguments in initialize.
Attributes
Public Class Methods
A new instance of Signal
.
@param [Hash] hashed_args Hashed arguments. Required keys are :data and
:sample_rate. See ARG_SPECS for more details.
# File lib/spcore/util/signal.rb, line 22 def initialize hashed_args hash_make hashed_args, Signal::ARG_SPECS end
Public Instance Methods
Access signal data.
# File lib/spcore/util/signal.rb, line 57 def [](arg) @data[arg] end
Operate on copy of the Signal
object with the absolute value function.
# File lib/spcore/util/signal.rb, line 353 def abs self.clone.abs! end
Operate on the signal data (in place) with the absolute value function.
# File lib/spcore/util/signal.rb, line 347 def abs! @data = @data.map {|x| x.abs } return self end
Add value, values in array, or values in other signal to the current data values, and return a new Signal
object with the results. @param other Can be Numeric (add same value to all data values), Array, or Signal
.
# File lib/spcore/util/signal.rb, line 439 def add(other) clone.add! other end
Add value, values in array, or values in other signal to the current data values, and update the current data with the results. @param other Can be Numeric (add same value to all data values), Array, or Signal
.
# File lib/spcore/util/signal.rb, line 415 def add!(other) if other.is_a?(Numeric) @data.each_index do |i| @data[i] += other end elsif other.is_a?(Signal) raise ArgumentError, "other.data.size #{other.size} is not equal to data.size #{@data.size}" if other.data.size != @data.size @data.each_index do |i| @data[i] += other.data[i] end elsif other.is_a?(Array) raise ArgumentError, "other.size #{other.size} is not equal to data.size #{@data.size}" if other.size != @data.size @data.each_index do |i| @data[i] += other[i] end else raise ArgumentError, "other is not a Numeric, Signal, or Array" end return self end
Add data in array or other signal to the end of current data.
# File lib/spcore/util/signal.rb, line 408 def append other clone.append! other end
Add data in array or other signal to the end of current data.
# File lib/spcore/util/signal.rb, line 398 def append! other if other.is_a?(Array) @data = @data.concat other elsif other.is_a?(Signal) @data = @data.concat other.data end return self end
Run a discrete bandpass filter over the signal data (using DualSincFilter
). Return result in a new Signal
object.
# File lib/spcore/util/signal.rb, line 123 def bandpass left_cutoff, right_cutoff, order self.clone.bandpass! left_cutoff, right_cutoff, order end
Run a discrete bandpass filter over the signal data (using DualSincFilter
). Modifies current object.
# File lib/spcore/util/signal.rb, line 110 def bandpass! left_cutoff, right_cutoff, order filter = DualSincFilter.new( :sample_rate => @sample_rate, :order => order, :left_cutoff_freq => left_cutoff, :right_cutoff_freq => right_cutoff ) @data = filter.bandpass(@data) return self end
Run a discrete bandstop filter over the signal data (using DualSincFilter
). Return result in a new Signal
object.
# File lib/spcore/util/signal.rb, line 142 def bandstop left_cutoff, right_cutoff, order self.clone.bandstop! left_cutoff, right_cutoff, order end
Run a discrete bandstop filter over the signal data (using DualSincFilter
). Modifies current object.
# File lib/spcore/util/signal.rb, line 129 def bandstop! left_cutoff, right_cutoff, order filter = DualSincFilter.new( :sample_rate => @sample_rate, :order => order, :left_cutoff_freq => left_cutoff, :right_cutoff_freq => right_cutoff ) @data = filter.bandstop(@data) return self end
Produce a new Signal
object with the same data.
# File lib/spcore/util/signal.rb, line 27 def clone new = Signal.new(:data => @data.clone, :sample_rate => @sample_rate) new.instance_variable_set(:@frequency_domain, @frequency_domain) return new end
Apply Statistics.correlation
to the signal data (as the image).
# File lib/spcore/util/signal.rb, line 312 def correlation feature, zero_padding = 0 Statistics.correlation @data, feature, zero_padding end
Size of the signal data.
# File lib/spcore/util/signal.rb, line 47 def count @data.size end
Applies Calculus.derivative
on the signal data, and returns a new Signal
object with the result.
# File lib/spcore/util/signal.rb, line 547 def derivative return Signal.new(:sample_rate => @sample_rate, :data => Calculus.derivative(@data)) end
Divide value, values in array, or values in other signal into the current data values, and return a new Signal
object with the results. @param other Can be Numeric (divide same all data values by the same value),
Array, or Signal.
# File lib/spcore/util/signal.rb, line 536 def divide(other) clone.divide! other end
Divide value, values in array, or values in other signal into the current data values, and update the current data with the results. @param other Can be Numeric (divide same all data values by the same value),
Array, or Signal.
# File lib/spcore/util/signal.rb, line 511 def divide!(other) if other.is_a?(Numeric) @data.each_index do |i| @data[i] /= other end elsif other.is_a?(Signal) raise ArgumentError, "other.data.size #{other.size} is not equal to data.size #{@data.size}" if other.data.size != @data.size @data.each_index do |i| @data[i] /= other.data[i] end elsif other.is_a?(Array) raise ArgumentError, "other.size #{other.size} is not equal to data.size #{@data.size}" if other.size != @data.size @data.each_index do |i| @data[i] /= other[i] end else raise ArgumentError, "other is not a Numeric, Signal, or Array" end return self end
Decrease the sample rate of signal data by the given factor using discrete downsampling method. Return result in a new Signal
object. @param [Fixnum] downsample_factor Decrease the sample rate by this factor. @param [Fixnum] filter_order The filter order for the discrete lowpass filter.
# File lib/spcore/util/signal.rb, line 178 def downsample_discrete downsample_factor, filter_order return self.clone.downsample_discrete!(downsample_factor, filter_order) end
Decrease the sample rate of signal data by the given factor using discrete downsampling method. Modifies current object. @param [Fixnum] downsample_factor Decrease the sample rate by this factor. @param [Fixnum] filter_order The filter order for the discrete lowpass filter.
# File lib/spcore/util/signal.rb, line 168 def downsample_discrete! downsample_factor, filter_order @data = DiscreteResampling.downsample @data, @sample_rate, downsample_factor, filter_order @sample_rate /= downsample_factor return self end
Signal
duration in seconds.
# File lib/spcore/util/signal.rb, line 52 def duration return @data.size.to_f / @sample_rate end
Calculate the energy in current signal data.
# File lib/spcore/util/signal.rb, line 291 def energy return @data.inject(0.0){|sum,x| sum + (x * x)} end
Apply Features.envelope
to the signal data, and return either a new Signal
object or the raw envelope data. @param [True/False] as_signal If true, return envelope data in a new signal
object. Otherwise, return raw envelope data. Set to true by default.
# File lib/spcore/util/signal.rb, line 372 def envelope as_signal = true env_data = Features.envelope @data if as_signal return Signal.new(:sample_rate => @sample_rate, :data => env_data) else return env_data end end
Apply Features.extrema
to the signal data.
# File lib/spcore/util/signal.rb, line 317 def extrema return Features.extrema(@data) end
Return the full output of forward FFT
on the signal data.
# File lib/spcore/util/signal.rb, line 252 def fft_full frequency_domain.fft_full end
Return the first half of the the forward FFT
on the signal data.
# File lib/spcore/util/signal.rb, line 257 def fft_half frequency_domain.fft_half end
Run FFT
on signal data to find magnitude of frequency components. @return [Hash] contains frequencies mapped to magnitudes.
# File lib/spcore/util/signal.rb, line 263 def freq_magnitudes freq_magnitudes = {} fft_half.each_index do |i| freq = frequency_domain.idx_to_freq(i) freq_magnitudes[freq] = fft_half[i] end return freq_magnitudes end
Apply FrequencyDomain.peaks
to the signal and return the result.
# File lib/spcore/util/signal.rb, line 275 def freq_peaks return frequency_domain.peaks end
# File lib/spcore/util/signal.rb, line 240 def frequency_domain fft_format = FrequencyDomain::FFT_MAGNITUDE_DECIBEL if @frequency_domain.nil? @frequency_domain = FrequencyDomain.new( :time_data => @data, :sample_rate => @sample_rate, :fft_format => fft_format ) end return @frequency_domain end
Return the lowest frequency of the signal harmonic series.
# File lib/spcore/util/signal.rb, line 286 def fundamental opts return harmonic_series(opts).min end
Apply FrequencyDomain.harmonic_series
to the signal frequency peaks and return the result.
# File lib/spcore/util/signal.rb, line 281 def harmonic_series opts = {} return frequency_domain.harmonic_series(opts) end
Run a discrete highpass filter over the signal data (using SincFilter
). Return result in a new Signal
object.
# File lib/spcore/util/signal.rb, line 104 def highpass cutoff_freq, order self.clone.highpass! cutoff_freq, order end
Run a discrete highpass filter over the signal data (using SincFilter
). Modifies current object.
# File lib/spcore/util/signal.rb, line 96 def highpass! cutoff_freq, order filter = SincFilter.new(:sample_rate => @sample_rate, :order => order, :cutoff_freq => cutoff_freq) @data = filter.highpass(@data) return self end
Applies Calculus.integral
on the signal data, and returns a new Signal
object with the result.
# File lib/spcore/util/signal.rb, line 553 def integral return Signal.new(:sample_rate => @sample_rate, :data => Calculus.integral(@data)) end
Removes all but the given range of frequencies from the signal, using frequency domain filtering. Modifes a clone of the current object, returning the clone.
# File lib/spcore/util/signal.rb, line 579 def keep_frequencies freq_range return self.clone.keep_frequencies!(freq_range) end
Removes all but the given range of frequencies from the signal, using frequency domain filtering. Modifes and returns the current object.
# File lib/spcore/util/signal.rb, line 572 def keep_frequencies! freq_range modify_freq_content freq_range, :keep end
Run a discrete lowpass filter over the signal data (using SincFilter
). Return result in a new Signal
object.
# File lib/spcore/util/signal.rb, line 90 def lowpass cutoff_freq, order self.clone.lowpass! cutoff_freq, order end
Run a discrete lowpass filter over the signal data (using SincFilter
). Modifies current object.
# File lib/spcore/util/signal.rb, line 82 def lowpass! cutoff_freq, order filter = SincFilter.new(:sample_rate => @sample_rate, :order => order, :cutoff_freq => cutoff_freq) @data = filter.lowpass(@data) return self end
Apply Features.maxima
to the signal data.
# File lib/spcore/util/signal.rb, line 337 def maxima return Features.maxima(@data) end
Apply Statistics.mean
to the signal data.
# File lib/spcore/util/signal.rb, line 302 def mean Statistics.mean @data end
Apply Features.minima
to the signal data.
# File lib/spcore/util/signal.rb, line 327 def minima return Features.minima(@data) end
Multiply value, values in array, or values in other signal with the current data values, and return a new Signal
object with the results. @param other Can be Numeric (multiply all data values by the same value),
Array, or Signal.
# File lib/spcore/util/signal.rb, line 503 def multiply(other) clone.multiply! other end
Multiply value, values in array, or values in other signal with the current data values, and update the current data with the results. @param other Can be Numeric (multiply all data values by the same value),
Array, or Signal.
# File lib/spcore/util/signal.rb, line 478 def multiply!(other) if other.is_a?(Numeric) @data.each_index do |i| @data[i] *= other end elsif other.is_a?(Signal) raise ArgumentError, "other.data.size #{other.size} is not equal to data.size #{@data.size}" if other.data.size != @data.size @data.each_index do |i| @data[i] *= other.data[i] end elsif other.is_a?(Array) raise ArgumentError, "other.size #{other.size} is not equal to data.size #{@data.size}" if other.size != @data.size @data.each_index do |i| @data[i] *= other[i] end else raise ArgumentError, "other is not a Numeric, Signal, or Array" end return self end
Apply Features.negative_minima
to the signal data.
# File lib/spcore/util/signal.rb, line 332 def negative_minima return Features.negative_minima(@data) end
reduce all samples to the given level
# File lib/spcore/util/signal.rb, line 363 def normalize level = 1.0 self.clone.normalize! level end
reduce all samples to the given level
# File lib/spcore/util/signal.rb, line 358 def normalize! level = 1.0 self.divide!(@data.max / level) end
Apply Features.outer_extrema
to the signal data.
# File lib/spcore/util/signal.rb, line 322 def outer_extrema return Features.outer_extrema(@data) end
Plot the signal data against sample numbers.
# File lib/spcore/util/signal.rb, line 62 def plot_1d plotter = Plotter.new(:title => "Signal: values vs. sample number", :xtitle => "sample number", :ytitle => "sample value") plotter.plot_1d "signal data" => @data end
Plot the signal data against time.
# File lib/spcore/util/signal.rb, line 68 def plot_2d plotter = Plotter.new(:title => "Signal: values vs. time", :xtitle => "time (sec)", :ytitle => "sample value") data_vs_time = {} sp = 1.0 / @sample_rate @data.each_index do |i| data_vs_time[i * sp] = @data[i] end plotter.plot_2d "signal data" => data_vs_time end
Apply Features.positive_maxima
to the signal data.
# File lib/spcore/util/signal.rb, line 342 def positive_maxima return Features.positive_maxima(@data) end
Add data in array or other signal to the beginning of current data.
# File lib/spcore/util/signal.rb, line 393 def prepend other clone.prepend! other end
Add data in array or other signal to the beginning of current data.
# File lib/spcore/util/signal.rb, line 383 def prepend! other if other.is_a?(Array) @data = other.concat @data elsif other.is_a?(Signal) @data = other.data.concat @data end return self end
Removes the given range of frequencies from the signal, using frequency domain filtering. Modifes a clone of the current object, returning the clone.
# File lib/spcore/util/signal.rb, line 566 def remove_frequencies freq_range return self.clone.remove_frequencies!(freq_range) end
Removes all but the given range of frequencies from the signal, using frequency domain filtering. Modifes and returns the current object.
# File lib/spcore/util/signal.rb, line 559 def remove_frequencies! freq_range modify_freq_content freq_range, :remove end
Change the sample rate of signal data by the given up/down factors, using discrete upsampling and downsampling methods. Return result in a new Signal
object. @param [Fixnum] upsample_factor Increase the sample rate by this factor. @param [Fixnum] downsample_factor Decrease the sample rate by this factor. @param [Fixnum] filter_order The filter order for the discrete lowpass filter.
# File lib/spcore/util/signal.rb, line 199 def resample_discrete upsample_factor, downsample_factor, filter_order return self.clone.resample_discrete!(upsample_factor, downsample_factor, filter_order) end
Change the sample rate of signal data by the given up/down factors, using discrete upsampling and downsampling methods. Modifies current object. @param [Fixnum] upsample_factor Increase the sample rate by this factor. @param [Fixnum] downsample_factor Decrease the sample rate by this factor. @param [Fixnum] filter_order The filter order for the discrete lowpass filter.
# File lib/spcore/util/signal.rb, line 187 def resample_discrete! upsample_factor, downsample_factor, filter_order @data = DiscreteResampling.resample @data, @sample_rate, upsample_factor, downsample_factor, filter_order @sample_rate *= upsample_factor @sample_rate /= downsample_factor return self end
Change the sample rate of signal data by the given up/down factors, using polynomial upsampling and discrete downsampling. Return result as a new Signal
object. @param [Fixnum] upsample_factor Increase the sample rate by this factor. @param [Fixnum] downsample_factor Decrease the sample rate by this factor. @param [Fixnum] filter_order The filter order for the discrete lowpass filter.
# File lib/spcore/util/signal.rb, line 236 def resample_hybrid upsample_factor, downsample_factor, filter_order return self.clone.resample_hybrid!(upsample_factor, downsample_factor, filter_order) end
Change the sample rate of signal data by the given up/down factors, using polynomial upsampling and discrete downsampling. Modifies current Signal
object. @param [Fixnum] upsample_factor Increase the sample rate by this factor. @param [Fixnum] downsample_factor Decrease the sample rate by this factor. @param [Fixnum] filter_order The filter order for the discrete lowpass filter.
# File lib/spcore/util/signal.rb, line 224 def resample_hybrid! upsample_factor, downsample_factor, filter_order @data = HybridResampling.resample @data, @sample_rate, upsample_factor, downsample_factor, filter_order @sample_rate *= upsample_factor @sample_rate /= downsample_factor return self end
Calculate signal RMS (root-mean square), also known as quadratic mean, a statistical measure of the magnitude.
# File lib/spcore/util/signal.rb, line 297 def rms Math.sqrt(energy / size) end
Size of the signal data.
# File lib/spcore/util/signal.rb, line 42 def size @data.size end
Apply Statistics.std_dev
to the signal data.
# File lib/spcore/util/signal.rb, line 307 def std_dev Statistics.std_dev @data end
Produce a new Signal
object with a subset of the current signal data. @param [Range] range Used to pick the data range.
# File lib/spcore/util/signal.rb, line 35 def subset range new = Signal.new(:data => @data[range], :sample_rate => @sample_rate) new.instance_variable_set(:@frequency_domain, @frequency_domain) return new end
Subtract value, values in array, or values in other signal from the current data values, and return a new Signal
object with the results. @param other Can be Numeric (subtract same value from all data values), Array, or Signal
.
# File lib/spcore/util/signal.rb, line 470 def subtract(other) clone.subtract! other end
Subtract value, values in array, or values in other signal from the current data values, and update the current data with the results. @param other Can be Numeric (subtract same value from all data values), Array, or Signal
.
# File lib/spcore/util/signal.rb, line 446 def subtract!(other) if other.is_a?(Numeric) @data.each_index do |i| @data[i] -= other end elsif other.is_a?(Signal) raise ArgumentError, "other.data.size #{other.size} is not equal to data.size #{@data.size}" if other.data.size != @data.size @data.each_index do |i| @data[i] -= other.data[i] end elsif other.is_a?(Array) raise ArgumentError, "other.size #{other.size} is not equal to data.size #{@data.size}" if other.size != @data.size @data.each_index do |i| @data[i] -= other[i] end else raise ArgumentError, "other is not a Numeric, Signal, or Array" end return self end
Increase the sample rate of signal data by the given factor using discrete upsampling method. Return result in a new Signal
object. @param [Fixnum] upsample_factor Increase the sample rate by this factor. @param [Fixnum] filter_order The filter order for the discrete lowpass filter.
# File lib/spcore/util/signal.rb, line 160 def upsample_discrete upsample_factor, filter_order return self.clone.upsample_discrete!(upsample_factor, filter_order) end
Increase the sample rate of signal data by the given factor using discrete upsampling method. Modifies current object. @param [Fixnum] upsample_factor Increase the sample rate by this factor. @param [Fixnum] filter_order The filter order for the discrete lowpass filter.
# File lib/spcore/util/signal.rb, line 150 def upsample_discrete! upsample_factor, filter_order @data = DiscreteResampling.upsample @data, @sample_rate, upsample_factor, filter_order @sample_rate *= upsample_factor return self end
Increase the sample rate of signal data by the given factor using polynomial interpolation. Returns result as a new Signal
object. @param [Fixnum] upsample_factor Increase the sample rate by this factor.
# File lib/spcore/util/signal.rb, line 215 def upsample_polynomial upsample_factor return self.clone.upsample_polynomial!(upsample_factor) end
Increase the sample rate of signal data by the given factor using polynomial interpolation. Modifies current Signal
object. @param [Fixnum] upsample_factor Increase the sample rate by this factor.
# File lib/spcore/util/signal.rb, line 206 def upsample_polynomial! upsample_factor @data = PolynomialResampling.upsample @data, upsample_factor @sample_rate *= upsample_factor return self end
Private Instance Methods
# File lib/spcore/util/signal.rb, line 585 def modify_freq_content freq_range, mod_type nyquist = @sample_rate / 2 unless freq_range.min.between?(0, nyquist) raise ArgumentError, "freq_range.min #{freq_range.min} is not between 0 and #{nyquist}" end unless freq_range.max.between?(0, nyquist) raise ArgumentError, "freq_range.min #{freq_range.min} is not between 0 and #{nyquist}" end power_of_two = FFT.power_of_two?(size) if power_of_two freq_domain = FFT.forward @data else freq_domain = DFT.forward @data end # cutoff indices for real half a = ((freq_range.min * size) / @sample_rate).round b = ((freq_range.max * size) / @sample_rate).round window_size = b - a + 1 window_data = RectangularWindow.new(window_size).data case mod_type when :keep new_freq_data = Array.new(size, Complex(0)) window_size.times do |n| i = n + a new_freq_data[i] = freq_domain[i] * window_data[n] end window_size.times do |n| i = n + (size - 1 - b) new_freq_data[i] = freq_domain[i] * window_data[n] end when :remove new_freq_data = freq_domain.clone window_size.times do |n| i = n + a new_freq_data[i] = freq_domain[i] * (Complex(1.0) - window_data[n]) end window_size.times do |n| i = n + (size - 1 - b) new_freq_data[i] = freq_domain[i] * (Complex(1.0) - window_data[n]) end else raise ArgumentError, "unkown mod_type #{mod_type}" end if power_of_two data = FFT.inverse new_freq_data else data = DFT.inverse new_freq_data end @data = data.map {|complex| complex.real } return self end