Skip to content

Commit

Permalink
Merge pull request #14 from oclay1st/develop
Browse files Browse the repository at this point in the history
Refactor headers API
  • Loading branch information
oclay1st authored Mar 1, 2024
2 parents 3c28c02 + 66c7dd2 commit f9c2ec7
Show file tree
Hide file tree
Showing 11 changed files with 134 additions and 129 deletions.
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Filter filter = new Filter.Builder().startTime(start).endTime(end).build();
SingleSegementRecord record = SingleSegmentRecord.parse(path, filter);
```

Parse and filter a single-segment record by singal indices:
Parse and filter a single-segment record by signal indices:
```
Path path = Path.of(...);
Filter filter = new Filter.Builder().signals(new int[]{0, 1, 2}).build();
Expand All @@ -49,8 +49,7 @@ MultiSegmentRecord record = MultiSegmentRecord.parse(path);

Export a single-segment record:
```
Path path = Path.of(...);
Filter fiter = new Filter(...);
...
SingleSegmentRecord record = SingleSegmentRecord.parse(path, filter);
Path exportPath = Path.of(...);
record.export(exportPath);
Expand Down
3 changes: 1 addition & 2 deletions src/main/java/io/github/oclay1st/wfdb/filters/Filter.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,7 @@ public boolean equals(Object object) {

@Override
public int hashCode() {
int result = Objects.hash(startTime, endTime);
return 31 * result + Arrays.hashCode(signals);
return 31 * Objects.hash(startTime, endTime) + Arrays.hashCode(signals);
}

@Override
Expand Down
20 changes: 12 additions & 8 deletions src/main/java/io/github/oclay1st/wfdb/filters/FilterProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ private FilterProcessor(Filter filter, SingleSegmentHeader header, boolean isFil
* @return a new instance of a filter processor
*/
public static FilterProcessor process(Filter filter, SingleSegmentHeader header) {
long duration = header.headerRecord().durationTime().toMillis();
long duration = header.record().durationTime().toMillis();
long startMilliseconds = filter.startTime() != null ? filter.startTime() : 0;
long endMilliseconds = filter.endTime() != null ? filter.endTime() : duration;
int[] indices = IntStream.range(0, header.headerSignals().length).toArray();
int[] indices = IntStream.range(0, header.signals().length).toArray();
if (filter.signals() != null) {
indices = filter.signals();
}
Expand All @@ -54,7 +54,10 @@ public static FilterProcessor process(Filter filter, SingleSegmentHeader header)
* @return a {@link SingleSegmentHeader} instance
*/
public SingleSegmentHeader generateFilteredHeader() {
return isFilterAsDefault ? header : new SingleSegmentHeader(generateHeaderRecord(), generateHeaderSignals());
if (isFilterAsDefault) {
return header;
}
return new SingleSegmentHeader(generateHeaderRecord(), generateHeaderSignals(), header.comments());
}

/**
Expand All @@ -63,7 +66,7 @@ public SingleSegmentHeader generateFilteredHeader() {
* @return a new {@link HeaderRecord} instance
*/
private HeaderRecord generateHeaderRecord() {
HeaderRecord headerRecord = header.headerRecord();
HeaderRecord headerRecord = header.record();
int numberOfSignals = filter.signals().length;
long duration = filter.endTime() - filter.startTime();
int numberOfSamplesPerSignal = Math.round(headerRecord.samplingFrequency() * duration / 1000);
Expand All @@ -80,7 +83,7 @@ private HeaderRecord generateHeaderRecord() {
private HeaderSignal[] generateHeaderSignals() {
HeaderSignal[] headerSignals = new HeaderSignal[filter.signals().length];
for (int i = 0; i < headerSignals.length; i++) {
HeaderSignal headerSignal = header.headerSignals()[filter.signals()[i]];
HeaderSignal headerSignal = header.signals()[filter.signals()[i]];
headerSignals[i] = new HeaderSignal(headerSignal.filename(), headerSignal.format(),
headerSignal.samplesPerFrame(), headerSignal.skew(), headerSignal.bytesOffset(),
headerSignal.adcGain(), headerSignal.baseline(), headerSignal.unit(), headerSignal.adcResolution(),
Expand Down Expand Up @@ -109,14 +112,15 @@ public BytesRange calculateBytesRange(HeaderSignal[] headerSignals) {
* Calculate the byte index given the milliseconds, the bytes per sample and the
* number of signals
*
* @param milliseconds the value of the milliseconds
* @param bytesPerSample the value of the bytes per sample
* @param milliseconds the value of the milliseconds
* @param bytesPerSample the value of the bytes per sample
* @param numberOfSignals the value of number of signals
* @return the byte index value
*/
private long calculateByteIndex(long milliseconds, float bytesPerSample, int numberOfSignals) {
int numberOfSamplesPerSignal = Math.round(header.headerRecord().samplingFrequency() * milliseconds / 1000);
int numberOfSamplesPerSignal = Math.round(header.record().samplingFrequency() * milliseconds / 1000);
int numberOfSamples = numberOfSamplesPerSignal * numberOfSignals;
return (int) Math.ceil(numberOfSamples * bytesPerSample);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@
/**
* Represent the info from a multi-segment header
*
* @param headerRecord the header record
* @param headerSegments the array of header segments
* @param record the header record
* @param segments the array of header segments
* @see HeaderRecord
* @see HeaderSegment
*/
public record MultiSegmentHeader(HeaderRecord headerRecord, HeaderSegment[] headerSegments) {
public record MultiSegmentHeader(HeaderRecord record, HeaderSegment[] segments, String comments) { // NOSONAR

/**
* Parse the multi-segment header from an input form.
Expand All @@ -43,12 +43,15 @@ public static MultiSegmentHeader parse(InputStream input) throws IOException, Pa
int segmentIndex = 0;
String headerLine;
boolean headerRecordProcessed = false;
StringBuilder commentsBuilder = new StringBuilder();
while ((headerLine = reader.readLine()) != null) {
String stripedHeaderLine = headerLine.strip();
if (stripedHeaderLine.isEmpty() || stripedHeaderLine.charAt(0) == '#') {
if (stripedHeaderLine.isEmpty()) {
continue;
}
if (!headerRecordProcessed) {
if (stripedHeaderLine.charAt(0) == '#') {
commentsBuilder.append('\n').append(stripedHeaderLine);
} else if (!headerRecordProcessed) {
headerRecord = HeaderRecord.parse(stripedHeaderLine);
headerSegments = new HeaderSegment[headerRecord.numberOfSegments()];
headerRecordProcessed = true;
Expand All @@ -57,7 +60,7 @@ public static MultiSegmentHeader parse(InputStream input) throws IOException, Pa
segmentIndex++;
}
}
return new MultiSegmentHeader(headerRecord, headerSegments);
return new MultiSegmentHeader(headerRecord, headerSegments, commentsBuilder.toString());
}

/**
Expand All @@ -67,29 +70,28 @@ public static MultiSegmentHeader parse(InputStream input) throws IOException, Pa
*/
public String toTextBlock() {
StringBuilder builder = new StringBuilder();
builder.append(headerRecord.toTextLine());
for (HeaderSegment headerSegment : headerSegments) {
builder.append("\n");
builder.append(headerSegment.toTextLine());
builder.append(record.toTextLine());
for (HeaderSegment headerSegment : segments) {
builder.append('\n').append(headerSegment.toTextLine());
}
return builder.toString();
}

@Override
public String toString() {
return "MultiSegmentHeader [headerRecord = " + headerRecord + ", headerSegments = "
+ Arrays.toString(headerSegments) + "]";
return "MultiSegmentHeader [headerRecord = " + record + ", headerSegments = "
+ Arrays.toString(segments) + "]";
}

@Override
public boolean equals(Object object) {
return object instanceof MultiSegmentHeader instance && headerRecord.equals(instance.headerRecord)
&& Arrays.equals(headerSegments, instance.headerSegments);
return object instanceof MultiSegmentHeader instance && record.equals(instance.record)
&& Arrays.equals(segments, instance.segments);
}

@Override
public int hashCode() {
int result = Objects.hash(headerRecord);
return 31 * result + Arrays.hashCode(headerSegments);
return 31 * Objects.hash(record) + Arrays.hashCode(segments);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ public static MultiSegmentRecord parse(Path recordPath) throws IOException, Pars
// Parse the multi-segment header file
MultiSegmentHeader header = MultiSegmentHeader.parse(inputStream);
int recordIndex = 0;
SingleSegmentRecord[] singleSegmentRecords = new SingleSegmentRecord[header.headerRecord()
SingleSegmentRecord[] singleSegmentRecords = new SingleSegmentRecord[header.record()
.numberOfSamplesPerSignal()];
for (HeaderSegment segment : header.headerSegments()) {
for (HeaderSegment segment : header.segments()) {
Path segmentRecordPath = recordPath.resolveSibling(segment.name());
SingleSegmentRecord singleSegmentRecord = SingleSegmentRecord.parse(segmentRecordPath);
singleSegmentRecords[recordIndex] = singleSegmentRecord;
Expand All @@ -51,8 +51,7 @@ public boolean equals(Object object) {

@Override
public int hashCode() {
int result = Objects.hash(header);
return 31 * result + Arrays.hashCode(records);
return 31 * Objects.hash(header) + Arrays.hashCode(records);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
/**
* Represents a single-segment record header.
*
* @param headerRecord the header record
* @param headerSignals the array of header signals
* @param record the header record
* @param signals the array of header signals
*/
public record SingleSegmentHeader(HeaderRecord headerRecord, HeaderSignal[] headerSignals) {
public record SingleSegmentHeader(HeaderRecord record, HeaderSignal[] signals, String comments) { // NOSONAR

/**
* Parse the single-segment header from an input form.
Expand Down Expand Up @@ -47,12 +47,15 @@ public static SingleSegmentHeader parse(InputStream input) throws IOException, P
int signalIndex = 0;
String headerLine;
boolean headerRecordProcessed = false;
StringBuilder commentsBuilder = new StringBuilder();
while ((headerLine = reader.readLine()) != null) {
String stripedHeaderLine = headerLine.strip();
if (stripedHeaderLine.isEmpty() || stripedHeaderLine.charAt(0) == '#') {
if (stripedHeaderLine.isEmpty()) {
continue;
}
if (!headerRecordProcessed) {
if (stripedHeaderLine.charAt(0) == '#') {
commentsBuilder.append('\n').append(stripedHeaderLine);
} else if (!headerRecordProcessed) {
headerRecord = HeaderRecord.parse(stripedHeaderLine);
headerSignals = new HeaderSignal[headerRecord.numberOfSignals()];
headerRecordProcessed = true;
Expand All @@ -61,7 +64,7 @@ public static SingleSegmentHeader parse(InputStream input) throws IOException, P
signalIndex++;
}
}
return new SingleSegmentHeader(headerRecord, headerSignals);
return new SingleSegmentHeader(headerRecord, headerSignals, commentsBuilder.toString());
}

/**
Expand All @@ -70,7 +73,7 @@ public static SingleSegmentHeader parse(InputStream input) throws IOException, P
* @return the group of header signals
*/
public Map<String, List<HeaderSignal>> groupHeaderSignalsByFilename() {
return Arrays.stream(headerSignals)
return Arrays.stream(signals)
.collect(Collectors.groupingBy(HeaderSignal::filename, LinkedHashMap::new, Collectors.toList()));
}

Expand All @@ -81,24 +84,23 @@ public Map<String, List<HeaderSignal>> groupHeaderSignalsByFilename() {
*/
public String toTextBlock() {
StringBuilder builder = new StringBuilder();
builder.append(headerRecord.toTextLine());
for (HeaderSignal headerSignal : headerSignals) {
builder.append("\n");
builder.append(headerSignal.toTextLine());
builder.append(record.toTextLine());
for (HeaderSignal headerSignal : signals) {
builder.append('\n').append(headerSignal.toTextLine());
}
return builder.toString();
}

/**
* Generate a copy of the header with the checksum of the singals.
* Generate a copy of the header with the checksum of the signals.
*
* @param samplesPerSignal the array of samples per singal
* @param samplesPerSignal the array of samples per signal
* @return a new {@link SingleSegmentHeader} instance
*/
public SingleSegmentHeader generateChecksumCopy(int[][] samplesPerSignal) {
HeaderSignal[] newHeaderSignals = new HeaderSignal[headerSignals.length];
for (int i = 0; i < headerSignals.length; i++) {
HeaderSignal headerSignal = headerSignals[i];
HeaderSignal[] newHeaderSignals = new HeaderSignal[signals.length];
for (int i = 0; i < signals.length; i++) {
HeaderSignal headerSignal = signals[i];
int initialValue = samplesPerSignal[i][0];
int checksum = HeaderSignal.calculateChecksum(samplesPerSignal[i]);
newHeaderSignals[i] = new HeaderSignal(headerSignal.filename(), headerSignal.format(),
Expand All @@ -107,24 +109,24 @@ public SingleSegmentHeader generateChecksumCopy(int[][] samplesPerSignal) {
headerSignal.adcZero(), initialValue, checksum, headerSignal.blockSize(),
headerSignal.description());
}
return new SingleSegmentHeader(headerRecord, newHeaderSignals);
return new SingleSegmentHeader(record, newHeaderSignals, comments);
}

@Override
public String toString() {
return "SingleSegmentHeader [headerRecord = " + headerRecord + ", headerSignals = "
+ Arrays.toString(headerSignals) + "]";
return "SingleSegmentHeader [headerRecord = " + record + ", headerSignals = "
+ Arrays.toString(signals) + "]";
}

@Override
public boolean equals(Object object) {
return object instanceof SingleSegmentHeader instance && headerRecord.equals(instance.headerRecord)
&& Arrays.equals(headerSignals, instance.headerSignals);
return object instanceof SingleSegmentHeader instance && record.equals(instance.record)
&& Arrays.equals(signals, instance.signals);
}

@Override
public int hashCode() {
int result = Objects.hash(headerRecord);
return 31 * result + Arrays.hashCode(headerSignals);
return 31 * Objects.hash(record) + Arrays.hashCode(signals);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public static SingleSegmentRecord parse(Path recordPath, Filter filter) throws I
SingleSegmentHeader header = parseHeaderFile(recordPath);
FilterProcessor filterProcessor = FilterProcessor.process(filter, header);
SingleSegmentHeader filteredHeader = filterProcessor.generateFilteredHeader();
int numberOfSignals = filteredHeader.headerRecord().numberOfSignals();
int numberOfSignals = filteredHeader.record().numberOfSignals();
int[][] samplesPerSignal = new int[numberOfSignals][0];
int samplesPerSignalIndex = 0;
// Group the signals by filename
Expand Down Expand Up @@ -148,7 +148,7 @@ private static SignalFormatter resolveSignalFormatter(HeaderSignal[] headerSigna
/**
* Export the single-segment record. Generate the header and signal(s) files.
*
* @param recordPath the path of the record
* @param recordPath the path of the record
* @throws IOException if the record can't be exported.
*/
public void export(Path recordPath) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,17 @@ void shouldParseTheMultiSegmentHeader() throws IOException, ParseException {
ByteArrayInputStream headerInput = new ByteArrayInputStream(headerText.getBytes());
MultiSegmentHeader header = MultiSegmentHeader.parse(headerInput);
assertNotNull(header);
assertNotNull(header.headerRecord());
assertTrue(header.headerRecord().isMultiSegment());
assertEquals("multi", header.headerRecord().name());
assertEquals(4, header.headerRecord().numberOfSignals());
assertEquals(360, header.headerRecord().samplingFrequency());
assertEquals(45000, header.headerRecord().numberOfSamplesPerSignal());
assertEquals(2, header.headerSegments().length);
assertEquals("test", header.headerSegments()[0].name());
assertEquals(22500, header.headerSegments()[0].numberOfSamplesPerSignal());
assertEquals("other", header.headerSegments()[1].name());
assertEquals(22500, header.headerSegments()[1].numberOfSamplesPerSignal());
assertNotNull(header.record());
assertTrue(header.record().isMultiSegment());
assertEquals("multi", header.record().name());
assertEquals(4, header.record().numberOfSignals());
assertEquals(360, header.record().samplingFrequency());
assertEquals(45000, header.record().numberOfSamplesPerSignal());
assertEquals(2, header.segments().length);
assertEquals("test", header.segments()[0].name());
assertEquals(22500, header.segments()[0].numberOfSamplesPerSignal());
assertEquals("other", header.segments()[1].name());
assertEquals(22500, header.segments()[1].numberOfSamplesPerSignal());
}

@Test
Expand All @@ -54,9 +54,9 @@ void shouldIgnoreCommentsAndBlankLines() throws IOException, ParseException {
ByteArrayInputStream headerInput = new ByteArrayInputStream(headerText.getBytes());
MultiSegmentHeader header = MultiSegmentHeader.parse(headerInput);
assertNotNull(header);
assertNotNull(header.headerRecord());
assertTrue(header.headerRecord().isMultiSegment());
assertEquals(3, header.headerSegments().length);
assertNotNull(header.record());
assertTrue(header.record().isMultiSegment());
assertEquals(3, header.segments().length);
}

@ParameterizedTest(name = "in {0}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ void shouldParseMultiSegmentRecord() throws IOException, ParseException {
Path recordPath = Path.of("src", "test", "resources", "multi-segment", "v102s", "v102s");
MultiSegmentRecord multiSegmentRecord = MultiSegmentRecord.parse(recordPath);
assertNotNull(multiSegmentRecord);
assertEquals(3, multiSegmentRecord.header().headerSegments().length);
assertEquals(3, multiSegmentRecord.header().segments().length);
}

}
Loading

0 comments on commit f9c2ec7

Please sign in to comment.