Name | SigMF JSON |
Version |
1.2.2
JSON |
| download |
home_page | None |
Summary | Easily interact with Signal Metadata Format (SigMF) recordings. |
upload_time | 2024-06-06 20:54:11 |
maintainer | None |
docs_url | None |
author | None |
requires_python | >=3.7 |
license | None |
keywords |
gnuradio
radio
|
VCS |
|
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|
<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) recordings. This module works with Python 3.7+ 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:
```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
# 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: dt.datetime.utcnow().isoformat()+'Z',
})
# 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
'<c8'
```
Another supported mode is the case where you might have an archive that *is not
on disk* but instead is simply `bytes` in a python variable.
Instead of needing to write this out to a temporary file before being able to
read it, this can be done "in mid air" or "without touching the ground (disk)".
```python
>>> 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.
Raw data
{
"_id": null,
"home_page": null,
"name": "SigMF",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.7",
"maintainer_email": null,
"keywords": "gnuradio, radio",
"author": null,
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/9e/ba/a938e6df9e90a3278fcce05d784d32fbfc93d900f6188818cabd8e5457cd/sigmf-1.2.2.tar.gz",
"platform": null,
"description": "<p align=\"center\"><img src=\"https://github.com/sigmf/SigMF/blob/v1.2.0/logo/sigmf_logo.png\" alt=\"Rendered SigMF Logo\"/></p>\n\nThis python module makes it easy to interact with Signal Metadata Format\n(SigMF) recordings. This module works with Python 3.7+ and is distributed\nfreely under the terms GNU Lesser GPL v3 License.\n\nThis 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).\n\n# Installation\n\nTo install the latest PyPi release, install from pip:\n\n```bash\npip install sigmf\n```\n\nTo install the latest git release, build from source:\n\n```bash\ngit clone https://github.com/sigmf/sigmf-python.git\ncd sigmf-python\npip install .\n```\n\nTesting can be run with a variety of tools:\n\n```bash\n# pytest and coverage run locally\npytest\ncoverage run\n# run coverage in a venv\ntox run\n# other useful tools\npylint sigmf tests\npytype\nblack\nflake8\n```\n\n# Examples\n\n### Load a SigMF archive; read all samples & metadata\n\n```python\nimport sigmf\nhandle = sigmf.sigmffile.fromfile('example.sigmf')\nhandle.read_samples() # returns all timeseries data\nhandle.get_global_info() # returns 'global' dictionary\nhandle.get_captures() # returns list of 'captures' dictionaries\nhandle.get_annotations() # returns list of all annotations\n```\n\n### Verify SigMF dataset integrity & compliance\n\n```bash\nsigmf_validate example.sigmf\n```\n\n### Load a SigMF dataset; read its annotation, metadata, and samples\n\n```python\nfrom sigmf import SigMFFile, sigmffile\n\n# Load a dataset\nfilename = 'logo/sigmf_logo' # extension is optional\nsignal = sigmffile.fromfile(filename)\n\n# Get some metadata and all annotations\nsample_rate = signal.get_global_field(SigMFFile.SAMPLE_RATE_KEY)\nsample_count = signal.sample_count\nsignal_duration = sample_count / sample_rate\nannotations = signal.get_annotations()\n\n# Iterate over annotations\nfor adx, annotation in enumerate(annotations):\n annotation_start_idx = annotation[SigMFFile.START_INDEX_KEY]\n annotation_length = annotation[SigMFFile.LENGTH_INDEX_KEY]\n annotation_comment = annotation.get(SigMFFile.COMMENT_KEY, \"[annotation {}]\".format(adx))\n\n # Get capture info associated with the start of annotation\n capture = signal.get_capture_info(annotation_start_idx)\n freq_center = capture.get(SigMFFile.FREQUENCY_KEY, 0)\n freq_min = freq_center - 0.5*sample_rate\n freq_max = freq_center + 0.5*sample_rate\n\n # Get frequency edges of annotation (default to edges of capture)\n freq_start = annotation.get(SigMFFile.FLO_KEY)\n freq_stop = annotation.get(SigMFFile.FHI_KEY)\n\n # Get the samples corresponding to annotation\n samples = signal.read_samples(annotation_start_idx, annotation_length)\n```\n\n### Create and save a Collection of SigMF Recordings from numpy arrays\n\nFirst, create a single SigMF Recording and save it to disk\n\n```python\nimport datetime as dt\nimport numpy as np\nimport sigmf\nfrom sigmf import SigMFFile\nfrom sigmf.utils import get_data_type_str\n\n# suppose we have an complex timeseries signal\ndata = np.zeros(1024, dtype=np.complex64)\n\n# write those samples to file in cf32_le\ndata.tofile('example_cf32.sigmf-data')\n\n# create the metadata\nmeta = SigMFFile(\n data_file='example_cf32.sigmf-data', # extension is optional\n global_info = {\n SigMFFile.DATATYPE_KEY: get_data_type_str(data), # in this case, 'cf32_le'\n SigMFFile.SAMPLE_RATE_KEY: 48000,\n SigMFFile.AUTHOR_KEY: 'jane.doe@domain.org',\n SigMFFile.DESCRIPTION_KEY: 'All zero complex float32 example file.',\n }\n)\n\n# create a capture key at time index 0\nmeta.add_capture(0, metadata={\n SigMFFile.FREQUENCY_KEY: 915000000,\n SigMFFile.DATETIME_KEY: dt.datetime.utcnow().isoformat()+'Z',\n})\n\n# add an annotation at sample 100 with length 200 & 10 KHz width\nmeta.add_annotation(100, 200, metadata = {\n SigMFFile.FLO_KEY: 914995000.0,\n SigMFFile.FHI_KEY: 915005000.0,\n SigMFFile.COMMENT_KEY: 'example annotation',\n})\n\n# check for mistakes & write to disk\nmeta.tofile('example_cf32.sigmf-meta') # extension is optional\n```\n\nNow lets add another SigMF Recording and associate them with a SigMF Collection:\n\n```python\nfrom sigmf import SigMFCollection\n\ndata_ci16 = np.zeros(1024, dtype=np.complex64)\n\n#rescale and save as a complex int16 file:\ndata_ci16 *= pow(2, 15)\ndata_ci16.view(np.float32).astype(np.int16).tofile('example_ci16.sigmf-data')\n\n# create the metadata for the second file\nmeta_ci16 = SigMFFile(\n data_file='example_ci16.sigmf-data', # extension is optional\n global_info = {\n SigMFFile.DATATYPE_KEY: 'ci16_le', # get_data_type_str() is only valid for numpy types\n SigMFFile.SAMPLE_RATE_KEY: 48000,\n SigMFFile.DESCRIPTION_KEY: 'All zero complex int16 file.',\n }\n)\nmeta_ci16.add_capture(0, metadata=meta.get_capture_info(0))\nmeta_ci16.tofile('example_ci16.sigmf-meta')\n\ncollection = SigMFCollection(['example_cf32.sigmf-meta', 'example_ci16.sigmf-meta'],\n metadata = {'collection': {\n SigMFCollection.AUTHOR_KEY: 'sigmf@sigmf.org',\n SigMFCollection.DESCRIPTION_KEY: 'Collection of two all zero files.',\n }\n }\n)\nstreams = collection.get_stream_names()\nsigmf = [collection.get_SigMFFile(stream) for stream in streams]\ncollection.tofile('example_zeros.sigmf-collection')\n```\n\nThe SigMF Collection and its associated Recordings can now be loaded like this:\n\n```python\nfrom sigmf import sigmffile\ncollection = sigmffile.fromfile('example_zeros')\nci16_sigmffile = collection.get_SigMFFile(stream_name='example_ci16')\ncf32_sigmffile = collection.get_SigMFFile(stream_name='example_cf32')\n```\n\n### Load a SigMF Archive and slice its data without untaring it\n\nSince an *archive* is merely a tarball (uncompressed), and since there any many\nexcellent tools for manipulating tar files, it's fairly straightforward to\naccess the *data* part of a SigMF archive without un-taring it. This is a\ncompelling feature because __1__ archives make it harder for the `-data` and\nthe `-meta` to get separated, and __2__ some datasets are so large that it can\nbe impractical (due to available disk space, or slow network speeds if the\narchive file resides on a network file share) or simply obnoxious to untar it\nfirst.\n\n```python\n>>> import sigmf\n>>> arc = sigmf.SigMFArchiveReader('/src/LTE.sigmf')\n>>> arc.shape\n(15379532,)\n>>> arc.ndim\n1\n>>> arc[:10]\narray([-20.+11.j, -21. -6.j, -17.-20.j, -13.-52.j, 0.-75.j, 22.-58.j,\n 48.-44.j, 49.-60.j, 31.-56.j, 23.-47.j], dtype=complex64)\n```\n\nThe preceeding example exhibits another feature of this approach; the archive\n`LTE.sigmf` is actually `complex-int16`'s on disk, for which there is no\ncorresponding type in `numpy`. However, the `.sigmffile` member keeps track of\nthis, and converts the data to `numpy.complex64` *after* slicing it, that is,\nafter reading it from disk.\n\n```python\n>>> arc.sigmffile.get_global_field(sigmf.SigMFFile.DATATYPE_KEY)\n'ci16_le'\n\n>>> arc.sigmffile._memmap.dtype\ndtype('int16')\n\n>>> arc.sigmffile._return_type\n'<c8'\n```\n\nAnother supported mode is the case where you might have an archive that *is not\non disk* but instead is simply `bytes` in a python variable.\nInstead of needing to write this out to a temporary file before being able to\nread it, this can be done \"in mid air\" or \"without touching the ground (disk)\".\n\n```python\n>>> import sigmf, io\n>>> sigmf_bytes = io.BytesIO(open('/src/LTE.sigmf', 'rb').read())\n>>> arc = sigmf.SigMFArchiveReader(archive_buffer=sigmf_bytes)\n>>> arc[:10]\narray([-20.+11.j, -21. -6.j, -17.-20.j, -13.-52.j, 0.-75.j, 22.-58.j,\n 48.-44.j, 49.-60.j, 31.-56.j, 23.-47.j], dtype=complex64)\n```\n\n# Frequently Asked Questions\n\n### Is this a GNU Radio effort?\n\n*No*, this is not a GNU Radio-specific effort.\nThis effort first emerged from a group of GNU Radio core\ndevelopers, but the goal of the project to provide a standard that will be\nuseful to anyone and everyone, regardless of tool or workflow.\n\n### Is this specific to wireless communications?\n\n*No*, similar to the response, above, the goal is to create something that is\ngenerally applicable to _signal processing_, regardless of whether or not the\napplication is communications related.\n",
"bugtrack_url": null,
"license": null,
"summary": "Easily interact with Signal Metadata Format (SigMF) recordings.",
"version": "1.2.2",
"project_urls": {
"repository": "https://github.com/sigmf/sigmf-python"
},
"split_keywords": [
"gnuradio",
" radio"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "b296814a31e105c2b4126b5908e3269ef4981b52b1c625da87e953f29f2bad11",
"md5": "b6842ed559eea259e1ae66ccacdedf22",
"sha256": "6cd5470d1294b34f4ad4d0f0502330f5712aa8629f912b913500c67ed1e0d7d4"
},
"downloads": -1,
"filename": "SigMF-1.2.2-py3-none-any.whl",
"has_sig": false,
"md5_digest": "b6842ed559eea259e1ae66ccacdedf22",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.7",
"size": 39558,
"upload_time": "2024-06-06T20:54:09",
"upload_time_iso_8601": "2024-06-06T20:54:09.330959Z",
"url": "https://files.pythonhosted.org/packages/b2/96/814a31e105c2b4126b5908e3269ef4981b52b1c625da87e953f29f2bad11/SigMF-1.2.2-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "9ebaa938e6df9e90a3278fcce05d784d32fbfc93d900f6188818cabd8e5457cd",
"md5": "4d1d0f20ef0a3dfa811bd74da80758b6",
"sha256": "9d2eeea6e3c69f90ad83c05e8faa7f5caabd24ce79e809e75ff5cc62d6765d5f"
},
"downloads": -1,
"filename": "sigmf-1.2.2.tar.gz",
"has_sig": false,
"md5_digest": "4d1d0f20ef0a3dfa811bd74da80758b6",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.7",
"size": 42100,
"upload_time": "2024-06-06T20:54:11",
"upload_time_iso_8601": "2024-06-06T20:54:11.010748Z",
"url": "https://files.pythonhosted.org/packages/9e/ba/a938e6df9e90a3278fcce05d784d32fbfc93d900f6188818cabd8e5457cd/sigmf-1.2.2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-06-06 20:54:11",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "sigmf",
"github_project": "sigmf-python",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "sigmf"
}