Skip to content

Commit 2fefb43

Browse files
authored
feature/readthedocs (#97)
* add ReadtheDocs config; officially support python3.13 * add shields for docs, downloads, workflow status * move most of README to rst; split examples into quickstart & advanced * increment patch version
1 parent c497d9e commit 2fefb43

18 files changed

+703
-247
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
runs-on: ubuntu-22.04
1313
strategy:
1414
matrix:
15-
python-version: ["3.7", "3.9", "3.12"]
15+
python-version: ["3.7", "3.9", "3.11", "3.13"]
1616
steps:
1717
- uses: actions/checkout@v3
1818
- name: Set up Python ${{ matrix.python-version }}

.gitignore

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
# temp files
2+
__pycache__/
23
*.swp
3-
*.pyc
4+
*.py[cod]
45
.cache
56

6-
# setuptools related
7-
dist/*
8-
build/*
9-
.eggs/*
7+
# packaging related
8+
dist/
9+
build/
10+
eggs/
11+
.eggs/
1012
SigMF.egg-info/*
1113

1214
# test related
@@ -16,3 +18,7 @@ SigMF.egg-info/*
1618
coverage.xml
1719
pytest.xml
1820
htmlcov/*
21+
22+
# docs related
23+
docs/_build/
24+
docs/source/_autosummary/

.readthedocs.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Read the Docs configuration file
2+
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
3+
4+
# Required
5+
version: 2
6+
7+
# Set the OS, Python version, and other tools you might need
8+
build:
9+
os: ubuntu-24.04
10+
tools:
11+
python: "3.13"
12+
13+
# declare the Python requirements required to build your documentation
14+
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
15+
python:
16+
install:
17+
- method: pip
18+
path: .
19+
extra_requirements:
20+
- test
21+
- apps
22+
- requirements: docs/requirements.txt
23+
24+
# Build documentation in the "docs/" directory with Sphinx
25+
sphinx:
26+
configuration: docs/source/conf.py

README.md

Lines changed: 11 additions & 240 deletions
Original file line numberDiff line numberDiff line change
@@ -1,250 +1,21 @@
1-
<p align="center"><img src="https://github.com/sigmf/SigMF/blob/v1.2.0/logo/sigmf_logo.png" alt="Rendered SigMF Logo"/></p>
1+
![Rendered SigMF Logo](https://raw.githubusercontent.com/sigmf/SigMF/refs/heads/main/logo/sigmf_logo.png)
22

3-
This python module makes it easy to interact with Signal Metadata Format
4-
(SigMF) recordings. This module works with Python 3.7+ and is distributed
3+
[![PyPI Version Shield](https://img.shields.io/pypi/v/sigmf)](https://pypi.org/project/SigMF/)
4+
[![Build Status Shield](https://img.shields.io/github/actions/workflow/status/sigmf/sigmf-python/main.yml)](https://github.com/sigmf/sigmf-python/actions?query=branch%3Amain)
5+
[![License Shield](https://img.shields.io/pypi/l/sigmf)](https://en.wikipedia.org/wiki/GNU_Lesser_General_Public_License)
6+
[![Documentation Shield](https://img.shields.io/readthedocs/sigmf)](https://sigmf.readthedocs.io/en/latest/)
7+
[![PyPI Downloads Shield](https://img.shields.io/pypi/dm/sigmf)](https://pypi.org/project/SigMF/)
8+
9+
The `sigmf` library makes it easy to interact with Signal Metadata Format
10+
(SigMF) recordings. This library is compatible with Python 3.7-3.13 and is distributed
511
freely under the terms GNU Lesser GPL v3 License.
612

713
This module follows the SigMF specification [html](https://sigmf.org/)/[pdf](https://sigmf.github.io/SigMF/sigmf-spec.pdf) from the [spec repository](https://github.com/sigmf/SigMF).
814

9-
# Installation
10-
11-
To install the latest PyPi release, install from pip:
15+
To install the latest PyPI release, install from pip:
1216

1317
```bash
1418
pip install sigmf
1519
```
1620

17-
To install the latest git release, build from source:
18-
19-
```bash
20-
git clone https://github.com/sigmf/sigmf-python.git
21-
cd sigmf-python
22-
pip install .
23-
```
24-
25-
Testing can be run with a variety of tools:
26-
27-
```bash
28-
# pytest and coverage run locally
29-
pytest
30-
coverage run
31-
# run coverage in a venv
32-
tox run
33-
# other useful tools
34-
pylint sigmf tests
35-
pytype
36-
black
37-
flake8
38-
```
39-
40-
# Examples
41-
42-
### Load a SigMF archive; read all samples & metadata
43-
44-
```python
45-
import sigmf
46-
handle = sigmf.sigmffile.fromfile('example.sigmf')
47-
handle.read_samples() # returns all timeseries data
48-
handle.get_global_info() # returns 'global' dictionary
49-
handle.get_captures() # returns list of 'captures' dictionaries
50-
handle.get_annotations() # returns list of all annotations
51-
```
52-
53-
### Verify SigMF dataset integrity & compliance
54-
55-
```bash
56-
sigmf_validate example.sigmf
57-
```
58-
59-
### Load a SigMF dataset; read its annotation, metadata, and samples
60-
61-
```python
62-
from sigmf import SigMFFile, sigmffile
63-
64-
# Load a dataset
65-
filename = 'logo/sigmf_logo' # extension is optional
66-
signal = sigmffile.fromfile(filename)
67-
68-
# Get some metadata and all annotations
69-
sample_rate = signal.get_global_field(SigMFFile.SAMPLE_RATE_KEY)
70-
sample_count = signal.sample_count
71-
signal_duration = sample_count / sample_rate
72-
annotations = signal.get_annotations()
73-
74-
# Iterate over annotations
75-
for adx, annotation in enumerate(annotations):
76-
annotation_start_idx = annotation[SigMFFile.START_INDEX_KEY]
77-
annotation_length = annotation[SigMFFile.LENGTH_INDEX_KEY]
78-
annotation_comment = annotation.get(SigMFFile.COMMENT_KEY, "[annotation {}]".format(adx))
79-
80-
# Get capture info associated with the start of annotation
81-
capture = signal.get_capture_info(annotation_start_idx)
82-
freq_center = capture.get(SigMFFile.FREQUENCY_KEY, 0)
83-
freq_min = freq_center - 0.5*sample_rate
84-
freq_max = freq_center + 0.5*sample_rate
85-
86-
# Get frequency edges of annotation (default to edges of capture)
87-
freq_start = annotation.get(SigMFFile.FLO_KEY)
88-
freq_stop = annotation.get(SigMFFile.FHI_KEY)
89-
90-
# Get the samples corresponding to annotation
91-
samples = signal.read_samples(annotation_start_idx, annotation_length)
92-
```
93-
94-
### Create and save a Collection of SigMF Recordings from numpy arrays
95-
96-
First, create a single SigMF Recording and save it to disk
97-
98-
```python
99-
import datetime as dt
100-
import numpy as np
101-
import sigmf
102-
from sigmf import SigMFFile
103-
from sigmf.utils import get_data_type_str, get_sigmf_iso8601_datetime_now
104-
105-
# suppose we have an complex timeseries signal
106-
data = np.zeros(1024, dtype=np.complex64)
107-
108-
# write those samples to file in cf32_le
109-
data.tofile('example_cf32.sigmf-data')
110-
111-
# create the metadata
112-
meta = SigMFFile(
113-
data_file='example_cf32.sigmf-data', # extension is optional
114-
global_info = {
115-
SigMFFile.DATATYPE_KEY: get_data_type_str(data), # in this case, 'cf32_le'
116-
SigMFFile.SAMPLE_RATE_KEY: 48000,
117-
SigMFFile.AUTHOR_KEY: 'jane.doe@domain.org',
118-
SigMFFile.DESCRIPTION_KEY: 'All zero complex float32 example file.',
119-
}
120-
)
121-
122-
# create a capture key at time index 0
123-
meta.add_capture(0, metadata={
124-
SigMFFile.FREQUENCY_KEY: 915000000,
125-
SigMFFile.DATETIME_KEY: get_sigmf_iso8601_datetime_now(),
126-
})
127-
128-
# add an annotation at sample 100 with length 200 & 10 KHz width
129-
meta.add_annotation(100, 200, metadata = {
130-
SigMFFile.FLO_KEY: 914995000.0,
131-
SigMFFile.FHI_KEY: 915005000.0,
132-
SigMFFile.COMMENT_KEY: 'example annotation',
133-
})
134-
135-
# check for mistakes & write to disk
136-
meta.tofile('example_cf32.sigmf-meta') # extension is optional
137-
```
138-
139-
Now lets add another SigMF Recording and associate them with a SigMF Collection:
140-
141-
```python
142-
from sigmf import SigMFCollection
143-
144-
data_ci16 = np.zeros(1024, dtype=np.complex64)
145-
146-
#rescale and save as a complex int16 file:
147-
data_ci16 *= pow(2, 15)
148-
data_ci16.view(np.float32).astype(np.int16).tofile('example_ci16.sigmf-data')
149-
150-
# create the metadata for the second file
151-
meta_ci16 = SigMFFile(
152-
data_file='example_ci16.sigmf-data', # extension is optional
153-
global_info = {
154-
SigMFFile.DATATYPE_KEY: 'ci16_le', # get_data_type_str() is only valid for numpy types
155-
SigMFFile.SAMPLE_RATE_KEY: 48000,
156-
SigMFFile.DESCRIPTION_KEY: 'All zero complex int16 file.',
157-
}
158-
)
159-
meta_ci16.add_capture(0, metadata=meta.get_capture_info(0))
160-
meta_ci16.tofile('example_ci16.sigmf-meta')
161-
162-
collection = SigMFCollection(['example_cf32.sigmf-meta', 'example_ci16.sigmf-meta'],
163-
metadata = {'collection': {
164-
SigMFCollection.AUTHOR_KEY: 'sigmf@sigmf.org',
165-
SigMFCollection.DESCRIPTION_KEY: 'Collection of two all zero files.',
166-
}
167-
}
168-
)
169-
streams = collection.get_stream_names()
170-
sigmf = [collection.get_SigMFFile(stream) for stream in streams]
171-
collection.tofile('example_zeros.sigmf-collection')
172-
```
173-
174-
The SigMF Collection and its associated Recordings can now be loaded like this:
175-
176-
```python
177-
from sigmf import sigmffile
178-
collection = sigmffile.fromfile('example_zeros')
179-
ci16_sigmffile = collection.get_SigMFFile(stream_name='example_ci16')
180-
cf32_sigmffile = collection.get_SigMFFile(stream_name='example_cf32')
181-
```
182-
183-
### Load a SigMF Archive and slice its data without untaring it
184-
185-
Since an *archive* is merely a tarball (uncompressed), and since there any many
186-
excellent tools for manipulating tar files, it's fairly straightforward to
187-
access the *data* part of a SigMF archive without un-taring it. This is a
188-
compelling feature because __1__ archives make it harder for the `-data` and
189-
the `-meta` to get separated, and __2__ some datasets are so large that it can
190-
be impractical (due to available disk space, or slow network speeds if the
191-
archive file resides on a network file share) or simply obnoxious to untar it
192-
first.
193-
194-
```python
195-
>>> import sigmf
196-
>>> arc = sigmf.SigMFArchiveReader('/src/LTE.sigmf')
197-
>>> arc.shape
198-
(15379532,)
199-
>>> arc.ndim
200-
1
201-
>>> arc[:10]
202-
array([-20.+11.j, -21. -6.j, -17.-20.j, -13.-52.j, 0.-75.j, 22.-58.j,
203-
48.-44.j, 49.-60.j, 31.-56.j, 23.-47.j], dtype=complex64)
204-
```
205-
206-
The preceeding example exhibits another feature of this approach; the archive
207-
`LTE.sigmf` is actually `complex-int16`'s on disk, for which there is no
208-
corresponding type in `numpy`. However, the `.sigmffile` member keeps track of
209-
this, and converts the data to `numpy.complex64` *after* slicing it, that is,
210-
after reading it from disk.
211-
212-
```python
213-
>>> arc.sigmffile.get_global_field(sigmf.SigMFFile.DATATYPE_KEY)
214-
'ci16_le'
215-
216-
>>> arc.sigmffile._memmap.dtype
217-
dtype('int16')
218-
219-
>>> arc.sigmffile._return_type
220-
'<c8'
221-
```
222-
223-
Another supported mode is the case where you might have an archive that *is not
224-
on disk* but instead is simply `bytes` in a python variable.
225-
Instead of needing to write this out to a temporary file before being able to
226-
read it, this can be done "in mid air" or "without touching the ground (disk)".
227-
228-
```python
229-
>>> import sigmf, io
230-
>>> sigmf_bytes = io.BytesIO(open('/src/LTE.sigmf', 'rb').read())
231-
>>> arc = sigmf.SigMFArchiveReader(archive_buffer=sigmf_bytes)
232-
>>> arc[:10]
233-
array([-20.+11.j, -21. -6.j, -17.-20.j, -13.-52.j, 0.-75.j, 22.-58.j,
234-
48.-44.j, 49.-60.j, 31.-56.j, 23.-47.j], dtype=complex64)
235-
```
236-
237-
# Frequently Asked Questions
238-
239-
### Is this a GNU Radio effort?
240-
241-
*No*, this is not a GNU Radio-specific effort.
242-
This effort first emerged from a group of GNU Radio core
243-
developers, but the goal of the project to provide a standard that will be
244-
useful to anyone and everyone, regardless of tool or workflow.
245-
246-
### Is this specific to wireless communications?
247-
248-
*No*, similar to the response, above, the goal is to create something that is
249-
generally applicable to _signal processing_, regardless of whether or not the
250-
application is communications related.
21+
**[Please visit the documentation for examples & more info.](https://sigmf.readthedocs.io/en/latest/)**

docs/Makefile

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Minimal makefile for Sphinx documentation
2+
#
3+
4+
# You can set these variables from the command line, and also
5+
# from the environment for the first two.
6+
SPHINXOPTS ?=
7+
SPHINXBUILD ?= sphinx-build
8+
SOURCEDIR = source
9+
BUILDDIR = build
10+
11+
# Put it first so that "make" without argument is like "make help".
12+
help:
13+
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14+
15+
.PHONY: help Makefile
16+
17+
# Catch-all target: route all unknown targets to Sphinx using the new
18+
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19+
%: Makefile
20+
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

docs/make.bat

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
@ECHO OFF
2+
3+
pushd %~dp0
4+
5+
REM Command file for Sphinx documentation
6+
7+
if "%SPHINXBUILD%" == "" (
8+
set SPHINXBUILD=sphinx-build
9+
)
10+
set SOURCEDIR=source
11+
set BUILDDIR=build
12+
13+
if "%1" == "" goto help
14+
15+
%SPHINXBUILD% >NUL 2>NUL
16+
if errorlevel 9009 (
17+
echo.
18+
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
19+
echo.installed, then set the SPHINXBUILD environment variable to point
20+
echo.to the full path of the 'sphinx-build' executable. Alternatively you
21+
echo.may add the Sphinx directory to PATH.
22+
echo.
23+
echo.If you don't have Sphinx installed, grab it from
24+
echo.http://sphinx-doc.org/
25+
exit /b 1
26+
)
27+
28+
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
29+
goto end
30+
31+
:help
32+
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
33+
34+
:end
35+
popd

docs/requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# pinned 2025-01-15
2+
sphinx==8.1.3
3+
sphinx-rtd-theme==3.0.2

0 commit comments

Comments
 (0)