edfize 0.1.0.pre → 0.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2067540b9f8f67b1f56d3b14932dd5941aaa8c5e
4
- data.tar.gz: 78bd7d36d221cd07a77a9ecefb9b6f67b68d0f1c
3
+ metadata.gz: f5f84db69e7bbe84842214eee8dd220ee6a89a72
4
+ data.tar.gz: 0f447049e2a23134804e007de36b70186295845a
5
5
  SHA512:
6
- metadata.gz: bf85cdd7723bb127c1dcbfaa2cf5707b6916a65c9729ea51857d38107bb1042f1655cdf0b5584cc1a951601fe9f2b87a0deadeba56c2e654f28f033da8a70090
7
- data.tar.gz: b120948717c189f774f6a6c58a6764d2d7d7df5a98aceca29cf3323acd70e509d97471b1519797ed507d142771d2e170d08716af8a62ed2635bba6773e7b1e94
6
+ metadata.gz: 1decca6f10873141a21d139ba6b76126e5b9750c4ee28f6225ad4c498a08aac977dfc977435f5a82d937829fef9bcb3d8fc1e97a80b4072771a7f34cf7dda77a
7
+ data.tar.gz: d0f21d699870bac05cb8c2694e26860ca35d31bf3999247ce4244351436f88866348c08cd489b3c2c162a54d4cf10d493fc97fdc4f071c22f09cd7ea102070e7
data/CHANGELOG.md CHANGED
@@ -1,8 +1,21 @@
1
- ## 0.1.0
1
+ ## 0.1.0 (May 28, 2014)
2
2
  - Initial EDF class to load headers and signals into Ruby objects
3
3
  - `edfize` command has the following actions:
4
- - `check`: Validates EDFs in the current directory for errors
5
- - `test`: Same as `check`
6
- - `run`: Prints out the headers of all edfs in the current directory
4
+ - `test`: Validates EDFs in the current directory and subdirectories for errors
5
+ - To only show failing tests, add the flag `--failing`
6
+ - `edfize test --failing`
7
+ - To suppress descriptive test failures, add the flag `--quiet`
8
+ - `edfize test --quiet`
9
+ - Both flags can be used together
10
+ - `edfize test --failing --quiet`
11
+ - `run`: Prints out the headers of all edfs in the current directory and subdirectories
7
12
  - `help`: Displays information about `edfize` along with all available commands
8
13
  - `version`: Displays the current `edfize` version
14
+ - `edfize test` checks for the following:
15
+ - Expected Length Check: The expected total size is computed from the (`number
16
+ of data records` * `total samples across all signals`) + `size of header` and
17
+ this is compared to the actual file size.
18
+ - Reserved Area Checks: The header and the individual header reserved areas are
19
+ checked to validate that they are blank. Non-blank areas are a sign that the
20
+ edf header is corrupt and that data from the signal data block have leaked into
21
+ the header itself.
data/README.md CHANGED
@@ -8,33 +8,35 @@ Ruby gem used to load, validate, and parse European Data Format files. Used for
8
8
 
9
9
  ## Installation
10
10
 
11
- Add this line to your application's Gemfile:
11
+ Use `gem install edfize` to update Edfize to the latest stable
12
12
 
13
- gem 'edfize'
13
+ Use `gem install edfize --pre` to update Edfize to the latest prerelease
14
14
 
15
- And then execute:
15
+ ## Usage
16
16
 
17
- $ bundle
17
+ ### Validate EDFs
18
18
 
19
- Or install it yourself as:
19
+ Use `edfize test` to test that EDFs stored in the current directory have a valid format.
20
20
 
21
- $ gem install edfize
21
+ cd <edf-directory>
22
+ edfize test
22
23
 
23
- ## Usage
24
+ A list of validations performed is:
24
25
 
25
- ### Validate EDFs
26
+ - **Expected Length Check**: Compares the calculated size of the file based on signal sizes defined in the header with the actual file size. A failure may indicate corruption in the header (if the expected is less than the actual file size), or a partial/truncated file (if the expected is more than the actual file size).
27
+ - **Reserved Area Checks**: Check that reserved areas are blank. Non-blank reserved areas can indicate a sign of header or EDF file corruption.
26
28
 
27
- Use `edfize check` to check that EDFs stored in the current directory have a valid format.
29
+ Flags that can be added to the `test` command include:
28
30
 
29
- cd <edf-directory>
30
- edfize check
31
+ - `--failing`: Only display EDFs with failing tests
32
+ - `--quiet`: Suppress detailed failure descriptions that show the expected versus the actual result of the test
31
33
 
32
34
  ### Print Signal Header information
33
35
 
34
36
  Use `edfize run` to print out signal header information for each EDF in the current directory.
35
37
 
36
38
  cd <edf-directory>
37
- edfize check
39
+ edfize run
38
40
 
39
41
  ### View A List of All Available Commands for Edfize
40
42
 
@@ -48,11 +50,118 @@ Use `edfize version` to check the version of Edfize.
48
50
 
49
51
  edfize version
50
52
 
