Skip to content

Commit 1386965

Browse files
vejretvejretmessybear
and
messybear
authored
Fix count samples from annotations (#47)
Fixed two scenarios where SigMFFile._count_samples() failed: * No data_file registered: sample_count should be calculated from annotation with highest end index, not from annotation with highest start index * If no core:sample_count is provided in the annotation, core:sample_start should be used (sample count must at least be equal to this) --------- Co-authored-by: messybear <[email protected]>
1 parent 7c979a4 commit 1386965

File tree

2 files changed

+97
-13
lines changed

2 files changed

+97
-13
lines changed

sigmf/sigmffile.py

+42-13
Original file line numberDiff line numberDiff line change
@@ -402,11 +402,23 @@ def get_annotations(self, index=None):
402402
list of dict
403403
Each dictionary contains one annotation for the sample at `index`.
404404
'''
405-
return [
406-
x for x in self._metadata.get(self.ANNOTATION_KEY, [])
407-
if index is None or (x[self.START_INDEX_KEY] <= index
408-
and x[self.START_INDEX_KEY] + x[self.LENGTH_INDEX_KEY] > index)
409-
]
405+
annotations = self._metadata.get(self.ANNOTATION_KEY, [])
406+
if index is None:
407+
return annotations
408+
409+
annotations_including_index = []
410+
for annotation in annotations:
411+
if index < annotation[self.START_INDEX_KEY]:
412+
# index is before annotation starts -> skip
413+
continue
414+
if self.LENGTH_INDEX_KEY in annotation:
415+
# Annotation includes sample_count -> check end index
416+
if index >= annotation[self.START_INDEX_KEY] + annotation[self.LENGTH_INDEX_KEY]:
417+
# index is after annotation end -> skip
418+
continue
419+
420+
annotations_including_index.append(annotation)
421+
return annotations_including_index
410422

411423
def get_sample_size(self):
412424
"""
@@ -418,16 +430,13 @@ def get_sample_size(self):
418430
def _count_samples(self):
419431
"""
420432
Count, set, and return the total number of samples in the data file.
421-
If there is no data file but there are annotations, use the end index
422-
of the final annotation instead. If there are no annotations, use 0.
433+
If there is no data file but there are annotations, use the sample_count
434+
from the annotation with the highest end index. If there are no annotations,
435+
use 0.
423436
For complex data, a 'sample' includes both the real and imaginary part.
424437
"""
425-
annotations = self.get_annotations()
426438
if self.data_file is None:
427-
if len(annotations) > 0:
428-
sample_count = annotations[-1][self.START_INDEX_KEY] + annotations[-1][self.LENGTH_INDEX_KEY]
429-
else:
430-
sample_count = 0
439+
sample_count = self._get_sample_count_from_annotations()
431440
else:
432441
header_bytes = sum([c.get(self.HEADER_BYTES_KEY, 0) for c in self.get_captures()])
433442
file_size = path.getsize(self.data_file) if self.offset_and_size is None else self.offset_and_size[1]
@@ -438,12 +447,32 @@ def _count_samples(self):
438447
if file_data_size % (sample_size * num_channels) != 0:
439448
warnings.warn(f'File `{self.data_file}` does not contain an integer '
440449
'number of samples across channels. It may be invalid data.')
441-
if len(annotations) > 0 and annotations[-1][self.START_INDEX_KEY] + annotations[-1][self.LENGTH_INDEX_KEY] > sample_count:
450+
if self._get_sample_count_from_annotations() > sample_count:
442451
warnings.warn(f'File `{self.data_file}` ends before the final annotation '
443452
'in the corresponding SigMF metadata.')
444453
self.sample_count = sample_count
445454
return sample_count
446455

456+
def _get_sample_count_from_annotations(self):
457+
"""
458+
Returns the number of samples based on annotation with highest end index.
459+
NOTE: Annotations are ordered by START_INDEX_KEY and not end index, so we
460+
need to go through all annotations
461+
"""
462+
annon_sample_count = []
463+
for annon in self.get_annotations():
464+
if self.LENGTH_INDEX_KEY in annon:
465+
# Annotation with sample_count
466+
annon_sample_count.append(annon[self.START_INDEX_KEY] + annon[self.LENGTH_INDEX_KEY])
467+
else:
468+
# Annotation without sample_count - sample count must be at least sample_start
469+
annon_sample_count.append(annon[self.START_INDEX_KEY])
470+
471+
if annon_sample_count:
472+
return max(annon_sample_count)
473+
else:
474+
return 0
475+
447476
def calculate_hash(self):
448477
"""
449478
Calculates the hash of the data file and adds it to the global section.

tests/test_sigmffile.py

+55
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from pathlib import Path
2626
import numpy as np
2727
import unittest
28+
import copy
2829

2930
from sigmf import sigmffile, utils
3031
from sigmf.sigmffile import SigMFFile
@@ -61,6 +62,60 @@ def test_iterator_basic(self):
6162
count += 1
6263
self.assertEqual(count, len(self.sigmf_object))
6364

65+
class TestAnnotationHandling(unittest.TestCase):
66+
67+
def test_get_annotations_with_index(self):
68+
"""Test that only annotations containing index are returned from get_annotations()"""
69+
smf = SigMFFile(copy.deepcopy(TEST_METADATA))
70+
smf.add_annotation(start_index=1)
71+
smf.add_annotation(start_index=4, length=4)
72+
annotations_idx10 = smf.get_annotations(index=10)
73+
self.assertListEqual(
74+
annotations_idx10,
75+
[
76+
{SigMFFile.START_INDEX_KEY: 0, SigMFFile.LENGTH_INDEX_KEY: 16},
77+
{SigMFFile.START_INDEX_KEY: 1},
78+
]
79+
)
80+
81+
def test__count_samples_from_annotation(self):
82+
"""Make sure sample count from annotations use correct end index"""
83+
smf = SigMFFile(copy.deepcopy(TEST_METADATA))
84+
smf.add_annotation(start_index=0, length=32)
85+
smf.add_annotation(start_index=4, length=4)
86+
sample_count = smf._count_samples()
87+
self.assertEqual(sample_count, 32)
88+
89+
def test_set_data_file_without_annotations(self):
90+
"""
91+
Make sure setting data_file with no annotations registered does not
92+
raise any errors
93+
"""
94+
smf = SigMFFile(copy.deepcopy(TEST_METADATA))
95+
smf._metadata[SigMFFile.ANNOTATION_KEY].clear()
96+
with tempfile.TemporaryDirectory() as tmpdir:
97+
temp_path_data = os.path.join(tmpdir, "datafile")
98+
TEST_FLOAT32_DATA.tofile(temp_path_data)
99+
smf.set_data_file(temp_path_data)
100+
samples = smf.read_samples()
101+
self.assertTrue(len(samples)==16)
102+
103+
def test_set_data_file_with_annotations(self):
104+
"""
105+
Make sure setting data_file with annotations registered use sample
106+
count from data_file and issue a warning if annotations have end
107+
indices bigger than file end index
108+
"""
109+
smf = SigMFFile(copy.deepcopy(TEST_METADATA))
110+
smf.add_annotation(start_index=0, length=32)
111+
with tempfile.TemporaryDirectory() as tmpdir:
112+
temp_path_data = os.path.join(tmpdir, "datafile")
113+
TEST_FLOAT32_DATA.tofile(temp_path_data)
114+
with self.assertWarns(Warning):
115+
# Issues warning since file ends before the final annotatio
116+
smf.set_data_file(temp_path_data)
117+
samples = smf.read_samples()
118+
self.assertTrue(len(samples)==16)
64119

65120
def simulate_capture(sigmf_md, n, capture_len):
66121
start_index = capture_len * n

0 commit comments

Comments
 (0)