spcore 0.1.9 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -48,4 +48,10 @@ Add Envelope and Extrema classes, for signal analysis.
48
48
  === 0.1.9 / 2013-05-06
49
49
 
50
50
  Add TukeyWindow.
51
- Add Signal#keep_frequences, Signal#remove_frequencies, and Plotter#plot_signals.
51
+ Add Signal#keep_frequences, Signal#remove_frequencies, and Plotter#plot_signals.
52
+
53
+ === 0.2.0 / 2013-05-21
54
+
55
+ Add instance methods to Signal class: #duration, #normalize, #derivative, #lowpass, #highpass, #bandpass, #bandstop, #plot_1d, and #plot_2d.
56
+ Add Correlation class to find similarity between signals (used to find a feature in an image).
57
+ Make envelopes smoother with polynomial upsampling. In Signal#envelope and Signal, always return a Signal object (so also remove make_signal flag).
@@ -25,6 +25,9 @@ A library of signal processing methods and classes.
25
25
  * Oscillator with selectable wave type (sine, square, triangle, sawtooth)
26
26
  * Signal abstraction class
27
27
  * Extrema & Envelope measurement
28
+ * Normalization
29
+ * Derivatives
30
+ * Correlation
28
31
 
29
32
  == Examples
30
33
 
@@ -41,6 +41,7 @@ require 'spcore/resampling/discrete_resampling'
41
41
  require 'spcore/resampling/polynomial_resampling'
42
42
  require 'spcore/resampling/hybrid_resampling'
43
43
 
44
+ require 'spcore/analysis/correlation'
44
45
  require 'spcore/analysis/extrema'
45
46
  require 'spcore/analysis/envelope'
46
47
  require 'spcore/analysis/signal'
@@ -0,0 +1,55 @@
1
+ module SPCore
2
+ # Determines the normalized cross-correlation of a feature with an image.
3
+ # Normalization is from -1 to +1, where +1 is high correlation, -1 is high
4
+ # correlation (of inverse), and 0 is no correlation.
5
+ #
6
+ # For autocorrelation, just cross-correlate a signal with itself.
7
+ #
8
+ # @author James Tunnell
9
+ class Correlation
10
+ attr_reader :data
11
+
12
+ # @param [Array] image The values which are actually recieved/measured.
13
+ # @param [Array] feature The values to be searched for in the image. Size must not be greater than size of image.
14
+ # @param [Fixnum] zero_padding Number of zeros to surround the image with.
15
+ def initialize image, feature, zero_padding = 0
16
+ raise ArgumentError, "feature size is > image size" if feature.size > image.size
17
+
18
+ unless zero_padding == 0
19
+ image = Array.new(zero_padding, 0) + image + Array.new(zero_padding, 0)
20
+ end
21
+
22
+ feature_mean = feature.inject(0){ |s, x| s + x } / feature.size.to_f
23
+ feature_diff = feature.map {|x| x - feature_mean }
24
+ sx = feature_diff.inject(0){ |s, x| s + x**2 }
25
+
26
+ @data = []
27
+ for i in 0...(1 + image.size - feature.size)
28
+ region = image[i...(i + feature.size)]
29
+ region_mean = region.inject(0){|s,x| s + x } / feature.size.to_f
30
+ region_diff = region.map {|x| x - region_mean }
31
+ sy = region_diff.inject(0){ |s, x| s + x**2 }
32
+
33
+ if sx == 0 || sy == 0
34
+ if sx == 0 && sy == 0
35
+ @data.push 1.0
36
+ else
37
+ @data.push 0.0
38
+ end
39
+
40
+ next
41
+ end
42
+
43
+ denom = Math.sqrt(sx*sy)
44
+
45
+ sum = 0
46
+ feature.size.times do |j|
47
+ sum += (region_diff[j] * feature_diff[j])
48
+ end
49
+
50
+ r = sum / denom
51
+ @data.push(r)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -11,47 +11,65 @@ class Envelope
11
11
  def initialize samples