51
- ### Upgrade Edfize
52
-
53
- Use `gem install edfize` to update Edfize to the latest stable
54
-
55
- Use `gem install edfize --pre` to update Edfize to the latest prerelease
53
+ ### Example of how to Load and Analyze EDFs in a Ruby Script
54
+
55
+ The following Ruby file demonstrates how to make use of the Edfize gem to load EDF signals into arrays for analysis.
56
+
57
+ `tutorial_01_load_edf_and_signals.rb`
58
+ ```ruby
59
+ # Tutorial 01 - Load EDF and Signals
60
+ #
61
+ # gem install edfize
62
+ #
63
+ # ruby tutorial_01_load_edf_and_signals.rb
64
+ #
65
+ # The EDF exists at:
66
+ #
67
+ # https://sleepdata.org/datasets/shhs/files/edfs/shhs1?f=shhs1-200001.edf
68
+ #
69
+
70
+ require 'rubygems'
71
+ require 'edfize'
72
+
73
+ # Loads the file and reads the EDF Header
74
+ edf = Edfize::Edf.new('shhs1-200001.edf')
75
+
76
+ # Loads the data section of the EDF into Signal objects
77
+ edf.load_signals
78
+
79
+ # Print out information on the signals
80
+ puts "EDF #{edf.filename} contains the following #{edf.signals.count} signal#{'s' unless edf.signals.count == 1}:\n\n"
81
+
82
+ edf.signals.each do |signal|
83
+ puts "Signal"
84
+ puts " Label : #{signal.label}"
85
+ puts " Samples Per Data Record : #{signal.samples_per_data_record}"
86
+ puts " First 10 Physical Values : #{(signal.physical_values[0..10] + ['...']).inspect}\n\n"
87
+ end
88
+ ```
89
+
90
+ When run, the code above will output the following:
91
+
92
+ ```console
93
+ EDF shhs1-200001.edf contains the following 14 signals:
94
+
95
+ Signal
96
+ Label : SaO2
97
+ Samples Per Data Record : 1
98
+ First 10 Physical Values : [95.31242847333486, 95.31242847333486, 95.31242847333486, 95.31242847333486, 95.31242847333486, 95.31242847333486, 95.31242847333486, 95.31242847333486, 94.14053559166858, 94.14053559166858, 94.14053559166858, "..."]
99
+
100
+ Signal
101
+ Label : H.R.
102
+ Samples Per Data Record : 1
103
+ First 10 Physical Values : [77.34416723887999, 77.34416723887999, 77.34416723887999, 76.56595712214848, 76.56595712214848, 76.56595712214848, 75.00190737773708, 75.00190737773708, 75.00190737773708, 75.00190737773708, 75.00190737773708, "..."]
104
+
105
+ Signal
106
+ Label : EEG(sec)
107
+ Samples Per Data Record : 125
108
+ First 10 Physical Values : [-4.411764705882348, 5.392156862745111, 2.4509803921568647, 0.49019607843136725, -0.49019607843136725, -10.294117647058826, 3.4313725490196134, 12.25490196078431, -1.470588235294116, -2.4509803921568647, -8.333333333333329, "..."]
109
+
110
+ Signal
111
+ Label : ECG
112
+ Samples Per Data Record : 125
113
+ First 10 Physical Values : [0.03431372549019618, 0.03431372549019618, 0.03431372549019618, 0.03431372549019618, 0.044117647058823595, 0.044117647058823595, 0.044117647058823595, 0.044117647058823595, 0.044117647058823595, 0.03431372549019618, 0.03431372549019618, "..."]
114
+
115
+ Signal
116
+ Label : EMG
117
+ Samples Per Data Record : 125
118
+ First 10 Physical Values : [12.622549019607845, 3.7990196078431353, -3.5539215686274517, -2.5735294117647065, 8.455882352941174, 1.5931372549019613, 9.436274509803923, -8.700980392156861, -2.5735294117647065, 13.112745098039213, -12.867647058823529, "..."]
119
+
120
+ Signal
121
+ Label : EOG(L)
122
+ Samples Per Data Record : 50
123
+ First 10 Physical Values : [28.921568627450966, 17.15686274509804, 25.0, 19.117647058823536, -5.392156862745097, -9.313725490196077, -0.49019607843136725, -1.470588235294116, 1.470588235294116, -1.470588235294116, 0.49019607843136725, "..."]
124
+
125
+ Signal
126
+ Label : EOG(R)
127
+ Samples Per Data Record : 50
128
+ First 10 Physical Values : [12.25490196078431, 1.470588235294116, 10.294117647058812, 5.392156862745111, 17.15686274509804, 18.137254901960773, 25.980392156862735, 32.84313725490196, 25.0, 26.960784313725497, 22.058823529411768, "..."]
129
+
130
+ Signal
131
+ Label : EEG
132
+ Samples Per Data Record : 125
133
+ First 10 Physical Values : [-2.4509803921568647, 1.470588235294116, -9.313725490196077, -6.372549019607845, -0.49019607843136725, -10.294117647058826, -12.25490196078431, -12.25490196078431, -7.352941176470594, 1.470588235294116, 6.372549019607845, "..."]
134
+
135
+ Signal
136
+ Label : THOR RES
137
+ Samples Per Data Record : 10
138
+ First 10 Physical Values : [0.207843137254902, 0.207843137254902, 0.15294117647058825, 0.0980392156862745, 0.03529411764705881, -0.0117647058823529, -0.050980392156862786, -0.08235294117647052, -0.10588235294117654, -0.1215686274509804, -0.13725490196078427, "..."]
139
+
140
+ Signal
141
+ Label : ABDO RES
142
+ Samples Per Data Record : 10
143
+ First 10 Physical Values : [0.30980392156862746, 0.24705882352941178, 0.16078431372549018, 0.06666666666666665, -0.0039215686274509665, -0.08235294117647052, -0.1607843137254903, -0.2078431372549019, -0.2313725490196079, -0.2549019607843137, -0.2705882352941176, "..."]
144
+
145
+ Signal
146
+ Label : POSITION
147
+ Samples Per Data Record : 1
148
+ First 10 Physical Values : [2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, "..."]
149
+
150
+ Signal
151
+ Label : LIGHT
152
+ Samples Per Data Record : 1
153
+ First 10 Physical Values : [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, "..."]
154
+
155
+ Signal
156
+ Label : NEW AIR
157
+ Samples Per Data Record : 10
158
+ First 10 Physical Values : [6.372549019607845, 6.372549019607845, 5.392156862745111, 3.4313725490196134, 7.35294117647058, 6.372549019607845, 8.333333333333343, 9.313725490196077, 6.372549019607845, 6.372549019607845, 7.35294117647058, "..."]
159
+
160
+ Signal
161
+ Label : OX stat
162
+ Samples Per Data Record : 1
163
+ First 10 Physical Values : [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2.0, "..."]
164
+ ```
56
165
 
