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 +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
|