12
12
  # combine absolute values of positive maxima and negative minima
13
13
  extrema = Extrema.new(samples)
14
- points = {}
14
+ starting_outline = {}
15
15
  extrema.minima.each do |idx,val|
16
16
  if val <= 0.0
17
- points[idx] = val.abs
17
+ starting_outline[idx] =val.abs
18
18
  end
19
19
  end
20
20
 
21
21
  extrema.maxima.each do |idx,val|
22
22
  if val >= 0.0
23
- points[idx] = val.abs
23
+ starting_outline[idx] = val.abs
24
24
  end
25
25
  end
26
26
 
27
27
  # add in first and last samples so the envelope follows entire signal
28
- points[0] = samples[0].abs
29
- points[samples.count - 1] = samples[samples.count - 1].abs
28
+ starting_outline[0] = samples[0].abs
29
+ starting_outline[samples.count - 1] = samples[samples.count - 1].abs
30
30
 
31
- indices = points.keys.sort
32
- @data = Array.new(samples.count, 0)
31
+ # the extrema we have now are probably not spaced evenly. Upsampling at
32
+ # this point would lead to a time-distorted signal. So the next step is to
33
+ # interpolate between all the extrema to make a crude but properly sampled
34
+ # envelope.
35
+
36
+ proper_outline = Array.new(samples.count, 0)
37
+ indices = starting_outline.keys.sort
33
38
 
34
- # interpolate between all the extrema for the rest of the values. We
35
- # manually added first/last index so the whole signal should be covered
36
- # by this
37
39
  for i in 1...indices.count
38
40
  l_idx = indices[i-1]
39
41
  r_idx = indices[i]
40
42
 
41
- l_val = points[l_idx]
42
- r_val = points[r_idx]
43
+ l_val = starting_outline[l_idx]
44
+ r_val = starting_outline[r_idx]
43
45
 
44
- @data[l_idx] = l_val
45
- @data[r_idx] = r_val
46
+ proper_outline[l_idx] = l_val
47
+ proper_outline[r_idx] = r_val
46
48
 
47
49
  idx_span = r_idx - l_idx
48
50
 
49
51
  for j in (l_idx + 1)...(r_idx)
50
52
  x = (j - l_idx).to_f / idx_span
51
53
  y = Interpolation.linear l_val, r_val, x
52
- @data[j] = y
54
+ proper_outline[j] = y
53
55
  end
54
56
  end
57
+
58
+ # Now downsample by dropping samples, back to the number of starting_outline we had
59
+ # with just the extrema, but this time with samples properly spaced so as
60
+ # to avoid time distortion after upsampling.
61
+
62
+ downsample_factor = (samples.count / starting_outline.count).to_i
63
+ downsampled_outline = []
64
+
65
+ (0...proper_outline.count).step(downsample_factor) do |n|
66
+ downsampled_outline.push proper_outline[n]
67
+ end
68
+
69
+ # finally, use polynomial interpolation to upsample to the original sample rate.
70
+
71
+ upsample_factor = samples.count / downsampled_outline.count.to_f
72
+ @data = PolynomialResampling.upsample(downsampled_outline, upsample_factor)
55
73
  end
56
74
 
57
75
  end
@@ -37,22 +37,107 @@ class Signal
37
37
  def size
38
38
  @data.size
39
39
  end
40
+
41
+ # Size of the signal data.
42
+ def count
43
+ @data.size
44
+ end
45
+
46
+ # Signal duration in seconds.
47
+ def duration
48
+ return @data.size.to_f / @sample_rate
49
+ end
40
50
 
41
51
  # Access signal data.
42
52
  def [](arg)
43
53
  @data[arg]
44
54
  end
