Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/NTIA/sigmf-python into mult…
Browse files Browse the repository at this point in the history
…irecording2_its_review
  • Loading branch information
jhazentia committed Apr 19, 2024
2 parents 81ec8b6 + ccc1cbf commit f133a52
Show file tree
Hide file tree
Showing 29 changed files with 482 additions and 383 deletions.
9 changes: 4 additions & 5 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Python package

on:
on:
push:
pull_request:
types: [opened, synchronize]
Expand All @@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
matrix:
python-version: ["3.6"]
python-version: ["3.7", "3.9", "3.12"]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
Expand All @@ -20,8 +20,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest
pip install .
pip install .[test,apps]
- name: Test with pytest
run: |
pytest
coverage run
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ build/*
.eggs/*
SigMF.egg-info/*

# pytest & coverage related
# test related
.coverage
pytest.xml
coverage.xml
.tox/
htmlcov/*
29 changes: 18 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,41 @@
<p align="center"><img src="https://github.com/sigmf/SigMF/raw/sigmf-v1.x/logo/sigmf_logo.png" width="30%" /></p>
<p align="center"><img src="https://github.com/sigmf/SigMF/blob/v1.2.0/logo/sigmf_logo.png" alt="Rendered SigMF Logo"/></p>

This python module makes it easy to interact with Signal Metadata Format
(SigMF) objects. This module works with Python 3.6+ and is distributed freely
under the terms GNU Lesser GPL v3 License.
(SigMF) recordings. This module works with Python 3.7+ and is distributed
freely under the terms GNU Lesser GPL v3 License.

The [SigMF specification document](https://github.com/sigmf/SigMF/blob/HEAD/sigmf-spec.md)
is located in the [SigMF](https://github.com/gnuradio/SigMF) repository.
is located in the [SigMF](https://github.com/sigmf/SigMF) repository.

# Installation

To install the latest release, install from pip:
To install the latest PyPi release, install from pip:

```bash
pip install sigmf
```

To install the latest development version, build from source:
To install the latest git release, build from source:

```bash
git clone https://github.com/sigmf/sigmf-python.git
cd sigmf-python
pip install .
```

To run the included QA tests:
Testing can be run with a variety of tools:

```bash
# basic
python3 -m pytest tests/
# fancy
coverage run --a --source sigmf -m pytest --doctest-modules
# 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
Expand Down
104 changes: 104 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
[project]
name = "SigMF"
description = "Easily interact with Signal Metadata Format (SigMF) recordings."
keywords = ["gnuradio", "radio"]
classifiers = [
"Development Status :: 5 - Production/Stable",
"License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Scientific/Engineering",
"Topic :: Communications :: Ham Radio",
]
dynamic = ["version", "readme"]
requires-python = ">=3.7"
dependencies = [
"numpy", # for vector math
"jsonschema", # for spec validation
]
[project.urls]
repository = "https://github.com/sigmf/sigmf-python"
[project.scripts]
sigmf_validate = "sigmf.validate:main"
sigmf_gui = "sigmf.apps.gui:main [apps]"
sigmf_convert_wav = "sigmf.apps.convert_wav:main [apps]"
[project.optional-dependencies]
test = [
"pylint",
"pytest",
"pytest-cov",
"hypothesis", # next-gen testing framework
]
apps = [
"scipy", # for wav i/o
# FIXME: PySimpleGUI 2024-02-12 v5.0.0 release seems to have a bug. Unpin version when possible.
"PySimpleGUI < 5.0.0", # for gui interface
]

[tool.setuptools]
packages = ["sigmf"]
[tool.setuptools.dynamic]
version = {attr = "sigmf.__version__"}
readme = {file = ["README.md"], content-type = "text/markdown"}
[tool.setuptools.package-data]
sigmf = ["*.json"]

[build-system]
requires = ["setuptools>=65.0", "setuptools-scm"]
build-backend = "setuptools.build_meta"

[tool.coverage.run]
branch = true
source = ["sigmf", "tests"]
# -rA captures stdout from all tests and places it after the pytest summary
command_line = "-m pytest -rA --doctest-modules --junitxml=pytest.xml"

[tool.pytest.ini_options]
addopts = "--doctest-modules"

[tool.pylint]
[tool.pylint.main]
load-plugins = [
"pylint.extensions.typing",
"pylint.extensions.docparams",
]
exit-zero = true
[tool.pylint.messages_control]
disable = [
"logging-not-lazy",
"missing-module-docstring",
"import-error",
"unspecified-encoding",
]
max-line-length = 120
[tool.pylint.REPORTS]
# omit from the similarity reports
ignore-comments = 'yes'
ignore-docstrings = 'yes'
ignore-imports = 'yes'
ignore-signatures = 'yes'
min-similarity-lines = 4

[tool.pytype]
inputs = ['sigmf', 'tests']

[tool.black]
line-length = 120

[tool.tox]
legacy_tox_ini = '''
[tox]
skip_missing_interpreters = True
envlist = py{37,38,39,310,311,312}
[testenv]
usedevelop = True
deps = .[test,apps]
commands = coverage run
'''
2 changes: 0 additions & 2 deletions setup.cfg

This file was deleted.

47 changes: 0 additions & 47 deletions setup.py

This file was deleted.

11 changes: 7 additions & 4 deletions sigmf/__init__.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
# Copyright: Multiple Authors
#
# This file is part of SigMF. https://github.com/sigmf/sigmf-python
# This file is part of sigmf-python. https://github.com/sigmf/sigmf-python
#
# SPDX-License-Identifier: LGPL-3.0-or-later

__version__ = "1.1.5"
# version of this python module
__version__ = "1.2.1"
# matching version of the SigMF specification
__specification__ = "1.2.0"

from .archive import SigMFArchive
from .sigmffile import SigMFFile, SigMFCollection
from .archivereader import SigMFArchiveReader

from . import archive
from . import archivereader
from . import error
from . import schema
from . import sigmffile
from . import validate
from . import utils
from . import archivereader
from . import validate
File renamed without changes.
93 changes: 93 additions & 0 deletions sigmf/apps/convert_wav.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Copyright: Multiple Authors
#
# This file is part of sigmf-python. https://github.com/sigmf/sigmf-python
#
# SPDX-License-Identifier: LGPL-3.0-or-later

"""converter for wav containers"""

import argparse
import datetime
import getpass
import logging
import os
import pathlib
import tempfile

from scipy.io import wavfile

from .. import SigMFFile, __specification__
from .. import __version__ as toolversion
from .. import archive
from ..utils import get_data_type_str

log = logging.getLogger()


def convert_wav(input_wav_filename, archive_filename=None, start_datetime=None, author=None):
"""
read a .wav and write a .sigmf archive
"""
input_path = pathlib.Path(input_wav_filename)
input_stem = input_path.stem
samp_rate, wav_data = wavfile.read(input_wav_filename)

global_info = {
SigMFFile.AUTHOR_KEY: getpass.getuser() if author is None else author,
SigMFFile.DATATYPE_KEY: get_data_type_str(wav_data),
SigMFFile.DESCRIPTION_KEY: f"Converted from {input_wav_filename}",
SigMFFile.NUM_CHANNELS_KEY: 1 if len(wav_data.shape) < 2 else wav_data.shape[1],
SigMFFile.RECORDER_KEY: os.path.basename(__file__),
SigMFFile.SAMPLE_RATE_KEY: samp_rate,
SigMFFile.VERSION_KEY: __specification__,
}

if start_datetime is None:
mtime = datetime.datetime.fromtimestamp(input_path.stat().st_mtime)
start_datetime = mtime.isoformat() + "Z"

capture_info = {SigMFFile.START_INDEX_KEY: 0}
if start_datetime is not None:
capture_info[SigMFFile.DATETIME_KEY] = start_datetime

tmpdir = tempfile.mkdtemp()
sigmf_data_filename = input_stem + archive.SIGMF_DATASET_EXT
sigmf_data_path = os.path.join(tmpdir, sigmf_data_filename)
wav_data.tofile(sigmf_data_path)

meta = SigMFFile(data_file=sigmf_data_path, global_info=global_info)
meta.add_capture(0, metadata=capture_info)

if archive_filename is None:
archive_filename = input_stem + archive.SIGMF_ARCHIVE_EXT
meta.tofile(archive_filename, toarchive=True)
return os.path.abspath(archive_filename)


def main():
"""
entry-point for sigmf_convert_wav
"""
parser = argparse.ArgumentParser(description="Convert .wav to .sigmf container.")
parser.add_argument("input", type=str, help="Wavfile path")
parser.add_argument("--author", type=str, default=None, help=f"set {SigMFFile.AUTHOR_KEY} metadata")
parser.add_argument('-v', '--verbose', action='count', default=0)
parser.add_argument('--version', action='version', version=f'%(prog)s v{toolversion}')
args = parser.parse_args()

level_lut = {
0: logging.WARNING,
1: logging.INFO,
2: logging.DEBUG,
}
logging.basicConfig(level=level_lut[min(args.verbose, 2)])

out_fname = convert_wav(
input_wav_filename=args.input,
author=args.author,
)
log.info(f"Write {out_fname}")


if __name__ == "__main__":
main()

0 comments on commit f133a52

Please sign in to comment.