Advanced
Here we discuss more advanced techniques for working with collections and archives.
Iterate over SigMF Annotations
Here we will load a SigMF dataset and iterate over the annotations. You can get the recording of the SigMF logo used in this example from the specification.
from sigmf import SigMFFile, sigmffile
# Load a dataset
path = "logo/sigmf_logo" # extension is optional
signal = sigmffile.fromfile(path)
# Get some metadata and all annotations
sample_rate = signal.get_global_field(sigmf.SAMPLE_RATE_KEY)
sample_count = signal.sample_count
signal_duration = sample_count / sample_rate
annotations = signal.get_annotations()
# Iterate over annotations
for adx, annotation in enumerate(annotations):
annotation_start_idx = annotation[sigmf.SAMPLE_START_KEY]
annotation_length = annotation[sigmf.SAMPLE_COUNT_KEY]
annotation_comment = annotation.get(
sigmf.COMMENT_KEY, "[annotation {}]".format(adx)
)
# Get capture info associated with the start of annotation
capture = signal.get_capture_info(annotation_start_idx)
freq_center = capture.get(sigmf.FREQUENCY_KEY, 0)
freq_min = freq_center - 0.5 * sample_rate
freq_max = freq_center + 0.5 * sample_rate
# Get frequency edges of annotation (default to edges of capture)
freq_start = annotation.get(sigmf.FREQ_LOWER_EDGE_KEY)
freq_stop = annotation.get(sigmf.FREQ_UPPER_EDGE_KEY)
# Get the samples corresponding to annotation
samples = signal.read_samples(annotation_start_idx, annotation_length)
# Do something with the samples & metadata for each annotation here
Save a Collection of SigMF Recordings
First, create a single SigMF Recording and save it to disk:
import datetime as dt
import numpy as np
import sigmf
from sigmf import SigMFFile
from sigmf.utils import get_data_type_str, get_sigmf_iso8601_datetime_now
# suppose we have a complex timeseries signal
data = np.zeros(1024, dtype=np.complex64)
# write those samples to file in cf32_le
data.tofile("example_cf32.sigmf-data")
# create the metadata
meta = SigMFFile(
data_file="example_cf32.sigmf-data", # extension is optional
global_info={
sigmf.DATATYPE_KEY: get_data_type_str(data), # in this case, 'cf32_le'
sigmf.SAMPLE_RATE_KEY: 48000,
sigmf.AUTHOR_KEY: "jane.doe@domain.org",
sigmf.DESCRIPTION_KEY: "All zero complex float32 example file.",
},
)
# create a capture key at time index 0
meta.add_capture(
0,
metadata={
sigmf.FREQUENCY_KEY: 915000000,
sigmf.DATETIME_KEY: get_sigmf_iso8601_datetime_now(),
},
)
# add an annotation at sample 100 with length 200 & 10 KHz width
meta.add_annotation(
100,
200,
metadata={
sigmf.FREQ_LOWER_EDGE_KEY: 914995000.0,
sigmf.FREQ_UPPER_EDGE_KEY: 915005000.0,
sigmf.COMMENT_KEY: "example annotation",
},
)
# check for mistakes & write to disk
meta.tofile("example_cf32.sigmf-meta") # extension is optional
Now lets add another SigMF Recording and associate them with a SigMF Collection:
from sigmf import SigMFFile, SigMFCollection
data_ci16 = np.zeros(1024, dtype=np.complex64)
# rescale and save as a complex int16 file:
data_ci16 *= pow(2, 15)
data_ci16.view(np.float32).astype(np.int16).tofile("example_ci16.sigmf-data")
# create the metadata for the second file
meta_ci16 = SigMFFile(
data_file="example_ci16.sigmf-data", # extension is optional
global_info={
sigmf.DATATYPE_KEY: "ci16_le", # get_data_type_str() is only valid for numpy types
sigmf.SAMPLE_RATE_KEY: 48000,
sigmf.DESCRIPTION_KEY: "All zero complex int16 file.",
},
)
meta_ci16.add_capture(0, metadata=meta.get_capture_info(0))
meta_ci16.tofile("example_ci16.sigmf-meta")
collection = SigMFCollection(
["example_cf32.sigmf-meta", "example_ci16.sigmf-meta"],
metadata={
"collection": {
SigMFCollection.AUTHOR_KEY: "sigmf@sigmf.org",
SigMFCollection.DESCRIPTION_KEY: "Collection of two all zero files.",
}
},
)
streams = collection.get_stream_names()
sigmf = [collection.get_SigMFFile(stream) for stream in streams]
collection.tofile("example_zeros.sigmf-collection")
The SigMF Collection and its associated Recordings can now be loaded like this:
import sigmf
collection = sigmf.fromfile("example_zeros")
ci16_sigmffile = collection.get_SigMFFile(stream_name="example_ci16")
cf32_sigmffile = collection.get_SigMFFile(stream_name="example_cf32")
Load a SigMF Archive and slice without untaring
Since an archive is a tarball (uncompressed by default), you can access the
data part of a SigMF archive without un-taring it. This is a compelling
feature because 1 archives make it harder for the -data and the
-meta to get separated, and 2 some datasets are so large that it can be
impractical (due to available disk space, or slow network speeds if the archive
file resides on a network file share) or simply obnoxious to untar it first.
>>> import sigmf
>>> signal = sigmf.fromarchive('/src/LTE.sigmf')
>>> signal.shape
(15379532,)
>>> signal.ndim
1
>>> signal[:10]
array([-0.023+0.012j, -0.021-0.006j, -0.017-0.020j, -0.013-0.052j,
0.000-0.075j, 0.022-0.058j, 0.048-0.044j, 0.049-0.060j,
0.031-0.056j, 0.023-0.047j], dtype=complex64)
Archives can contain fixed-point data types like complex-int16 (ci16),
which have no direct numpy equivalent. By default, this data is automatically
scaled to floating-point values in the range [-1.0, 1.0] and returned as
numpy.complex64:
>>> signal.get_global_field(sigmf.DATATYPE_KEY)
'ci16_le'
Compressed SigMF Archives
SigMF archives can be compressed using gzip, xz, or zip. The file extension determines the archive format:
Extension |
Format |
|---|---|
|
uncompressed |
|
gzip tar |
|
xz tar |
|
zip archive |
Writing compressed archives:
>>> import sigmf
>>> signal = sigmf.sigmffile.fromfile('recording.sigmf-meta')
# extension determines format
>>> signal.tofile('recording.sigmf.xz')
>>> signal.archive('recording.sigmf.gz')
# compression parameter creates archive with correct extension
>>> signal.tofile('recording', compression='xz') # → recording.sigmf.xz
>>> signal.archive('recording', compression='gz') # → recording.sigmf.gz
Reading compressed archives:
>>> signal = sigmf.fromfile('recording.sigmf.xz')
>>> signal[:10]
array([-0.023+0.012j, -0.021-0.006j, ...], dtype=complex64)
Memory behavior:
Uncompressed .sigmf archives use numpy.memmap for zero-copy access.
Compressed archives must decompress into RAM before access.