57
166
  ## Contributing
58
167
 
data/edfize.gemspec CHANGED
@@ -26,6 +26,8 @@ Gem::Specification.new do |spec|
26
26
  spec.test_files = spec.files.grep(%r{^(test)/})
27
27
  spec.require_paths = ["lib"]
28
28
 
29
+ spec.add_dependency "colorize", "~> 0.7.2"
30
+
29
31
  spec.add_development_dependency "bundler", "~> 1.6"
30
32
  spec.add_development_dependency "rake"
31
33
  end
data/lib/edfize/edf.rb CHANGED
@@ -2,196 +2,226 @@ require 'edfize/signal'
2
2
 
3
3
  module Edfize
4
4
  class Edf
5
- attr_reader :signals
6
-
7
- HEADER_OFFSET = 256
5
+ # EDF File Path
6
+ attr_reader :filename
7
+
8
+ # Header Information
9
+ attr_accessor :version
10
+ attr_accessor :local_patient_identification
11
+ attr_accessor :local_recording_identification
12
+ attr_accessor :start_date_of_recording
13
+ attr_accessor :start_time_of_recording
14
+ attr_accessor :number_of_bytes_in_header
15
+ attr_accessor :reserved
16
+ attr_accessor :number_of_data_records
17
+ attr_accessor :duration_of_a_data_record
18
+ attr_accessor :number_of_signals
19
+
20
+ attr_accessor :signals
21
+
22
+ HEADER_CONFIG = {
23
+ version: { size: 8, after_read: :to_i, name: 'Version' },
24
+ local_patient_identification: { size: 80, after_read: :strip, name: 'Local Patient Identification' },
25
+ local_recording_identification: { size: 80, after_read: :strip, name: 'Local Recording Identification' },
26
+ start_date_of_recording: { size: 8, name: 'Start Date of Recording', description: '(dd.mm.yy)' },
27
+ start_time_of_recording: { size: 8, name: 'Start Time of Recording', description: '(hh.mm.ss)'},
28
+ number_of_bytes_in_header: { size: 8, after_read: :to_i, name: 'Number of Bytes in Header' },
29
+ reserved: { size: 44, name: 'Reserved' },
30
+ number_of_data_records: { size: 8, after_read: :to_i, name: 'Number of Data Records' },
31
+ duration_of_a_data_record: { size: 8, after_read: :to_i, name: 'Duration of a Data Record', units: 'second' },
32
+ number_of_signals: { size: 4, after_read: :to_i, name: 'Number of Signals' }
33
+ }
34
+
35
+ HEADER_OFFSET = HEADER_CONFIG.collect{|k,h| h[:size]}.inject(:+)
36
+
37
+ SIZE_OF_SAMPLE_IN_BYTES = 2
38
+
39
+ # Used by tests
40
+ RESERVED_SIZE = HEADER_CONFIG[:reserved][:size]
41
+
42
+ def self.create(filename, &block)
43
+ edf = self.new(filename)
44
+ yield edf if block_given?
45
+ edf
46
+ end
8
47
 
9
48
  def initialize(filename)
10
49
  @filename = filename
11
50
  @signals = []
12
- signal_labels
13
- transducer_types
14
- physical_dimensions
15
- physical_minimums
16
- physical_maximums
17
- digital_minimums
18
- digital_maximums
19
- prefilterings
20
- samples_in_data_records
21
- reserved_areas
22
- end
23
-
24
- def size
25
- File.size(@filename)
51
+
52
+ read_header
53
+ read_signal_header
54
+ self
26
55
  end
27
56
 
