edfize 0.1.0.pre → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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