├── .gitignore ├── dist └── edf4j-1.0b.jar ├── resources ├── invalid.edf ├── junit-4.12.jar ├── test_generator.edf └── ru │ └── mipt │ └── edf │ ├── channel_info.format │ └── header.format ├── README.md ├── license.txt └── src ├── ru └── mipt │ └── edf │ ├── EDFSignal.java │ ├── EDFParserResult.java │ ├── EDFParserException.java │ ├── EDFAnnotation.java │ ├── ParseUtils.java │ ├── EDFConstants.java │ ├── EDFHeader.java │ ├── EDF.java │ ├── EDFWriter.java │ ├── EDFAnnotationFileHeaderBuilder.java │ └── EDFParser.java └── test └── ru └── mipt └── edf └── EDFWriterTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /.settings/ 3 | /.classpath 4 | /.project 5 | /*.jar 6 | -------------------------------------------------------------------------------- /dist/edf4j-1.0b.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIOB/EDF4J/HEAD/dist/edf4j-1.0b.jar -------------------------------------------------------------------------------- /resources/invalid.edf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIOB/EDF4J/HEAD/resources/invalid.edf -------------------------------------------------------------------------------- /resources/junit-4.12.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIOB/EDF4J/HEAD/resources/junit-4.12.jar -------------------------------------------------------------------------------- /resources/test_generator.edf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIOB/EDF4J/HEAD/resources/test_generator.edf -------------------------------------------------------------------------------- /resources/ru/mipt/edf/channel_info.format: -------------------------------------------------------------------------------- 1 | Labels of the channel: {0} 2 | Transducer type: {1} 3 | Physical dimension of channel: {2} 4 | Physical minimum in units of physical dimension: {3} 5 | Physical maximum in units of physical dimension: {4} 6 | Digital minimum: {5} 7 | Digital maximum: {6} 8 | Prefiltering: {7} 9 | Number of samples in each data record: {8} 10 | Reserved Information: {9} -------------------------------------------------------------------------------- /resources/ru/mipt/edf/header.format: -------------------------------------------------------------------------------- 1 | Identification code: {0} 2 | Local subject identification: {1} 3 | Local recording identification: {2} 4 | Startdate of recording: {3} 5 | Starttime of recording: {4} 6 | Number of bytes in header record: {5} 7 | Version of data format: {6} 8 | Number of data records: {7} 9 | Duration of a data record: {8} seconds 10 | Number of channels in data record: {9} 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | EDF4J 2 | ===== 3 | 4 | This is a Java-Parser for the file formats EDF and EDF+. 5 | 6 | License 7 | ------- 8 | 9 | This project is licensed under the terms of the MIT license. See license.txt. 10 | 11 | Usage 12 | ===== 13 | 14 | The parser is available in the file EDFParser.java 15 | 16 | Example usage 17 | ------------- 18 | 19 | String pathToEdfFile = ""; 20 | InputStream is = new BufferedInputStream(new FileInputStream(new File(pathToEdfFile))); 21 | EDFParserResult result = EDFParser.parseEDF(is); 22 | 23 | Example program 24 | --------------- 25 | 26 | An example program is available in the file EDF.java. This example parse EDF (or EDF+) file and write it into several txt files. 27 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | (The MIT license) 2 | 3 | Copyright (c) 2012 MIPT (mr.santak@gmail.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 6 | and associated documentation files (the "Software"), to deal in the Software without restriction, 7 | including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies 12 | or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 16 | AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /src/ru/mipt/edf/EDFSignal.java: -------------------------------------------------------------------------------- 1 | /* 2 | * (The MIT license) 3 | * 4 | * Copyright (c) 2012 MIPT (mr.santak@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 7 | * and associated documentation files (the "Software"), to deal in the Software without restriction, 8 | * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 10 | * subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all copies 13 | * or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 17 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | package ru.mipt.edf; 22 | 23 | /** 24 | * This class represents the complete data records of an EDF-File. 25 | */ 26 | public class EDFSignal 27 | { 28 | 29 | Double[] unitsInDigit; 30 | short[][] digitalValues; 31 | double[][] valuesInUnits; 32 | 33 | public Double[] getUnitsInDigit() 34 | { 35 | return unitsInDigit; 36 | } 37 | 38 | public short[][] getDigitalValues() 39 | { 40 | return digitalValues; 41 | } 42 | 43 | public double[][] getValuesInUnits() 44 | { 45 | return valuesInUnits; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/ru/mipt/edf/EDFParserResult.java: -------------------------------------------------------------------------------- 1 | /* 2 | * (The MIT license) 3 | * 4 | * Copyright (c) 2012 MIPT (mr.santak@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 7 | * and associated documentation files (the "Software"), to deal in the Software without restriction, 8 | * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 10 | * subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all copies 13 | * or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 17 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | package ru.mipt.edf; 22 | 23 | import java.util.List; 24 | 25 | /** 26 | * This class represents the complete content of an EDF-File. 27 | */ 28 | public class EDFParserResult 29 | { 30 | EDFHeader header; 31 | EDFSignal signal; 32 | List annotations; 33 | 34 | public EDFHeader getHeader() 35 | { 36 | return header; 37 | } 38 | 39 | public EDFSignal getSignal() 40 | { 41 | return signal; 42 | } 43 | 44 | public List getAnnotations() 45 | { 46 | return annotations; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/ru/mipt/edf/EDFParserException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * (The MIT license) 3 | * 4 | * Copyright (c) 2012 MIPT (mr.santak@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 7 | * and associated documentation files (the "Software"), to deal in the Software without restriction, 8 | * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 10 | * subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all copies 13 | * or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 17 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | package ru.mipt.edf; 22 | 23 | import java.io.IOException; 24 | 25 | /** 26 | * This exception is thrown if the file format is not according to EDF. 27 | */ 28 | public class EDFParserException extends IOException 29 | { 30 | private static final long serialVersionUID = 3807109927368496625L; 31 | 32 | public EDFParserException() 33 | { 34 | this("File format not according to EDF/EDF+ specification.", null); 35 | } 36 | 37 | public EDFParserException(Throwable th) 38 | { 39 | this("File format not according to EDF/EDF+ specification.", th); 40 | } 41 | 42 | public EDFParserException(String message, Throwable th) 43 | { 44 | super(message, th); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/ru/mipt/edf/EDFAnnotation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * (The MIT license) 3 | * 4 | * Copyright (c) 2012 MIPT (mr.santak@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 7 | * and associated documentation files (the "Software"), to deal in the Software without restriction, 8 | * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 10 | * subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all copies 13 | * or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 17 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | package ru.mipt.edf; 22 | 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | import java.util.Objects; 26 | 27 | public class EDFAnnotation 28 | { 29 | private double onSet = 0; 30 | private double duration = 0; 31 | private final List annotations = new ArrayList<>(); 32 | 33 | EDFAnnotation(String onSet, String duration, String[] annotations) 34 | { 35 | this.onSet = Double.parseDouble(onSet); 36 | if (duration != null && !Objects.equals(duration, "")) 37 | this.duration = Double.parseDouble(duration); 38 | for (int i = 0; i < annotations.length; i++) 39 | { 40 | if (annotations[i] == null || annotations[i].trim().equals("")) 41 | continue; 42 | this.annotations.add(annotations[i]); 43 | } 44 | } 45 | 46 | public double getOnSet() 47 | { 48 | return onSet; 49 | } 50 | 51 | public double getDuration() 52 | { 53 | return duration; 54 | } 55 | 56 | public List getAnnotations() 57 | { 58 | return annotations; 59 | } 60 | 61 | @Override 62 | public String toString() 63 | { 64 | return "Annotation [onSet=" + onSet + ", duration=" + duration + ", annotations=" + annotations + "]"; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/ru/mipt/edf/ParseUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * (The MIT license) 3 | * 4 | * Copyright (c) 2012 MIPT (mr.santak@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 7 | * and associated documentation files (the "Software"), to deal in the Software without restriction, 8 | * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 10 | * subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all copies 13 | * or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 17 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | package ru.mipt.edf; 22 | 23 | import java.io.IOException; 24 | import java.io.InputStream; 25 | import java.util.Arrays; 26 | 27 | abstract class ParseUtils 28 | { 29 | public static String[] readBulkASCIIFromStream(InputStream is, int size, int length) throws IOException 30 | { 31 | String[] result = new String[length]; 32 | for (int i = 0; i < length; i++) 33 | { 34 | result[i] = readASCIIFromStream(is, size); 35 | } 36 | return result; 37 | } 38 | 39 | public static Double[] readBulkDoubleFromStream(InputStream is, int size, int length) throws IOException 40 | { 41 | Double[] result = new Double[length]; 42 | for (int i = 0; i < length; i++) 43 | result[i] = Double.parseDouble(readASCIIFromStream(is, size).trim()); 44 | return result; 45 | } 46 | 47 | public static Integer[] readBulkIntFromStream(InputStream is, int size, int length) throws IOException 48 | { 49 | Integer[] result = new Integer[length]; 50 | for (int i = 0; i < length; i++) 51 | result[i] = Integer.parseInt(readASCIIFromStream(is, size).trim()); 52 | return result; 53 | } 54 | 55 | public static String readASCIIFromStream(InputStream is, int size) throws IOException 56 | { 57 | int len; 58 | byte[] data = new byte[size]; 59 | len = is.read(data); 60 | if (len != data.length) 61 | throw new EDFParserException(); 62 | return new String(data, EDFConstants.CHARSET); 63 | } 64 | 65 | public static T[] removeElement(T[] array, int i) 66 | { 67 | if (i < 0) 68 | return array; 69 | if (i == 0) 70 | return Arrays.copyOfRange(array, 1, array.length); 71 | T[] result = Arrays.copyOfRange(array, 0, array.length - 1); 72 | System.arraycopy(array, i + 1, result, i + 1 - 1, array.length - (i + 1)); 73 | return result; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/ru/mipt/edf/EDFConstants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * (The MIT license) 3 | * 4 | * Copyright (c) 2012 MIPT (mr.santak@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 7 | * and associated documentation files (the "Software"), to deal in the Software without restriction, 8 | * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 10 | * subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all copies 13 | * or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 17 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | package ru.mipt.edf; 22 | 23 | import java.nio.charset.Charset; 24 | 25 | /** 26 | * This class contains constants for the EDF/EDF+ specification. 27 | */ 28 | class EDFConstants 29 | { 30 | private EDFConstants() {} 31 | 32 | static final Charset CHARSET = Charset.forName("ASCII"); 33 | 34 | static final int IDENTIFICATION_CODE_SIZE = 8; 35 | static final int LOCAL_SUBJECT_IDENTIFICATION_SIZE = 80; 36 | static final int LOCAL_REOCRDING_IDENTIFICATION_SIZE = 80; 37 | static final int START_DATE_SIZE = 8; 38 | static final int START_TIME_SIZE = 8; 39 | static final int HEADER_SIZE = 8; 40 | static final int DATA_FORMAT_VERSION_SIZE = 44; 41 | static final int DURATION_DATA_RECORDS_SIZE = 8; 42 | static final int NUMBER_OF_DATA_RECORDS_SIZE = 8; 43 | static final int NUMBER_OF_CHANELS_SIZE = 4; 44 | 45 | static final int LABEL_OF_CHANNEL_SIZE = 16; 46 | static final int TRANSDUCER_TYPE_SIZE = 80; 47 | static final int PHYSICAL_DIMENSION_OF_CHANNEL_SIZE = 8; 48 | static final int PHYSICAL_MIN_IN_UNITS_SIZE = 8; 49 | static final int PHYSICAL_MAX_IN_UNITS_SIZE = 8; 50 | static final int DIGITAL_MIN_SIZE = 8; 51 | static final int DIGITAL_MAX_SIZE = 8; 52 | static final int PREFILTERING_SIZE = 80; 53 | static final int NUMBER_OF_SAMPLES_SIZE = 8; 54 | static final int RESERVED_SIZE = 32; 55 | 56 | /** The size of the EDF-Header-Record containing information about the recording */ 57 | static final int HEADER_SIZE_RECORDING_INFO 58 | = IDENTIFICATION_CODE_SIZE + LOCAL_SUBJECT_IDENTIFICATION_SIZE + LOCAL_REOCRDING_IDENTIFICATION_SIZE 59 | + START_DATE_SIZE + START_TIME_SIZE + HEADER_SIZE + DATA_FORMAT_VERSION_SIZE + DURATION_DATA_RECORDS_SIZE 60 | + NUMBER_OF_DATA_RECORDS_SIZE + NUMBER_OF_CHANELS_SIZE; 61 | 62 | /** The size per channel of the EDF-Header-Record containing information a channel of the recording */ 63 | static final int HEADER_SIZE_PER_CHANNEL 64 | = LABEL_OF_CHANNEL_SIZE + TRANSDUCER_TYPE_SIZE + PHYSICAL_DIMENSION_OF_CHANNEL_SIZE 65 | + PHYSICAL_MIN_IN_UNITS_SIZE + PHYSICAL_MAX_IN_UNITS_SIZE + DIGITAL_MIN_SIZE + DIGITAL_MAX_SIZE 66 | + PREFILTERING_SIZE + NUMBER_OF_SAMPLES_SIZE + RESERVED_SIZE; 67 | } 68 | -------------------------------------------------------------------------------- /src/ru/mipt/edf/EDFHeader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * (The MIT license) 3 | * 4 | * Copyright (c) 2012 MIPT (mr.santak@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 7 | * and associated documentation files (the "Software"), to deal in the Software without restriction, 8 | * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 10 | * subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all copies 13 | * or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 17 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | package ru.mipt.edf; 22 | 23 | /** 24 | * This class represents the complete header of an EDF-File. 25 | */ 26 | public class EDFHeader 27 | { 28 | String idCode = null; 29 | String subjectID = null; 30 | String recordingID = null; 31 | String startDate = null; 32 | String startTime = null; 33 | int bytesInHeader = 0; 34 | String formatVersion = null; 35 | int numberOfRecords = 0; 36 | double durationOfRecords = 0; 37 | int numberOfChannels = 0; 38 | String[] channelLabels = null; 39 | String[] transducerTypes = null; 40 | String[] dimensions = null; 41 | Double[] minInUnits = null; 42 | Double[] maxInUnits = null; 43 | Integer[] digitalMin = null; 44 | Integer[] digitalMax = null; 45 | String[] prefilterings = null; 46 | Integer[] numberOfSamples = null; 47 | byte[][] reserveds = null; 48 | 49 | public String getIdCode() 50 | { 51 | return idCode; 52 | } 53 | 54 | public String getSubjectID() 55 | { 56 | return subjectID; 57 | } 58 | 59 | public String getRecordingID() 60 | { 61 | return recordingID; 62 | } 63 | 64 | public String getStartDate() 65 | { 66 | return startDate; 67 | } 68 | 69 | public String getStartTime() 70 | { 71 | return startTime; 72 | } 73 | 74 | public int getBytesInHeader() 75 | { 76 | return bytesInHeader; 77 | } 78 | 79 | public String getFormatVersion() 80 | { 81 | return formatVersion; 82 | } 83 | 84 | public int getNumberOfRecords() 85 | { 86 | return numberOfRecords; 87 | } 88 | 89 | public double getDurationOfRecords() 90 | { 91 | return durationOfRecords; 92 | } 93 | 94 | public int getNumberOfChannels() 95 | { 96 | return numberOfChannels; 97 | } 98 | 99 | public String[] getChannelLabels() 100 | { 101 | return channelLabels; 102 | } 103 | 104 | public String[] getTransducerTypes() 105 | { 106 | return transducerTypes; 107 | } 108 | 109 | public String[] getDimensions() 110 | { 111 | return dimensions; 112 | } 113 | 114 | public Double[] getMinInUnits() 115 | { 116 | return minInUnits; 117 | } 118 | 119 | public Double[] getMaxInUnits() 120 | { 121 | return maxInUnits; 122 | } 123 | 124 | public Integer[] getDigitalMin() 125 | { 126 | return digitalMin; 127 | } 128 | 129 | public Integer[] getDigitalMax() 130 | { 131 | return digitalMax; 132 | } 133 | 134 | public String[] getPrefilterings() 135 | { 136 | return prefilterings; 137 | } 138 | 139 | public Integer[] getNumberOfSamples() 140 | { 141 | return numberOfSamples; 142 | } 143 | 144 | public byte[][] getReserveds() 145 | { 146 | return reserveds; 147 | } 148 | 149 | } 150 | -------------------------------------------------------------------------------- /src/ru/mipt/edf/EDF.java: -------------------------------------------------------------------------------- 1 | /* 2 | * (The MIT license) 3 | * 4 | * Copyright (c) 2012 MIPT (mr.santak@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 7 | * and associated documentation files (the "Software"), to deal in the Software without restriction, 8 | * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 10 | * subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all copies 13 | * or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 17 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | package ru.mipt.edf; 23 | 24 | import java.io.Closeable; 25 | import java.io.File; 26 | import java.io.FileInputStream; 27 | import java.io.FileOutputStream; 28 | import java.io.IOException; 29 | import java.io.InputStream; 30 | import java.io.OutputStream; 31 | import java.text.MessageFormat; 32 | import java.util.List; 33 | import java.util.Scanner; 34 | 35 | import javax.swing.JFileChooser; 36 | import javax.swing.UIManager; 37 | import javax.swing.UnsupportedLookAndFeelException; 38 | 39 | public class EDF 40 | { 41 | private static EDFParserResult result = null; 42 | 43 | public static void main(String... args) throws IOException, ClassNotFoundException, InstantiationException, 44 | IllegalAccessException, UnsupportedLookAndFeelException 45 | { 46 | File file; 47 | if (args.length == 0) 48 | { 49 | UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); 50 | JFileChooser fileChooser = new JFileChooser(); 51 | if (fileChooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) 52 | file = fileChooser.getSelectedFile(); 53 | else 54 | return; 55 | } else 56 | file = new File(args[0]); 57 | 58 | new File(file.getParent() + "/data").getAbsoluteFile().mkdir(); 59 | 60 | InputStream is = null; 61 | FileOutputStream fos = null; 62 | InputStream format = null; 63 | try 64 | { 65 | is = new FileInputStream(file); 66 | fos = new FileOutputStream(file.getParent() + "/" + file.getName().replaceAll("[.].*", "_header.txt")); 67 | format = EDFParser.class.getResourceAsStream("header.format"); 68 | result = EDFParser.parseEDF(is); 69 | writeHeaderData(fos, getPattern(format)); 70 | } finally 71 | { 72 | close(is); 73 | close(fos); 74 | close(format); 75 | } 76 | String channelFormat = null; 77 | format = null; 78 | try 79 | { 80 | format = EDFParser.class.getResourceAsStream("channel_info.format"); 81 | channelFormat = getPattern(format); 82 | } finally 83 | { 84 | close(format); 85 | } 86 | 87 | for (int i = 0; i < result.getHeader().getNumberOfChannels(); i++) 88 | { 89 | try 90 | { 91 | fos = new FileOutputStream(file.getParent() + "/" 92 | + file.getName().replaceAll("[.].*", "_channel_info_" + i + ".txt")); 93 | writeChannelData(fos, channelFormat, i); 94 | } finally 95 | { 96 | close(fos); 97 | } 98 | 99 | try 100 | { 101 | fos = new FileOutputStream(file.getParent() + "/data/" 102 | + file.getName().replaceAll("[.].*", "_" + i + ".txt")); 103 | for (int j = 0; j < result.getSignal().getValuesInUnits()[i].length; j++) 104 | fos.write((result.getSignal().getValuesInUnits()[i][j] + "\n").getBytes("UTF-8")); 105 | } finally 106 | { 107 | close(fos); 108 | } 109 | } 110 | List annotations = result.getAnnotations(); 111 | if (annotations == null || annotations.size() == 0) 112 | return; 113 | try 114 | { 115 | fos = new FileOutputStream(file.getParent() + "/" + file.getName().replaceAll("[.].*", "_annotation.txt")); 116 | for (EDFAnnotation annotation : annotations) 117 | { 118 | if (annotation.getAnnotations().size() != 0) 119 | { 120 | StringBuffer buffer = new StringBuffer(); 121 | buffer.append(annotation.getOnSet()).append(";").append(annotation.getDuration()); 122 | for (int i = 0; i < annotation.getAnnotations().size(); i++) 123 | buffer.append(";").append(annotation.getAnnotations().get(i)); 124 | buffer.append("\n"); 125 | fos.write(buffer.toString().getBytes()); 126 | } 127 | } 128 | } finally 129 | { 130 | close(fos); 131 | } 132 | } 133 | 134 | private static void writeHeaderData(OutputStream os, String pattern) throws IOException 135 | { 136 | String message = MessageFormat.format(pattern, result.getHeader().getIdCode().trim(), result.getHeader() 137 | .getSubjectID().trim(), result.getHeader().getRecordingID().trim(), result.getHeader().getStartDate() 138 | .trim(), result.getHeader().getStartTime().trim(), result.getHeader().getBytesInHeader(), result 139 | .getHeader().getFormatVersion().trim(), result.getHeader().getNumberOfRecords(), result.getHeader() 140 | .getDurationOfRecords(), result.getHeader().getNumberOfChannels()); 141 | os.write(message.getBytes("UTF-8")); 142 | } 143 | 144 | private static void writeChannelData(OutputStream os, String pattern, int i) throws IOException 145 | { 146 | String message = MessageFormat.format(pattern, result.getHeader().getChannelLabels()[i].trim(), result 147 | .getHeader().getTransducerTypes()[i].trim(), result.getHeader().getDimensions()[i].trim(), result 148 | .getHeader().getMinInUnits()[i], result.getHeader().getMaxInUnits()[i], result.getHeader() 149 | .getDigitalMin()[i], result.getHeader().getDigitalMax()[i], result.getHeader().getPrefilterings()[i] 150 | .trim(), result.getHeader().getNumberOfSamples()[i], new String(result.getHeader().getReserveds()[i]) 151 | .trim()); 152 | os.write(message.getBytes("UTF-8")); 153 | } 154 | 155 | private static String getPattern(InputStream is) 156 | { 157 | StringBuilder str = new StringBuilder(); 158 | Scanner scn = null; 159 | try 160 | { 161 | scn = new Scanner(is); 162 | while (scn.hasNextLine()) 163 | str.append(scn.nextLine()).append("\n"); 164 | } finally 165 | { 166 | close(scn); 167 | } 168 | return str.toString(); 169 | } 170 | 171 | private static final void close(Closeable c) 172 | { 173 | try 174 | { 175 | c.close(); 176 | } catch (Exception e) 177 | { 178 | // do nothing 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/ru/mipt/edf/EDFWriter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * (The MIT license) 3 | * 4 | * Copyright (c) 2012 - 2015 Wolfgang Halbeisen (halbeisen.wolfgang@gmail.com) and Codemart (beniamin.oniga@codemart.ro, lia.domide@codemart.ro) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 7 | * and associated documentation files (the "Software"), to deal in the Software without restriction, 8 | * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 10 | * subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all copies 13 | * or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 17 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | package ru.mipt.edf; 22 | 23 | import java.io.IOException; 24 | import java.io.OutputStream; 25 | import java.nio.ByteBuffer; 26 | import java.nio.ByteOrder; 27 | import java.text.DecimalFormat; 28 | import java.text.DecimalFormatSymbols; 29 | 30 | import static ru.mipt.edf.EDFConstants.*; 31 | 32 | /** 33 | * This class is capable of writing EDF+ data structures. 34 | * Changed for fixing issue #3 from Github: https://github.com/MIOB/EDF4J/issues/3 35 | */ 36 | public class EDFWriter 37 | { 38 | public static final String SHORT_DECIMAL_FORMAT = "#0.0"; 39 | public static final String LONG_DECIMAL_FORMAT = "#0.0####"; 40 | 41 | /** 42 | * Writes the EDFHeader into the OutputStream. 43 | * 44 | * @param header The header to write 45 | * @param outputStream The OutputStream to write into 46 | * @throws IOException Will be thrown if it is not possible to write into the outputStream 47 | */ 48 | public static void writeIntoOutputStream(EDFHeader header, OutputStream outputStream) throws IOException 49 | { 50 | DecimalFormatSymbols dfs = new DecimalFormatSymbols(); 51 | dfs.setDecimalSeparator('.'); 52 | DecimalFormat shortFormatter = new DecimalFormat(SHORT_DECIMAL_FORMAT, dfs); 53 | DecimalFormat longFormatter = new DecimalFormat(LONG_DECIMAL_FORMAT, dfs); 54 | 55 | ByteBuffer bb = ByteBuffer.allocate(header.bytesInHeader); 56 | putIntoBuffer(bb, IDENTIFICATION_CODE_SIZE, header.idCode); 57 | putIntoBuffer(bb, LOCAL_SUBJECT_IDENTIFICATION_SIZE, header.subjectID); 58 | putIntoBuffer(bb, LOCAL_REOCRDING_IDENTIFICATION_SIZE, header.recordingID); 59 | putIntoBuffer(bb, START_DATE_SIZE, header.startDate); 60 | putIntoBuffer(bb, START_TIME_SIZE, header.startTime); 61 | putIntoBuffer(bb, HEADER_SIZE, header.bytesInHeader); 62 | putIntoBuffer(bb, DATA_FORMAT_VERSION_SIZE, header.formatVersion); 63 | putIntoBuffer(bb, NUMBER_OF_DATA_RECORDS_SIZE, header.numberOfRecords); 64 | putIntoBuffer(bb, DURATION_DATA_RECORDS_SIZE, header.durationOfRecords, longFormatter); 65 | putIntoBuffer(bb, NUMBER_OF_CHANELS_SIZE, header.numberOfChannels); 66 | 67 | putIntoBuffer(bb, LABEL_OF_CHANNEL_SIZE, header.channelLabels); 68 | putIntoBuffer(bb, TRANSDUCER_TYPE_SIZE, header.transducerTypes); 69 | putIntoBuffer(bb, PHYSICAL_DIMENSION_OF_CHANNEL_SIZE, header.dimensions); 70 | putIntoBuffer(bb, PHYSICAL_MIN_IN_UNITS_SIZE, header.minInUnits, shortFormatter); 71 | putIntoBuffer(bb, PHYSICAL_MAX_IN_UNITS_SIZE, header.maxInUnits, shortFormatter); 72 | putIntoBuffer(bb, DIGITAL_MIN_SIZE, header.digitalMin); 73 | putIntoBuffer(bb, DIGITAL_MAX_SIZE, header.digitalMax); 74 | putIntoBuffer(bb, PREFILTERING_SIZE, header.prefilterings); 75 | putIntoBuffer(bb, NUMBER_OF_SAMPLES_SIZE, header.numberOfSamples); 76 | putIntoBuffer(bb, header.reserveds); 77 | 78 | outputStream.write(bb.array()); 79 | } 80 | 81 | /** 82 | * Write the signals in output stream 83 | * 84 | * @param edfSignal The signals to write 85 | * @param header The header of EDF file 86 | * @param outputStream The OutputStream to write into 87 | * @throws IOException Will be thrown if it is not possible to write into the outputStream 88 | */ 89 | public static void writeIntoOutputStream(EDFSignal edfSignal, EDFHeader header, OutputStream outputStream) 90 | throws IOException { 91 | 92 | short[] data = buildDataArray(edfSignal.getDigitalValues(), header); 93 | writeIntoOutputStream(data, outputStream); 94 | } 95 | 96 | /** 97 | * Write signals data in output stream 98 | * 99 | * @param data The signals in edf short array format 100 | * @param outputStream The OutputStream to write into 101 | * @throws IOException Will be thrown if it is not possible to write into the outputStream 102 | */ 103 | public static void writeIntoOutputStream(short[] data, OutputStream outputStream) 104 | throws IOException { 105 | 106 | ByteBuffer bb = ByteBuffer.allocate(data.length * 2); 107 | bb.order(ByteOrder.LITTLE_ENDIAN); 108 | putIntoBuffer(bb, data); 109 | outputStream.write(bb.array()); 110 | } 111 | 112 | /** 113 | * Convert data signals from two dimensions format ( {channels} {time, samples} ) to one 114 | * dimension format ( channels {samples for each channel} grouped by time ) 115 | * 116 | * @param digitalValues The signals data in two dimensions format 117 | * @param header The header of edf format 118 | * @return The signals data in one dimensions format 119 | */ 120 | public static short[] buildDataArray(short[][] digitalValues, EDFHeader header) { 121 | int index; 122 | int totalDataLength = 0; 123 | int previousSamples = 0; 124 | int timeChunkLength = 0; 125 | 126 | // compute the total length of the new short array 127 | for (short[] digitalValue : digitalValues) { 128 | totalDataLength += digitalValue.length; 129 | } 130 | short[] signalsData = new short[totalDataLength]; 131 | 132 | 133 | for (Integer sample : header.getNumberOfSamples()) { 134 | timeChunkLength += sample; 135 | } 136 | 137 | // build the signals array, which is a short one 138 | for (int channel = 0; channel < digitalValues.length; channel++) { 139 | short[] channelValues = digitalValues[channel]; 140 | int noOfSamples = header.getNumberOfSamples()[channel]; 141 | 142 | for (int t = 0; t < header.getNumberOfRecords(); t++) { 143 | 144 | for (int sample = 0; sample < noOfSamples; sample++) { 145 | short shortValue = channelValues[t * noOfSamples + sample]; 146 | index = t * timeChunkLength + previousSamples + sample; 147 | signalsData[index] = shortValue; 148 | } 149 | 150 | } 151 | previousSamples += noOfSamples; 152 | } 153 | return signalsData; 154 | } 155 | 156 | private static void putIntoBuffer(ByteBuffer bb, int lengthPerValue, Double[] values, DecimalFormat df) 157 | { 158 | for (Double value : values) 159 | { 160 | putIntoBuffer(bb, lengthPerValue, value, df); 161 | } 162 | } 163 | 164 | private static void putIntoBuffer(ByteBuffer bb, int length, Double value, DecimalFormat df) 165 | { 166 | if (Math.floor(value) == value) { 167 | putIntoBuffer(bb, length, value.intValue()); 168 | } else { 169 | putIntoBuffer(bb, length, df.format(value)); 170 | } 171 | } 172 | 173 | private static void putIntoBuffer(ByteBuffer bb, int lengthPerValue, Integer[] values) 174 | { 175 | for (Integer value : values) 176 | { 177 | putIntoBuffer(bb, lengthPerValue, value); 178 | } 179 | } 180 | 181 | private static void putIntoBuffer(ByteBuffer bb, int length, int value) 182 | { 183 | putIntoBuffer(bb, length, String.valueOf(value)); 184 | } 185 | 186 | private static void putIntoBuffer(ByteBuffer bb, int lengthPerValue, String[] values) 187 | { 188 | for (String value : values) 189 | { 190 | putIntoBuffer(bb, lengthPerValue, value); 191 | } 192 | } 193 | 194 | private static void putIntoBuffer(ByteBuffer bb, int length, String value) 195 | { 196 | ByteBuffer valueBuffer = ByteBuffer.allocate(length); 197 | valueBuffer.put(value.getBytes(EDFConstants.CHARSET)); 198 | while (valueBuffer.remaining() > 0) { 199 | valueBuffer.put(" ".getBytes()); 200 | } 201 | 202 | valueBuffer.rewind(); 203 | bb.put(valueBuffer); 204 | } 205 | 206 | private static void putIntoBuffer(ByteBuffer bb, byte[][] values) 207 | { 208 | for (byte[] val : values) 209 | { 210 | bb.put(val); 211 | } 212 | } 213 | 214 | private static void putIntoBuffer(ByteBuffer bb, short[] values) 215 | { 216 | for (short val : values) 217 | { 218 | bb.putShort(val); 219 | } 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/ru/mipt/edf/EDFAnnotationFileHeaderBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * (The MIT license) 3 | * 4 | * Copyright (c) 2012 - 2015 Wolfgang Halbeisen (halbeisen.wolfgang@gmail.com) and Codemart (beniamin.oniga@codemart.ro, lia.domide@codemart.ro) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 7 | * and associated documentation files (the "Software"), to deal in the Software without restriction, 8 | * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 10 | * subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all copies 13 | * or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 17 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | package ru.mipt.edf; 22 | 23 | import java.text.SimpleDateFormat; 24 | import java.util.Arrays; 25 | import java.util.Date; 26 | import static ru.mipt.edf.EDFConstants.*; 27 | 28 | /** 29 | * This builder is capable of building an EDFHeader for an EDF+ file 30 | * which will contain annotations. 31 | * 32 | * The annotations has to be available in an array according to the EDF+ specification. 33 | * Changed for issue #3 from Github: https://github.com/MIOB/EDF4J/issues/3 34 | */ 35 | public class EDFAnnotationFileHeaderBuilder 36 | { 37 | private String recordingId; 38 | private String recordingStartDate; 39 | private String startDate; 40 | private String startTime; 41 | private double durationOfRecord; 42 | private Integer numberOfChannels; 43 | private Integer numberOfRecords; 44 | 45 | private String patientCode = "X"; 46 | private String patientSex = "X"; 47 | private String patientBirthdate = "X"; 48 | private String patientName = "X"; 49 | private String recordingHospital = "X"; 50 | private String recordingTechnician = "X"; 51 | private String recordingEquipment = "X"; 52 | private final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd.MM.yy"); 53 | private final SimpleDateFormat simpleTimeFormat = new SimpleDateFormat("HH.mm.ss"); 54 | 55 | private String[] channelLabels; 56 | private String[] transducerTypes; 57 | private String[] dimensions; 58 | private Double[] minInUnits; 59 | private Double[] maxInUnits; 60 | private Integer[] digitalMin; 61 | private Integer[] digitalMax; 62 | private String[] prefilterings; 63 | private Integer[] numberOfSamples; 64 | private byte[][] reserveds; 65 | 66 | public EDFAnnotationFileHeaderBuilder recordingId(String recordingId) 67 | { 68 | assert recordingId != null; 69 | this.recordingId = nonSpaceString(recordingId); 70 | return this; 71 | } 72 | 73 | public EDFAnnotationFileHeaderBuilder startOfRecording(Date startOfRecording) 74 | { 75 | assert startOfRecording != null; 76 | recordingStartDate = new SimpleDateFormat("dd-MMM-yyyy").format(startOfRecording).toUpperCase(); 77 | startDate = simpleDateFormat.format(startOfRecording); 78 | startTime = simpleTimeFormat.format(startOfRecording); 79 | return this; 80 | } 81 | 82 | public EDFAnnotationFileHeaderBuilder durationOfRecord(double val) 83 | { 84 | assert val > 0; 85 | durationOfRecord = val; 86 | return this; 87 | } 88 | 89 | public EDFAnnotationFileHeaderBuilder patientCode(String val) 90 | { 91 | assert val != null; 92 | patientCode = nonSpaceString(val); 93 | return this; 94 | } 95 | 96 | public EDFAnnotationFileHeaderBuilder patientIsMale(boolean val) 97 | { 98 | patientSex = val ? "M" : "F"; 99 | return this; 100 | } 101 | 102 | public EDFAnnotationFileHeaderBuilder patientBirthdate(Date birthdate) 103 | { 104 | assert birthdate != null; 105 | patientBirthdate = new SimpleDateFormat("dd-MMM-yyyy").format(birthdate).toUpperCase(); 106 | return this; 107 | } 108 | 109 | public EDFAnnotationFileHeaderBuilder patientName(String val) 110 | { 111 | assert val != null; 112 | patientName = nonSpaceString(val); 113 | return this; 114 | } 115 | 116 | public EDFAnnotationFileHeaderBuilder recordingHospital(String val) 117 | { 118 | assert val != null; 119 | recordingHospital = nonSpaceString(val); 120 | return this; 121 | } 122 | 123 | public EDFAnnotationFileHeaderBuilder recordingTechnician(String val) 124 | { 125 | assert val != null; 126 | recordingTechnician = nonSpaceString(val); 127 | return this; 128 | } 129 | 130 | public EDFAnnotationFileHeaderBuilder recordingEquipment(String val) 131 | { 132 | assert val != null; 133 | recordingEquipment = nonSpaceString(val); 134 | return this; 135 | } 136 | 137 | public void numberOfChannels(int val) 138 | { 139 | assert val > 0; 140 | numberOfChannels = val; 141 | } 142 | 143 | public void numberOfRecords(int val) 144 | { 145 | assert val > 0; 146 | numberOfRecords = val; 147 | } 148 | 149 | public EDFAnnotationFileHeaderBuilder channelLabels(String[] channelLabels) 150 | { 151 | this.channelLabels = channelLabels; 152 | return this; 153 | } 154 | 155 | public EDFAnnotationFileHeaderBuilder transducerTypes(String[] transducerTypes) 156 | { 157 | this.transducerTypes = transducerTypes; 158 | return this; 159 | } 160 | 161 | public EDFAnnotationFileHeaderBuilder dimensions(String[] dimensions) 162 | { 163 | this.dimensions = dimensions; 164 | return this; 165 | } 166 | 167 | public EDFAnnotationFileHeaderBuilder minInUnits(Double[] minInUnits) 168 | { 169 | this.minInUnits = minInUnits; 170 | return this; 171 | } 172 | 173 | public EDFAnnotationFileHeaderBuilder maxInUnits(Double[] maxInUnits) 174 | { 175 | this.maxInUnits = maxInUnits; 176 | return this; 177 | } 178 | 179 | public EDFAnnotationFileHeaderBuilder digitalMin(Integer[] digitalMin) 180 | { 181 | this.digitalMin = digitalMin; 182 | return this; 183 | } 184 | 185 | public EDFAnnotationFileHeaderBuilder digitalMax(Integer[] digitalMax) 186 | { 187 | this.digitalMax = digitalMax; 188 | return this; 189 | } 190 | 191 | public EDFAnnotationFileHeaderBuilder prefilterings(String[] prefilterings) 192 | { 193 | this.prefilterings = prefilterings; 194 | return this; 195 | } 196 | 197 | public EDFAnnotationFileHeaderBuilder numberOfSamples(Integer[] numberOfSamples) 198 | { 199 | this.numberOfSamples = numberOfSamples; 200 | return this; 201 | } 202 | 203 | public EDFAnnotationFileHeaderBuilder reserveds(byte[][] reserveds) 204 | { 205 | this.reserveds = reserveds; 206 | return this; 207 | } 208 | 209 | private String nonSpaceString(String val) 210 | { 211 | return val.replaceAll(" ", "_"); 212 | } 213 | 214 | public EDFHeader build() 215 | { 216 | assert recordingStartDate != null; 217 | assert startDate != null; 218 | assert startTime != null; 219 | assert durationOfRecord > 0; 220 | 221 | EDFHeader header = new EDFHeader(); 222 | header.idCode = createStringWithSpaces(String.valueOf(0), IDENTIFICATION_CODE_SIZE); 223 | header.subjectID = createStringWithSpaces(buildPatientString(), LOCAL_SUBJECT_IDENTIFICATION_SIZE); 224 | header.recordingID = recordingId != null ? recordingId : createStringWithSpaces(buildRecordingString(), LOCAL_REOCRDING_IDENTIFICATION_SIZE); 225 | 226 | header.startDate = startDate != null ? startDate : simpleDateFormat.format(new Date()); 227 | header.startDate = appendSpacesToString(header.startDate, START_DATE_SIZE - header.startDate.length()); 228 | 229 | header.startTime = startTime != null ? startTime : simpleTimeFormat.format(new Date()); 230 | header.startTime = appendSpacesToString(header.startTime, START_TIME_SIZE - header.startTime.length()); 231 | 232 | header.formatVersion = createStringWithSpaces("", DATA_FORMAT_VERSION_SIZE); 233 | header.numberOfRecords = numberOfRecords != null ? numberOfRecords : 1; 234 | header.durationOfRecords = durationOfRecord; 235 | header.numberOfChannels = numberOfChannels != null ? numberOfChannels : 1; 236 | header.bytesInHeader = EDFConstants.HEADER_SIZE_RECORDING_INFO + 237 | header.numberOfChannels * EDFConstants.HEADER_SIZE_PER_CHANNEL; 238 | 239 | header.channelLabels = channelLabels; 240 | header.transducerTypes = transducerTypes; 241 | header.dimensions = dimensions; 242 | header.minInUnits = minInUnits; 243 | header.maxInUnits = maxInUnits; 244 | header.digitalMin = digitalMin; 245 | header.digitalMax = digitalMax; 246 | header.prefilterings = prefilterings; 247 | header.numberOfSamples = numberOfSamples; 248 | header.reserveds = reserveds; 249 | 250 | return header; 251 | } 252 | 253 | private String createStringWithSpaces(String root, int totalSize) { 254 | 255 | return appendSpacesToString(root, totalSize - root.length()); 256 | } 257 | 258 | private String appendSpacesToString(String original, int times) { 259 | 260 | char[] repeat = new char[times]; 261 | Arrays.fill(repeat, ' '); 262 | return original + new String(repeat); 263 | } 264 | 265 | private String buildPatientString() 266 | { 267 | return patientCode + " " + patientSex + " " + patientBirthdate + " " + patientName; 268 | } 269 | 270 | private String buildRecordingString() 271 | { 272 | return "Startdate" + " " + recordingStartDate + " " + recordingHospital + " " + recordingTechnician + 273 | " " + recordingEquipment; 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /src/ru/mipt/edf/EDFParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * (The MIT license) 3 | * 4 | * Copyright (c) 2012 MIPT (mr.santak@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 7 | * and associated documentation files (the "Software"), to deal in the Software without restriction, 8 | * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 10 | * subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all copies 13 | * or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 17 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | package ru.mipt.edf; 22 | 23 | import java.io.IOException; 24 | import java.io.InputStream; 25 | import java.nio.ByteBuffer; 26 | import java.nio.ByteOrder; 27 | import java.nio.channels.Channels; 28 | import java.nio.channels.ReadableByteChannel; 29 | import java.util.ArrayList; 30 | import java.util.List; 31 | 32 | import static ru.mipt.edf.EDFConstants.*; 33 | 34 | /** 35 | * This is an EDFParser which is capable of parsing files in the formats EDF and 36 | * EDF+. 37 | * 38 | * For information about EDF or EDF+ see http://www.edfplus.info/ 39 | */ 40 | public class EDFParser 41 | { 42 | /** 43 | * Parse the InputStream which should be at the start of an EDF-File. The 44 | * method returns an object containing the complete content of the EDF-File. 45 | * 46 | * @param is 47 | * the InputStream to the EDF-File 48 | * @return the parsed result 49 | * @throws EDFParserException 50 | * if there is an error during parsing 51 | */ 52 | public static EDFParserResult parseEDF(InputStream is) throws EDFParserException 53 | { 54 | EDFParserResult result = parseHeader(is); 55 | parseSignal(is, result); 56 | 57 | return result; 58 | } 59 | 60 | /** 61 | * Parse the InputStream which should be at the start of an EDF-File. The 62 | * method returns an object containing the complete header of the EDF-File 63 | * 64 | * @param is 65 | * the InputStream to the EDF-File 66 | * @return the parsed result 67 | * @throws EDFParserException 68 | * if there is an error during parsing 69 | */ 70 | public static EDFParserResult parseHeader(InputStream is) throws EDFParserException 71 | { 72 | try 73 | { 74 | EDFHeader header = new EDFHeader(); 75 | EDFParserResult result = new EDFParserResult(); 76 | result.header = header; 77 | 78 | header.idCode = ParseUtils.readASCIIFromStream(is, IDENTIFICATION_CODE_SIZE); 79 | if (!header.idCode.trim().equals("0")) { 80 | throw new EDFParserException(); 81 | } 82 | header.subjectID = ParseUtils.readASCIIFromStream(is, LOCAL_SUBJECT_IDENTIFICATION_SIZE); 83 | header.recordingID = ParseUtils.readASCIIFromStream(is, LOCAL_REOCRDING_IDENTIFICATION_SIZE); 84 | header.startDate = ParseUtils.readASCIIFromStream(is, START_DATE_SIZE); 85 | header.startTime = ParseUtils.readASCIIFromStream(is, START_TIME_SIZE); 86 | header.bytesInHeader = Integer.parseInt(ParseUtils.readASCIIFromStream(is, HEADER_SIZE).trim()); 87 | header.formatVersion = ParseUtils.readASCIIFromStream(is, DATA_FORMAT_VERSION_SIZE); 88 | header.numberOfRecords = Integer.parseInt( 89 | ParseUtils.readASCIIFromStream(is, NUMBER_OF_DATA_RECORDS_SIZE).trim()); 90 | header.durationOfRecords = Double.parseDouble( 91 | ParseUtils.readASCIIFromStream(is, DURATION_DATA_RECORDS_SIZE).trim()); 92 | header.numberOfChannels = Integer.parseInt( 93 | ParseUtils.readASCIIFromStream(is, NUMBER_OF_CHANELS_SIZE).trim()); 94 | 95 | parseChannelInformation(is, result); 96 | 97 | return result; 98 | } catch (IOException e) 99 | { 100 | throw new EDFParserException(e); 101 | } 102 | } 103 | 104 | /** 105 | * Parse only data EDF file. This method should be invoked only after 106 | * parseHeader method. 107 | * It will be populated in result parameter. 108 | * 109 | * @param is 110 | * stream with EDF file. 111 | * @param result 112 | * results from {parseHeader(is) parseHeader} method 113 | * @throws EDFParserException 114 | * throws if parser don't recognized EDF (EDF+) format in 115 | * stream. 116 | */ 117 | private static void parseSignal(InputStream is, EDFParserResult result) throws EDFParserException 118 | { 119 | try 120 | { 121 | EDFSignal signal = new EDFSignal(); 122 | EDFHeader header = result.getHeader(); 123 | 124 | signal.unitsInDigit = new Double[header.numberOfChannels]; 125 | for (int i = 0; i < signal.unitsInDigit.length; i++) 126 | signal.unitsInDigit[i] = (header.maxInUnits[i] - header.minInUnits[i]) 127 | / (header.digitalMax[i] - header.digitalMin[i]); 128 | 129 | signal.digitalValues = new short[header.numberOfChannels][]; 130 | signal.valuesInUnits = new double[header.numberOfChannels][]; 131 | for (int i = 0; i < header.numberOfChannels; i++) 132 | { 133 | signal.digitalValues[i] = new short[header.numberOfRecords * header.numberOfSamples[i]]; 134 | signal.valuesInUnits[i] = new double[header.numberOfRecords * header.numberOfSamples[i]]; 135 | } 136 | 137 | int samplesPerRecord = 0; 138 | for (int nos : header.numberOfSamples) 139 | { 140 | samplesPerRecord += nos; 141 | } 142 | 143 | ReadableByteChannel ch = Channels.newChannel(is); 144 | ByteBuffer bytebuf = ByteBuffer.allocate(samplesPerRecord * 2); 145 | bytebuf.order(ByteOrder.LITTLE_ENDIAN); 146 | 147 | for (int i = 0; i < header.numberOfRecords; i++) 148 | { 149 | bytebuf.rewind(); 150 | ch.read(bytebuf); 151 | bytebuf.rewind(); 152 | for (int j = 0; j < header.numberOfChannels; j++) 153 | for (int k = 0; k < header.numberOfSamples[j]; k++) 154 | { 155 | int s = header.numberOfSamples[j] * i + k; 156 | signal.digitalValues[j][s] = bytebuf.getShort(); 157 | signal.valuesInUnits[j][s] = signal.digitalValues[j][s] * signal.unitsInDigit[j]; 158 | } 159 | } 160 | 161 | result.annotations = parseAnnotation(header, signal); 162 | 163 | result.signal = signal; 164 | } catch (IOException e) 165 | { 166 | throw new EDFParserException(e); 167 | } 168 | } 169 | 170 | private static List parseAnnotation(EDFHeader header, EDFSignal signal) 171 | { 172 | 173 | if (!header.formatVersion.startsWith("EDF+")) 174 | return null; 175 | 176 | int annotationIndex = -1; 177 | for (int i = 0; i < header.numberOfChannels; i++) 178 | { 179 | if ("EDF Annotations".equals(header.channelLabels[i].trim())) 180 | { 181 | annotationIndex = i; 182 | break; 183 | } 184 | } 185 | if (annotationIndex == -1) 186 | return null; 187 | 188 | short[] s = signal.digitalValues[annotationIndex]; 189 | byte[] b = new byte[s.length * 2]; 190 | for (int i = 0; i < s.length * 2; i += 2) 191 | { 192 | b[i] = (byte) (s[i / 2] % 256); 193 | b[i + 1] = (byte) (s[i / 2] / 256 % 256); 194 | } 195 | 196 | removeAnnotationSignal(header, signal, annotationIndex); 197 | 198 | return parseAnnotations(b); 199 | 200 | } 201 | 202 | private static List parseAnnotations(byte[] b) 203 | { 204 | List annotations = new ArrayList<>(); 205 | int onSetIndex = 0; 206 | int durationIndex = -1; 207 | int annotationIndex = -2; 208 | int endIndex = -3; 209 | for (int i = 0; i < b.length - 1; i++) 210 | { 211 | if (b[i] == 21) 212 | { 213 | durationIndex = i; 214 | continue; 215 | } 216 | if (b[i] == 20 && onSetIndex > annotationIndex) 217 | { 218 | annotationIndex = i; 219 | continue; 220 | } 221 | if (b[i] == 20 && b[i + 1] == 0) 222 | { 223 | endIndex = i; 224 | continue; 225 | } 226 | if (b[i] != 0 && onSetIndex < endIndex) 227 | { 228 | 229 | String onSet; 230 | String duration; 231 | if (durationIndex > onSetIndex) 232 | { 233 | onSet = new String(b, onSetIndex, durationIndex - onSetIndex); 234 | duration = new String(b, durationIndex, annotationIndex - durationIndex); 235 | } else 236 | { 237 | onSet = new String(b, onSetIndex, annotationIndex - onSetIndex); 238 | duration = ""; 239 | } 240 | String annotation = new String(b, annotationIndex, endIndex - annotationIndex); 241 | annotations.add(new EDFAnnotation(onSet, duration, annotation.split("[\u0014]"))); 242 | onSetIndex = i; 243 | } 244 | } 245 | return annotations; 246 | } 247 | 248 | private static void removeAnnotationSignal(EDFHeader header, EDFSignal signal, int annotationIndex) 249 | { 250 | header.numberOfChannels--; 251 | ParseUtils.removeElement(header.channelLabels, annotationIndex); 252 | ParseUtils.removeElement(header.transducerTypes, annotationIndex); 253 | ParseUtils.removeElement(header.dimensions, annotationIndex); 254 | ParseUtils.removeElement(header.minInUnits, annotationIndex); 255 | ParseUtils.removeElement(header.maxInUnits, annotationIndex); 256 | ParseUtils.removeElement(header.digitalMin, annotationIndex); 257 | ParseUtils.removeElement(header.digitalMax, annotationIndex); 258 | ParseUtils.removeElement(header.prefilterings, annotationIndex); 259 | ParseUtils.removeElement(header.numberOfSamples, annotationIndex); 260 | ParseUtils.removeElement(header.reserveds, annotationIndex); 261 | 262 | ParseUtils.removeElement(signal.digitalValues, annotationIndex); 263 | ParseUtils.removeElement(signal.unitsInDigit, annotationIndex); 264 | ParseUtils.removeElement(signal.valuesInUnits, annotationIndex); 265 | } 266 | 267 | private static void parseChannelInformation(InputStream is, EDFParserResult result) throws EDFParserException 268 | { 269 | try 270 | { 271 | EDFHeader header = result.getHeader(); 272 | int numberOfChannels = header.numberOfChannels; 273 | header.channelLabels = ParseUtils 274 | .readBulkASCIIFromStream(is, LABEL_OF_CHANNEL_SIZE, numberOfChannels); 275 | header.transducerTypes = ParseUtils 276 | .readBulkASCIIFromStream(is, TRANSDUCER_TYPE_SIZE, numberOfChannels); 277 | header.dimensions = ParseUtils 278 | .readBulkASCIIFromStream(is, PHYSICAL_DIMENSION_OF_CHANNEL_SIZE, numberOfChannels); 279 | header.minInUnits = ParseUtils 280 | .readBulkDoubleFromStream(is, PHYSICAL_MIN_IN_UNITS_SIZE, numberOfChannels); 281 | header.maxInUnits = ParseUtils 282 | .readBulkDoubleFromStream(is, PHYSICAL_MAX_IN_UNITS_SIZE, numberOfChannels); 283 | header.digitalMin = ParseUtils.readBulkIntFromStream(is, DIGITAL_MIN_SIZE, numberOfChannels); 284 | header.digitalMax = ParseUtils.readBulkIntFromStream(is, DIGITAL_MAX_SIZE, numberOfChannels); 285 | header.prefilterings = ParseUtils 286 | .readBulkASCIIFromStream(is, PREFILTERING_SIZE, numberOfChannels); 287 | header.numberOfSamples = ParseUtils 288 | .readBulkIntFromStream(is, NUMBER_OF_SAMPLES_SIZE, numberOfChannels); 289 | header.reserveds = new byte[numberOfChannels][]; 290 | for (int i = 0; i < header.reserveds.length; i++) 291 | { 292 | header.reserveds[i] = new byte[RESERVED_SIZE]; 293 | is.read(header.reserveds[i]); 294 | } 295 | } catch (IOException e) 296 | { 297 | throw new EDFParserException(e); 298 | } 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /src/test/ru/mipt/edf/EDFWriterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * (The MIT license) 3 | * 4 | * Copyright (c) 2012 - 2015 MIPT (mr.santak@gmail.com) and Codemart (beniamin.oniga@codemart.ro, lia.domide@codemart.ro) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 7 | * and associated documentation files (the "Software"), to deal in the Software without restriction, 8 | * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 10 | * subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all copies 13 | * or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 17 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | package ru.mipt.edf; 22 | 23 | import org.junit.After; 24 | import org.junit.Before; 25 | import org.junit.Test; 26 | import java.io.*; 27 | import java.net.URL; 28 | import java.nio.BufferOverflowException; 29 | import java.nio.file.Files; 30 | import java.nio.file.Path; 31 | import java.nio.file.Paths; 32 | import java.util.ArrayList; 33 | import java.util.Arrays; 34 | import java.util.Date; 35 | import java.util.List; 36 | import static org.junit.Assert.assertArrayEquals; 37 | import static org.junit.Assert.assertEquals; 38 | import static org.junit.Assert.assertTrue; 39 | 40 | public class EDFWriterTest { 41 | 42 | private static final String EDF_INPUT_FILE = "test_generator.edf"; 43 | private static final String EDF_OUTPUT_FILE = "outputEdf.edf"; 44 | private static final String INVALID_EDF = "invalid.edf"; 45 | 46 | @Before 47 | @After 48 | public void cleanup() { 49 | 50 | try { 51 | Path outputFilePath = Paths.get(EDF_OUTPUT_FILE); 52 | Files.delete(outputFilePath); 53 | 54 | } catch (Exception ex) { 55 | // ignore, as not being relevant. 56 | } 57 | } 58 | 59 | @Test 60 | public void writeAndReadHeaderShouldReturnTheSameHeader() throws IOException { 61 | 62 | EDFHeader header = buildHeader(); 63 | 64 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 65 | EDFWriter.writeIntoOutputStream(header, out); 66 | 67 | EDFHeader parsedHeader = EDFParser.parseHeader(new ByteArrayInputStream(out.toByteArray())).getHeader(); 68 | assertHeader(header, parsedHeader); 69 | } 70 | 71 | @Test 72 | public void testEdfWriter() throws Exception { 73 | 74 | Path outputFilePath = Paths.get(EDF_OUTPUT_FILE); 75 | Files.createFile(outputFilePath); 76 | 77 | // read and parse a real edf file 78 | URL resource = getClass().getClassLoader().getResource(EDF_INPUT_FILE); 79 | assert resource != null; 80 | Path edfInputFile = Paths.get(resource.toURI()); 81 | FileInputStream is = new FileInputStream(edfInputFile.toFile()); 82 | EDFParserResult result = EDFParser.parseEDF(is); 83 | EDFHeader header = result.getHeader(); 84 | EDFSignal signal = result.getSignal(); 85 | 86 | // write header and data to a new output file 87 | FileOutputStream fos = new FileOutputStream(outputFilePath.toFile()); 88 | EDFWriter.writeIntoOutputStream(header, fos); 89 | EDFWriter.writeIntoOutputStream(signal, header, fos); 90 | fos.close(); 91 | 92 | // read and parse the edf file created previously and compare with the header and data 93 | // of the initial edf file 94 | is = new FileInputStream(outputFilePath.toFile()); 95 | result = EDFParser.parseEDF(is); 96 | is.close(); 97 | assertHeader(header, result.getHeader()); 98 | assertSignal(signal, result.getSignal()); 99 | 100 | } 101 | 102 | @Test(expected = BufferOverflowException.class) 103 | public void testWriteLessBytesInHeader() throws Exception { 104 | 105 | Path outputFilePath = Paths.get(EDF_OUTPUT_FILE); 106 | Files.createFile(outputFilePath); 107 | 108 | // read and parse a real edf file 109 | URL resource = getClass().getClassLoader().getResource(EDF_INPUT_FILE); 110 | assert resource != null; 111 | Path edfInputFile = Paths.get(resource.toURI()); 112 | FileInputStream is = new FileInputStream(edfInputFile.toFile()); 113 | EDFParserResult result = EDFParser.parseEDF(is); 114 | EDFHeader header = result.getHeader(); 115 | header.bytesInHeader--; 116 | 117 | try (FileOutputStream fos = new FileOutputStream(outputFilePath.toFile())) { 118 | EDFWriter.writeIntoOutputStream(header, fos); 119 | } 120 | } 121 | 122 | @Test 123 | public void testWriteMoreBytesInHeader() throws Exception { 124 | 125 | Path outputFilePath = Paths.get(EDF_OUTPUT_FILE); 126 | Files.createFile(outputFilePath); 127 | 128 | // read and parse a real edf file 129 | URL resource = getClass().getClassLoader().getResource(EDF_INPUT_FILE); 130 | assert resource != null; 131 | Path edfInputFile = Paths.get(resource.toURI()); 132 | FileInputStream is = new FileInputStream(edfInputFile.toFile()); 133 | EDFParserResult result = EDFParser.parseEDF(is); 134 | EDFHeader header = result.getHeader(); 135 | header.bytesInHeader *= 2; 136 | 137 | try (FileOutputStream fos = new FileOutputStream(outputFilePath.toFile())) { 138 | EDFWriter.writeIntoOutputStream(header, fos); 139 | } 140 | } 141 | 142 | @Test(expected = BufferOverflowException.class) 143 | public void testExceedIdCodeSize() throws Exception { 144 | 145 | Path outputFilePath = Paths.get(EDF_OUTPUT_FILE); 146 | Files.createFile(outputFilePath); 147 | 148 | EDFHeader header = buildHeader(); 149 | header.idCode = " "; 150 | assertTrue(header.idCode.length() > EDFConstants.IDENTIFICATION_CODE_SIZE); 151 | 152 | try (FileOutputStream fos = new FileOutputStream(outputFilePath.toFile())) { 153 | EDFWriter.writeIntoOutputStream(header, fos); 154 | } 155 | } 156 | 157 | @Test(expected = BufferOverflowException.class) 158 | public void testWriteArrayMoreElements() throws Exception { 159 | 160 | Path outputFilePath = Paths.get(EDF_OUTPUT_FILE); 161 | Files.createFile(outputFilePath); 162 | 163 | EDFHeader header = buildHeader(); 164 | header.channelLabels = new String[] { "ch1", "ch2" }; 165 | 166 | try (FileOutputStream fos = new FileOutputStream(outputFilePath.toFile())) { 167 | EDFWriter.writeIntoOutputStream(header, fos); 168 | } 169 | } 170 | 171 | @Test(expected = NumberFormatException.class) 172 | public void testWriteArrayLessElements() throws Exception { 173 | 174 | Path outputFilePath = Paths.get(EDF_OUTPUT_FILE); 175 | Files.createFile(outputFilePath); 176 | 177 | // read and parse a real edf file 178 | URL resource = getClass().getClassLoader().getResource(EDF_INPUT_FILE); 179 | assert resource != null; 180 | Path edfInputFile = Paths.get(resource.toURI()); 181 | FileInputStream is = new FileInputStream(edfInputFile.toFile()); 182 | EDFParserResult result = EDFParser.parseEDF(is); 183 | EDFHeader header = result.getHeader(); 184 | EDFSignal signal = result.getSignal(); 185 | 186 | // remove two elements 187 | List channelsList = new ArrayList<>(Arrays.asList(header.channelLabels)); 188 | channelsList.remove(0); 189 | channelsList.remove(1); 190 | header.channelLabels = channelsList.toArray(new String[channelsList.size()]); 191 | 192 | // write header and data to a new output file 193 | FileOutputStream fos = new FileOutputStream(outputFilePath.toFile()); 194 | EDFWriter.writeIntoOutputStream(header, fos); 195 | EDFWriter.writeIntoOutputStream(signal, header, fos); 196 | fos.close(); 197 | 198 | // read and parse the edf file created previously and compare with the header and data 199 | // of the initial edf file 200 | try (FileInputStream is2 = new FileInputStream(outputFilePath.toFile())){ 201 | EDFParser.parseEDF(is2); 202 | } 203 | } 204 | 205 | @Test 206 | public void testWriteDurationOfRecord() throws Exception { 207 | 208 | Path outputFilePath = Paths.get(EDF_OUTPUT_FILE); 209 | Files.createFile(outputFilePath); 210 | 211 | // read and parse a real edf file 212 | URL resource = getClass().getClassLoader().getResource(EDF_INPUT_FILE); 213 | assert resource != null; 214 | Path edfInputFile = Paths.get(resource.toURI()); 215 | FileInputStream is = new FileInputStream(edfInputFile.toFile()); 216 | EDFParserResult result = EDFParser.parseEDF(is); 217 | EDFHeader header = result.getHeader(); 218 | EDFSignal signal = result.getSignal(); 219 | 220 | // set value for duration of a record 221 | header.durationOfRecords = 0.00006; 222 | 223 | // write header and data to a new output file 224 | FileOutputStream fos = new FileOutputStream(outputFilePath.toFile()); 225 | EDFWriter.writeIntoOutputStream(header, fos); 226 | EDFWriter.writeIntoOutputStream(signal, header, fos); 227 | fos.close(); 228 | 229 | // read and parse the edf file created previously and compare with the header and data 230 | // of the initial edf file 231 | try (FileInputStream is2 = new FileInputStream(outputFilePath.toFile())){ 232 | result = EDFParser.parseEDF(is2); 233 | assertEquals(header.durationOfRecords, result.header.durationOfRecords, 0); 234 | } 235 | } 236 | 237 | @Test(expected = EDFParserException.class) 238 | public void testParseInvalidEdf() throws Exception { 239 | 240 | // read and parse an invalid edf file 241 | URL resource = getClass().getClassLoader().getResource(INVALID_EDF); 242 | assert resource != null; 243 | Path edfInputFile = Paths.get(resource.toURI()); 244 | FileInputStream is = new FileInputStream(edfInputFile.toFile()); 245 | EDFParser.parseEDF(is); 246 | } 247 | 248 | private void assertHeader(EDFHeader expected, EDFHeader actual) { 249 | 250 | assertEquals(expected.getIdCode(), actual.getIdCode()); 251 | assertEquals(expected.getSubjectID(), actual.getSubjectID()); 252 | assertEquals(expected.getRecordingID(), actual.getRecordingID()); 253 | assertEquals(expected.getStartDate(), actual.getStartDate()); 254 | assertEquals(expected.getStartTime(), actual.getStartTime()); 255 | assertEquals(expected.getBytesInHeader(), actual.getBytesInHeader()); 256 | assertEquals(expected.getFormatVersion(), actual.getFormatVersion()); 257 | assertEquals(expected.getNumberOfRecords(), actual.getNumberOfRecords()); 258 | assertEquals(0, Double.compare(expected.getDurationOfRecords(), actual.getDurationOfRecords())); 259 | assertEquals(expected.getNumberOfChannels(), actual.getNumberOfChannels()); 260 | 261 | for (int i = 0; i < expected.getNumberOfChannels(); i++) { 262 | assertEquals(expected.getChannelLabels()[i].trim(), actual.getChannelLabels()[i].trim()); 263 | assertEquals(expected.getTransducerTypes()[i].trim(), actual.getTransducerTypes()[i].trim()); 264 | assertEquals(expected.getDimensions()[i].trim(), actual.getDimensions()[i].trim()); 265 | assertEquals(0, Double.compare(expected.getMinInUnits()[i], actual.getMinInUnits()[i])); 266 | assertEquals(0, Double.compare(expected.getMaxInUnits()[i], actual.getMaxInUnits()[i])); 267 | assertEquals(expected.getDigitalMin()[i], actual.getDigitalMin()[i]); 268 | assertEquals(expected.getDigitalMax()[i], actual.getDigitalMax()[i]); 269 | assertEquals(expected.getPrefilterings()[i].trim(), actual.getPrefilterings()[i].trim()); 270 | assertEquals(expected.getNumberOfSamples()[i], actual.getNumberOfSamples()[i]); 271 | assertArrayEquals(expected.getReserveds()[i], actual.getReserveds()[i]); 272 | } 273 | } 274 | 275 | private void assertSignal(EDFSignal expected, EDFSignal actual) { 276 | 277 | short[][] expectedDigitalValues = expected.getDigitalValues(); 278 | short[][] actualDigitalValues = actual.getDigitalValues(); 279 | assertEquals(expectedDigitalValues.length, actualDigitalValues.length); 280 | 281 | for (int i = 0; i < expectedDigitalValues.length; i++) { 282 | short[] expectedArray = expectedDigitalValues[i]; 283 | short[] actualArray = actualDigitalValues[i]; 284 | assertEquals(expectedArray.length, actualArray.length); 285 | assertArrayEquals(expectedArray, actualArray); 286 | } 287 | } 288 | 289 | private EDFHeader buildHeader() { 290 | return new EDFAnnotationFileHeaderBuilder() 291 | .startOfRecording(new Date()).durationOfRecord(1).numberOfSamples(new Integer[] { 100 }) 292 | .patientCode("1234").patientIsMale(true).patientBirthdate(new Date()).patientName("The patient") 293 | .recordingHospital("Hosp.").recordingTechnician("Techn.").recordingEquipment("Equ.") 294 | .channelLabels(new String[] { "EDF Annotations" }).transducerTypes(new String[] { "" }) 295 | .dimensions(new String[] { "" }).minInUnits(new Double[] { 0.0 }).maxInUnits( 296 | new Double[] { 1.0 }) 297 | .digitalMin(new Integer[] { -32768 }).digitalMax(new Integer[] { 32767 }) 298 | .prefilterings(new String[] { "" }).numberOfSamples(new Integer[] { 100 }) 299 | .reserveds(new byte[1][EDFConstants.RESERVED_SIZE]).build(); 300 | } 301 | 302 | } 303 | --------------------------------------------------------------------------------