Class: Digiproc::FFT
- Inherits:
-
Object
- Object
- Digiproc::FFT
- Defined in:
- lib/fft.rb
Overview
Class to calculate and store the Discrete Fourier Transform of a siugnal
Instance Attribute Summary collapse
-
#inverse_strategy ⇒ Object
Returns the value of attribute inverse_strategy.
-
#processed_time_data ⇒ Object
Returns the value of attribute processed_time_data.
-
#strategy ⇒ Object
Returns the value of attribute strategy.
-
#time_data_size ⇒ Object
Returns the value of attribute time_data_size.
-
#window ⇒ Object
Returns the value of attribute window.
Class Method Summary collapse
-
.calculate(time_data) ⇒ Object
- Calculate the FFT of given time data == Input arg time_data
-
.new_from_spectrum(data) ⇒ Object
- Calculate the IFFT of the given frequency data Input frequency data, perform the Inverse FFT to populate the time data == Input arg data
Instance Method Summary collapse
-
#*(obj) ⇒ Object
Allows multioplication of FFT objects with anything with a @data reader which holds an Array of Numerics.
-
#angle ⇒ Object
Returns the angle of the frequency domain data, as an array of floats (in radians).
-
#calculate ⇒ Object
Performs FFT caclulation if not yet performed.
-
#calculate_at_size(size) ⇒ Object
Input argument of an Integer describing the required size of the FFT.
-
#conjugate ⇒ Object
Return the complex conjugate of the frequency domain data, as an array of numerics (float or complex).
-
#data ⇒ Object
Reader for @data Allows for lazy calculation of @data (which holds frequency domain data) If @data is nil, the #calculate method will be called.
-
#dB ⇒ Object
Return the decible of the frequency domain data, as an Array of floats.
-
#fft ⇒ Object
Returns the frequency domain data as an Array of Numerics (Float or Complex).
-
#graph_magnitude(file_name = "fft") ⇒ Object
TODO: Remove plots magnitude using Gruff directly.
-
#graph_time_data ⇒ Object
TODO: Remove Plots time data using Gruff directly.
-
#ifft ⇒ Object
Calculate the IFFT of the frequency data.
-
#ifft_ds ⇒ Object
Calculate the IFFT and return it as a Digiproc::DigitalSignal.
-
#imaginary ⇒ Object
Returns the imaginary part of the frequency domain data, as an array of floats.
-
#initialize(strategy: Digiproc::Strategies::Radix2Strategy, time_data: nil, size: nil, window: Digiproc::RectangularWindow, freq_data: nil, inverse_strategy: Digiproc::Strategies::IFFTConjugateStrategy) ⇒ FFT
constructor
Input Args strategy:: FFT Strategy, see Digiproc::Strategies::Radix2Strategy to see required Protocol time_data:: Array time data to be transformed to the frequency domain via the FFT strategy size (Optional):: Integer, defaults to nil.
-
#local_maxima(num = 1) ⇒ Object
Returns the local maximum value(s) of the magnitude of the frequency signal as an Array of OpenStructs with an index and value property.
-
#magnitude ⇒ Object
Return the magnitude of the frequency domain values as an array of floats.
-
#maxima(num = 1) ⇒ Object
Returns the maximum value(s) of the magnitude of the frequency signal as an Array of OpenStructs with an index and value property.
-
#plot_db(path: "./") ⇒ Object
Uses Plottable module to plot the db values.
-
#plot_magnitude(path: "./") ⇒ Object
uses Plottable module to plot the magnitude values.
-
#process_with_window ⇒ Object
Processes the time_data with the chosen window valuesand calculates the FFT based of of the window-processed time domain signals.
-
#real ⇒ Object
Returns the real part of the frequency domain data, as an array of floats.
-
#size ⇒ Object
Return the number of frequency domain datapoints.
-
#time_data ⇒ Object
Returns the time_data as an Array of Numerics (floats or Complex numbers).
Methods included from Convolvable::InstanceMethods
#acorr, #auto_correlation, #conv, #convolution_strategy, #convolve, #cross_correlation, included, #xcorr
Methods included from Plottable::InstanceMethods
Constructor Details
#initialize(strategy: Digiproc::Strategies::Radix2Strategy, time_data: nil, size: nil, window: Digiproc::RectangularWindow, freq_data: nil, inverse_strategy: Digiproc::Strategies::IFFTConjugateStrategy) ⇒ FFT
Input Args
- strategy
-
FFT Strategy, see Digiproc::Strategies::Radix2Strategy to see required Protocol
- time_data
-
Array time data to be transformed to the frequency domain via the FFT strategy
- size (Optional)
-
Integer, defaults to nil. If not set, your data will be zero padded to the closest higher power of 2 (for Radix2Strategy), or not changed at all
- window (Optional)
-
Digiproc::Window, defaults to Digiproc::RectangularWindow. Changes the window used during #process_with_window method
- freq_data (Optional)
-
Array, required if time_data not given
- inverse_strategy (Optional)
-
Digiproc::Strategies::IFFTConjugateStrategy is the default value and shows the required protocol
Note: Using size with a Radix2Strategy will only ensure a minimum amount of zero-padding, it will mostly likely not determine the final size of the time_data You need to have EITHER time_data or freq_data, but not both.
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/fft.rb', line 46 def initialize(strategy: Digiproc::Strategies::Radix2Strategy, time_data: nil, size: nil, window: Digiproc::RectangularWindow, freq_data: nil, inverse_strategy: Digiproc::Strategies::IFFTConjugateStrategy) raise ArgumentError.new("Either time or frequency data must be given") if time_data.nil? and freq_data.nil? raise ArgumentError.new('Size must be an integer') if not size.nil? and not size.is_a?(Integer) raise ArguemntError.new('Size must be greater than zero') if not size.nil? and size <= 0 raise ArgumentError.new('time_data must be an array') if not time_data.respond_to?(:calculate) and not time_data.is_a? Array if time_data.is_a? Array @time_data_size = time_data.length if not size.nil? if size <= time_data.length @time_data = time_data.dup.map{ |val| val.dup }.take(size) else zero_fill = Array.new(size - time_data.length, 0) @time_data = time_data.dup.map{ |val| val.dup }.concat zero_fill end else @time_data = time_data.dup.map{ |val| val.dup} end @strategy = strategy.new(@time_data.map{ |val| val.dup}) @window = window.new(size: time_data_size) else @time_data = time_data @strategy = strategy.new @window = window.new(size: freq_data.length) end @inverse_strategy = inverse_strategy @data = freq_data end |
Instance Attribute Details
#inverse_strategy ⇒ Object
Returns the value of attribute inverse_strategy.
31 32 33 |
# File 'lib/fft.rb', line 31 def inverse_strategy @inverse_strategy end |
#processed_time_data ⇒ Object
Returns the value of attribute processed_time_data.
31 32 33 |
# File 'lib/fft.rb', line 31 def processed_time_data @processed_time_data end |
#strategy ⇒ Object
Returns the value of attribute strategy.
31 32 33 |
# File 'lib/fft.rb', line 31 def strategy @strategy end |
#time_data_size ⇒ Object
Returns the value of attribute time_data_size.
31 32 33 |
# File 'lib/fft.rb', line 31 def time_data_size @time_data_size end |
#window ⇒ Object
Returns the value of attribute window.
31 32 33 |
# File 'lib/fft.rb', line 31 def window @window end |
Class Method Details
.calculate(time_data) ⇒ Object
9 10 11 |
# File 'lib/fft.rb', line 9 def self.calculate(time_data) Radix2Strategy.calculate(time_data) end |
.new_from_spectrum(data) ⇒ Object
Calculate the IFFT of the given frequency data Input frequency data, perform the Inverse FFT to populate the time data
Input arg
- data
18 19 20 21 |
# File 'lib/fft.rb', line 18 def self.new_from_spectrum(data) time_data = Digiproc::Strategies::IFFTConjugateStrategy.new(data) new(freq_data: data, time_data: time_data) end |
Instance Method Details
#*(obj) ⇒ Object
Allows multioplication of FFT objects with anything with a @data reader which holds an Array of Numerics. The return value is a new FFT object whose frequency data is the element-by-element multiplication of the two data arrays
206 207 208 209 210 211 212 |
# File 'lib/fft.rb', line 206 def *(obj) if obj.respond_to?(:data) return self.class.new_from_spectrum(self.data.times obj.data) elsif obj.is_a? Array return self.class.new_from_spectrum(self.data.times obj) end end |
#angle ⇒ Object
Returns the angle of the frequency domain data, as an array of floats (in radians)
167 168 169 |
# File 'lib/fft.rb', line 167 def angle self.data.map(&:angle) end |
#calculate ⇒ Object
Performs FFT caclulation if not yet performed. Returns FFT data as an Array of Floats (or an array of Complex numbers)
77 78 79 80 81 |
# File 'lib/fft.rb', line 77 def calculate self.strategy.data = time_data if @strategy.data.nil? @fft = self.strategy.calculate @data = @fft end |
#calculate_at_size(size) ⇒ Object
Input argument of an Integer describing the required size of the FFT. IF using a strategy requiring a certain amount of data points (ie Radix2), you will be guaranteed tha the FFT is greater than or equal to the input size. Otherwise, your FFT will be this size
86 87 88 89 90 91 92 93 94 95 |
# File 'lib/fft.rb', line 86 def calculate_at_size(size) if size > self.data.size zero_fill = Array.new(size - @time_data.length, 0) @time_data = time_data.concat zero_fill elsif size < self.data.size @time_data = time_data.take(size) end self.strategy.data = time_data calculate end |
#conjugate ⇒ Object
Return the complex conjugate of the frequency domain data, as an array of numerics (float or complex)
153 154 155 |
# File 'lib/fft.rb', line 153 def conjugate self.data.map(&:conjugate) end |
#data ⇒ Object
Reader for @data Allows for lazy calculation of @data (which holds frequency domain data) If @data is nil, the #calculate method will be called
26 27 28 29 |
# File 'lib/fft.rb', line 26 def data calculate if @data.nil? @data end |
#dB ⇒ Object
Return the decible of the frequency domain data, as an Array of floats
159 160 161 162 163 |
# File 'lib/fft.rb', line 159 def dB self.magnitude.map do |m| Math.db(m) end end |
#fft ⇒ Object
Returns the frequency domain data as an Array of Numerics (Float or Complex)
133 134 135 |
# File 'lib/fft.rb', line 133 def fft self.data end |
#graph_magnitude(file_name = "fft") ⇒ Object
TODO: Remove plots magnitude using Gruff directly
238 239 240 241 242 243 244 |
# File 'lib/fft.rb', line 238 def graph_magnitude(file_name = "fft") if @fft g = Gruff::Line.new g.data :fft, self.magnitude g.write("./#{file_name}.png") end end |
#graph_time_data ⇒ Object
TODO: Remove Plots time data using Gruff directly
249 250 251 252 |
# File 'lib/fft.rb', line 249 def graph_time_data g = Gruff::Line.new g.data :data, @time_data end |
#ifft ⇒ Object
Calculate the IFFT of the frequency data
99 100 101 |
# File 'lib/fft.rb', line 99 def ifft inverse_strategy.new(data).calculate end |
#ifft_ds ⇒ Object
Calculate the IFFT and return it as a Digiproc::DigitalSignal
105 106 107 |
# File 'lib/fft.rb', line 105 def ifft_ds Digiproc::DigitalSignal.new(data: ifft) end |
#imaginary ⇒ Object
Returns the imaginary part of the frequency domain data, as an array of floats
179 180 181 |
# File 'lib/fft.rb', line 179 def imaginary self.data.map(&:imaginary) end |
#local_maxima(num = 1) ⇒ Object
Returns the local maximum value(s) of the magnitude of the frequency signal as an Array of OpenStructs with an index and value property. Local maxima are determined by Digiproc::DataProperties.local_maxima, and the returned maxima are determined based off of their relative hight to adjacent maxima. This is useful for looking for spikes in frequency data
Input arg
- num (Optional)
-
The number of maxima desired, defaults to 1
199 200 201 |
# File 'lib/fft.rb', line 199 def local_maxima(num = 1) Digiproc::DataProperties.local_maxima(self.magnitude, num) end |
#magnitude ⇒ Object
Return the magnitude of the frequency domain values as an array of floats
145 146 147 148 149 |
# File 'lib/fft.rb', line 145 def magnitude data.map do |f| f.abs end end |
#maxima(num = 1) ⇒ Object
Returns the maximum value(s) of the magnitude of the frequency signal as an Array of OpenStructs with an index and value property.
Input arg
- num (Optional)
-
The number of maxima desired, defaults to 1
188 189 190 |
# File 'lib/fft.rb', line 188 def maxima(num = 1) Digiproc::DataProperties.maxima(self.magnitude, num) end |
#plot_db(path: "./") ⇒ Object
Uses Plottable module to plot the db values
216 217 218 219 220 221 222 |
# File 'lib/fft.rb', line 216 def plot_db(path: "./") self.plot(method: :dB, xsteps: 8, path: path) do |g| g.title = "Decibles" g.x_axis_label = "Normalized Frequency" g.y_axis_label = "Magnitude" end end |
#plot_magnitude(path: "./") ⇒ Object
uses Plottable module to plot the magnitude values
226 227 228 229 230 231 232 |
# File 'lib/fft.rb', line 226 def plot_magnitude(path: "./" ) self.plot(method: :magnitude, xsteps: 8, path: path) do |g| g.title = "Magnitude" g.x_axis_label = "Normalized Frequency" g.y_axis_label = "Magnitude" end end |
#process_with_window ⇒ Object
Processes the time_data with the chosen window valuesand calculates the FFT based of of the window-processed time domain signals
124 125 126 127 128 129 |
# File 'lib/fft.rb', line 124 def process_with_window @processed_time_data = time_data.take(time_data_size).times self.window.values self.strategy.data = @processed_time_data @fft = self.strategy.calculate @data = @fft end |
#real ⇒ Object
Returns the real part of the frequency domain data, as an array of floats
173 174 175 |
# File 'lib/fft.rb', line 173 def real self.data.map(&:real) end |
#size ⇒ Object
Return the number of frequency domain datapoints
139 140 141 |
# File 'lib/fft.rb', line 139 def size self.data.length end |
#time_data ⇒ Object
Returns the time_data as an Array of Numerics (floats or Complex numbers)
112 113 114 115 116 117 118 119 120 |
# File 'lib/fft.rb', line 112 def time_data if @time_data.is_a? Array @time_data elsif @time_data.respond_to? :calculate @time_data = @time_data.calculate else raise TypeError.new("time_data needs to be an array or an ifft strategy, not a #{@time_data.class}") end end |