28
- def print_header
29
- puts @filename
30
- puts "#{size} bytes (Total File Size)"
31
- puts "'#{header_version}' (0)"
32
- puts "'#{header_local_patient_identification}' (local patient identification)"
33
- puts "'#{header_local_recording_identification}' (local recording indentification)"
34
- puts "'#{header_start_date_of_recording}' (dd.mm.yy start date of recording)"
35
- puts "'#{header_start_time_of_recording}' (hh.mm.ss start time of recording)"
36
- # puts "--- RESERVED"
37
- puts "'#{number_of_data_records}' seconds (number of data records, -1 if unknown)"
38
- puts "'#{duration_of_a_data_record}' seconds (duration of a data record)"
39
- puts "'#{number_of_signals}' number of signals (ns) in data record"
40
- signals.each_with_index do |signal, index|
41
- puts "'#{signal.label}' (signal[#{index+1}] label)"
42
- puts "'#{signal.physical_dimension}' (signal[#{index+1}] physical_dimension)"
43
- puts "'#{signal.transducer_type}' (signal[#{index+1}] transducer_type)"
44
- puts "'#{signal.physical_minimum}' (signal[#{index+1}] physical_minimum)"
45
- puts "'#{signal.physical_maximum}' (signal[#{index+1}] physical_maximum)"
46
- puts "'#{signal.digital_minimum}' (signal[#{index+1}] digital_minimum)"
47
- puts "'#{signal.digital_maximum}' (signal[#{index+1}] digital_maximum)"
48
- puts "'#{signal.prefiltering}' (signal[#{index+1}] prefiltering)"
49
- puts "'#{signal.samples_in_data_record}' (signal[#{index+1}] samples_in_data_record)"
50
- puts "'#{signal.reserved_area}' (signal[#{index+1}] reserved_area)"
51
- end
57
+ def load_signals
58
+ get_data_records
52
59
  end
53
60
 
54
- def header_version
55
- IO.binread(@filename, 8)
61
+ def size_of_header
62
+ HEADER_OFFSET + ns * Signal::SIGNAL_CONFIG.collect{|k,h| h[:size]}.inject(:+)
56
63
  end
57
64
 
58
- # 80 ascii : local patient identification (mind item 3 of the additional EDF+ specs)
59
- def header_local_patient_identification
60
- IO.binread(@filename, 80, 8)
65
+ def expected_size_of_header
66
+ @number_of_bytes_in_header
61
67
  end
62
68
 
63
- # 80 ascii : local recording identification (mind item 4 of the additional EDF+ specs)
64
- def header_local_recording_identification
65
- IO.binread(@filename, 80, 88)
69
+ # Total File Size In Bytes
70
+ def edf_size
71
+ File.size(@filename)
66
72
  end
67
73
 
68
- # 8 ascii : startdate of recording (dd.mm.yy) (mind item 2 of the additional EDF+ specs)
69
- def header_start_date_of_recording
70
- IO.binread(@filename, 8, 168)
74
+ # Data Section Size In Bytes
75
+ def expected_data_size
76
+ @signals.collect(&:samples_per_data_record).inject(:+).to_i * @number_of_data_records * SIZE_OF_SAMPLE_IN_BYTES
71
77
  end
72
78
 
73
- # 8 ascii : starttime of recording (hh.mm.ss)
74
- def header_start_time_of_recording
75
- IO.binread(@filename, 8, 176)
79
+ def expected_edf_size
80
+ expected_data_size + size_of_header
76
81
  end
77
82
 
78
- # 8 ascii : number of bytes in header record
79
- def number_of_bytes_in_header
80
- IO.binread(@filename, 8, 184)
83
+ def section_value_to_string(section)
84
+ self.instance_variable_get("@#{section}").to_s
81
85
  end
82
86
 
83
- # 44 ascii : reserved
84
- # def reserved
85
- # IO.binread(@filename, 44, 192)
86
- # end
87
+ def section_units(section)
88
+ units = HEADER_CONFIG[section][:units].to_s
89
+ result = if units == ''
90
+ ''
91
+ else
92
+ " #{units}" + (self.instance_variable_get("@#{section}") == 1 ? '' : 's')
93
+ end
94
+ result
95
+ end
87
96
 
88
- # 8 ascii : number of data records (-1 if unknown, obey item 10 of the additional EDF+ specs)
89
- def number_of_data_records
90
- IO.binread(@filename, 8, 236)
97
+ def section_description(section)
98
+ description = HEADER_CONFIG[section][:description].to_s
99
+ result = if description == ''
100
+ ''
101
+ else
102
+ " #{description}"
103
+ end
104
+ result
91
105
  end
92
106
 
93
- # 8 ascii : duration of a data record, in seconds
94
- def duration_of_a_data_record
95
- IO.binread(@filename, 8, 244)
107
+ def print_header
108
+ puts "\nEDF : #{@filename}"
109
+ puts "Total File Size : #{edf_size} bytes"
110
+ puts "\nHeader Information"
111
+ HEADER_CONFIG.each do |section, hash|
112
+ puts "#{hash[:name]}#{' '*(31 - hash[:name].size)}: " + section_value_to_string(section) + section_units(section) + section_description(section)
113
+ end
114
+ puts "\nSignal Information"
115
+ signals.each_with_index do |signal, index|
116
+ puts "\n Position : #{index + 1}"
117
+ signal.print_header
118
+ end
119
+ puts "\nGeneral Information"
120
+ puts "Size of Header (bytes) : #{size_of_header}"
121
+ puts "Size of Data (bytes) : #{data_size}"
122
+ puts "Total Size (bytes) : #{edf_size}"
123
+
124
+ puts "Expected Size of Header (bytes): #{expected_size_of_header}"
125
+ puts "Expected Size of Data (bytes): #{expected_data_size}"
126
+ puts "Expected Total Size (bytes): #{expected_edf_size}"
96
127
  end
