Skip to content

Commit cf17f1f

Browse files
committed
integrate feedback from KelseyCreekSoftware
* add conversion checks * add UX improvements * windows path issues * require input and output files for converters
1 parent bb6c3dc commit cf17f1f

8 files changed

Lines changed: 64 additions & 46 deletions

File tree

docs/source/converters.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,10 @@ BLUE Converter
9797

9898
The BLUE converter handles CDIF (.cdif) recordings while placing BLUE header information into the following global fields:
9999

100-
* ``blue:fixed`` - fixed header information (at start of file)
101-
* ``blue:adjunct`` - adjunct header information (after fixed header)
102-
* ``blue:extended`` - extended header information (at end of file)
103-
* ``blue:keywords`` - user-defined key-value pairs
100+
* ``blue:fixed`` - Fixed header information (at start of file).
101+
* ``blue:adjunct`` - Adjunct header information (after fixed header).
102+
* ``blue:extended`` - Extended header information (at end of file). Note any duplicate fields will have a suffix like ``_1``, ``_2``, etc appended.
103+
* ``blue:keywords`` - User-defined key-value pairs.
104104

105105
.. autofunction:: sigmf.convert.blue.blue_to_sigmf
106106

sigmf/__init__.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,7 @@
55
# SPDX-License-Identifier: LGPL-3.0-or-later
66

77
# version of this python module
8-
<<<<<<< HEAD
9-
__version__ = "1.5.1"
10-
=======
118
__version__ = "1.6.0"
12-
>>>>>>> 9220522... increment minor version
139
# matching version of the SigMF specification
1410
__specification__ = "1.2.6"
1511