55
+
56
+ # Plot the signal data against sample numbers.
57
+ def plot_1d
58
+ plotter = Plotter.new(:title => "Signal: values vs. sample number", :xtitle => "sample number", :ytitle => "sample value")
59
+ plotter.plot_1d "signal data" => @data
60
+ end
61
+
62
+ # Plot the signal data against time.
63
+ def plot_2d
64
+ plotter = Plotter.new(:title => "Signal: values vs. time", :xtitle => "time (sec)", :ytitle => "sample value")
65
+
66
+ data_vs_time = {}
67
+ sp = 1.0 / @sample_rate
68
+ @data.each_index do |i|
69
+ data_vs_time[i * sp] = @data[i]
70
+ end
71
+
72
+ plotter.plot_2d "signal data" => data_vs_time
73
+ end
74
+
75
+ # Run a discrete lowpass filter over the signal data (using SincFilter).
76
+ # Modifies current object.
77
+ def lowpass! cutoff_freq, order
78
+ filter = SincFilter.new(:sample_rate => @sample_rate, :order => order, :cutoff_freq => cutoff_freq)
79
+ @data = filter.lowpass(@data)
80
+ return self
81
+ end
82
+
83
+ # Run a discrete lowpass filter over the signal data (using SincFilter).
84
+ # Return result in a new Signal object.
85
+ def lowpass cutoff_freq, order
86
+ self.clone.lowpass! cutoff_freq, order
87
+ end
88
+
89
+ # Run a discrete highpass filter over the signal data (using SincFilter).
90
+ # Modifies current object.
91
+ def highpass! cutoff_freq, order
92
+ filter = SincFilter.new(:sample_rate => @sample_rate, :order => order, :cutoff_freq => cutoff_freq)
93
+ @data = filter.highpass(@data)
94
+ return self
95
+ end
96
+
97
+ # Run a discrete highpass filter over the signal data (using SincFilter).
98
+ # Return result in a new Signal object.
99
+ def highpass cutoff_freq, order
100
+ self.clone.highpass! cutoff_freq, order
101
+ end
102
+
103
+ # Run a discrete bandpass filter over the signal data (using DualSincFilter).
104
+ # Modifies current object.
105
+ def bandpass! left_cutoff, right_cutoff, order
106
+ filter = DualSincFilter.new(
107
+ :sample_rate => @sample_rate,
108
+ :order => order,
109
+ :left_cutoff_freq => left_cutoff,
110
+ :right_cutoff_freq => right_cutoff
111
+ )
112
+ @data = filter.bandpass(@data)
113
+ return self
114
+ end
45
115
 
46
- # Plot the signal data, either against sample numbers or fraction of total samples.
47
- # @param plot_against_fraction If false, plot data against sample number. If true,
48
- # plot against fraction of total samples.
49
- def plot_data plot_against_fraction
50
- xtitle = (plot_against_fraction ? "fraction of total samples" : "sample numbers")
51
- plotter = Plotter.new(:title => "signal data sequence", :xtitle => xtitle, :ytitle => "sample values")
52
- titled_sequence = {"signal data" => @data}
53
- plotter.plot_1d titled_sequence, plot_against_fraction
116
+ # Run a discrete bandpass filter over the signal data (using DualSincFilter).
117
+ # Return result in a new Signal object.
118
+ def bandpass left_cutoff, right_cutoff, order
119
+ self.clone.bandpass! left_cutoff, right_cutoff, order
120
+ end
121
+
122
+ # Run a discrete bandstop filter over the signal data (using DualSincFilter).
123
+ # Modifies current object.
124
+ def bandstop! left_cutoff, right_cutoff, order
125
+ filter = DualSincFilter.new(
126
+ :sample_rate => @sample_rate,
127
+ :order => order,
128
+ :left_cutoff_freq => left_cutoff,
129
+ :right_cutoff_freq => right_cutoff
130
+ )
131
+ @data = filter.bandstop(@data)
132
+ return self
54
133
  end
55
134
 
135
+ # Run a discrete bandstop filter over the signal data (using DualSincFilter).
136
+ # Return result in a new Signal object.
137
+ def bandstop left_cutoff, right_cutoff, order
138
+ self.clone.bandstop! left_cutoff, right_cutoff, order
139
+ end
140
+
56
141
  # Increase the sample rate of signal data by the given factor using
57
142
  # discrete upsampling method. Modifies current object.
