Skip to content

Commit 9c3ed91

Browse files
committed
merge main; address PR suggestions; fix docs
1 parent f5c6eab commit 9c3ed91

20 files changed

Lines changed: 138 additions & 197 deletions

docs/source/advanced.rst

Lines changed: 35 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -22,26 +22,28 @@ the recording of the SigMF logo used in this example `from the specification
2222
signal = sigmffile.fromfile(path)
2323
2424
# Get some metadata and all annotations
25-
sample_rate = signal.get_global_field(SigMFFile.SAMPLE_RATE_KEY)
25+
sample_rate = signal.get_global_field(sigmf.SAMPLE_RATE_KEY)
2626
sample_count = signal.sample_count
2727
signal_duration = sample_count / sample_rate
2828
annotations = signal.get_annotations()
2929
3030
# Iterate over annotations
3131
for adx, annotation in enumerate(annotations):
32-
annotation_start_idx = annotation[SigMFFile.START_INDEX_KEY]
33-
annotation_length = annotation[SigMFFile.LENGTH_INDEX_KEY]
34-
annotation_comment = annotation.get(SigMFFile.COMMENT_KEY, "[annotation {}]".format(adx))
32+
annotation_start_idx = annotation[sigmf.SAMPLE_START_KEY]
33+
annotation_length = annotation[sigmf.SAMPLE_COUNT_KEY]
34+
annotation_comment = annotation.get(
35+
sigmf.COMMENT_KEY, "[annotation {}]".format(adx)
36+
)
3537
3638
# Get capture info associated with the start of annotation
3739
capture = signal.get_capture_info(annotation_start_idx)
38-
freq_center = capture.get(SigMFFile.FREQUENCY_KEY, 0)
40+
freq_center = capture.get(sigmf.FREQUENCY_KEY, 0)
3941
freq_min = freq_center - 0.5 * sample_rate
4042
freq_max = freq_center + 0.5 * sample_rate
4143
4244
# Get frequency edges of annotation (default to edges of capture)
43-
freq_start = annotation.get(SigMFFile.FREQ_LOWER_EDGE_KEY)
44-
freq_stop = annotation.get(SigMFFile.FREQ_UPPER_EDGE_KEY)
45+
freq_start = annotation.get(sigmf.FREQ_LOWER_EDGE_KEY)
46+
freq_stop = annotation.get(sigmf.FREQ_UPPER_EDGE_KEY)
4547
4648
# Get the samples corresponding to annotation
4749
samples = signal.read_samples(annotation_start_idx, annotation_length)
@@ -72,19 +74,19 @@ First, create a single SigMF Recording and save it to disk:
7274
meta = SigMFFile(
7375
data_file="example_cf32.sigmf-data", # extension is optional
7476
global_info={
75-
SigMFFile.DATATYPE_KEY: get_data_type_str(data), # in this case, 'cf32_le'
76-
SigMFFile.SAMPLE_RATE_KEY: 48000,
77-
SigMFFile.AUTHOR_KEY: "jane.doe@domain.org",
78-
SigMFFile.DESCRIPTION_KEY: "All zero complex float32 example file.",
77+
sigmf.DATATYPE_KEY: get_data_type_str(data), # in this case, 'cf32_le'
78+
sigmf.SAMPLE_RATE_KEY: 48000,
79+
sigmf.AUTHOR_KEY: "jane.doe@domain.org",
80+
sigmf.DESCRIPTION_KEY: "All zero complex float32 example file.",
7981
},
8082
)
8183
8284
# create a capture key at time index 0
8385
meta.add_capture(
8486
0,
8587
metadata={
86-
SigMFFile.FREQUENCY_KEY: 915000000,
87-
SigMFFile.DATETIME_KEY: get_sigmf_iso8601_datetime_now(),
88+
sigmf.FREQUENCY_KEY: 915000000,
89+
sigmf.DATETIME_KEY: get_sigmf_iso8601_datetime_now(),
8890
},
8991
)
9092
@@ -93,9 +95,9 @@ First, create a single SigMF Recording and save it to disk:
9395
100,
9496
200,
9597
metadata={
96-
SigMFFile.FLO_KEY: 914995000.0,
97-
SigMFFile.FHI_KEY: 915005000.0,
98-
SigMFFile.COMMENT_KEY: "example annotation",
98+
sigmf.FREQ_LOWER_EDGE_KEY: 914995000.0,
99+
sigmf.FREQ_UPPER_EDGE_KEY: 915005000.0,
100+
sigmf.COMMENT_KEY: "example annotation",
99101
},
100102
)
101103
@@ -118,9 +120,9 @@ Now lets add another SigMF Recording and associate them with a SigMF Collection:
118120
meta_ci16 = SigMFFile(
119121
data_file="example_ci16.sigmf-data", # extension is optional
120122
global_info={
121-
SigMFFile.DATATYPE_KEY: "ci16_le", # get_data_type_str() is only valid for numpy types
122-
SigMFFile.SAMPLE_RATE_KEY: 48000,
123-
SigMFFile.DESCRIPTION_KEY: "All zero complex int16 file.",
123+
sigmf.DATATYPE_KEY: "ci16_le", # get_data_type_str() is only valid for numpy types
124+
sigmf.SAMPLE_RATE_KEY: 48000,
125+
sigmf.DESCRIPTION_KEY: "All zero complex int16 file.",
124126
},
125127
)
126128
meta_ci16.add_capture(0, metadata=meta.get_capture_info(0))
@@ -153,59 +155,34 @@ The SigMF Collection and its associated Recordings can now be loaded like this:
153155
Load a SigMF Archive and slice without untaring
154156
-----------------------------------------------
155157

156-
Since an *archive* is a tarball (uncompressed by default), and since there are many
157-
excellent tools for manipulating tar files, it's fairly straightforward to
158-
access the *data* part of a SigMF archive without un-taring it. This is a
159-
compelling feature because **1** archives make it harder for the ``-data`` and
160-
the ``-meta`` to get separated, and **2** some datasets are so large that it
161-
can be impractical (due to available disk space, or slow network speeds if the
162-
archive file resides on a network file share) or simply obnoxious to untar it
163-
first.
158+
Since an *archive* is a tarball (uncompressed by default), you can access the
159+
*data* part of a SigMF archive without un-taring it. This is a compelling
160+
feature because **1** archives make it harder for the ``-data`` and the
161+
``-meta`` to get separated, and **2** some datasets are so large that it can be
162+
impractical (due to available disk space, or slow network speeds if the archive
163+
file resides on a network file share) or simply obnoxious to untar it first.
164164

165165
::
166166

167167
>>> import sigmf
168-
>>> arc = sigmf.SigMFArchiveReader('/src/LTE.sigmf')
169-
>>> arc.shape
168+
>>> signal = sigmf.fromarchive('/src/LTE.sigmf')
169+
>>> signal.shape
170170
(15379532,)
171-
>>> arc.ndim
171+
>>> signal.ndim
172172
1
173-
>>> arc[:10]
173+
>>> signal[:10]
174174
array([-20.+11.j, -21. -6.j, -17.-20.j, -13.-52.j, 0.-75.j, 22.-58.j,
175175
48.-44.j, 49.-60.j, 31.-56.j, 23.-47.j], dtype=complex64)
176176

177-
The preceeding example exhibits another feature of this approach; the archive
178-
``LTE.sigmf`` is actually ``complex-int16``'s on disk, for which there is no
179-
corresponding type in ``numpy``. However, the ``.sigmffile`` member keeps track of
180-
this, and converts the data to ``numpy.complex64`` *after* slicing it, that is,
181-
after reading it from disk.
177+
If the archive contains ``complex-int16`` data on disk (which has no
178+
corresponding ``numpy`` type), the data is automatically converted to
179+
``numpy.complex64`` after slicing:
182180

183181
::
184182

185-
>>> arc.sigmffile.get_global_field(sigmf.SigMFFile.DATATYPE_KEY)
183+
>>> signal.get_global_field(sigmf.DATATYPE_KEY)
186184
'ci16_le'
187185

188-
>>> arc.sigmffile._memmap.dtype
189-
dtype('int16')
190-
191-
>>> arc.sigmffile._return_type
192-
'<c8'
193-
194-
Another supported mode is the case where you might have an archive that *is not
195-
on disk* but instead is simply ``bytes`` in a python variable.
196-
197-
Instead of needing to write this out to a temporary file before being able to
198-
read it, this can be done "in mid air" or "without touching the ground (disk)".
199-
200-
::
201-
202-
>>> import sigmf, io
203-
>>> sigmf_bytes = io.BytesIO(open('/src/LTE.sigmf', 'rb').read())
204-
>>> arc = sigmf.SigMFArchiveReader(archive_buffer=sigmf_bytes)
205-
>>> arc[:10]
206-
array([-20.+11.j, -21. -6.j, -17.-20.j, -13.-52.j, 0.-75.j, 22.-58.j,
207-
48.-44.j, 49.-60.j, 31.-56.j, 23.-47.j], dtype=complex64)
208-
209186
------------------------------
210187
Compressed SigMF Archives
211188
------------------------------

docs/source/quickstart.rst

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -91,19 +91,19 @@ For full control over global fields, captures, and annotations:
9191
meta = SigMFFile(
9292
data_file="example.sigmf-data", # extension is optional
9393
global_info={
94-
SigMFFile.DATATYPE_KEY: get_data_type_str(data), # in this case, "cf32_le"
95-
SigMFFile.SAMPLE_RATE_KEY: 48000,
96-
SigMFFile.AUTHOR_KEY: "jane.doe@domain.org",
97-
SigMFFile.DESCRIPTION_KEY: "All zero complex float32 example file.",
94+
sigmf.DATATYPE_KEY: get_data_type_str(data), # in this case, "cf32_le"
95+
sigmf.SAMPLE_RATE_KEY: 48000,
96+
sigmf.AUTHOR_KEY: "jane.doe@domain.org",
97+
sigmf.DESCRIPTION_KEY: "All zero complex float32 example file.",
9898
},
9999
)
100100
101101
# create a capture key at time index 0
102102
meta.add_capture(
103103
0,
104104
metadata={
105-
SigMFFile.FREQUENCY_KEY: 915000000,
106-
SigMFFile.DATETIME_KEY: get_sigmf_iso8601_datetime_now(),
105+
sigmf.FREQUENCY_KEY: 915000000,
106+
sigmf.DATETIME_KEY: get_sigmf_iso8601_datetime_now(),
107107
},
108108
)
109109
@@ -112,9 +112,9 @@ For full control over global fields, captures, and annotations:
112112
100,
113113
200,
114114
metadata={
115-
SigMFFile.FLO_KEY: 914995000.0,
116-
SigMFFile.FHI_KEY: 915005000.0,
117-
SigMFFile.COMMENT_KEY: "example annotation",
115+
sigmf.FREQ_LOWER_EDGE_KEY: 914995000.0,
116+
sigmf.FREQ_UPPER_EDGE_KEY: 915005000.0,
117+
sigmf.COMMENT_KEY: "example annotation",
118118
},
119119
)
120120

sigmf/archive.py

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -75,36 +75,6 @@ def _get_archive_basename(path):
7575
return name[: -len(SIGMF_ARCHIVE_EXT)]
7676
return path.stem
7777

78-
SIGMF_COMPRESSED_EXTS = {
79-
# compression type -> unique compound extension
80-
"gz": ".sigmf.gz",
81-
"xz": ".sigmf.xz",
82-
"zip": ".sigmf.zip",
83-
}
84-
85-
# all recognized archive extensions (uncompressed + compressed)
86-
SIGMF_ARCHIVE_EXTS = {SIGMF_ARCHIVE_EXT} | set(SIGMF_COMPRESSED_EXTS.values())
87-
88-
89-
def _detect_compression(path):
90-
"""Detect compression type from a file path's extension(s).
91-
92-
Parameters
93-
----------
94-
path : Path
95-
Path to check.
96-
97-
Returns
98-
-------
99-
str or None
100-
Compression type ("gz", "xz", "zip") or None for uncompressed.
101-
"""
102-
name = str(path).lower()
103-
for comp_type, ext in SIGMF_COMPRESSED_EXTS.items():
104-
if name.endswith(ext):
105-
return comp_type
106-
return None
107-
10878

10979
def _get_archive_basename(path):
11080
"""Get the archive base name (without any sigmf archive extension).
@@ -207,7 +177,6 @@ def __init__(self, sigmffile, name=None, fileobj=None, compression=None, overwri
207177
with open(meta_path, "w") as handle:
208178
self.sigmffile.dump(handle)
209179
if isinstance(self.sigmffile.data_buffer, io.BytesIO):
210-
self.sigmffile.data_file = data_path
211180
with open(data_path, "wb") as handle:
212181
handle.write(self.sigmffile.data_buffer.getbuffer())
213182
else:

sigmf/archivereader.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ def _read_tar_obj(self, tar_obj):
116116
with tar_obj.extractfile(memb) as fid:
117117
data_buffer = io.BytesIO(fid.read())
118118

119+
if json_contents is None:
120+
raise SigMFFileError("No .sigmf-meta file found in archive!")
119121
if data_buffer is None:
120122
raise SigMFFileError("No .sigmf-data file found in archive!")
121123
return json_contents, data_buffer, data_size_bytes
@@ -151,6 +153,8 @@ def _read_zip_obj(self, zf):
151153
data_size_bytes = len(raw)
152154
data_buffer = io.BytesIO(raw)
153155

156+
if json_contents is None:
157+
raise SigMFFileError("No .sigmf-meta file found in archive!")
154158
if data_buffer is None:
155159
raise SigMFFileError("No .sigmf-data file found in archive!")
156160
return json_contents, data_buffer, data_size_bytes
@@ -188,6 +192,8 @@ def _init_from_tar_memmap(self, path, skip_checksum, map_readonly, autoscale):
188192

189193
tar_obj.close()
190194

195+
if json_contents is None:
196+
raise SigMFFileError("No .sigmf-meta file found in archive!")
191197
if data_offset is None:
192198
raise SigMFFileError("No .sigmf-data file found in archive!")
193199

sigmf/convert/blue.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
import numpy as np
2626
from packaging.version import InvalidVersion, Version
2727

28-
from ..error import SigMFConversionError
2928
from .. import keys
29+
from ..error import SigMFConversionError
3030
from ..sigmffile import SigMFFile, fromfile, get_sigmf_filenames
3131
from ..utils import SIGMF_DATETIME_ISO8601_FMT
3232

sigmf/convert/signalhound.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@
1818
import defusedxml.ElementTree as ET
1919
import numpy as np
2020

21-
from .. import SigMFFile, fromfile
22-
from .. import keys
21+
from .. import SigMFFile, fromfile, keys
2322
from ..error import SigMFConversionError
2423
from ..sigmffile import get_sigmf_filenames
2524
from ..utils import SIGMF_DATETIME_ISO8601_FMT

sigmf/convert/wav.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,8 @@
1717
import numpy as np
1818

1919
from .. import SigMFFile
20-
from .. import keys
2120
from .. import __version__ as toolversion
22-
from .. import fromfile
21+
from .. import fromfile, keys
2322
from ..error import SigMFFileExistsError
2423
from ..sigmffile import get_sigmf_filenames
2524
from ..utils import SIGMF_DATETIME_ISO8601_FMT, get_data_type_str

sigmf/keys.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,14 @@
138138
# all recognized archive extensions (uncompressed + compressed)
139139
SIGMF_ARCHIVE_EXTS = {SIGMF_ARCHIVE_EXT} | set(SIGMF_COMPRESSED_EXTS.values())
140140

141+
# all SigMF file suffixes
142+
SIGMF_SUFFIXES = [
143+
SIGMF_DATASET_EXT,
144+
SIGMF_METADATA_EXT,
145+
SIGMF_ARCHIVE_EXT,
146+
SIGMF_COLLECTION_EXT,
147+
]
148+
141149
# ---------------------------------------------------------------------------
142150
# deprecated alias map — used by _SigMFDeprecatingMeta in sigmffile.py
143151
# maps old_name -> (new_name, value)
@@ -210,4 +218,5 @@
210218
"SIGMF_COLLECTION_EXT",
211219
"SIGMF_COMPRESSED_EXTS",
212220
"SIGMF_ARCHIVE_EXTS",
221+
"SIGMF_SUFFIXES",
213222
]

sigmf/siggen.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -561,7 +561,7 @@ def create_full_signal_annotation(label: str) -> dict:
561561
annotations.append(phase_annotation)
562562

563563
# sort annotations by sample_start to satisfy sigmf ordering requirement
564-
annotations.sort(key=lambda a: a[SigMFFile.START_INDEX_KEY])
564+
annotations.sort(key=lambda a: a[keys.SAMPLE_START_KEY])
565565

566566
return annotations
567567

sigmf/sigmffile.py

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@
3939

4040

4141
class _DeprecatingKey:
42-
'''Descriptor that emits DeprecationWarning when a field key constant is
42+
"""Descriptor that emits DeprecationWarning when a field key constant is
4343
accessed from the class or an instance, directing users to the sigmf module level.
44-
Each attribute name only warns once per interpreter session.'''
44+
Each attribute name only warns once per interpreter session."""
4545

4646
_warned: set = set()
4747

@@ -1368,15 +1368,15 @@ def fromarray(data, sample_rate, frequency=None, global_info=None):
13681368

13691369
# build metadata
13701370
info = {
1371-
SigMFFile.DATATYPE_KEY: get_data_type_str(data),
1372-
SigMFFile.SAMPLE_RATE_KEY: sample_rate,
1371+
keys.DATATYPE_KEY: get_data_type_str(data),
1372+
keys.SAMPLE_RATE_KEY: sample_rate,
13731373
}
13741374
if global_info is not None:
13751375
info.update(global_info)
13761376

13771377
capture_meta = None
13781378
if frequency is not None:
1379-
capture_meta = {SigMFFile.FREQUENCY_KEY: frequency}
1379+
capture_meta = {keys.FREQUENCY_KEY: frequency}
13801380

13811381
# create sigmffile object with in-memory buffer
13821382
meta = SigMFFile(global_info=info)
@@ -1537,13 +1537,7 @@ def get_sigmf_filenames(filename):
15371537
# If the path has a sigmf suffix, remove it. Otherwise do not remove the
15381538
# suffix, because the filename might contain '.' characters which are part
15391539
# of the filename rather than an extension.
1540-
sigmf_suffixes = [
1541-
SIGMF_DATASET_EXT,
1542-
SIGMF_METADATA_EXT,
1543-
SIGMF_ARCHIVE_EXT,
1544-
SIGMF_COLLECTION_EXT,
1545-
]
1546-
if stem_path.suffix in sigmf_suffixes:
1540+
if stem_path.suffix in keys.SIGMF_SUFFIXES:
15471541
with_suffix_path = stem_path
15481542
stem_path = stem_path.with_suffix("")
15491543
else:

0 commit comments

Comments
 (0)