sigmf/convert/__main__.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,37 @@ def main() -> None:
2727
The converter detects the file type based on magic bytes and invokes the appropriate conversion function.
2828
2929
By default it will output a SigMF pair (.sigmf-meta and .sigmf-data).
30+
31+
Converter Processing Pattern
32+
----------------------------
33+
if out_path is None:
34+
create_ncd = True
35+
<create global_info and capture_info>
36+
if create_ncd:
37+
<create Non-Conforming Dataset (NCD) with .sigmf-meta only>
38+
if out_path:
39+
<write out_path.sigmf-meta>
40+
return SigMFFile
41+
if create_archive:
42+
with TemporaryDirectory() as temp_dir:
43+
<write .sigmf-data>
44+
<write out_path.sigmf>
45+
else:
46+
<write out_path.sigmf-data>
47+
<write out_path.sigmf-meta>
48+
return SigMFFile
3049
"""
3150
parser = argparse.ArgumentParser(
3251
description=textwrap.dedent(main.__doc__),
3352
formatter_class=argparse.RawDescriptionHelpFormatter,
53+
prog="sigmf_convert",
3454
)
3555
parser.add_argument("input", type=str, help="Input recording path")
3656
parser.add_argument("output", type=str, help="Output SigMF path (no extension)")
3757
parser.add_argument("-v", "--verbose", action="count", default=0, help="Increase verbosity level")
38-
parser.add_argument("-a", "--archive", action="store_true", help="Output .sigmf archive only")
39-
parser.add_argument(
58+
exclusive_group = parser.add_mutually_exclusive_group()
59+
exclusive_group.add_argument("-a", "--archive", action="store_true", help="Output .sigmf archive only")
60+
exclusive_group.add_argument(
4061
"--ncd", action="store_true", help="Output .sigmf-meta only and process as a Non-Conforming Dataset (NCD)"
4162
)
4263
parser.add_argument("--version", action="version", version=f"%(prog)s v{toolversion}")
@@ -52,13 +73,17 @@ def main() -> None:
5273
input_path = Path(args.input)
5374
output_path = Path(args.output)
5475

55-
# validate that ncd files are in same directory as input
76+
# for ncd check that input & output files are in same directory
5677
if args.ncd and input_path.parent.resolve() != output_path.parent.resolve():
5778
raise SigMFConversionError(
5879
f"NCD files must be in the same directory as input file. "
5980
f"Input: {input_path.parent.resolve()}, Output: {output_path.parent.resolve()}"
6081
)
6182

83+
# check that the output path is a file and not a directory
84+
if output_path.is_dir():
85+
raise SigMFConversionError(f"Output path must be a filename, not a directory: {output_path}")
86+
6287
# detect file type using magic bytes (same logic as fromfile())
6388
magic_bytes = get_magic_bytes(input_path, count=4, offset=0)
6489

sigmf/convert/blue.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ def read_hcb(file_path):
224224

225225
try:
226226
spec_str = "Unknown"
227-
version = Version(h_keywords.get("VER", "1.0"))
227+
version = Version(h_keywords.get("VER", "0.0"))
228228
if version.major == 1:
229229
spec_str = f"BLUE {version}"
230230
elif version.major == 2:
@@ -381,7 +381,7 @@ def data_loopback(blue_path: Path, data_path: Path, h_fixed: dict) -> None:
381381

382382
# save out as SigMF IQ data file
383383
samples.tofile(data_path)
384-
log.info("wrote %s", data_path)
384+
log.info("wrote SigMF dataset to %s", data_path)
385385

386386

387387
def _build_common_metadata(
@@ -432,7 +432,7 @@ def get_tag(tag):
432432

433433
# get sigmf datatype from blue format and endianness
434434
datatype = blue_to_sigmf_type_str(h_fixed)
435-
log.info(f"Using SigMF datatype: {datatype} for BLUE format {h_fixed['format']}")
435+
log.info(f"using SigMF datatype {datatype} for BLUE format {h_fixed['format']} {h_fixed['data_rep']}")
436436

437437
# sample rate: prefer adjunct.xdelta, else extended header SAMPLE_RATE
438438
if "xdelta" in h_adjunct:
@@ -490,8 +490,9 @@ def get_tag(tag):
490490
blue_start_time += float(h_keywords.get("TC_PREC", 0))
491491

492492
capture_info = {}
493+
493494
if blue_start_time == 0:
494-
log.warning("BLUE timecode is zero or missing; datetime metadata will be absent.")
495+
log.warning("BLUE timecode is zero or missing; capture datetime metadata will be absent.")
495496
else:
496497
# timecode uses 1950-01-01 as epoch, datetime uses 1970-01-01
497498
blue_epoch = blue_start_time - 631152000 # seconds between 1950 and 1970
@@ -651,17 +652,17 @@ def construct_sigmf(
651652
global_info=global_info,
652653
)
653654
meta.add_capture(0, metadata=capture_info)
654-
log.debug("created %r", meta)
655655

656656
if create_archive:
657657
meta.tofile(filenames["archive_fn"], toarchive=True)
658-
log.info("wrote %s", filenames["archive_fn"])
658+
log.info("wrote SigMF archive to %s", filenames["archive_fn"])
659659
# metadata returned should be for this archive
660660
meta = fromfile(filenames["archive_fn"])
661661
else:
662662
meta.tofile(filenames["meta_fn"], toarchive=False)
663-
log.info("wrote %s", filenames["meta_fn"])
663+
log.info("wrote SigMF metadata to %s", filenames["meta_fn"])
664664

665+
log.debug("created %r", meta)
665666
return meta
666667

667668

@@ -790,10 +791,10 @@ def blue_to_sigmf(
790791
data_size_bytes = int(h_fixed.get("data_size", 0))
791792
metadata_only = data_size_bytes == 0
792793

793-
# handle NCD case where no output files are created
794-
if create_ncd and out_path is None:
794+
# handle NCD case
795+
if create_ncd:
795796
# create metadata-only SigMF for NCD pointing to original file
796-
return construct_sigmf_ncd(
797+
ncd_meta = construct_sigmf_ncd(
797798
blue_path=blue_path,
798799
h_fixed=h_fixed,
799800
h_keywords=h_keywords,
@@ -803,6 +804,13 @@ def blue_to_sigmf(
803804
trailing_bytes=trailing_bytes,
804805
)
805806

807+
# write NCD metadata to specified output path if provided
808+
if out_path is not None:
809+
ncd_meta.tofile(filenames["meta_fn"])
810+
log.info("wrote SigMF non-conforming metadata to %s", filenames["meta_fn"])
811+
812+
return ncd_meta
813+
806814
with tempfile.TemporaryDirectory() as temp_dir:
807815
if not metadata_only:
808816
if create_archive:

sigmf/convert/wav.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,6 @@ def wav_to_sigmf(
161161
global_info[SigMFFile.DATASET_KEY] = wav_path.name
162162
capture_info[SigMFFile.HEADER_BYTES_KEY] = header_bytes
163163

164-
if create_ncd:
165164
# create metadata-only SigMF for NCD pointing to original file
166165
meta = SigMFFile(global_info=global_info)
167166
meta.set_data_file(data_file=wav_path, offset=header_bytes)
@@ -174,9 +173,9 @@ def wav_to_sigmf(
174173
output_dir = filenames["meta_fn"].parent
175174
output_dir.mkdir(parents=True, exist_ok=True)
176175
meta.tofile(filenames["meta_fn"], toarchive=False)
177-
log.info("wrote %s", filenames["meta_fn"])
176+
log.info("wrote SigMF non-conforming metadata to %s", filenames["meta_fn"])
178177

179-
log.debug("created NCD SigMF: %r", meta)
178+
log.debug("created %r", meta)
180179
return meta
181180

182181
if out_path is None:
@@ -197,23 +196,22 @@ def wav_to_sigmf(
197196

198197
meta = SigMFFile(data_file=data_path, global_info=global_info)
199198
meta.add_capture(0, metadata=capture_info)
200-
log.debug("created %r", meta)
201199

202200
meta.tofile(filenames["archive_fn"], toarchive=True)
203-
log.info("wrote %s", filenames["archive_fn"])
204-
201+
log.info("wrote SigMF archive to %s", filenames["archive_fn"])
205202
# metadata returned should be for this archive
206203
meta = fromfile(filenames["archive_fn"])
207204
else:
208205
# write separate meta and data files
209206
data_path = filenames["data_fn"]
210207
wav_data.tofile(data_path)
208+
log.info("wrote SigMF dataset to %s", data_path)
211209

212210
meta = SigMFFile(data_file=data_path, global_info=global_info)
213211
meta.add_capture(0, metadata=capture_info)
214-
log.debug("created %r", meta)
215212

216213
meta.tofile(filenames["meta_fn"], toarchive=False)
217-
log.info("wrote %s and %s", filenames["meta_fn"], filenames["data_fn"])
214+
log.info("wrote SigMF metadata to %s", filenames["meta_fn"])
218215

216+
log.debug("created %r", meta)
219217
return meta

sigmf/sigmffile.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -883,7 +883,6 @@ def _read_datafile(self, first_byte, nitems):
883883
is_fixedpoint_data = dtype["is_fixedpoint"]
884884
is_unsigned_data = dtype["is_unsigned"]
885885
data_type_in = dtype["sample_dtype"]
886-
component_type_in = dtype["component_dtype"]
887886
component_size = dtype["component_size"]
888887

889888
data_type_out = np.dtype("f4") if not self.is_complex_data else np.dtype("f4, f4")
@@ -1315,6 +1314,9 @@ def fromfile(filename, skip_checksum=False, autoscale=True):
13151314

13161315
# try auto-detection for non-SigMF files only
13171316
if Path.is_file(file_path) and not ext.endswith(sigmf_extensions):
1317+
if not autoscale:
1318+
# TODO: allow autoscale=False for converters
1319+
warnings.warn("non-SigMF auto-detection conversion only supports autoscale=True; ignoring autoscale=False")
13181320
magic_bytes = get_magic_bytes(file_path, count=4, offset=0)
13191321

13201322
if magic_bytes == b"RIFF":

tests/test_convert_blue.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,26 +46,23 @@ def tearDown(self) -> None:
4646
def test_sigmf_pair(self):
4747
"""test standard blue to sigmf conversion with file pairs"""
4848
for blue_path in self.blue_paths:
49+
print(blue_path)
4950
sigmf_path = self.tmp_path / blue_path.stem
5051
meta = blue_to_sigmf(blue_path=blue_path, out_path=sigmf_path)
5152
self.assertIsInstance(meta, sigmf.SigMFFile)
52-
# FIXME: REPLACE BELOW WITH BELOW COMMENTED AFTER PR #121 MERGED
5353
if not meta.get_global_field("core:metadata_only"):
54-
_ = meta.read_samples(count=10)
5554
# check sample read consistency
56-
# np.testing.assert_array_equal(meta.read_samples(count=10), meta[0:10])
55+
np.testing.assert_array_equal(meta.read_samples(count=10), meta[0:10])
5756

5857
def test_sigmf_archive(self):
5958
"""test blue to sigmf conversion with archive output"""
6059
for blue_path in self.blue_paths:
6160
sigmf_path = self.tmp_path / f"{blue_path.stem}_archive"
6261
meta = blue_to_sigmf(blue_path=blue_path, out_path=sigmf_path, create_archive=True)
6362
self.assertIsInstance(meta, sigmf.SigMFFile)
64-
# FIXME: REPLACE BELOW WITH BELOW COMMENTED AFTER PR #121 MERGED
6563
if not meta.get_global_field("core:metadata_only"):
66-
_ = meta.read_samples(count=10)
6764
# check sample read consistency
68-
# np.testing.assert_array_equal(meta.read_samples(count=10), meta[0:10])
65+
np.testing.assert_array_equal(meta.read_samples(count=10), meta[0:10])
6966

7067
def test_create_ncd(self):
7168
"""test direct NCD conversion"""

tests/test_convert_wav.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -129,27 +129,19 @@ def test_sigmf_pair(self):
129129
sigmf_path = self.tmp_path / wav_path.stem
130130
meta = wav_to_sigmf(wav_path=wav_path, out_path=sigmf_path)
131131
self.assertIsInstance(meta, sigmf.SigMFFile)
132-
# FIXME: REPLACE BELOW WITH BELOW COMMENTED AFTER PR #121 MERGED
133132
if not meta.get_global_field("core:metadata_only"):
134-
_ = meta.read_samples(count=10)
135133
# check sample read consistency
136-
# np.testing.assert_array_equal(meta.read_samples(count=10), meta[0:10])
134+
np.testing.assert_array_equal(meta.read_samples(count=10), meta[0:10])
137135

138136
def test_sigmf_archive(self):
139137
"""test wav to sigmf conversion with archive output"""
140138
for wav_path in self.wav_paths:
141139
sigmf_path = self.tmp_path / f"{wav_path.stem}_archive"
142140
meta = wav_to_sigmf(wav_path=wav_path, out_path=sigmf_path, create_archive=True)
143-
# FIXME: I believe this error related to sample_count being 0 is fixed by PR 121
144-
print("dbug", meta)
145-
print("dbug len", len(meta))
146-
print("dbug sample_count", meta.sample_count)
147141
self.assertIsInstance(meta, sigmf.SigMFFile)
148-
# FIXME: REPLACE BELOW WITH BELOW COMMENTED AFTER PR #121 MERGED
149142
if not meta.get_global_field("core:metadata_only"):
150-
_ = meta.read_samples(count=10)
151143
# check sample read consistency
152-
# np.testing.assert_array_equal(meta.read_samples(count=10), meta[0:10])
144+
np.testing.assert_array_equal(meta.read_samples(count=10), meta[0:10])
153145

154146
def test_create_ncd(self):
155147
"""test direct NCD conversion"""

0 commit comments

Comments
 (0)