58
143
  # @param [Fixnum] upsample_factor Increase the sample rate by this factor.
@@ -181,21 +266,43 @@ class Signal
181
266
  Math.sqrt(energy / size)
182
267
  end
183
268
 
269
+ # Compute the mean of signal data.
270
+ def mean
271
+ sum = @data.inject(0){ |s, x| s + x }
272
+ return sum.to_f / size
273
+ end
274
+
184
275
  # Find extrema (maxima, minima) within signal data.
185
276
  def extrema
186
277
  return Extrema.new(@data)
187
278
  end
188
279
 
280
+ # Operate on the signal data (in place) with the absolute value function.
281
+ def abs!
282
+ @data = @data.map {|x| x.abs }
283
+ return self
284
+ end
285
+
286
+ # Operate on copy of the Signal object with the absolute value function.
287
+ def abs
288
+ self.clone.abs!
289
+ end
290
+
291
+ def normalize! level = 1.0
292
+ self.divide!(@data.max / level)
293
+ end
294
+
295
+ # reduce all samples to
296
+ def normalize level = 1.0
297
+ self.clone.normalize! level
298
+ end
299
+
189
300
  # Determine the envelope of the current Signal and return either a Envelope
190
301
  # or a new Signal object as a result.
191
302
  # @param [True/False] make_signal If true, return envelope data in a new
192
303
  # Otherwise, return an Envelope object.
193
- def envelope make_signal = false
194
- if make_signal
195
- return Signal.new(:sample_rate => @sample_rate, :data => Envelope.new(@data).data)
196
- else
197
- return Envelope.new(@data)
198
- end
304
+ def envelope
305
+ Signal.new(:sample_rate => @sample_rate, :data => Envelope.new(@data).data)
199
306
  end
200
307
 
201
308
  # Add data in array or other signal to the beginning of current data.
@@ -437,7 +544,26 @@ class Signal
437
544
  end
438
545
 
439
546
  return cross_correlation
440
- end
547
+ end
548
+
549
+ # Differentiates the signal data.
550
+ # @param [true/false] make_signal If true, return the result as a new
551
+ # Signal object. Otherwise, return result
552
+ # as an array.
553
+ def derivative
554
+ raise "Signal does not have at least 2 samples" unless @data.size > 2
555
+
556
+ derivative = Array.new(@data.size)
557
+ sample_period = 1.0 / @sample_rate
558
+
559
+ for i in 1...@data.count
560
+ derivative[i] = (@data[i] - @data[i-1]) / sample_period
561
+ end
562
+
563
+ derivative[0] = derivative[1]
564
+
565
+ return Signal.new(:sample_rate => @sample_rate, :data => derivative)
566
+ end
441
567
 
442
568
  # Removes all but the given range of frequencies from the signal, using
443
569
  # frequency domain filtering. Modifes and returns the current object.
@@ -25,11 +25,12 @@ class Plotter
25
25
  end
26
26
 
27
27
  # Plot XY datapoints.
28
- # @param [Hash] titled_hashes A hash that maps dataset titles to data. The data itself
29
- # is a hash also, that maps x values to y values.
28
+ # @param [Hash] titled_hashes A hash that maps title strings to 2d datasets.
29
+ # The dataset itself is a hash also, that maps
30
+ # x values to y values.
30
31
  #
31
32
  # @example
32
- # Plotter.plot_2d "somedata" => {0.0 => 4.0, 1.0 => 2.0}
33
+ # Plotter.new.plot_2d "somedata" => {0.0 => 4.0, 1.0 => 2.0}
33
34
  def plot_2d titled_hashes
34
35
  datasets = []
35
36
  titled_hashes.each do |title, hash|
@@ -51,24 +52,34 @@ class Plotter
51
52
  plot_datasets datasets
52
53
  end
53
54
 
