Skip to content

Commit 3a7d97f

Browse files
authored
fix capture byte boundaries for archives (#124)
1 parent ef0d8ce commit 3a7d97f

File tree

3 files changed

+52
-27
lines changed

3 files changed

+52
-27
lines changed

sigmf/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
# SPDX-License-Identifier: LGPL-3.0-or-later
66

77
# version of this python module
8-
__version__ = "1.4.0"
8+
__version__ = "1.5.0"
99
# matching version of the SigMF specification
1010
__specification__ = "1.2.6"
1111

sigmf/sigmffile.py

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,9 @@ class SigMFFile(SigMFMetafile):
182182
]
183183
VALID_KEYS = {GLOBAL_KEY: VALID_GLOBAL_KEYS, CAPTURE_KEY: VALID_CAPTURE_KEYS, ANNOTATION_KEY: VALID_ANNOTATION_KEYS}
184184

185-
def __init__(self, metadata=None, data_file=None, global_info=None, skip_checksum=False, map_readonly=True, autoscale=True):
185+
def __init__(
186+
self, metadata=None, data_file=None, global_info=None, skip_checksum=False, map_readonly=True, autoscale=True
187+
):
186188
"""
187189
API for SigMF I/O
188190
@@ -339,7 +341,7 @@ def __getitem__(self, sli):
339341
if self.is_complex_data:
340342
data = data.view(np.complex64)
341343
# for single-channel complex data, flatten the last dimension
342-
if data.ndim > 1 and self.get_num_channels() == 1:
344+
if data.ndim > 1 and self.num_channels == 1:
343345
data = data.flatten()
344346
return data[0] if isinstance(sli, int) else data
345347
else:
@@ -509,7 +511,7 @@ def get_capture_start(self, index):
509511
raise SigMFAccessError("Capture {} does not have required {} key".format(index, self.START_INDEX_KEY))
510512
return start
511513

512-
def get_capture_byte_boundarys(self, index):
514+
def get_capture_byte_boundaries(self, index):
513515
"""
514516
Returns a tuple of the file byte range in a dataset of a given SigMF
515517
capture of the form [start, stop). This function works on either
@@ -531,7 +533,13 @@ def get_capture_byte_boundarys(self, index):
531533

532534
end_byte = start_byte
533535
if index == len(self.get_captures()) - 1: # last captures...data is the rest of the file
534-
end_byte = self.data_file.stat().st_size - self.get_global_field(self.TRAILING_BYTES_KEY, 0)
536+
if self.data_file is not None:
537+
file_size = self.data_file.stat().st_size
538+
elif self.data_buffer is not None:
539+
file_size = len(self.data_buffer.getbuffer())
540+
else:
541+
raise SigMFFileError("Neither data_file nor data_buffer is available")
542+
end_byte = file_size - self.get_global_field(self.TRAILING_BYTES_KEY, 0)
535543
else:
536544
end_byte += (
537545
(self.get_capture_start(index + 1) - self.get_capture_start(index))
@@ -540,6 +548,15 @@ def get_capture_byte_boundarys(self, index):
540548
)
541549
return (start_byte, end_byte)
542550