97
128
 
98
- # 4 ascii : number of signals (ns) in data record
99
- def number_of_signals
100
- IO.binread(@filename, 4, 252)
129
+ protected
130
+
131
+ def read_header
132
+ HEADER_CONFIG.keys.each do |section|
133
+ read_header_section(section)
134
+ end
101
135
  end
102
136
 
103
- def ns
104
- Integer(self.number_of_signals) rescue 0
137
+ def read_header_section(section)
138
+ result = IO.binread(@filename, HEADER_CONFIG[section][:size], compute_offset(section) )
139
+ result = result.to_s.send(HEADER_CONFIG[section][:after_read]) unless HEADER_CONFIG[section][:after_read].to_s == ''
140
+ self.instance_variable_set("@#{section}", result)
105
141
  end
106
142
 
107
- # ns * 16 ascii : ns * label (e.g. EEG Fpz-Cz or Body temp) (mind item 9 of the additional EDF+ specs)
108
- def signal_labels
109
- offset = HEADER_OFFSET
110
- (0..ns-1).to_a.each do |signal_number|
111
- @signals[signal_number] ||= Signal.new()
112
- @signals[signal_number].label = IO.binread(@filename, 16, offset+(signal_number*16))
143
+ def compute_offset(section)
144
+ offset = 0
145
+ HEADER_CONFIG.each do |key, hash|
146
+ break if key == section
147
+ offset += hash[:size]
113
148
  end
149
+ offset
114
150
  end
115
151
 
116
- # ns * 80 ascii : ns * transducer type (e.g. AgAgCl electrode)
117
- def transducer_types
118
- offset = HEADER_OFFSET + ns * 16
119
- (0..ns-1).to_a.each do |signal_number|
120
- @signals[signal_number] ||= Signal.new()
121
- @signals[signal_number].transducer_type = IO.binread(@filename, 80, offset+(signal_number*80))
122
- end
152
+ def ns
153
+ @number_of_signals
123
154
  end
124
155
 
125
- # ns * 8 ascii : ns * physical dimension (e.g. uV or degreeC)
126
- def physical_dimensions
127
- offset = HEADER_OFFSET + ns * (16 + 80)
156
+ def create_signals
128
157
  (0..ns-1).to_a.each do |signal_number|
129
158
  @signals[signal_number] ||= Signal.new()
130
- @signals[signal_number].physical_dimension = IO.binread(@filename, 8, offset+(signal_number*8))
131
159
  end
132
160
  end
133
161
 
134
- # ns * 8 ascii : ns * physical minimum (e.g. -500 or 34)
135
- def physical_minimums
136
- offset = HEADER_OFFSET + ns * (16 + 80 + 8)
137
- (0..ns-1).to_a.each do |signal_number|
138
- @signals[signal_number] ||= Signal.new()
139
- @signals[signal_number].physical_minimum = IO.binread(@filename, 8, offset+(signal_number*8))
162
+ def read_signal_header
163
+ create_signals
164
+ Signal::SIGNAL_CONFIG.keys.each do |section|
165
+ read_signal_header_section(section)
140
166
  end
141
167
  end
142
168
 
143
- # ns * 8 ascii : ns * physical maximum (e.g. 500 or 40)
144
- def physical_maximums
145
- offset = HEADER_OFFSET + ns * (16 + 80 + 8 + 8)
146
- (0..ns-1).to_a.each do |signal_number|
147
- @signals[signal_number] ||= Signal.new()
148
- @signals[signal_number].physical_maximum = IO.binread(@filename, 8, offset+(signal_number*8))
169
+ def compute_signal_offset(section)
170
+ offset = 0
171
+ Signal::SIGNAL_CONFIG.each do |key, hash|
172
+ break if key == section
173
+ offset += hash[:size]
149
174
  end
175
+ offset
150
176
  end
151
177
 
152
- # ns * 8 ascii : ns * digital minimum (e.g. -2048)
153
- def digital_minimums
154
- offset = HEADER_OFFSET + ns * (16 + 80 + 8 + 8 + 8)
178
+ def read_signal_header_section(section)
179
+ offset = HEADER_OFFSET + ns * compute_signal_offset(section)
155
180
  (0..ns-1).to_a.each do |signal_number|
156
- @signals[signal_number] ||= Signal.new()
157
- @signals[signal_number].digital_minimum = IO.binread(@filename, 8, offset+(signal_number*8))
181
+ section_size = Signal::SIGNAL_CONFIG[section][:size]
182
+ result = IO.binread(@filename, section_size, offset+(signal_number*section_size))
183
+ result = result.to_s.send(Signal::SIGNAL_CONFIG[section][:after_read]) unless Signal::SIGNAL_CONFIG[section][:after_read].to_s == ''
184
+ @signals[signal_number].send("#{section}=", result)
158
185
  end
