Skip to content
Merged
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,28 @@ meta = sigmf.fromfile("recording.sigmf-meta")
samples = meta[0:1024] # get first 1024 samples
sample_rate = meta.sample_rate # get sample rate

# read compressed SigMF archives
meta = sigmf.fromfile("recording.sigmf.gz") # gzip-compressed
meta = sigmf.fromfile("recording.sigmf.xz") # xz-compressed
meta = sigmf.fromfile("recording.sigmf.zip") # zip archive

# read other formats containing RF time series as SigMF
meta = sigmf.fromfile("recording.wav") # WAV
meta = sigmf.fromfile("recording.cdif") # BLUE / Platinum
meta = sigmf.fromfile("recording.xml") # Signal Hound Spike
```

### Write SigMF

```python
import numpy as np
import sigmf

data = np.array([0.1 + 0.2j, 0.3 + 0.4j], dtype=np.complex64)
# creates recording.sigmf-data and recording.sigmf-meta
meta = sigmf.tofile("recording", data, sample_rate=48000)
```

### Docs

**[Please visit our documentation for full API reference and more info.](https://sigmf.readthedocs.io/en/latest/)**
62 changes: 55 additions & 7 deletions docs/source/advanced.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ the recording of the SigMF logo used in this example `from the specification

# Iterate over annotations
for adx, annotation in enumerate(annotations):
annotation_start_idx = annotation[SigMFFile.START_INDEX_KEY]
annotation_length = annotation[SigMFFile.LENGTH_INDEX_KEY]
annotation_start_idx = annotation[SigMFFile.SAMPLE_START_KEY]
annotation_length = annotation[SigMFFile.SAMPLE_COUNT_KEY]
annotation_comment = annotation.get(SigMFFile.COMMENT_KEY, "[annotation {}]".format(adx))
Comment thread
Teque5 marked this conversation as resolved.
Outdated