55
+ # Plot XY datapoints.
56
+ # @param [Hash] titled_hashes A hash that maps title strings to 2d datasets.
57
+ # The dataset itself is a hash also, that maps
58
+ # x values to y values.
59
+ #
60
+ # @example
61
+ # Plotter.plot_2d "somedata" => {0.0 => 4.0, 1.0 => 2.0}
62
+ def self.plot_2d titled_hashes
63
+ return Plotter.new.plot_2d titled_hashes
64
+ end
65
+
54
66
  # Plot a sequence of values.
55
- # @param [Hash] titled_sequences A hash that maps sequence titles to data. The data itself
56
- # is an array of values. In the plot, values will be mapped to
57
- # their index in the sequence.
58
- # @param plot_against_fraction If true, instead of plotting samples against sample number, plot
59
- # them against the fraction (sample_number / total_samples).
67
+ # @param [Hash] titled_sequences A hash that maps title strings to data
68
+ # sequences. The data itself is an array of
69
+ # values. In the plot, values will be mapped to
70
+ # their index in the sequence.
60
71
  #
61
72
  # @example
62
- # Plotter.plot_1d "somedata" => [0,2,3,6,3,-1]
63
- def plot_1d titled_sequences, plot_against_fraction = false
73
+ # Plotter.new.plot_1d "somedata" => [0,2,3,6,3,-1]
74
+ def plot_1d titled_sequences
64
75
  datasets = []
65
76
  titled_sequences.each do |title, sequence|
66
77
  indices = Array.new(sequence.size)
67
78
  sequence.each_index do |i|
68
79
  indices[i] = i
69
- if plot_against_fraction
70
- indices[i] /= sequence.size.to_f
71
- end
80
+ #if plot_against_fraction
81
+ # indices[i] /= sequence.size.to_f
82
+ #end
72
83
  end
73
84
 