159
186
  end
160
187
 
161
- # ns * 8 ascii : ns * digital maximum (e.g. 2047)
162
- def digital_maximums
163
- offset = HEADER_OFFSET + ns * (16 + 80 + 8 + 8 + 8 + 8)
164
- (0..ns-1).to_a.each do |signal_number|
165
- @signals[signal_number] ||= Signal.new()
166
- @signals[signal_number].digital_maximum = IO.binread(@filename, 8, offset+(signal_number*8))
167
- end
188
+ def get_data_records
189
+ load_digital_signals()
190
+ calculate_physical_values!()
168
191
  end
169
192
 
170
- # ns * 80 ascii : ns * prefiltering (e.g. HP:0.1Hz LP:75Hz)
171
- def prefilterings
172
- offset = HEADER_OFFSET + ns * (16 + 80 + 8 + 8 + 8 + 8 + 8)
173
- (0..ns-1).to_a.each do |signal_number|
174
- @signals[signal_number] ||= Signal.new()
175
- @signals[signal_number].prefiltering = IO.binread(@filename, 80, offset+(signal_number*80))
193
+ # 16-bit signed integer size = 2 Bytes = 2 ASCII characters
194
+ # 16-bit signed integer in "Little Endian" format (least significant byte first)
195
+ # unpack: s< 16-bit signed, (little-endian) byte order
196
+ def load_digital_signals
197
+ @all_signal_data = IO.binread(@filename, nil, size_of_header).unpack('s<*')
198
+
199
+ all_samples_per_data_record = @signals.collect{|s| s.samples_per_data_record}
200
+ total_samples_per_data_record = all_samples_per_data_record.inject(:+).to_i
201
+
202
+ offset = 0
203
+ offsets = []
204
+ all_samples_per_data_record.each do |samples_per_data_record|
205
+ offsets << offset
206
+ offset += samples_per_data_record
176
207
  end
177
- end
178
208
 
179
- # ns * 8 ascii : ns * nr of samples in each data record
180
- def samples_in_data_records
181
- offset = HEADER_OFFSET + ns * (16 + 80 + 8 + 8 + 8 + 8 + 8 + 80)
182
- (0..ns-1).to_a.each do |signal_number|
183
- @signals[signal_number] ||= Signal.new()
184
- @signals[signal_number].samples_in_data_record = IO.binread(@filename, 8, offset+(signal_number*8))
209
+ (0..@number_of_data_records-1).to_a.each do |data_record_index|
210
+ @signals.each_with_index do |signal, signal_index|
211
+ read_start = data_record_index * total_samples_per_data_record + offsets[signal_index]
212
+ (0..signal.samples_per_data_record - 1).to_a.each do |value_index|
213
+ signal.digital_values << @all_signal_data[read_start+value_index]
214
+ end
215
+ end
185
216
  end
186
217
  end
187
218
 
188
- # ns * 32 ascii : ns * reserved
189
- def reserved_areas
190
- offset = HEADER_OFFSET + ns * (16 + 80 + 8 + 8 + 8 + 8 + 8 + 80 + 8)
191
- (0..ns-1).to_a.each do |signal_number|
192
- @signals[signal_number] ||= Signal.new()
193
- @signals[signal_number].reserved_area = IO.binread(@filename, 32, offset+(signal_number*32))
194
- end
219
+ def calculate_physical_values!
220
+ @signals.each{|signal| signal.calculate_physical_values!}
221
+ end
222
+
223
+ def data_size
224
+ IO.binread(@filename, nil, size_of_header).size
195
225
  end
196
226
  end
197
227
  end
data/lib/edfize/signal.rb CHANGED
@@ -1,19 +1,49 @@
1
1
  module Edfize
2
2
  class Signal
3
- attr_accessor :label, :transducer_type, :physical_dimension, :physical_minimum, :physical_maximum,
4
- :digital_minimum, :digital_maximum, :prefiltering, :samples_in_data_record, :reserved_area
3
+ attr_accessor :label, :transducer_type, :physical_dimension,
4
+ :physical_minimum, :physical_maximum,
5
+ :digital_minimum, :digital_maximum,
6
+ :prefiltering, :samples_per_data_record,
7
+ :reserved_area, :digital_values, :physical_values
8
+
9
+ SIGNAL_CONFIG = {
10
+ label: { size: 16, after_read: :strip, name: 'Label' },
11
+ transducer_type: { size: 80, after_read: :strip, name: 'Transducer Type' },
12
+ physical_dimension: { size: 8, after_read: :strip, name: 'Physical Dimension' },
13
+ physical_minimum: { size: 8, after_read: :to_f, name: 'Physical Minimum' },
14
+ physical_maximum: { size: 8, after_read: :to_f, name: 'Physical Maximum' },
15
+ digital_minimum: { size: 8, after_read: :to_i, name: 'Digital Minimum' },
16
+ digital_maximum: { size: 8, after_read: :to_i, name: 'Digital Maximum' },
17
+ prefiltering: { size: 80, after_read: :strip, name: 'Prefiltering' },
18
+ samples_per_data_record: { size: 8, after_read: :to_i, name: 'Samples Per Data Record' },
19
+ reserved_area: { size: 32, name: 'Reserved Area' }
20
+ }
5
21
 
