diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index e9f7109..e688093 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-22.04
strategy:
matrix:
- python-version: ["3.7", "3.9", "3.12"]
+ python-version: ["3.7", "3.9", "3.11", "3.13"]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
diff --git a/.gitignore b/.gitignore
index 5c9c73d..85bad5a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,12 +1,14 @@
# temp files
+__pycache__/
*.swp
-*.pyc
+*.py[cod]
.cache
-# setuptools related
-dist/*
-build/*
-.eggs/*
+# packaging related
+dist/
+build/
+eggs/
+.eggs/
SigMF.egg-info/*
# test related
@@ -16,3 +18,7 @@ SigMF.egg-info/*
coverage.xml
pytest.xml
htmlcov/*
+
+# docs related
+docs/_build/
+docs/source/_autosummary/
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 0000000..9ee2227
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,26 @@
+# Read the Docs configuration file
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+
+# Required
+version: 2
+
+# Set the OS, Python version, and other tools you might need
+build:
+ os: ubuntu-24.04
+ tools:
+ python: "3.13"
+
+# declare the Python requirements required to build your documentation
+# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
+python:
+ install:
+ - method: pip
+ path: .
+ extra_requirements:
+ - test
+ - apps
+ - requirements: docs/requirements.txt
+
+# Build documentation in the "docs/" directory with Sphinx
+sphinx:
+ configuration: docs/source/conf.py
diff --git a/README.md b/README.md
index 2f4d4d5..2dca188 100644
--- a/README.md
+++ b/README.md
@@ -1,250 +1,21 @@
-

+
-This python module makes it easy to interact with Signal Metadata Format
-(SigMF) recordings. This module works with Python 3.7+ and is distributed
+[](https://pypi.org/project/SigMF/)
+[](https://github.com/sigmf/sigmf-python/actions?query=branch%3Amain)
+[](https://en.wikipedia.org/wiki/GNU_Lesser_General_Public_License)
+[](https://sigmf.readthedocs.io/en/latest/)
+[](https://pypi.org/project/SigMF/)
+
+The `sigmf` library makes it easy to interact with Signal Metadata Format
+(SigMF) recordings. This library is compatible with Python 3.7-3.13 and is distributed
freely under the terms GNU Lesser GPL v3 License.
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).
-# Installation
-
-To install the latest PyPi release, install from pip:
+To install the latest PyPI release, install from pip:
```bash
pip install sigmf
```
-To install the latest git release, build from source:
-
-```bash
-git clone https://github.com/sigmf/sigmf-python.git
-cd sigmf-python
-pip install .
-```
-
-Testing can be run with a variety of tools:
-
-```bash
-# pytest and coverage run locally
-pytest
-coverage run
-# run coverage in a venv
-tox run
-# other useful tools
-pylint sigmf tests
-pytype
-black
-flake8
-```
-
-# Examples
-
-### Load a SigMF archive; read all samples & metadata
-
-```python
-import sigmf
-handle = sigmf.sigmffile.fromfile('example.sigmf')
-handle.read_samples() # returns all timeseries data
-handle.get_global_info() # returns 'global' dictionary
-handle.get_captures() # returns list of 'captures' dictionaries
-handle.get_annotations() # returns list of all annotations
-```
-
-### Verify SigMF dataset integrity & compliance
-
-```bash
-sigmf_validate example.sigmf
-```
-
-### Load a SigMF dataset; read its annotation, metadata, and samples
-
-```python
-from sigmf import SigMFFile, sigmffile
-
-# Load a dataset
-filename = 'logo/sigmf_logo' # extension is optional
-signal = sigmffile.fromfile(filename)
-
-# Get some metadata and all annotations
-sample_rate = signal.get_global_field(SigMFFile.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[SigMFFile.START_INDEX_KEY]
- annotation_length = annotation[SigMFFile.LENGTH_INDEX_KEY]
- annotation_comment = annotation.get(SigMFFile.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(SigMFFile.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(SigMFFile.FLO_KEY)
- freq_stop = annotation.get(SigMFFile.FHI_KEY)
-
- # Get the samples corresponding to annotation
- samples = signal.read_samples(annotation_start_idx, annotation_length)
-```
-
-### Create and save a Collection of SigMF Recordings from numpy arrays
-
-First, create a single SigMF Recording and save it to disk
-
-```python
-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 an 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 = {
- SigMFFile.DATATYPE_KEY: get_data_type_str(data), # in this case, 'cf32_le'
- SigMFFile.SAMPLE_RATE_KEY: 48000,
- SigMFFile.AUTHOR_KEY: 'jane.doe@domain.org',
- SigMFFile.DESCRIPTION_KEY: 'All zero complex float32 example file.',
- }
-)
-
-# create a capture key at time index 0
-meta.add_capture(0, metadata={
- SigMFFile.FREQUENCY_KEY: 915000000,
- SigMFFile.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 = {
- SigMFFile.FLO_KEY: 914995000.0,
- SigMFFile.FHI_KEY: 915005000.0,
- SigMFFile.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:
-
-```python
-from sigmf import 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 = {
- SigMFFile.DATATYPE_KEY: 'ci16_le', # get_data_type_str() is only valid for numpy types
- SigMFFile.SAMPLE_RATE_KEY: 48000,
- SigMFFile.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:
-
-```python
-from sigmf import sigmffile
-collection = sigmffile.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 its data without untaring it
-
-Since an *archive* is merely a tarball (uncompressed), and since there any 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
-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.
-
-```python
->>> import sigmf
->>> arc = sigmf.SigMFArchiveReader('/src/LTE.sigmf')
->>> arc.shape
-(15379532,)
->>> arc.ndim
-1
->>> 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)
-```
-
-The preceeding example exhibits another feature of this approach; the archive
-`LTE.sigmf` is actually `complex-int16`'s on disk, for which there is no
-corresponding type in `numpy`. However, the `.sigmffile` member keeps track of
-this, and converts the data to `numpy.complex64` *after* slicing it, that is,
-after reading it from disk.
-
-```python
->>> arc.sigmffile.get_global_field(sigmf.SigMFFile.DATATYPE_KEY)
-'ci16_le'
-
->>> arc.sigmffile._memmap.dtype
-dtype('int16')
-
->>> arc.sigmffile._return_type
-'>> import sigmf, io
->>> sigmf_bytes = io.BytesIO(open('/src/LTE.sigmf', 'rb').read())
->>> arc = sigmf.SigMFArchiveReader(archive_buffer=sigmf_bytes)
->>> 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)
-```
-
-# Frequently Asked Questions
-
-### Is this a GNU Radio effort?
-
-*No*, this is not a GNU Radio-specific effort.
-This effort first emerged from a group of GNU Radio core
-developers, but the goal of the project to provide a standard that will be
-useful to anyone and everyone, regardless of tool or workflow.
-
-### Is this specific to wireless communications?
-
-*No*, similar to the response, above, the goal is to create something that is
-generally applicable to _signal processing_, regardless of whether or not the
-application is communications related.
+**[Please visit the documentation for examples & more info.](https://sigmf.readthedocs.io/en/latest/)**
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000..d0c3cbf
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS ?=
+SPHINXBUILD ?= sphinx-build
+SOURCEDIR = source
+BUILDDIR = build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/make.bat b/docs/make.bat
new file mode 100644
index 0000000..6247f7e
--- /dev/null
+++ b/docs/make.bat
@@ -0,0 +1,35 @@
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=source
+set BUILDDIR=build
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+ echo.
+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+ echo.installed, then set the SPHINXBUILD environment variable to point
+ echo.to the full path of the 'sphinx-build' executable. Alternatively you
+ echo.may add the Sphinx directory to PATH.
+ echo.
+ echo.If you don't have Sphinx installed, grab it from
+ echo.http://sphinx-doc.org/
+ exit /b 1
+)
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+
+:end
+popd
diff --git a/docs/requirements.txt b/docs/requirements.txt
new file mode 100644
index 0000000..1016144
--- /dev/null
+++ b/docs/requirements.txt
@@ -0,0 +1,3 @@
+# pinned 2025-01-15
+sphinx==8.1.3
+sphinx-rtd-theme==3.0.2
diff --git a/docs/source/_templates/custom-class-template.rst b/docs/source/_templates/custom-class-template.rst
new file mode 100644
index 0000000..f73eda5
--- /dev/null
+++ b/docs/source/_templates/custom-class-template.rst
@@ -0,0 +1,34 @@
+{{ fullname | escape | underline}}
+
+.. currentmodule:: {{ module }}
+
+.. autoclass:: {{ objname }}
+ :members:
+ :show-inheritance:
+ :inherited-members:
+ :special-members: __call__, __add__, __mul__
+
+ {% block methods %}
+ {% if methods %}
+ .. rubric:: {{ _('Methods') }}
+
+ .. autosummary::
+ :nosignatures:
+ {% for item in methods %}
+ {%- if not item.startswith('_') %}
+ ~{{ name }}.{{ item }}
+ {%- endif -%}
+ {%- endfor %}
+ {% endif %}
+ {% endblock %}
+
+ {% block attributes %}
+ {% if attributes %}
+ .. rubric:: {{ _('Attributes') }}
+
+ .. autosummary::
+ {% for item in attributes %}
+ ~{{ name }}.{{ item }}
+ {%- endfor %}
+ {% endif %}
+ {% endblock %}
diff --git a/docs/source/_templates/custom-module-template.rst b/docs/source/_templates/custom-module-template.rst
new file mode 100644
index 0000000..d066d0e
--- /dev/null
+++ b/docs/source/_templates/custom-module-template.rst
@@ -0,0 +1,66 @@
+{{ fullname | escape | underline}}
+
+.. automodule:: {{ fullname }}
+
+ {% block attributes %}
+ {% if attributes %}
+ .. rubric:: Module attributes
+
+ .. autosummary::
+ :toctree:
+ {% for item in attributes %}
+ {{ item }}
+ {%- endfor %}
+ {% endif %}
+ {% endblock %}
+
+ {% block functions %}
+ {% if functions %}
+ .. rubric:: {{ _('Functions') }}
+
+ .. autosummary::
+ :toctree:
+ :nosignatures:
+ {% for item in functions %}
+ {{ item }}
+ {%- endfor %}
+ {% endif %}
+ {% endblock %}
+
+ {% block classes %}
+ {% if classes %}
+ .. rubric:: {{ _('Classes') }}
+
+ .. autosummary::
+ :toctree:
+ :template: custom-class-template.rst
+ :nosignatures:
+ {% for item in classes %}
+ {{ item }}
+ {%- endfor %}
+ {% endif %}
+ {% endblock %}
+
+ {% block exceptions %}
+ {% if exceptions %}
+ .. rubric:: {{ _('Exceptions') }}
+
+ .. autosummary::
+ :toctree:
+ {% for item in exceptions %}
+ {{ item }}
+ {%- endfor %}
+ {% endif %}
+ {% endblock %}
+
+{% block modules %}
+{% if modules %}
+.. autosummary::
+ :toctree:
+ :template: custom-module-template.rst
+ :recursive:
+{% for item in modules %}
+ {{ item }}
+{%- endfor %}
+{% endif %}
+{% endblock %}
diff --git a/docs/source/advanced.rst b/docs/source/advanced.rst
new file mode 100644
index 0000000..c167c18
--- /dev/null
+++ b/docs/source/advanced.rst
@@ -0,0 +1,197 @@
+========
+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
+`_.
+
+.. code-block:: python
+
+ 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(SigMFFile.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[SigMFFile.START_INDEX_KEY]
+ annotation_length = annotation[SigMFFile.LENGTH_INDEX_KEY]
+ annotation_comment = annotation.get(SigMFFile.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(SigMFFile.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(SigMFFile.FLO_KEY)
+ freq_stop = annotation.get(SigMFFile.FHI_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:
+
+.. code-block:: python
+
+ 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 = {
+ SigMFFile.DATATYPE_KEY: get_data_type_str(data), # in this case, 'cf32_le'
+ SigMFFile.SAMPLE_RATE_KEY: 48000,
+ SigMFFile.AUTHOR_KEY: 'jane.doe@domain.org',
+ SigMFFile.DESCRIPTION_KEY: 'All zero complex float32 example file.',
+ }
+ )
+
+ # create a capture key at time index 0
+ meta.add_capture(0, metadata={
+ SigMFFile.FREQUENCY_KEY: 915000000,
+ SigMFFile.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 = {
+ SigMFFile.FLO_KEY: 914995000.0,
+ SigMFFile.FHI_KEY: 915005000.0,
+ SigMFFile.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:
+
+.. code-block:: python
+
+ from sigmf import 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 = {
+ SigMFFile.DATATYPE_KEY: 'ci16_le', # get_data_type_str() is only valid for numpy types
+ SigMFFile.SAMPLE_RATE_KEY: 48000,
+ SigMFFile.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:
+
+.. code-block:: python
+
+ from sigmf import sigmffile
+ collection = sigmffile.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 merely a tarball (uncompressed), and since there any 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
+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
+ >>> arc = sigmf.SigMFArchiveReader('/src/LTE.sigmf')
+ >>> arc.shape
+ (15379532,)
+ >>> arc.ndim
+ 1
+ >>> 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)
+
+The preceeding example exhibits another feature of this approach; the archive
+``LTE.sigmf`` is actually ``complex-int16``'s on disk, for which there is no
+corresponding type in ``numpy``. However, the ``.sigmffile`` member keeps track of
+this, and converts the data to ``numpy.complex64`` *after* slicing it, that is,
+after reading it from disk.
+
+::
+
+ >>> arc.sigmffile.get_global_field(sigmf.SigMFFile.DATATYPE_KEY)
+ 'ci16_le'
+
+ >>> arc.sigmffile._memmap.dtype
+ dtype('int16')
+
+ >>> arc.sigmffile._return_type
+ '>> import sigmf, io
+ >>> sigmf_bytes = io.BytesIO(open('/src/LTE.sigmf', 'rb').read())
+ >>> arc = sigmf.SigMFArchiveReader(archive_buffer=sigmf_bytes)
+ >>> 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)
\ No newline at end of file
diff --git a/docs/source/api.rst b/docs/source/api.rst
new file mode 100644
index 0000000..295a2dc
--- /dev/null
+++ b/docs/source/api.rst
@@ -0,0 +1,10 @@
+=========
+SigMF API
+=========
+
+.. autosummary::
+ :toctree: _autosummary
+ :template: custom-module-template.rst
+ :recursive:
+
+ sigmf
diff --git a/docs/source/conf.py b/docs/source/conf.py
new file mode 100644
index 0000000..2c1c9f4
--- /dev/null
+++ b/docs/source/conf.py
@@ -0,0 +1,71 @@
+# Copyright: Multiple Authors
+#
+# This file is part of sigmf-python. https://github.com/sigmf/sigmf-python
+#
+# SPDX-License-Identifier: LGPL-3.0-or-later
+"""Configuration file for the Sphinx documentation builder."""
+
+import datetime
+import re
+import sys
+from pathlib import Path
+
+# parse info from project files
+
+root = Path(__file__).parent.parent.parent
+with open(root / "sigmf" / "__init__.py", "r") as handle:
+ init = handle.read()
+ toolversion = re.search(r'__version__\s*=\s*[\'"]([^\'"]*)[\'"]', init).group(1)
+ specversion = re.search(r'__specification__\s*=\s*[\'"]([^\'"]*)[\'"]', init).group(1)
+
+# autodoc needs special pathing
+sys.path.append(str(root))
+
+# -- Project information
+
+project = "sigmf"
+author = "Multiple Authors"
+copyright = f"2017-{datetime.date.today().year}, {author}"
+
+release = toolversion
+version = toolversion
+
+# -- General configuration
+
+extensions = [
+ "sphinx.ext.autodoc",
+ "sphinx.ext.autosummary",
+ "sphinx.ext.doctest",
+ "sphinx.ext.duration",
+ "sphinx.ext.intersphinx",
+ "sphinx.ext.napoleon", # allows numpy-style docstrings
+]
+
+intersphinx_mapping = {
+ "python": ("https://docs.python.org/3/", None),
+ "sphinx": ("https://www.sphinx-doc.org/en/master/", None),
+}
+intersphinx_disabled_domains = ["std"]
+
+templates_path = ["_templates"]
+
+# -- Options for HTML output
+
+html_theme = "sphinx_rtd_theme"
+html_favicon = "https://raw.githubusercontent.com/wiki/sigmf/SigMF/logo/logo-icon-32-folder.png"
+html_logo = "https://raw.githubusercontent.com/sigmf/SigMF/refs/heads/main/logo/sigmf_logo.svg"
+
+# -- Options for EPUB output
+
+epub_show_urls = "footnote"
+
+# Method to use variables within rst files
+# https://stackoverflow.com/a/69211912/760099
+
+variables_to_export = [
+ "toolversion",
+ "specversion",
+]
+frozen_locals = dict(locals())
+rst_epilog = '\n'.join(map(lambda x: f".. |{x}| replace:: {frozen_locals[x]}", variables_to_export))
+del frozen_locals
\ No newline at end of file
diff --git a/docs/source/developers.rst b/docs/source/developers.rst
new file mode 100644
index 0000000..2d10cf5
--- /dev/null
+++ b/docs/source/developers.rst
@@ -0,0 +1,60 @@
+==========
+Developers
+==========
+
+This page is for developers of the ``sigmf-python`` module.
+
+-------
+Install
+-------
+
+To install the latest git release, build from source:
+
+.. code-block:: console
+
+ $ git clone https://github.com/sigmf/sigmf-python.git
+ $ cd sigmf-python
+ $ pip install .
+
+-------
+Testing
+-------
+
+This library contains many tests in the ``tests/`` folder. These can all be run locally:
+
+.. code-block:: console
+
+ $ coverage run
+
+Or tests can be run within a temporary environment on all supported python versions:
+
+.. code-block:: console
+
+ $ tox run
+
+To run a single (perhaps new) test that may be needed verbosely:
+
+.. code-block:: console
+
+ $ pytest -rA tests/test_archive.py
+
+To lint the entire project and get suggested changes:
+
+.. code-block:: console
+
+ $ pylint sigmf tests
+
+To autoformat the entire project according to our coding standard:
+
+.. code-block:: console
+
+ $ black sigmf tests # autoformat entire project
+ $ isort sigmf tests # format imports for entire project
+
+------
+Issues
+------
+
+Issues can be addressed by opening an `issue
+`_ or by forking the project and
+submitting a `pull request `_.
diff --git a/docs/source/faq.rst b/docs/source/faq.rst
new file mode 100644
index 0000000..370a84c
--- /dev/null
+++ b/docs/source/faq.rst
@@ -0,0 +1,28 @@
+==========================
+Frequently Asked Questions
+==========================
+
+.. contents::
+ :local:
+
+..
+ Frequently asked questions should be questions that actually got asked.
+ Formulate them as a question and an answer.
+ Consider that the answer is best as a reference to another place in the documentation.
+
+---------------------------
+Is this a GNU Radio effort?
+---------------------------
+
+*No*, this is not a GNU Radio-specific effort.
+This effort first emerged from a group of GNU Radio core
+developers, but the goal of the project to provide a standard that will be
+useful to anyone and everyone, regardless of tool or workflow.
+
+--------------------------------------------
+Is this specific to wireless communications?
+--------------------------------------------
+
+*No*, similar to the response, above, the goal is to create something that is
+generally applicable to *signal processing*, regardless of whether or not the
+application is communications related.
diff --git a/docs/source/index.rst b/docs/source/index.rst
new file mode 100644
index 0000000..a2ca1af
--- /dev/null
+++ b/docs/source/index.rst
@@ -0,0 +1,40 @@
+=================
+Welcome to SigMF!
+=================
+
+**sigmf** is a Python library for working with radio recordings captured in
+``.sigmf`` format according to the `SigMF standard `_. It
+offers a *simple* and *intuitive* API for python developers.
+
+..
+ Note below toolversion & specversion are replaced dynamically during build.
+
+This version |toolversion| of the library coincides with version |specversion|
+of the specification.
+
+Check out the :doc:`quickstart` section for further information, including
+how to :ref:`install` the project.
+
+
+.. toctree::
+ :maxdepth: 1
+ :hidden:
+ :caption: SigMF
+
+ quickstart
+ advanced
+ developers
+
+.. toctree::
+ :maxdepth: 1
+ :hidden:
+ :caption: Community
+
+ faq
+
+.. toctree::
+ :maxdepth: 1
+ :hidden:
+ :caption: API Reference
+
+ api
\ No newline at end of file
diff --git a/docs/source/quickstart.rst b/docs/source/quickstart.rst
new file mode 100644
index 0000000..ba4d093
--- /dev/null
+++ b/docs/source/quickstart.rst
@@ -0,0 +1,82 @@
+==========
+Quickstart
+==========
+
+Here we discuss how to do all basic operations with SigMF.
+
+.. _install:
+
+-------
+Install
+-------
+
+To install the latest PyPi release, install from pip:
+
+.. code-block:: console
+
+ $ pip install sigmf
+
+----------------------
+Read a SigMF Recording
+----------------------
+
+.. code-block:: python
+
+ import sigmf
+ handle = sigmf.sigmffile.fromfile("example.sigmf")
+ handle.read_samples() # returns all timeseries data
+ handle.get_global_info() # returns 'global' dictionary
+ handle.get_captures() # returns list of 'captures' dictionaries
+ handle.get_annotations() # returns list of all annotations
+ handle[10:50] # return memory slice of samples 10 through 50
+
+-----------------------------------
+Verify SigMF Integrity & Compliance
+-----------------------------------
+
+.. code-block:: console
+
+ $ sigmf_validate example.sigmf
+
+---------------------------------------
+Save a Numpy array as a SigMF Recording
+---------------------------------------
+
+.. code-block:: python
+
+ import numpy as np
+ 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 = {
+ SigMFFile.DATATYPE_KEY: get_data_type_str(data), # in this case, 'cf32_le'
+ SigMFFile.SAMPLE_RATE_KEY: 48000,
+ SigMFFile.AUTHOR_KEY: 'jane.doe@domain.org',
+ SigMFFile.DESCRIPTION_KEY: 'All zero complex float32 example file.',
+ }
+ )
+
+ # create a capture key at time index 0
+ meta.add_capture(0, metadata={
+ SigMFFile.FREQUENCY_KEY: 915000000,
+ SigMFFile.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 = {
+ SigMFFile.FLO_KEY: 914995000.0,
+ SigMFFile.FHI_KEY: 915005000.0,
+ SigMFFile.COMMENT_KEY: 'example annotation',
+ })
+
+ # check for mistakes & write to disk
+ meta.tofile('example_cf32.sigmf-meta') # extension is optional
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index 563e7ce..56bb7d1 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -13,6 +13,7 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: 3.13",
"Topic :: Scientific/Engineering",
"Topic :: Communications :: Ham Radio",
]
@@ -24,6 +25,12 @@ dependencies = [
]
[project.urls]
repository = "https://github.com/sigmf/sigmf-python"
+ documentation = "https://sigmf.readthedocs.io/en/latest/"
+ issues = "https://github.com/sigmf/sigmf-python/issues"
+ "Specification (HTML)" = "https://sigmf.org/"
+ "Specification (PDF)" = "https://sigmf.github.io/SigMF/sigmf-spec.pdf"
+ "Specification (Repo)" = "https://github.com/sigmf/SigMF"
+
[project.scripts]
sigmf_validate = "sigmf.validate:main"
sigmf_convert_wav = "sigmf.apps.convert_wav:main [apps]"
diff --git a/sigmf/__init__.py b/sigmf/__init__.py
index 98fc58a..060a9e5 100644
--- a/sigmf/__init__.py
+++ b/sigmf/__init__.py
@@ -5,7 +5,7 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# version of this python module
-__version__ = "1.2.6"
+__version__ = "1.2.7"
# matching version of the SigMF specification
__specification__ = "1.2.3"