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 +4 -4
- data/CHANGELOG.md +17 -4
- data/README.md +126 -17
- data/edfize.gemspec +2 -0
- data/lib/edfize/edf.rb +164 -134
- data/lib/edfize/signal.rb +42 -12
- data/lib/edfize/tests/check_length.rb +16 -0
- data/lib/edfize/tests/check_reserved_area.rb +15 -0
- data/lib/edfize/tests/check_reserved_signal_areas.rb +17 -0
- data/lib/edfize/tests/result.rb +7 -0
- data/lib/edfize/tests/runner.rb +43 -0
- data/lib/edfize/tests.rb +13 -0
- data/lib/edfize/version.rb +1 -1
- data/lib/edfize.rb +27 -10
- metadata +24 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f5f84db69e7bbe84842214eee8dd220ee6a89a72
|
4
|
+
data.tar.gz: 0f447049e2a23134804e007de36b70186295845a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
- `
|
5
|
-
|
6
|
-
|
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
|
-
|
11
|
+
Use `gem install edfize` to update Edfize to the latest stable
|
12
12
|
|
13
|
-
|
13
|
+
Use `gem install edfize --pre` to update Edfize to the latest prerelease
|
14
14
|
|
15
|
-
|
15
|
+
## Usage
|
16
16
|
|
17
|
-
|
17
|
+
### Validate EDFs
|
18
18
|
|
19
|
-
|
19
|
+
Use `edfize test` to test that EDFs stored in the current directory have a valid format.
|
20
20
|
|
21
|
-
|
21
|
+
cd <edf-directory>
|
22
|
+
edfize test
|
22
23
|
|
23
|
-
|
24
|
+
A list of validations performed is:
|
24
25
|
|
25
|
-
|
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
|
-
|
29
|
+
Flags that can be added to the `test` command include:
|
28
30
|
|
29
|
-
|
30
|
-
|
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
|
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
|
-
###
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
29
|
-
|
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
|
55
|
-
|
61
|
+
def size_of_header
|
62
|
+
HEADER_OFFSET + ns * Signal::SIGNAL_CONFIG.collect{|k,h| h[:size]}.inject(:+)
|
56
63
|
end
|
57
64
|
|
58
|
-
|
59
|
-
|
60
|
-
IO.binread(@filename, 80, 8)
|
65
|
+
def expected_size_of_header
|
66
|
+
@number_of_bytes_in_header
|
61
67
|
end
|
62
68
|
|
63
|
-
#
|
64
|
-
def
|
65
|
-
|
69
|
+
# Total File Size In Bytes
|
70
|
+
def edf_size
|
71
|
+
File.size(@filename)
|
66
72
|
end
|
67
73
|
|
68
|
-
#
|
69
|
-
def
|
70
|
-
|
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
|
-
|
74
|
-
|
75
|
-
IO.binread(@filename, 8, 176)
|
79
|
+
def expected_edf_size
|
80
|
+
expected_data_size + size_of_header
|
76
81
|
end
|
77
82
|
|
78
|
-
|
79
|
-
|
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
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
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
|
-
|
89
|
-
|
90
|
-
|
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
|
-
|
94
|
-
|
95
|
-
|
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
|
-
|
99
|
-
|
100
|
-
|
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
|
104
|
-
|
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
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
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
|
-
|
117
|
-
|
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
|
-
|
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
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
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
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
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
|
-
|
153
|
-
|
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
|
-
|
157
|
-
|
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
|
-
|
162
|
-
|
163
|
-
|
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
|
-
#
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
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
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
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
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
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,
|
4
|
-
:
|
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
|
-
@
|
8
|
-
@
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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,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
|
data/lib/edfize/tests.rb
ADDED
@@ -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
|
data/lib/edfize/version.rb
CHANGED
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
|
-
|
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
|
-
|
33
|
-
|
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
|
-
[
|
42
|
-
|
43
|
-
|
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.
|
54
|
-
Dir.glob('
|
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
|
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-
|
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:
|
97
|
+
version: '0'
|
78
98
|
requirements: []
|
79
99
|
rubyforge_project:
|
80
100
|
rubygems_version: 2.2.2
|