6
22
  def initialize
7
- @label = ''
8
- @physical_dimension = ''
9
- @transducer_type = ''
10
- @physical_minimum = ''
11
- @physical_maximum = ''
12
- @digital_minimum = ''
13
- @digital_maximum = ''
14
- @prefiltering = ''
15
- @samples_in_data_record = ''
16
- @reserved_area = ''
23
+ @digital_values = []
24
+ @physical_values = []
25
+ self
26
+ end
27
+
28
+ def self.create(&block)
29
+ signal = self.new
30
+ yield signal if block_given?
31
+ signal
32
+ end
33
+
34
+ def print_header
35
+ SIGNAL_CONFIG.each do |section, hash|
36
+ puts " #{hash[:name]}#{' '*(29 - hash[:name].size)}: " + self.send(section).to_s
37
+ end
38
+ end
39
+
40
+ # Physical value (dimension PhysiDim) = (ASCIIvalue-DigiMin)*(PhysiMax-PhysiMin)/(DigiMax-DigiMin) + PhysiMin.
41
+ def calculate_physical_values!
42
+ @physical_values = @digital_values.collect{|sample| ( sample - @digital_minimum ) * ( @physical_maximum - @physical_minimum ) / ( @digital_maximum - @digital_minimum) + @physical_minimum }
43
+ end
44
+
45
+ def samples
46
+ @physical_values
17
47
  end
18
48
 
19
49
  end
@@ -0,0 +1,16 @@
1
+ module Edfize
2
+ module Tests
3
+ module CheckLength
4
+ # This test checks that the length calculated from the EDF header matches
5
+ # the total length of the file
6
+ def test_expected_length(runner)
7
+ result = Result.new
8
+ result.passes = (runner.edf.expected_edf_size == runner.edf.edf_size)
9
+ result.pass_fail = " #{result.passes ? 'PASS' : 'FAIL'}".colorize( result.passes ? :green : :red ) + " Expected File Size"
10
+ result.expected = " Expected : #{runner.edf.expected_edf_size} bytes"
11
+ result.actual = " Actual : #{runner.edf.edf_size} bytes"
12
+ result
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ module Edfize
2
+ module Tests
3
+ module CheckReservedArea
4
+ # This test checks that the reserved area in the header is blank
5
+ def test_reserved_area_blank(runner)
6
+ result = Result.new
7
+ result.passes = (runner.edf.reserved == ' ' * Edf::RESERVED_SIZE)
8
+ result.pass_fail = " #{result.passes ? 'PASS' : 'FAIL'}".colorize( result.passes ? :green : :red ) + " Reserved Area Blank"
9
+ result.expected = " Expected : #{(' ' * Edf::RESERVED_SIZE).inspect}"
10
+ result.actual = " Actual : #{runner.edf.reserved.to_s.inspect}"
11
+ result
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ module Edfize
2
+ module Tests
3
+ module CheckReservedSignalAreas
4
+ # This test checks that the reserved areas in the signal headers are blank
5
+ def test_reserved_signal_areas_blank(runner)
6
+ reserved_areas = runner.edf.signals.collect(&:reserved_area)
7
+
8
+ result = Result.new
9
+ result.passes = (reserved_areas.reject{|r| r.to_s.strip == ''}.count == 0)
10
+ result.pass_fail = " #{result.passes ? 'PASS' : 'FAIL'}".colorize( result.passes ? :green : :red ) + " Signal Reserved Area Blank"
11
+ result.expected = " Expected : #{[''] * runner.edf.signals.count}"
12
+ result.actual = " Actual : #{reserved_areas}"
13
+ result
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,7 @@
1
+ module Edfize
2
+ module Tests
3
+ class Result
4
+ attr_accessor :passes, :pass_fail, :expected, :actual
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,43 @@
1
+ module Edfize
2
+ module Tests
3
+ class Runner
4
+ attr_reader :tests_run, :tests_failed, :edf, :verbose, :show_passing
5
+
6
+ TESTS = %w( expected_length reserved_area_blank reserved_signal_areas_blank )
7
+
8
+ def initialize(edf, argv)
9
+ @tests_run = 0
10
+ @tests_failed = 0
11
+ @edf = edf
12
+ @verbose = argv.include?('--quiet') ? false : true
13
+ @show_passing = argv.include?('--failing') ? false : true
14
+ end
15
+
16
+ def run_tests
17
+ results = []
18
+
19
+ TESTS.each do |test_name|
20
+ result = Edfize::Tests.send("test_#{test_name}", self)
21
+ @tests_failed += 1 unless result.passes
22
+ @tests_run += 1
23
+ results << result
24
+ end
25
+
26
+ puts "\n#{@edf.filename}" if results.reject{|r| r.passes}.count > 0 or @show_passing
27
+ results.each do |result|
28
+ print_result(result)
29
+ end
30
+ end
31
+
32
+ def print_result(result)
33
+ if self.show_passing or !result.passes
34
+ puts result.pass_fail
35
+ unless result.passes or not self.verbose
36
+ puts result.expected
37
+ puts result.actual
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,13 @@
1
+ require 'edfize/tests/result'
2
+ require 'edfize/tests/runner'
3
+ require 'edfize/tests/check_length'
4
+ require 'edfize/tests/check_reserved_area'
5
+ require 'edfize/tests/check_reserved_signal_areas'
6
+
7
+ module Edfize
8
+ module Tests
9
+ extend CheckLength
10
+ extend CheckReservedArea
11
+ extend CheckReservedSignalAreas
12
+ end
13
+ end
@@ -3,7 +3,7 @@ module Edfize
3
3
  MAJOR = 0