551+
def get_capture_byte_boundarys(self, index):
552+
warnings.warn(
553+
"get_capture_byte_boundarys() is deprecated and will be removed in a future version of sigmf. "
554+
"Use get_capture_byte_boundaries() instead.",
555+
DeprecationWarning,
556+
stacklevel=2,
557+
)
558+
return self.get_capture_byte_boundaries(index)
559+
543560
def add_annotation(self, start_index, length=None, metadata=None):
544561
"""
545562
Insert annotation at start_index with length (if != None).
@@ -789,7 +806,7 @@ def read_samples_in_capture(self, index=0):
789806
data : ndarray
790807
Samples are returned as an array of float or complex, with number of dimensions equal to NUM_CHANNELS_KEY.
791808
"""
792-
cb = self.get_capture_byte_boundarys(index)
809+
cb = self.get_capture_byte_boundaries(index)
793810
if (cb[1] - cb[0]) % (self.get_sample_size() * self.num_channels):
794811
warnings.warn(
795812
f"Capture `{index}` in `{self.data_file}` does not contain "
@@ -826,10 +843,7 @@ def read_samples(self, start_index=0, count=-1):
826843
else:
827844
raise SigMFFileError("No signal data file has been associated with the metadata.")
828845
first_byte = start_index * self.get_sample_size() * self.num_channels
829-
830-
if not self._is_conforming_dataset():
831-
warnings.warn(f"Recording dataset appears non-compliant, resulting data may be erroneous")
832-
return self._read_datafile(first_byte, count * self.get_num_channels())
846+
return self._read_datafile(first_byte, count * self.num_channels)
833847

834848
def _read_datafile(self, first_byte, nitems):
835849
"""

tests/test_sigmffile.py

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -254,8 +254,8 @@ def test_000(self) -> None:
254254
meta = self.prepare(TEST_U8_DATA0, TEST_U8_META0, np.uint8, autoscale=False)
255255
self.assertEqual(256, meta._count_samples())
256256
self.assertTrue(meta._is_conforming_dataset())
257-
self.assertTrue((0, 0), meta.get_capture_byte_boundarys(0))
258-
self.assertTrue((0, 256), meta.get_capture_byte_boundarys(1))
257+
self.assertTrue((0, 0), meta.get_capture_byte_boundaries(0))
258+
self.assertTrue((0, 256), meta.get_capture_byte_boundaries(1))
259259
self.assertTrue(np.array_equal(TEST_U8_DATA0, meta.read_samples()))
260260
self.assertTrue(np.array_equal(np.array([]), meta.read_samples_in_capture(0)))
261261
self.assertTrue(np.array_equal(TEST_U8_DATA0, meta.read_samples_in_capture(1)))
@@ -265,8 +265,8 @@ def test_001(self) -> None:
265265
meta = self.prepare(TEST_U8_DATA1, TEST_U8_META1, np.uint8, autoscale=False)
266266
self.assertEqual(192, meta._count_samples())
267267
self.assertFalse(meta._is_conforming_dataset())
268-
self.assertTrue((32, 160), meta.get_capture_byte_boundarys(0))
269-
self.assertTrue((160, 224), meta.get_capture_byte_boundarys(1))
268+
self.assertTrue((32, 160), meta.get_capture_byte_boundaries(0))
269+
self.assertTrue((160, 224), meta.get_capture_byte_boundaries(1))
270270
self.assertTrue(np.array_equal(np.arange(128), meta.read_samples_in_capture(0)))
271271
self.assertTrue(np.array_equal(np.arange(128, 192), meta.read_samples_in_capture(1)))
272272

@@ -275,8 +275,8 @@ def test_002(self) -> None:
275275
meta = self.prepare(TEST_U8_DATA2, TEST_U8_META2, np.uint8, autoscale=False)
276276
self.assertEqual(192, meta._count_samples())
277277
self.assertFalse(meta._is_conforming_dataset())
278-
self.assertTrue((32, 160), meta.get_capture_byte_boundarys(0))
279-
self.assertTrue((160, 224), meta.get_capture_byte_boundarys(1))
278+
self.assertTrue((32, 160), meta.get_capture_byte_boundaries(0))
279+
self.assertTrue((160, 224), meta.get_capture_byte_boundaries(1))
280280
self.assertTrue(np.array_equal(np.arange(128), meta.read_samples_in_capture(0)))
281281
self.assertTrue(np.array_equal(np.arange(128, 192), meta.read_samples_in_capture(1)))
282282

@@ -285,9 +285,9 @@ def test_003(self) -> None:
285285
meta = self.prepare(TEST_U8_DATA3, TEST_U8_META3, np.uint8, autoscale=False)
286286
self.assertEqual(192, meta._count_samples())
287287
self.assertFalse(meta._is_conforming_dataset())
288-
self.assertTrue((32, 64), meta.get_capture_byte_boundarys(0))
289-
self.assertTrue((64, 160), meta.get_capture_byte_boundarys(1))
290-
self.assertTrue((160, 224), meta.get_capture_byte_boundarys(2))
288+
self.assertTrue((32, 64), meta.get_capture_byte_boundaries(0))
289+
self.assertTrue((64, 160), meta.get_capture_byte_boundaries(1))
290+
self.assertTrue((160, 224), meta.get_capture_byte_boundaries(2))
291291
self.assertTrue(np.array_equal(np.arange(32), meta.read_samples_in_capture(0)))
292292
self.assertTrue(np.array_equal(np.arange(32, 128), meta.read_samples_in_capture(1)))
293293
self.assertTrue(np.array_equal(np.arange(128, 192), meta.read_samples_in_capture(2)))
@@ -297,8 +297,8 @@ def test_004(self) -> None:
297297
meta = self.prepare(TEST_U8_DATA4, TEST_U8_META4, np.uint8, autoscale=False)
298298
self.assertEqual(96, meta._count_samples())
299299
self.assertFalse(meta._is_conforming_dataset())
300-
self.assertTrue((32, 96), meta.get_capture_byte_boundarys(0))
301-
self.assertTrue((96, 160), meta.get_capture_byte_boundarys(1))
300+
self.assertTrue((32, 96), meta.get_capture_byte_boundaries(0))
301+
self.assertTrue((96, 160), meta.get_capture_byte_boundaries(1))
302302
self.assertTrue(np.array_equal(np.arange(64).repeat(2).reshape(-1, 2), meta.read_samples_in_capture(0)))
303303
self.assertTrue(np.array_equal(np.arange(64, 96).repeat(2).reshape(-1, 2), meta.read_samples_in_capture(1)))
304304

@@ -317,13 +317,24 @@ def test_slicing_rf32(self) -> None:
317317

318318
def test_slicing_multiple_channels(self) -> None:
319319
"""slice multiple channels"""
320-
meta_raw = self.prepare(TEST_U8_DATA4, TEST_U8_META4, np.uint8, autoscale=False)
321-
meta_scaled = self.prepare(TEST_U8_DATA4, TEST_U8_META4, np.uint8, autoscale=False) # use raw data for this test
320+
321+
meta = self.prepare(TEST_U8_DATA4, TEST_U8_META4, np.uint8, autoscale=False)
322322
channelized = np.array(TEST_U8_DATA4).reshape((-1, 2))
323-
self.assertTrue(np.array_equal(meta_scaled[:][:], channelized))
324-
self.assertTrue(np.array_equal(meta_raw[10:20, 0], meta_raw.read_samples()[10:20, 0]))
325-
self.assertTrue(np.array_equal(meta_scaled[0], channelized[0]))
326-
self.assertTrue(np.array_equal(meta_scaled[1, :], channelized[1]))
323+
self.assertTrue(np.array_equal(meta[:][:], channelized))
324+
self.assertTrue(np.array_equal(meta[10:20, 0], meta.read_samples()[10:20, 0]))
325+
self.assertTrue(np.array_equal(meta[0], channelized[0]))
326+
self.assertTrue(np.array_equal(meta[1, :], channelized[1]))
327+
328+
def test_boundaries(self) -> None:
329+
"""capture byte boundaries from pairs & archives"""
330+
# get a meta pair and archive
331+
meta = self.prepare(TEST_U8_DATA3, TEST_U8_META3, np.uint8)
332+
arc_path = self.temp_dir / "arc.sigmf"
333+
meta.tofile(arc_path, toarchive=True)
334+
arc = sigmf.fromfile(arc_path)
335+
for bdx in range(3):
336+
self.assertEqual(meta.get_capture_byte_boundaries(bdx), arc.get_capture_byte_boundaries(bdx))
337+
self.assertTrue(np.array_equal(meta.read_samples_in_capture(bdx), arc.read_samples_in_capture(bdx)))
327338

328339

329340
def simulate_capture(sigmf_md, n, capture_len):

0 commit comments

Comments
 (0)