# Get capture info associated with the start of annotation
Expand All @@ -40,8 +40,8 @@ the recording of the SigMF logo used in this example `from the specification
freq_max = freq_center + 0.5*sample_rate

# Get frequency edges of annotation (default to edges of capture)
freq_start = annotation.get(SigMFFile.FLO_KEY)
freq_stop = annotation.get(SigMFFile.FHI_KEY)
freq_start = annotation.get(SigMFFile.FREQ_LOWER_EDGE_KEY)
freq_stop = annotation.get(SigMFFile.FREQ_UPPER_EDGE_KEY)

# Get the samples corresponding to annotation
samples = signal.read_samples(annotation_start_idx, annotation_length)
Expand Down Expand Up @@ -87,8 +87,8 @@ First, create a single SigMF Recording and save it to disk:

# add an annotation at sample 100 with length 200 & 10 KHz width
meta.add_annotation(100, 200, metadata = {
SigMFFile.FLO_KEY: 914995000.0,
SigMFFile.FHI_KEY: 915005000.0,
SigMFFile.FREQ_LOWER_EDGE_KEY: 914995000.0,
SigMFFile.FREQ_UPPER_EDGE_KEY: 915005000.0,
SigMFFile.COMMENT_KEY: 'example annotation',
})

Expand Down Expand Up @@ -143,7 +143,7 @@ The SigMF Collection and its associated Recordings can now be loaded like this:
Load a SigMF Archive and slice without untaring
-----------------------------------------------

Since an *archive* is merely a tarball (uncompressed), and since there any many
Since an *archive* is a tarball (uncompressed by default), and since there are many
excellent tools for manipulating tar files, it's fairly straightforward to
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
Expand Down Expand Up @@ -195,3 +195,51 @@ read it, this can be done "in mid air" or "without touching the ground (disk)".
>>> arc[:10]
array([-20.+11.j, -21. -6.j, -17.-20.j, -13.-52.j, 0.-75.j, 22.-58.j,
48.-44.j, 49.-60.j, 31.-56.j, 23.-47.j], dtype=complex64)

------------------------------
Compressed SigMF Archives
------------------------------

SigMF archives can be compressed using gzip, xz, or zip. The compression format
is determined by the file extension:

+---------------------+-------------+
| Extension | Format |
+=====================+=============+
| ``.sigmf`` | uncompressed|
+---------------------+-------------+
| ``.sigmf.gz`` | gzip tar |
+---------------------+-------------+
| ``.sigmf.xz`` | xz tar |
+---------------------+-------------+
| ``.sigmf.zip`` | zip archive |
+---------------------+-------------+

**Writing compressed archives:**

::

>>> import sigmf
>>> signal = sigmf.sigmffile.fromfile('recording.sigmf-meta')

# compress by extension
>>> signal.archive('recording.sigmf.xz')

# or specify compression explicitly
>>> signal.archive('recording.sigmf', compression='gz')

**Reading compressed archives:**

::

>>> arc = sigmf.SigMFArchiveReader('recording.sigmf.xz')
>>> arc[:10]
array([-20.+11.j, ...], dtype=complex64)

**Memory behavior:**

Uncompressed ``.sigmf`` archives use ``numpy.memmap`` to access the data
directly inside the tar file — no extra memory is needed, even for very large
recordings. Compressed archives (``.sigmf.gz``, ``.sigmf.xz``, ``.sigmf.zip``)
must decompress the data into RAM before it can be accessed. Keep this in mind
when working with large compressed recordings.
30 changes: 28 additions & 2 deletions docs/source/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,32 @@ Verify SigMF Integrity & Compliance
Save a Numpy array as a SigMF Recording
---------------------------------------

.. code-block:: python

import numpy as np
import sigmf

# suppose we have a complex timeseries signal
data = np.zeros(1024, dtype=np.complex64)

# write to disk — datatype is inferred from the numpy array
meta = sigmf.tofile("example", data, sample_rate=48000, frequency=915e6)

# or write to a SigMF archive (example.sigmf)
meta = sigmf.tofile("example.sigmf", data, sample_rate=48000, frequency=915e6)

# or write directly to a compressed archive (example.sigmf.xz)
meta = sigmf.tofile("example", data, sample_rate=48000, compression="xz")

The returned ``SigMFFile`` object can be used to add captures, annotations,
or archive the recording.

---------------------------------------------------
Save a Numpy array with Full Metadata (Advanced)
---------------------------------------------------

For full control over global fields, captures, and annotations:

.. code-block:: python

import numpy as np
Expand Down Expand Up @@ -76,8 +102,8 @@ Save a Numpy array as a SigMF Recording

# add an annotation at sample 100 with length 200 & 10 KHz width
meta.add_annotation(100, 200, metadata = {
SigMFFile.FLO_KEY: 914995000.0,
SigMFFile.FHI_KEY: 915005000.0,
SigMFFile.FREQ_LOWER_EDGE_KEY: 914995000.0,
SigMFFile.FREQ_UPPER_EDGE_KEY: 915005000.0,
SigMFFile.COMMENT_KEY: "example annotation",
Comment thread
Teque5 marked this conversation as resolved.
Outdated
})

Expand Down
7 changes: 5 additions & 2 deletions sigmf/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@
# SPDX-License-Identifier: LGPL-3.0-or-later

# version of this python module
__version__ = "1.9.1"
__version__ = "1.12.0"

# matching version of the SigMF specification
__specification__ = "1.2.6"

from . import (
archive,
archivereader,
error,
keys,
schema,
siggen,
sigmffile,
Expand All @@ -21,5 +23,6 @@
)
from .archive import SigMFArchive
from .archivereader import SigMFArchiveReader
from .keys import * # noqa: F401, F403
from .siggen import SigMFGenerator
from .sigmffile import SigMFCollection, SigMFFile, fromarchive, fromfile
from .sigmffile import SigMFCollection, SigMFFile, fromarchive, fromfile, tofile
Loading
Loading