74
85
  dataset = Gnuplot::DataSet.new( [indices, sequence] ){ |ds|
@@ -82,6 +93,18 @@ class Plotter
82
93
  plot_datasets datasets
83
94
  end
84
95
 
96
+ # Plot a sequence of values.
97
+ # @param [Hash] titled_sequences A hash that maps title strings to data
98
+ # sequences. The data itself is an array of
99
+ # values. In the plot, values will be mapped to
100
+ # their index in the sequence.
101
+ #
102
+ # @example
103
+ # Plotter.plot_1d "somedata" => [0,2,3,6,3,-1]
104
+ def self.plot_1d titled_sequences
105
+ return Plotter.new.plot_1d titled_sequences
106
+ end
107
+
85
108
  # Plot Gnuplot::DataSet objects.
86
109
  # @param [Array] datasets An array of Gnuplot::DataSet objects.
87
110
  def plot_datasets datasets
@@ -98,7 +121,15 @@ class Plotter
98
121
  end
99
122
  end
100
123
  end
124
+
125
+ # Plot Gnuplot::DataSet objects.
126
+ # @param [Array] datasets An array of Gnuplot::DataSet objects.
127
+ def self.plot_datasets datasets
128
+ Plotter.new.plot_datasets datasets
129
+ end
101
130
 
131
+ # Plot data from Signal objects.
132
+ # @param [Hash] signals_hash A hash that maps title strings Signal objects
102
133
  def plot_signals signals_hash
103
134
  data_hash = {}
104
135
  signals_hash.each do |name, signal|
@@ -106,5 +137,11 @@ class Plotter
106
137
  end
107
138
  plot_1d data_hash
108
139
  end
140
+
141
+ # Plot data from Signal objects.
142
+ # @param [Hash] signals_hash A hash that maps title strings Signal objects
143
+ def self.plot_signals signals_hash
144
+ Plotter.new.plot_signals signals_hash
145
+ end
109
146
  end
110
147
  end
@@ -1,5 +1,5 @@
1
1
  # A library of signal processing methods and classes.
2
2
  module SPCore
3
3
  # spcore version
4
- VERSION = "0.1.9"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -0,0 +1,28 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe SPCore::Correlation do
4
+ context 'image => triangular window' do
5
+ before :all do
6
+ @size = size = 48
7
+ @triangle = TriangularWindow.new(size * 2).data
8
+ end
9
+
10
+ context 'feature => rising ramp (half size of triangular window)' do
11
+ it 'should have maximum correlation at beginning' do
12
+ rising_ramp = Array.new(@size){|i| i / @size.to_f }
13
+ correlation = Correlation.new(@triangle, rising_ramp)
14
+ correlation.data.first.should eq(correlation.data.max)
15
+ end
16
+ end
17
+
18
+ context 'feature => falling ramp (half size of triangular window)' do
19
+ it 'should have maximum correlation at end' do
20
+ falling_ramp = Array.new(@size){|i| (@size - i) / @size.to_f }
21
+ correlation = Correlation.new(@triangle, falling_ramp)
22
+ correlation.data.last.should eq(correlation.data.max)
23
+
24
+ #Plotter.plot_1d "correlate falling ramp" => correlation.data
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,22 +1,27 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
2
 
3
3
  describe SPCore::Envelope do
4
- it 'should produce an output that follows the amplitude of the input' do
4
+ before :all do
5
5
  sample_rate = 1000
6
- sample_count = 512
6
+ sample_count = 512 * 2
7
7
  generator = SignalGenerator.new(:size => sample_count, :sample_rate => sample_rate)
8
8
 
9
- modulation_signal = generator.make_signal [4.0]
10
- modulation_signal.multiply! BlackmanWindow.new(sample_count).data
9
+ @modulation_signal = generator.make_signal [4.0], :amplitude => 0.1
10
+ @modulation_signal.multiply! BlackmanWindow.new(sample_count).data
11
11
 
12
- base_signal = generator.make_signal [64.0]
13
- base_signal.multiply! modulation_signal
14
-
15
- envelope = base_signal.envelope(true)
16
-
12
+ @base_signal = generator.make_signal [64.0]
13
+ @base_signal.multiply! @modulation_signal
14
+ end
15
+
16
+ it 'should produce an output that follows the amplitude of the input' do
17
+ envelope = @base_signal.envelope
18
+ check_envelope(envelope)
19
+ end
20
+
21
+ def check_envelope envelope
17
22
  #signals = {
18
- # "signal" => base_signal,
19
- # "modulation" => modulation_signal,
23
+ # "signal" => @base_signal,
24
+ # "modulation (abs)" => @modulation_signal.abs,
20
25
  # "envelope" => envelope,
21
26
  #}
22
27
  #
@@ -26,15 +31,17 @@ describe SPCore::Envelope do
26
31
  # :ylabel => "values",
27
32
  #).plot_signals(signals)
28
33
 
34
+ #Plotter.new().plot_2d("envelop freq magnitudes" => envelope.freq_magnitudes)
35
+
29
36
  begin
30
- ideal = modulation_signal.energy
37
+ ideal = @modulation_signal.energy
31
38
  actual = envelope.energy
32
39
  error = (ideal - actual).abs / ideal
33
40
  error.should be_within(0.1).of(0.0)
34
41
  end
35
42
 
36
43
  begin
37
- ideal = modulation_signal.rms
44
+ ideal = @modulation_signal.rms
38
45
  actual = envelope.rms
39
46
  error = (ideal - actual).abs / ideal
40
47
  error.should be_within(0.1).of(0.0)
@@ -1,6 +1,74 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
2
 
3
3
  describe SPCore::Signal do
4
+ describe '#duration' do
5
+ it 'should produce duration in seconds, according to sample_rate' do
6
+ sample_rate = 2000
7
+ [5,50,100,1500].each do |count|
8
+ zeros = Array.new(count, 0)
9
+ signal = SPCore::Signal.new(:data => zeros, :sample_rate => sample_rate)
10
+ expected_duration = count.to_f / sample_rate
11
+ signal.duration.should eq expected_duration
12
+ end
13
+ end
14
+ end
15
+
16
+ describe '#derivative' do
17
+ before :all do
18
+ sample_rate = 200
19
+ sample_period = 1.0 / sample_rate
20
+ sample_count = sample_rate
21
+
22
+ range = -Math::PI..Math::PI
23
+ delta = (range.max - range.min) / sample_count
24
+
25
+ sin = []
26
+ cos = []
27
+
28
+ range.step(delta) do |x|
29
+ sin << Math::sin(x)
30
+ cos << (Math::PI * 2 * Math::cos(x))
31
+ end
32
+
33
+ @sin = SPCore::Signal.new(:sample_rate => sample_count, :data => sin)
34
+ @expected = SPCore::Signal.new(:sample_rate => sample_count, :data => cos)
35
+ @actual = @sin.derivative
36
+ end
37
+
38
+ it 'should produce a signal of same size' do
39
+ @actual.size.should eq @expected.size
40
+ end
41
+
42
+ it 'should produce a signal matching the 1st derivative' do
43
+ @actual.data.each_index do |i|
44
+ @actual[i].should be_within(0.1).of(@expected[i])
45
+ end
46
+ end
47
+ end
48
+
49
+ describe '#normalize' do
50
+ before :all do
51
+ generator = SignalGenerator.new(:sample_rate => 2000, :size => 256)
52
+ @signal = generator.make_signal([40.0, 160.0]).multiply(HannWindow.new(256).data)
53
+ @max = @signal.data.max
54
+ end
55
+
56
+ it 'should be able to normalize to a level lower than peak value' do
57
+ normalized = @signal.normalize(@max * 0.75)
58
+ normalized.data.max.should eq(@max * 0.75)
59
+ end
60
+
61
+ it 'should be able to normalize to a level higher than peak value' do
62
+ normalized = @signal.normalize(@max * 1.5)
63
+ normalized.data.max.should eq(@max * 1.5)
64
+ end
65
+
66
+ it 'should be able to normalize to a level equal to peak value' do
67
+ normalized = @signal.normalize(@max)
68
+ normalized.data.max.should eq(@max)
69
+ end
70
+ end
71
+
4
72
  describe '#remove_frequencies' do
5
73
  before :each do
6
74
  generator = SignalGenerator.new(:sample_rate => 2000, :size => 256)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spcore
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.9
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-05-06 00:00:00.000000000 Z
12
+ date: 2013-05-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: hashmake
@@ -144,6 +144,7 @@ files:
144
144
  - README.rdoc
145
145
  - Rakefile
146
146
  - lib/spcore.rb
147
+ - lib/spcore/analysis/correlation.rb
147
148
  - lib/spcore/analysis/envelope.rb
148
149
  - lib/spcore/analysis/extrema.rb
149
150
  - lib/spcore/analysis/signal.rb
@@ -190,6 +191,7 @@ files:
190
191
  - lib/spcore/windows/triangular_window.rb
191
192
  - lib/spcore/windows/tukey_window.rb
192
193
  - spcore.gemspec
194
+ - spec/analysis/correlation_spec.rb
193
195
  - spec/analysis/envelope_spec.rb
194
196
  - spec/analysis/extrema_spec.rb
195
197
  - spec/analysis/signal_spec.rb
@@ -228,7 +230,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
228
230
  version: '0'
229
231
  segments:
230
232
  - 0
231
- hash: -94862182169118755
233
+ hash: 688071795
232
234
  required_rubygems_version: !ruby/object:Gem::Requirement
233
235
  none: false
234
236
  requirements:
@@ -237,7 +239,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
237
239
  version: '0'
238
240
  segments:
239
241
  - 0
240
- hash: -94862182169118755
242
+ hash: 688071795
241
243
  requirements: []
242
244
  rubyforge_project:
243
245
  rubygems_version: 1.8.23
@@ -245,6 +247,7 @@ signing_key:
245
247
  specification_version: 3
246
248
  summary: A library of signal processing methods and classes.
247
249
  test_files:
250
+ - spec/analysis/correlation_spec.rb
248
251
  - spec/analysis/envelope_spec.rb
249
252
  - spec/analysis/extrema_spec.rb
250
253
  - spec/analysis/signal_spec.rb