4
4
  MINOR = 1
5
5
  TINY = 0
6
- BUILD = "pre"
6
+ BUILD = nil
7
7
 
8
8
  STRING = [MAJOR, MINOR, TINY, BUILD].compact.join('.')
9
9
  end
data/lib/edfize.rb CHANGED
@@ -1,13 +1,16 @@
1
1
  require 'edfize/edf'
2
+ require 'edfize/tests'
2
3
  require 'edfize/version'
3
4
 
5
+ require 'colorize'
6
+
4
7
  module Edfize
5
8
  def self.launch(argv)
6
9
  case argv.first.to_s.scan(/\w/).first
7
10
  when 'v'
8
11
  version
9
12
  when 'c', 't'
10
- check
13
+ check(argv[1..-1])
11
14
  when 'r'
12
15
  print_headers
13
16
  else
@@ -17,7 +20,7 @@ module Edfize
17
20
 
18
21
  def self.print_headers
19
22
  puts "----------------"
20
- edfs_in_current_directory.each do |edf_file_name|
23
+ edfs_in_current_directory_and_subdirectories.each do |edf_file_name|
21
24
  edf = Edfize::Edf.new(edf_file_name)
22
25
  edf.print_header
23
26
  puts "----------------"
@@ -28,9 +31,21 @@ module Edfize
28
31
  puts "Edfize #{Edfize::VERSION::STRING}"
29
32
  end
30
33
 
31
- def self.check
32
- edf_count = edfs_in_current_directory.count
33
- puts "Checking #{edf_count} EDF#{'s' unless edf_count == 1}"
34
+ def self.check(argv)
35
+ test_start_time = Time.now
36
+ edf_count = edfs_in_current_directory_and_subdirectories.count
37
+ test_count = 0
38
+ failure_count = 0
39
+ puts "Started\n"
40
+ edfs_in_current_directory_and_subdirectories.each do |edf_file_name|
41
+ edf = Edfize::Edf.new(edf_file_name)
42
+ runner = Edfize::Tests::Runner.new(edf, argv)
43
+ runner.run_tests
44
+ test_count += runner.tests_run
45
+ failure_count += runner.tests_failed
46
+ end
47
+ puts "\nFinished in #{Time.now - test_start_time}s"
48
+ puts "#{edf_count} EDF#{'s' unless edf_count == 1}, #{test_count} test#{'s' unless test_count == 1}, " + "#{failure_count} failure#{'s' unless failure_count == 1}".colorize( failure_count == 0 ? :green : :red )
34
49
  end
35
50
 
36
51
  def self.help
@@ -38,9 +53,11 @@ module Edfize
38
53
  Usage: edfize COMMAND [ARGS]
39
54
 
40
55
  The most common edfize commands are:
41
- [c]heck Check EDFs in current directory for errors
42
- [t]est Same as [c]heck
43
- [h]elp Show edfize command-line documentation
56
+ [t]est Check EDFs in directory and subdirectories
57
+ --failing Only display failing tests
58
+ --quiet Suppress failing test descriptions
59
+ [r]un Print EDF header information
60
+ [h]elp Show edfize command documentation
44
61
  [v]ersion Returns the version of Edfize
45
62
 
46
63
  Commands can be referenced by the first letter:
@@ -50,7 +67,7 @@ EOT
50
67
  puts help_message
51
68
  end
52
69
 
53
- def self.edfs_in_current_directory
54
- Dir.glob('*.edf')
70
+ def self.edfs_in_current_directory_and_subdirectories
71
+ Dir.glob('**/*.edf')
55
72
  end
56
73
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: edfize
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.pre
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Remo Mueller
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-04-30 00:00:00.000000000 Z
11
+ date: 2014-05-28 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: colorize
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.7.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.7.2
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: bundler
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -56,6 +70,12 @@ files:
56
70
  - lib/edfize.rb
57
71
  - lib/edfize/edf.rb
58
72
  - lib/edfize/signal.rb
73
+ - lib/edfize/tests.rb
74
+ - lib/edfize/tests/check_length.rb
75
+ - lib/edfize/tests/check_reserved_area.rb
76
+ - lib/edfize/tests/check_reserved_signal_areas.rb
77
+ - lib/edfize/tests/result.rb
78
+ - lib/edfize/tests/runner.rb
59
79
  - lib/edfize/version.rb
60
80
  homepage: https://github.com/sleepepi/edfize
61
81
  licenses:
@@ -72,9 +92,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
72
92
  version: '0'
73
93
  required_rubygems_version: !ruby/object:Gem::Requirement
74
94
  requirements:
75
- - - ">"
95
+ - - ">="
76
96
  - !ruby/object:Gem::Version
77
- version: 1.3.1
97
+ version: '0'
78
98
  requirements: []
79
99
  rubyforge_project:
80
100
  rubygems_version: 2.2.2