Skip to content

Commit c5aa8f3

Browse files
authored
Packaging and Test Improvements (#22)
* PyPi uses descriptions from setup, fix outdated entry. * black reformat & increment version * rewrite and improve archive tests * reformat as unittest class * fix link in readme; closes issue #20
1 parent ec8ac0e commit c5aa8f3

File tree

8 files changed

+172
-150
lines changed

8 files changed

+172
-150
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
.cache
55

66
# setuptools related
7+
dist/*
78
build/*
89
.eggs/*
910
SigMF.egg-info/*

README.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,15 @@
22
<img src="https://github.com/gnuradio/SigMF/blob/sigmf-v1.x/logo/sigmf_logo.png" width="30%" />
33
</p>
44

5-
This repository contains a python module for interacting with SigMF Objects.
5+
This repository contains a python module for interacting with SigMF Objects.
66
This module works with Python 3.6 and higher. This module is distributed freely
77
under the terms GNU Lesser GPL v3 License.
88

99
# Signal Metadata Format (SigMF)
1010

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

14-
1514
# Installation
1615

1716
To install the latest released version of the SigMF package, install it from pip:
@@ -203,7 +202,7 @@ In [4]: arc.ndim
203202
Out[4]: 1
204203

205204
In [5]: arc[:10]
206-
Out[5]:
205+
Out[5]:
207206
array([-20.+11.j, -21. -6.j, -17.-20.j, -13.-52.j, 0.-75.j, 22.-58.j,
208207
48.-44.j, 49.-60.j, 31.-56.j, 23.-47.j], dtype=complex64)
209208
```
@@ -238,7 +237,7 @@ In [2]: sigmf_bytes = io.BytesIO(open('/src/LTE.sigmf', 'rb').read())
238237
In [3]: arc = sigmf.SigMFArchiveReader(archive_buffer=sigmf_bytes)
239238

240239
In [4]: arc[:10]
241-
Out[4]:
240+
Out[4]:
242241
array([-20.+11.j, -21. -6.j, -17.-20.j, -13.-52.j, 0.-75.j, 22.-58.j,
243242
48.-44.j, 49.-60.j, 31.-56.j, 23.-47.j], dtype=complex64)
244243
```
@@ -256,4 +255,4 @@ useful to anyone and everyone, regardless of tool or workflow.
256255

257256
*No*, similar to the response, above, the goal is to create something that is
258257
generally applicable to _signal processing_, regardless of whether or not the
259-
application is communications related.
258+
application is communications related.

setup.py

Lines changed: 29 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,50 +3,45 @@
33
import os
44
import re
55

6-
shortdesc = 'Signal Metadata Format Specification'
7-
longdesc = '''
8-
The Signal Metadata Format (SigMF) specifies a way to describe
9-
sets of recorded digital signal samples with metadata written in JSON.
10-
SigMF can be used to describe general information about a collection
11-
of samples, the characteristics of the system that generated the
12-
samples, and features of the signal itself.
13-
'''
6+
short_description = "Python module for interacting with SigMF recordings."
147

15-
with open(os.path.join('sigmf', '__init__.py')) as handle:
8+
with open("README.md", encoding="utf-8") as handle:
9+
long_description = handle.read()
10+
11+
with open(os.path.join("sigmf", "__init__.py"), encoding="utf-8") as handle:
1612
version = re.search(r'__version__\s*=\s*[\'"]([^\'"]*)[\'"]', handle.read()).group(1)
1713

1814
setup(
19-
name='SigMF',
15+
name="SigMF",
2016
version=version,
21-
description=shortdesc,
22-
long_description=longdesc,
23-
url='https://github.com/sigmf/sigmf-python',
24-
17+
description=short_description,
18+
long_description=long_description,
19+
long_description_content_type="text/markdown",
20+
url="https://github.com/sigmf/sigmf-python",
21+
license="GNU Lesser General Public License v3 or later (LGPLv3+)",
2522
classifiers=[
26-
'License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)',
27-
'Operating System :: OS Independent',
28-
'Programming Language :: Python :: 3',
29-
'Programming Language :: Python :: 3.6',
30-
'Programming Language :: Python :: 3.7',
31-
'Programming Language :: Python :: 3.8',
32-
'Programming Language :: Python :: 3.9',
33-
'Programming Language :: Python :: 3.10',
23+
"License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)",
24+
"Operating System :: OS Independent",
25+
"Programming Language :: Python :: 3",
26+
"Programming Language :: Python :: 3.6",
27+
"Programming Language :: Python :: 3.7",
28+
"Programming Language :: Python :: 3.8",
29+
"Programming Language :: Python :: 3.9",
30+
"Programming Language :: Python :: 3.10",
3431
],
3532
entry_points={
36-
'console_scripts': [
37-
'sigmf_validate = sigmf.validate:main',
38-
'sigmf_gui = sigmf.gui:main [gui]',
33+
"console_scripts": [
34+
"sigmf_validate = sigmf.validate:main",
35+
"sigmf_gui = sigmf.gui:main [gui]",
3936
]
4037
},
41-
packages=['sigmf'],
38+
packages=["sigmf"],
4239
package_data={
43-
'sigmf': ['*.json'],
44-
},
45-
install_requires=['numpy', 'jsonschema'],
46-
extras_require={
47-
'gui': 'pysimplegui==4.0.0'
40+
"sigmf": ["*.json"],
4841
},
49-
setup_requires=['pytest-runner'],
50-
tests_require=['pytest>3'],
51-
zip_safe=False
42+
install_requires=["numpy", "jsonschema"],
43+
extras_require={"gui": "pysimplegui==4.0.0"},
44+
setup_requires=["pytest-runner"],
45+
tests_require=["pytest>3", "hypothesis"],
46+
zip_safe=False,
5247
)

sigmf/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#
55
# SPDX-License-Identifier: LGPL-3.0-or-later
66

7-
__version__ = '1.1.2'
7+
__version__ = "1.1.3"
88

99
from .archive import SigMFArchive
1010
from .sigmffile import SigMFFile, SigMFCollection

sigmf/sigmffile.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,10 +204,10 @@ def __getitem__(self, sli):
204204
if self._return_type is not None:
205205
# is_fixed_point and is_complex
206206
if self._memmap.ndim == 2:
207-
# num_channels==1
207+
# num_channels == 1
208208
a = a[:,0].astype(self._return_type) + 1.j * a[:,1].astype(self._return_type)
209209
elif self._memmap.ndim == 3:
210-
# num_channels>1
210+
# num_channels > 1
211211
a = a[:,:,0].astype(self._return_type) + 1.j * a[:,:,1].astype(self._return_type)
212212
else:
213213
raise ValueError("unhandled ndim in SigMFFile.__getitem__(); this shouldn't happen")

tests/conftest.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,15 @@
2929

3030
@pytest.fixture
3131
def test_data_file():
32+
"""when called, yields temporary file"""
3233
with tempfile.NamedTemporaryFile() as temp:
3334
TEST_FLOAT32_DATA.tofile(temp.name)
3435
yield temp
3536

3637

3738
@pytest.fixture
3839
def test_sigmffile(test_data_file):
40+
"""If pytest uses this signature, will return valid SigMF file."""
3941
sigf = SigMFFile()
4042
sigf.set_global_field("core:datatype", "rf32_le")
4143
sigf.add_annotation(start_index=0, length=len(TEST_FLOAT32_DATA))

tests/test_archivereader.py

Lines changed: 70 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,75 @@
1-
import codecs
2-
import json
3-
import tarfile
1+
# Copyright 2023 GNU Radio Foundation
42
import tempfile
5-
from os import path
6-
73
import numpy as np
8-
import pytest
4+
import unittest
95

10-
from sigmf import error
6+
import sigmf
117
from sigmf import SigMFFile, SigMFArchiveReader
12-
from sigmf.archive import SIGMF_DATASET_EXT, SIGMF_METADATA_EXT
13-
14-
def test_access_data_without_untar(test_sigmffile):
15-
global_info = {
16-
"core:author": "Glen M",
17-
"core:datatype": "ri16_le",
18-
"core:license": "https://creativecommons.org/licenses/by-sa/4.0/",
19-
"core:num_channels": 2,
20-
"core:sample_rate": 48000,
21-
"core:version": "1.0.0"
22-
}
23-
capture_info = {
24-
"core:datetime": "2021-06-18T23:17:51.163959Z",
25-
"core:sample_start": 0
8+
9+
10+
class TestArchiveReader(unittest.TestCase):
11+
def setUp(self):
12+
# in order to check shapes we need some positive number of samples to work with
13+
# number of samples should be lowest common factor of num_channels
14+
self.raw_count = 16
15+
self.lut = {
16+
"i8": np.int8,
17+
"u8": np.uint8,
18+
"i16": np.int16,
19+
"u16": np.uint16,
20+
"u32": np.uint32,
21+
"i32": np.int32,
22+
"f32": np.float32,
23+
"f64": np.float64,
2624
}
27-
28-
NUM_ROWS = 5
29-
30-
for dt in "ri16_le", "ci16_le", "rf32_le", "rf64_le", "cf32_le", "cf64_le":
31-
global_info["core:datatype"] = dt
32-
for num_chan in 1,3:
33-
global_info["core:num_channels"] = num_chan
34-
base_filename = dt + '_' + str(num_chan)
35-
archive_filename = base_filename + '.sigmf'
36-
37-
a = np.arange(NUM_ROWS * num_chan * (2 if 'c' in dt else 1))
38-
if 'i16' in dt:
39-
b = a.astype(np.int16)
40-
elif 'f32' in dt:
41-
b = a.astype(np.float32)
42-
elif 'f64' in dt:
43-
b = a.astype(np.float64)
44-
else:
45-
raise ValueError('whoops')
46-
47-
test_sigmffile.data_file = None
48-
with tempfile.NamedTemporaryFile() as temp:
49-
b.tofile(temp.name)
50-
meta = SigMFFile(data_file=temp.name, global_info=global_info)
51-
meta.add_capture(0, metadata=capture_info)
52-
meta.tofile(archive_filename, toarchive=True)
53-
54-
archi = SigMFArchiveReader(archive_filename, skip_checksum=True)
25+
26+
def test_access_data_without_untar(self):
27+
"""iterate through datatypes and verify IO is correct"""
28+
_, temp_path = tempfile.mkstemp()
29+
_, temp_archive = tempfile.mkstemp(suffix=".sigmf")
30+
31+
for key, dtype in self.lut.items():
32+
# for each type of storage
33+
temp_samples = np.arange(self.raw_count, dtype=dtype)
34+
temp_samples.tofile(temp_path)
35+
for num_channels in [1, 4, 8]:
36+
# for single or 8 channel
37+
for complex_prefix in ["r", "c"]:
38+
# for real or complex
39+
target_count = self.raw_count
40+
temp_meta = SigMFFile(
41+
data_file=temp_path,
42+
global_info={
43+
SigMFFile.DATATYPE_KEY: f"{complex_prefix}{key}_le",
44+
SigMFFile.NUM_CHANNELS_KEY: num_channels,
45+
SigMFFile.VERSION_KEY: sigmf.__version__,
46+
},
47+
)
48+
temp_meta.tofile(temp_archive, toarchive=True)
49+
50+
readback = SigMFArchiveReader(temp_archive)
51+
readback_samples = readback[:]
52+
53+
if complex_prefix == "c":
54+
# complex data will be half as long
55+
target_count //= 2
56+
self.assertTrue(np.all(np.iscomplex(readback_samples)))
57+
if num_channels != 1:
58+
# check expected # of channels
59+
self.assertEqual(
60+
readback_samples.ndim,
61+
2,
62+
"Mismatch in shape of readback samples.",
63+
)
64+
target_count //= num_channels
65+
66+
self.assertEqual(
67+
target_count,
68+
temp_meta._count_samples(),
69+
"Mismatch in expected metadata length.",
70+
)
71+
self.assertEqual(
72+
target_count,
73+
len(readback),
74+
"Mismatch in expected readback length",
75+
)

0 commit comments

Comments
 (0)