├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── edf.go ├── go.mod ├── processing ├── bilevel.go └── bilevel_test.go ├── reader.go ├── signals ├── annotations.go ├── data_signal.go ├── parser.go └── signals.go ├── testing ├── test_signal.go └── test_signal_test.go └── tools └── edf-tool.go /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution, 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EDF+ Parser library 2 | 3 | This package provides a simple, hopefully correct, and idiomatic library for parsing 4 | [EDF+](http://www.edfplus.info) files in the Go language. 5 | 6 | The `tool` directory contains an example program showing how to use the library. 7 | 8 | This is not an official Google product. 9 | -------------------------------------------------------------------------------- /edf.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package edf contains a parser for EDF+ files. 16 | package edf 17 | 18 | // Edf represents an EDF+ file. 19 | type Edf struct { 20 | Header *Header 21 | 22 | // Records 23 | Records []Record 24 | } 25 | 26 | // Header represents an EDF+ header. 27 | type Header struct { 28 | Version string 29 | PatiendID string 30 | RecordingID string 31 | StartDate string 32 | StartTime string 33 | HeaderSize uint32 34 | Reserved string 35 | NumDataRecords uint32 36 | DurationDataRecords float32 37 | NumSignals uint32 38 | Signals []SignalDefinition 39 | } 40 | 41 | // SignalDefinition holds the definition of an EDF signal. 42 | type SignalDefinition struct { 43 | Label string 44 | TransducerType string 45 | PhysicalDimension string 46 | PhysicalMinimum string 47 | PhysicalMaximum string 48 | DigitalMinimum string 49 | DigitalMaximum string 50 | Prefiltering string 51 | SamplesRecord uint32 52 | Reserved string 53 | } 54 | 55 | // Record holds a single record entry from the EDF file. 56 | type Record struct { 57 | Signals []SignalRecord 58 | } 59 | 60 | // SignalRecord holds the samples for a single signal inside a data record. 61 | type SignalRecord struct { 62 | Samples []int16 63 | } 64 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/google/edf 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /processing/bilevel.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package processing contains a processing utilities for EDF signal. 16 | package processing 17 | 18 | import ( 19 | "errors" 20 | "math" 21 | "time" 22 | 23 | "github.com/google/edf" 24 | "github.com/google/edf/signals" 25 | ) 26 | 27 | type Level int 28 | 29 | const ( 30 | TRANSITION Level = iota 31 | LOW 32 | HIGH 33 | ) 34 | 35 | // BiLevelSignal is an EDF signal coerced into two values (levels) only. 36 | type BiLevelSignal interface { 37 | signals.Signal 38 | Low() float64 39 | High() float64 40 | BiLevelRecording(start, end time.Time) ([]Level, error) 41 | } 42 | 43 | // biLevelSignal is the internal representation of BiLevelSignal 44 | type biLevelSignal struct { 45 | s signals.Signal 46 | low float64 47 | high float64 48 | tolerance float64 49 | } 50 | 51 | func (s *biLevelSignal) Low() float64 { 52 | return s.low 53 | } 54 | 55 | func (s *biLevelSignal) High() float64 { 56 | return s.high 57 | } 58 | 59 | func (s *biLevelSignal) Label() string { 60 | return s.s.Label() + " (bilevel)" 61 | } 62 | 63 | func (s *biLevelSignal) StartTime() time.Time { 64 | return s.s.StartTime() 65 | } 66 | 67 | func (s *biLevelSignal) EndTime() time.Time { 68 | return s.s.EndTime() 69 | } 70 | 71 | func (s *biLevelSignal) Definition() *edf.SignalDefinition { 72 | return nil 73 | } 74 | 75 | func (s *biLevelSignal) SamplingRate() time.Duration { 76 | ds, ok := s.s.(signals.DataSignal) 77 | if !ok { 78 | return 0 79 | } 80 | return ds.SamplingRate() 81 | } 82 | 83 | func (s *biLevelSignal) BiLevelRecording(start, end time.Time) ([]Level, error) { 84 | ds, ok := s.s.(signals.DataSignal) 85 | if !ok { 86 | return nil, errors.New("BiLevelRecording can only be created for data signals") 87 | } 88 | r, err := ds.Recording(start, end) 89 | if err != nil { 90 | return nil, err 91 | } 92 | data := make([]Level, len(r)) 93 | for i, dataPoint := range r { 94 | if math.Abs(dataPoint-s.low) < s.tolerance { 95 | data[i] = LOW 96 | } else if math.Abs(dataPoint-s.high) < s.tolerance { 97 | data[i] = HIGH 98 | } else { 99 | data[i] = TRANSITION 100 | } 101 | } 102 | return data, nil 103 | } 104 | 105 | func (s *biLevelSignal) Recording(start, end time.Time) ([]float64, error) { 106 | ds, ok := s.s.(signals.DataSignal) 107 | if !ok { 108 | return nil, errors.New("BiLevelRecording can only be created for data signals") 109 | } 110 | r, err := ds.Recording(start, end) 111 | if err != nil { 112 | return nil, err 113 | } 114 | for i, dataPoint := range r { 115 | if math.Abs(dataPoint-s.low) < s.tolerance { 116 | r[i] = s.low 117 | } else if math.Abs(dataPoint-s.high) < s.tolerance { 118 | r[i] = s.high 119 | } else { 120 | r[i] = dataPoint 121 | } 122 | } 123 | return r, nil 124 | } 125 | 126 | // NewBiLevelSignal transforms a signal into a bi-level signal. 127 | func NewBiLevelSignal(s signals.Signal, lowlevel, highlevel, tolerance float64) BiLevelSignal { 128 | return &biLevelSignal{s, lowlevel, highlevel, tolerance} 129 | } 130 | -------------------------------------------------------------------------------- /processing/bilevel_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package processing 16 | 17 | import ( 18 | "math/rand" 19 | "reflect" 20 | "testing" 21 | "time" 22 | 23 | edf_testing "github.com/google/edf/testing" 24 | ) 25 | 26 | func randFloat32(mean float64) float64 { 27 | return rand.Float64() + mean - 0.5 28 | } 29 | 30 | func TestBiLevelBasics(t *testing.T) { 31 | t1 := time.Now() 32 | t2 := t1.Add(time.Duration(10) * time.Second) 33 | records := []float64{randFloat32(10), randFloat32(10), 34 | randFloat32(5), randFloat32(5), 35 | randFloat32(10), randFloat32(10), 36 | randFloat32(7), randFloat32(5), 37 | randFloat32(10), randFloat32(10), 38 | randFloat32(5), randFloat32(5), 39 | randFloat32(10), randFloat32(10), 40 | randFloat32(5), randFloat32(5), 41 | randFloat32(10), randFloat32(10), 42 | randFloat32(5), randFloat32(5)} 43 | baseSignal := edf_testing.NewTestingSignal(t1, t2, records) 44 | biLevel := NewBiLevelSignal(baseSignal, 5, 10, 1) 45 | if biLevel.StartTime() != t1 { 46 | t.Errorf("%v should be equal to %v", biLevel.StartTime(), t1) 47 | } 48 | if biLevel.EndTime() != t2 { 49 | t.Errorf("%v should be equal to %v", biLevel.EndTime(), t2) 50 | } 51 | expectedSignal := []Level{HIGH, HIGH, 52 | LOW, LOW, 53 | HIGH, HIGH, 54 | TRANSITION, LOW, 55 | HIGH, HIGH, 56 | LOW, LOW, 57 | HIGH, HIGH, 58 | LOW, LOW, 59 | HIGH, HIGH, 60 | LOW, LOW} 61 | actualSignal, err := biLevel.BiLevelRecording(t1, t2) 62 | if err != nil { 63 | t.Error(err) 64 | } 65 | if !reflect.DeepEqual(actualSignal, expectedSignal) { 66 | t.Errorf("%v should be equal to %v", actualSignal, expectedSignal) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /reader.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package edf contains a parser for EDF+ files. 16 | package edf 17 | 18 | import ( 19 | "bufio" 20 | "encoding/binary" 21 | "io" 22 | "log" 23 | "os" 24 | "strconv" 25 | "strings" 26 | ) 27 | 28 | func init() { 29 | log.SetFlags(log.LstdFlags | log.Lshortfile) 30 | } 31 | 32 | // ReadEDF reads an EDF file. 33 | func ReadEDF(filename string) (*Edf, error) { 34 | var err error 35 | 36 | fileInput, err := os.Open(filename) 37 | if err != nil { 38 | log.Printf("Error: %v\n", err) 39 | return nil, err 40 | } 41 | 42 | defer fileInput.Close() 43 | input := bufio.NewReader(fileInput) 44 | edf := &Edf{} 45 | edf.Header, err = readHeader(input) 46 | if err != nil { 47 | log.Printf("Error: %v\n", err) 48 | return nil, err 49 | } 50 | if err := readRecords(input, edf); err != nil { 51 | log.Printf("Error: %v\n", err) 52 | return nil, err 53 | } 54 | 55 | return edf, nil 56 | } 57 | 58 | func readNextBytes(input io.Reader, size uint) ([]byte, error) { 59 | data := make([]byte, size) 60 | _, err := io.ReadFull(input, data) 61 | return data, err 62 | } 63 | 64 | // Reads the header of the EDF+ file. 65 | func readHeader(input io.Reader) (*Header, error) { 66 | header := Header{} 67 | var data []byte 68 | var iData uint64 69 | var fData float64 70 | var sData string 71 | var err error 72 | 73 | if data, err = readNextBytes(input, 8); err != nil { 74 | log.Printf("Error: %v\n", err) 75 | return nil, err 76 | } 77 | header.Version = strings.TrimSpace(string(data)) 78 | 79 | if data, err = readNextBytes(input, 80); err != nil { 80 | log.Printf("Error: %v\n", err) 81 | return nil, err 82 | } 83 | header.PatiendID = strings.TrimSpace(string(data)) 84 | 85 | if data, err = readNextBytes(input, 80); err != nil { 86 | log.Printf("Error: %v\n", err) 87 | return nil, err 88 | } 89 | header.RecordingID = strings.TrimSpace(string(data)) 90 | 91 | if data, err = readNextBytes(input, 8); err != nil { 92 | log.Printf("Error: %v\n", err) 93 | return nil, err 94 | } 95 | header.StartDate = strings.TrimSpace(string(data)) 96 | 97 | if data, err = readNextBytes(input, 8); err != nil { 98 | log.Printf("Error: %v\n", err) 99 | return nil, err 100 | } 101 | header.StartTime = strings.TrimSpace(string(data)) 102 | 103 | if data, err = readNextBytes(input, 8); err != nil { 104 | log.Printf("Error: %v\n", err) 105 | return nil, err 106 | } 107 | iData, err = strconv.ParseUint(strings.TrimSpace(string(data)), 10, 32) 108 | if err != nil { 109 | log.Printf("Error: %v\n", err) 110 | return nil, err 111 | } 112 | header.HeaderSize = uint32(iData) 113 | 114 | if data, err = readNextBytes(input, 44); err != nil { 115 | log.Printf("Error: %v\n", err) 116 | return nil, err 117 | } 118 | header.Reserved = strings.TrimSpace(string(data)) 119 | 120 | if data, err = readNextBytes(input, 8); err != nil { 121 | log.Printf("Error: %v\n", err) 122 | return nil, err 123 | } 124 | iData, err = strconv.ParseUint(strings.TrimSpace(string(data)), 10, 32) 125 | if err != nil { 126 | log.Printf("Error: %v\n", err) 127 | return nil, err 128 | } 129 | header.NumDataRecords = uint32(iData) 130 | 131 | if data, err = readNextBytes(input, 8); err != nil { 132 | log.Printf("Error: %v\n", err) 133 | return nil, err 134 | } 135 | fData, err = strconv.ParseFloat(strings.TrimSpace(string(data)), 32) 136 | if err != nil { 137 | log.Printf("Error: %v\n", err) 138 | return nil, err 139 | } 140 | header.DurationDataRecords = float32(fData) 141 | 142 | if data, err = readNextBytes(input, 4); err != nil { 143 | log.Printf("Error: %v\n", err) 144 | return nil, err 145 | } 146 | iData, err = strconv.ParseUint(strings.TrimSpace(string(data)), 10, 32) 147 | if err != nil { 148 | log.Printf("Error: %v\n", err) 149 | return nil, err 150 | } 151 | header.NumSignals = uint32(iData) 152 | 153 | header.Signals = make([]SignalDefinition, header.NumSignals) 154 | 155 | for signalIndex := uint32(0); signalIndex < header.NumSignals; signalIndex++ { 156 | signal := &header.Signals[signalIndex] 157 | if data, err = readNextBytes(input, 16); err != nil { 158 | log.Printf("Error: %v\n", err) 159 | return nil, err 160 | } 161 | signal.Label = strings.TrimSpace(string(data)) 162 | } 163 | 164 | for signalIndex := uint32(0); signalIndex < header.NumSignals; signalIndex++ { 165 | signal := &header.Signals[signalIndex] 166 | if data, err = readNextBytes(input, 80); err != nil { 167 | log.Printf("Error: %v\n", err) 168 | return nil, err 169 | } 170 | signal.TransducerType = strings.TrimSpace(string(data)) 171 | } 172 | 173 | for signalIndex := uint32(0); signalIndex < header.NumSignals; signalIndex++ { 174 | signal := &header.Signals[signalIndex] 175 | if data, err = readNextBytes(input, 8); err != nil { 176 | log.Printf("Error: %v\n", err) 177 | return nil, err 178 | } 179 | signal.PhysicalDimension = strings.TrimSpace(string(data)) 180 | } 181 | 182 | for signalIndex := uint32(0); signalIndex < header.NumSignals; signalIndex++ { 183 | signal := &header.Signals[signalIndex] 184 | if data, err = readNextBytes(input, 8); err != nil { 185 | log.Printf("Error: %v\n", err) 186 | return nil, err 187 | } 188 | signal.PhysicalMinimum = strings.TrimSpace(string(data)) 189 | } 190 | 191 | for signalIndex := uint32(0); signalIndex < header.NumSignals; signalIndex++ { 192 | signal := &header.Signals[signalIndex] 193 | if data, err = readNextBytes(input, 8); err != nil { 194 | log.Printf("Error: %v\n", err) 195 | return nil, err 196 | } 197 | signal.PhysicalMaximum = strings.TrimSpace(string(data)) 198 | } 199 | 200 | for signalIndex := uint32(0); signalIndex < header.NumSignals; signalIndex++ { 201 | signal := &header.Signals[signalIndex] 202 | if data, err = readNextBytes(input, 8); err != nil { 203 | log.Printf("Error: %v\n", err) 204 | return nil, err 205 | } 206 | signal.DigitalMinimum = strings.TrimSpace(string(data)) 207 | } 208 | 209 | for signalIndex := uint32(0); signalIndex < header.NumSignals; signalIndex++ { 210 | signal := &header.Signals[signalIndex] 211 | if data, err = readNextBytes(input, 8); err != nil { 212 | log.Printf("Error: %v\n", err) 213 | return nil, err 214 | } 215 | signal.DigitalMaximum = strings.TrimSpace(string(data)) 216 | } 217 | 218 | for signalIndex := uint32(0); signalIndex < header.NumSignals; signalIndex++ { 219 | signal := &header.Signals[signalIndex] 220 | if data, err = readNextBytes(input, 80); err != nil { 221 | log.Printf("Error: %v\n", err) 222 | return nil, err 223 | } 224 | signal.Prefiltering = strings.TrimSpace(string(data)) 225 | } 226 | 227 | for signalIndex := uint32(0); signalIndex < header.NumSignals; signalIndex++ { 228 | signal := &header.Signals[signalIndex] 229 | if data, err = readNextBytes(input, 8); err != nil { 230 | log.Printf("Error: %v\n", err) 231 | return nil, err 232 | } 233 | sData = strings.TrimSpace(string(data)) 234 | if iData, err = strconv.ParseUint(sData, 10, 32); err != nil { 235 | log.Printf("Error at signal %d: %v\n", signalIndex, err) 236 | return nil, err 237 | } 238 | signal.SamplesRecord = uint32(iData) 239 | } 240 | 241 | for signalIndex := uint32(0); signalIndex < header.NumSignals; signalIndex++ { 242 | signal := &header.Signals[signalIndex] 243 | if data, err = readNextBytes(input, 32); err != nil { 244 | log.Printf("Error: %v\n", err) 245 | return nil, err 246 | } 247 | signal.Reserved = strings.TrimSpace(string(data)) 248 | } 249 | 250 | return &header, nil 251 | } 252 | 253 | // Reads the data records from the EDF+ file. The header of the edf must be 254 | // parsed and filled. 255 | func readRecords(input io.Reader, edf *Edf) error { 256 | edf.Records = make([]Record, edf.Header.NumDataRecords) 257 | for i := uint32(0); i < edf.Header.NumDataRecords; i++ { 258 | record := &edf.Records[i] 259 | record.Signals = make([]SignalRecord, edf.Header.NumSignals) 260 | for s := uint32(0); s < edf.Header.NumSignals; s++ { 261 | signal := &record.Signals[s] 262 | signal.Samples = make([]int16, edf.Header.Signals[s].SamplesRecord) 263 | for d := uint32(0); d < edf.Header.Signals[s].SamplesRecord; d++ { 264 | err := binary.Read(input, binary.LittleEndian, &signal.Samples[d]) 265 | if err != nil { 266 | return err 267 | } 268 | } 269 | } 270 | } 271 | return nil 272 | } 273 | -------------------------------------------------------------------------------- /signals/annotations.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package signals 16 | 17 | import ( 18 | "bytes" 19 | "errors" 20 | "io" 21 | "strconv" 22 | "time" 23 | ) 24 | 25 | type timestamp struct { 26 | base time.Time 27 | Onset float64 28 | Duration float64 29 | } 30 | 31 | type timeStampedAnnotation struct { 32 | timestamp timestamp 33 | annotations []string 34 | } 35 | 36 | func (tsa *timeStampedAnnotation) Time() time.Time { 37 | return tsa.timestamp.base.Add(time.Duration(tsa.timestamp.Onset * float64(time.Second))) 38 | } 39 | 40 | func (tsa *timeStampedAnnotation) End() time.Time { 41 | return tsa.Time().Add(time.Duration(tsa.timestamp.Duration * float64(time.Second))) 42 | } 43 | 44 | func (tsa *timeStampedAnnotation) Annotations() []string { 45 | return tsa.annotations 46 | } 47 | 48 | type annotationSignal struct { 49 | Signal 50 | annotations []timeStampedAnnotation 51 | } 52 | 53 | func (as *annotationSignal) Annotations(start, end time.Time) ([]Annotation, error) { 54 | result := make([]Annotation, 0) 55 | if start.Before(as.StartTime()) || end.After(as.EndTime()) || start.After(end) { 56 | return nil, errors.New("Invalid start or end time") 57 | } 58 | for annotationIndex := range as.annotations { 59 | annotation := &(as.annotations[annotationIndex]) 60 | at := annotation.End() 61 | if (at == start || at.After(start)) && (at == end || at.Before(end)) { 62 | result = append(result, annotation) 63 | } 64 | } 65 | return result, nil 66 | } 67 | 68 | func newAnnotationSignal(baseSignal *edfSignal) (AnnotationSignal, error) { 69 | records := baseSignal.edf.Records 70 | aS := annotationSignal{baseSignal, []timeStampedAnnotation{}} 71 | for _, record := range records { 72 | tsa := timeStampedAnnotation{timestamp{baseSignal.StartTime(), 0, 0}, []string{}} 73 | signal := record.Signals[baseSignal.signalIndex] 74 | // Extract bytes from 16-bit integers. 75 | buffer := new(bytes.Buffer) 76 | for _, biChar := range signal.Samples { 77 | buffer.WriteByte(byte(biChar & 0xFF)) 78 | buffer.WriteByte(byte(biChar >> 8)) 79 | } 80 | // Zero bytes don't count. 81 | rawAnnotations := bytes.Split(bytes.Replace(buffer.Bytes(), []byte{'\x00'}, []byte{}, -1), []byte{'\x14'}) 82 | realAnnotations := [][]byte{} 83 | for _, annotation := range rawAnnotations { 84 | if len(annotation) != 0 { 85 | realAnnotations = append(realAnnotations, annotation) 86 | } 87 | } 88 | for annotationIndex, realAnnotation := range realAnnotations { 89 | if len(realAnnotation) == 0 { 90 | continue 91 | } 92 | // I have found in the wild timestamps both as the first or the second annotation. 93 | if annotationIndex == 0 || annotationIndex == 1 { 94 | timestamp, err := parseTimestamp(baseSignal.StartTime(), realAnnotation) 95 | if err == nil { 96 | tsa.timestamp = timestamp 97 | continue 98 | } 99 | } 100 | tsa.annotations = append(tsa.annotations, string(realAnnotation)) 101 | } 102 | aS.annotations = append(aS.annotations, tsa) 103 | } 104 | return &aS, nil 105 | } 106 | 107 | func parseTimestamp(base time.Time, tsBytes []byte) (timestamp, error) { 108 | buffer := bytes.NewBuffer(tsBytes) 109 | onsetBytes, err := buffer.ReadString('\x15') 110 | if err == io.EOF { 111 | // No duration 112 | onset, err := strconv.ParseFloat(onsetBytes, 64) 113 | if err != nil { 114 | return timestamp{}, err 115 | } 116 | return timestamp{base, onset, 0}, nil 117 | } else if err != nil { 118 | return timestamp{}, err 119 | } 120 | onset, err := strconv.ParseFloat(onsetBytes[0:len(onsetBytes)-1], 64) 121 | if err != nil { 122 | return timestamp{}, err 123 | } 124 | durationBytes := buffer.Bytes() 125 | duration, err := strconv.ParseFloat(string(durationBytes), 64) 126 | if err != nil { 127 | return timestamp{}, err 128 | } 129 | return timestamp{base, onset, duration}, nil 130 | } 131 | -------------------------------------------------------------------------------- /signals/data_signal.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package signals 16 | 17 | import "time" 18 | 19 | type dataSignal struct { 20 | Signal 21 | 22 | e *edfSignal 23 | } 24 | 25 | func newDataSignal(edfSignal *edfSignal) *dataSignal { 26 | return &dataSignal{ 27 | Signal: edfSignal, 28 | e: edfSignal, 29 | } 30 | } 31 | 32 | // Returns the time between two recording samples of this signal. 33 | func (s *dataSignal) SamplingRate() time.Duration { 34 | return time.Duration(s.e.edf.Header.DurationDataRecords/float32(s.Definition().SamplesRecord)) * time.Second 35 | } 36 | 37 | // Returns the recording data, in physical units. 38 | func (s *dataSignal) Recording(start, end time.Time) ([]float64, error) { 39 | r, err := getSignalData(s.e.edf, s.e.signalIndex, start, end) 40 | if err != nil { 41 | return nil, err 42 | } 43 | result := make([]float64, len(r)) 44 | for i, dataPoint := range r { 45 | result[i] = s.e.a*float64(dataPoint) + s.e.b 46 | } 47 | return result, nil 48 | } 49 | -------------------------------------------------------------------------------- /signals/parser.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package signals 16 | 17 | import ( 18 | "fmt" 19 | "strconv" 20 | "time" 21 | 22 | "github.com/google/edf" 23 | ) 24 | 25 | // GetSignals return the signals from an EDF dataset. 26 | func GetSignals(e *edf.Edf) ([]Signal, error) { 27 | signals := make([]Signal, e.Header.NumSignals) 28 | for i := range e.Header.Signals { 29 | signal, err := newEdfSignal(e, i) 30 | if err != nil { 31 | return nil, err 32 | } 33 | if e.Header.Signals[i].Label == "EDF Annotations" { 34 | signals[i], err = newAnnotationSignal(signal) 35 | if err != nil { 36 | return nil, err 37 | } 38 | } else { 39 | signals[i] = newDataSignal(signal) 40 | } 41 | } 42 | return signals, nil 43 | } 44 | 45 | type edfSignal struct { 46 | edf *edf.Edf 47 | startTime time.Time 48 | endTime time.Time 49 | signalIndex int 50 | 51 | // digital to physical conversion parameters 52 | a float64 53 | b float64 54 | } 55 | 56 | func newEdfSignal(e *edf.Edf, signalIndex int) (*edfSignal, error) { 57 | s := new(edfSignal) 58 | s.edf = e 59 | s.signalIndex = signalIndex 60 | start, err := getStartTime(e.Header) 61 | if err != nil { 62 | return nil, err 63 | } 64 | s.startTime = start 65 | end, err := getEndTime(e.Header) 66 | if err != nil { 67 | return nil, err 68 | } 69 | s.endTime = end 70 | 71 | def := &s.edf.Header.Signals[signalIndex] 72 | physMin, err := strconv.ParseFloat(def.PhysicalMinimum, 32) 73 | if err != nil { 74 | return nil, err 75 | } 76 | physMax, err := strconv.ParseFloat(def.PhysicalMaximum, 32) 77 | if err != nil { 78 | return nil, err 79 | } 80 | digiMin, err := strconv.ParseFloat(def.DigitalMinimum, 32) 81 | if err != nil { 82 | return nil, err 83 | } 84 | digiMax, err := strconv.ParseFloat(def.DigitalMaximum, 32) 85 | if err != nil { 86 | return nil, err 87 | } 88 | 89 | s.a = (physMax - physMin) / (digiMax - digiMin) 90 | s.b = physMin - s.a*digiMin 91 | 92 | return s, nil 93 | } 94 | 95 | func (s *edfSignal) Label() string { 96 | return s.Definition().Label 97 | } 98 | 99 | // Start date and time of the recording. 100 | func (s *edfSignal) StartTime() time.Time { 101 | return s.startTime 102 | } 103 | 104 | // End date and time of the recording. 105 | func (s *edfSignal) EndTime() time.Time { 106 | return s.endTime 107 | } 108 | 109 | // Signal definition. This may be nil for composite/generated signals. 110 | func (s *edfSignal) Definition() *edf.SignalDefinition { 111 | return &s.edf.Header.Signals[s.signalIndex] 112 | } 113 | 114 | // getStartTime returns the starting date and time of the recording 115 | func getStartTime(h *edf.Header) (time.Time, error) { 116 | return time.Parse("02.01.06 15.04.05", h.StartDate+" "+h.StartTime) 117 | } 118 | 119 | // getEndTime returns the end date and time of the recording 120 | func getEndTime(h *edf.Header) (time.Time, error) { 121 | start, err := getStartTime(h) 122 | if err != nil { 123 | return start, err 124 | } 125 | end := start.Add( 126 | time.Duration( 127 | float32(h.NumDataRecords)*h.DurationDataRecords) * time.Second) 128 | return end, nil 129 | } 130 | 131 | // getSignalData returns the signal samples between the specified times. 132 | func getSignalData(e *edf.Edf, signalIndex int, start, end time.Time) ([]int16, error) { 133 | recordingStart, err := getStartTime(e.Header) 134 | if err != nil { 135 | return nil, err 136 | } 137 | if recordingStart.After(start) { 138 | return nil, fmt.Errorf("Requesting data before the recording") 139 | } 140 | 141 | recordingEnd, err := getEndTime(e.Header) 142 | if err != nil { 143 | return nil, err 144 | } 145 | if recordingEnd.Before(end) { 146 | return nil, fmt.Errorf("Requesting data after the recording") 147 | } 148 | 149 | durationSample := float64(e.Header.DurationDataRecords) / float64(e.Header.Signals[signalIndex].SamplesRecord) 150 | 151 | skipStart := start.Sub(recordingStart) 152 | startRecord := uint32( 153 | skipStart.Seconds() / float64(e.Header.DurationDataRecords)) 154 | startSample := uint32( 155 | (skipStart.Seconds() - (float64(startRecord) * float64(e.Header.DurationDataRecords))) / (durationSample)) 156 | 157 | endRecord := uint32( 158 | end.Sub(recordingStart).Seconds() / float64(e.Header.DurationDataRecords)) 159 | endSample := uint32((end.Sub(recordingStart).Seconds() - (float64(endRecord) * float64(e.Header.DurationDataRecords))) / (durationSample)) 160 | 161 | numSamples := e.Header.Signals[signalIndex].SamplesRecord*(endRecord-startRecord-1) + endSample + (e.Header.Signals[signalIndex].SamplesRecord - startSample) 162 | 163 | result := make([]int16, numSamples) 164 | s := 0 165 | for i := startRecord; i <= endRecord; i++ { 166 | for j := uint32(0); j < e.Header.Signals[signalIndex].SamplesRecord; j++ { 167 | if i == startRecord && j == 0 { 168 | j = startSample 169 | } 170 | if i == endRecord && j >= endSample { 171 | break 172 | } 173 | result[s] = e.Records[i].Signals[signalIndex].Samples[j] 174 | s++ 175 | } 176 | } 177 | return result, nil 178 | } 179 | -------------------------------------------------------------------------------- /signals/signals.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package signals interprets a raw EDF file into data or annotation signals. 16 | package signals 17 | 18 | import ( 19 | "time" 20 | 21 | "github.com/google/edf" 22 | ) 23 | 24 | // Signal wraps all the data recorded on for a signal. 25 | type Signal interface { 26 | // Label of the signal. 27 | Label() string 28 | 29 | // StartTime returns the date and time of the recording. 30 | StartTime() time.Time 31 | 32 | // EndTime returns the end date and time of the recording. 33 | EndTime() time.Time 34 | 35 | // Definition returns the sgnal definition. This may be nil for 36 | // composite/generated signals. 37 | Definition() *edf.SignalDefinition 38 | } 39 | 40 | // DataSignal is a signal representing measures of a physical quantity sampled at regular intervals. 41 | type DataSignal interface { 42 | Signal 43 | 44 | // SamplingRate returns the time between two recording samples of this signal. 45 | SamplingRate() time.Duration 46 | 47 | // Recording returns the recording data, in physical units. 48 | Recording(start, end time.Time) ([]float64, error) 49 | } 50 | 51 | // Annotation is a single annotation from an annotation signal. 52 | type Annotation interface { 53 | // Time of the annotation. 54 | Time() time.Time 55 | 56 | // End time of the annotation. 57 | End() time.Time 58 | 59 | // Annotation contents. 60 | Annotations() []string 61 | } 62 | 63 | // AnnotationSignal is a signal containing text annotations (timestamped or at regular intervals) per the 64 | // EDF+ specification. 65 | type AnnotationSignal interface { 66 | Signal 67 | 68 | // Annotations returns the annotations. 69 | Annotations(start, end time.Time) ([]Annotation, error) 70 | } 71 | -------------------------------------------------------------------------------- /testing/test_signal.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package testing contains a testing utilities for the edf package and sub-packages. 16 | package testing 17 | 18 | import ( 19 | "fmt" 20 | "time" 21 | 22 | "github.com/google/edf" 23 | "github.com/google/edf/signals" 24 | ) 25 | 26 | type testingSignal struct { 27 | start time.Time 28 | end time.Time 29 | records []float64 30 | } 31 | 32 | func (ts *testingSignal) Label() string { 33 | return "Testing signal" 34 | } 35 | 36 | func (ts *testingSignal) StartTime() time.Time { 37 | return ts.start 38 | } 39 | 40 | func (ts *testingSignal) EndTime() time.Time { return ts.end } 41 | 42 | func (ts *testingSignal) Definition() *edf.SignalDefinition { return nil } 43 | 44 | func (ts *testingSignal) SamplingRate() time.Duration { 45 | return ts.end.Sub(ts.start) / time.Duration(len(ts.records)) 46 | } 47 | 48 | func (ts *testingSignal) Recording(start, end time.Time) ([]float64, error) { 49 | if start.Before(ts.start) { 50 | return nil, fmt.Errorf("%v is before %v", start, ts.start) 51 | } 52 | if end.After(ts.end) { 53 | return nil, fmt.Errorf("%v is after %v", end, ts.end) 54 | } 55 | begin := start.Sub(ts.start) / ts.SamplingRate() 56 | finish := end.Sub(ts.start) / ts.SamplingRate() 57 | result := make([]float64, int64(finish-begin)) 58 | copy(result, ts.records[begin:finish]) 59 | return result, nil 60 | } 61 | 62 | func NewTestingSignal(start, end time.Time, records []float64) signals.DataSignal { 63 | return &testingSignal{start: start, end: end, records: records} 64 | } 65 | -------------------------------------------------------------------------------- /testing/test_signal_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package testing 16 | 17 | import ( 18 | "math/rand" 19 | "reflect" 20 | "testing" 21 | "time" 22 | ) 23 | 24 | func TestBiLevelBasics(t *testing.T) { 25 | t1 := time.Now() 26 | t2 := t1.Add(time.Duration(10) * time.Second) 27 | records := []float64{rand.NormFloat64(), rand.NormFloat64(), 28 | rand.NormFloat64(), rand.NormFloat64(), 29 | rand.NormFloat64(), rand.NormFloat64(), 30 | rand.NormFloat64(), rand.NormFloat64(), 31 | rand.NormFloat64(), rand.NormFloat64(), 32 | rand.NormFloat64(), rand.NormFloat64(), 33 | rand.NormFloat64(), rand.NormFloat64(), 34 | rand.NormFloat64(), rand.NormFloat64(), 35 | rand.NormFloat64(), rand.NormFloat64(), 36 | rand.NormFloat64(), rand.NormFloat64()} 37 | baseSignal := NewTestingSignal(t1, t2, records) 38 | if baseSignal.StartTime() != t1 { 39 | t.Errorf("%v should be equal to %v", baseSignal.StartTime(), t1) 40 | } 41 | if baseSignal.EndTime() != t2 { 42 | t.Errorf("%v should be equal to %v", baseSignal.EndTime(), t2) 43 | } 44 | if baseSignal.SamplingRate() != time.Duration(500)*time.Millisecond { 45 | t.Errorf("Wrong sampling rate: %v should be %v", baseSignal.SamplingRate(), time.Duration(500)*time.Millisecond) 46 | } 47 | actualRecords, err := baseSignal.Recording( 48 | t1.Add(time.Duration(500)*time.Millisecond*3), 49 | t1.Add(time.Duration(500)*time.Millisecond*15)) 50 | if err != nil { 51 | t.Error(err) 52 | } 53 | if !reflect.DeepEqual(actualRecords, records[3:15]) { 54 | t.Errorf("%v should be equal to %v", actualRecords, records[3:15]) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tools/edf-tool.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google Inc. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /// This package is an example tool showing how to use the edf library. 16 | package main 17 | 18 | import ( 19 | "flag" 20 | "fmt" 21 | 22 | "github.com/google/edf" 23 | "github.com/google/edf/signals" 24 | ) 25 | 26 | var ( 27 | input = flag.String("input", "", "input") 28 | signalLabel = flag.String("signal", "", "signal") 29 | annotationLabel = flag.Bool("annotations", false, "annotations") 30 | ) 31 | 32 | func main() { 33 | flag.Parse() 34 | edfFile, err := edf.ReadEDF(*input) 35 | if err != nil { 36 | panic(err) 37 | } 38 | 39 | if *signalLabel == "" && !*annotationLabel { 40 | for _, signal := range edfFile.Header.Signals { 41 | fmt.Printf("Signal: '%s'\n", signal.Label) 42 | } 43 | return 44 | } 45 | 46 | edfSignals, err := signals.GetSignals(edfFile) 47 | if err != nil { 48 | panic(err) 49 | } 50 | for _, signal := range edfSignals { 51 | if signal.Label() != *signalLabel && (!*annotationLabel || signal.Label() != "EDF Annotations") { 52 | continue 53 | } 54 | fmt.Printf("Signal: %s\n", signal.Label()) 55 | if !(*annotationLabel) { 56 | values, err := signal.(signals.DataSignal).Recording(signal.StartTime(), signal.EndTime()) 57 | if err != nil { 58 | panic(err) 59 | } 60 | fmt.Println(values) 61 | } else { 62 | values, err := signal.(signals.AnnotationSignal).Annotations(signal.StartTime(), signal.EndTime()) 63 | if err != nil { 64 | panic(err) 65 | } 66 | for _, value := range values { 67 | fmt.Println(value.Time(), value.Annotations()) 68 | } 69 | } 70 | } 71 | } 72 | --------------------------------------------------------------------------------