Skip to content

Commit ccd39e7

Browse files
committed
move most of README to rst; split examples into quickstart & advanced
1 parent dbb3ddc commit ccd39e7

File tree

9 files changed

+415
-325
lines changed

9 files changed

+415
-325
lines changed

README.md

Lines changed: 3 additions & 244 deletions
Original file line numberDiff line numberDiff line change
@@ -4,251 +4,10 @@
44
[![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)
55
[![PyPI Downloads Shield](https://img.shields.io/pypi/dm/sigmf)](https://pypi.org/project/SigMF/)
66

7-
This python module makes it easy to interact with Signal Metadata Format
8-
(SigMF) recordings. This module works with Python 3.7+ and is distributed
7+
The `sigmf` module makes it easy to interact with Signal Metadata Format
8+
(SigMF) recordings. This module works with Python 3.7-3.13 and is distributed
99
freely under the terms GNU Lesser GPL v3 License.
1010

1111
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).
1212

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

0 commit comments

Comments
 (0)