Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .github/workflows/unittest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Testing
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
permissions:
contents: read
jobs:
tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Python 3.10 setup
uses: actions/setup-python@v3
with:
python-version: "3.10"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install .
pip install pytest

- name: Unit testing
run: |
pytest -v
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
.idea/
*.shr
build/
py_shr_parser.egg-info
__pycache__/
Expand Down
9 changes: 9 additions & 0 deletions Documentation/appendix.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@
Appendix
========

Related Software
================
* shr-parser_: A Rust library for parsing SHR files.
* shr-parser-py_: Another Python package for parsing SHR files written in Rust. This project is no longer
maintained at the time of writing.

.. _shr-parser: https://github.com/Xerrion/shr_parser
.. _shr-parser-py: https://pypi.org/project/shr-parser/

License
=======
Copyright (c) 2025, WiSELab-CMU
Expand Down
6 changes: 3 additions & 3 deletions Documentation/pyshrparser.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ This installs a package that can be used from Python (``import shr_parser``).
To install for all users on the system, administrator
rights (root) may be required.

From PyPI (Not published yet. This doesn't work yet)
From PyPI
----------------------------------------------------
pyshrparser can be installed from PyPI::

pip install pyshrparser
python3 -m pip install pyshrparser
pip install py-shr-parser
python3 -m pip install py-shr-parser

Developers also may be interested to get the source archive, because it
contains examples, tests and this documentation.
Expand Down
21 changes: 15 additions & 6 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,25 @@ Documentation
-------------
For API documentation, usage and examples, see the files in the "Documentation" directory.
The ".rst" files can be read in any text editor or being converted to HTML or PDF
using Sphinx.
using Sphinx_. Examples can be found in the documentation and tests.

Examples
--------
Examples do not exist yet...
.. _Sphinx: https://www.sphinx-doc.org/en/master/

Installation
------------
Eventually will be published on PyPl. Currently download the repo and run

.. code-block:: none

pip install <path to root of this repo>
pip install py-shr-parser

Basic Example
-------------
This shows how to open an SHR file and load all the sweep data.

.. code-block:: python

from shr_parser import ShrFileParser
with ShrFileParser("foo.shr") as parser:
sweeps = parser.get_all_sweeps()

The above example is very basic. For more advanced examples, please refer to the Documentation.
21 changes: 18 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,36 @@ requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"

[tool.setuptools.packages.find]
where = ["."]
where = ["src"]

[project]
name = "py-shr-parser"
version = "0.0.0"
version = "1.0.0"
authors = [
{ name = "Tom Schmitz", email="tschmitz@andrew.cmu.edu" },
]
description = "Python library for parsing Signal Hound SHR files"
requires-python = ">=3.8"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Programming Language :: Python :: 3",
"Operating System :: OS Independent"
"Operating System :: OS Independent",
]
dependencies = [
"numpy",
"matplotlib",
]
license = "BSD-3-Clause"
keywords = ["SHR", "Signal Hound",]
readme = "README.rst"

[project.urls]
Homepage = "https://github.com/WiseLabCMU/py-shr-parser"
"Bug Tracker" = "https://github.com/WiseLabCMU/py-shr-parser/issues"

[tool.pytest.ini_options]
minversion = "6.0"
addopts = "-ra -q"
testpaths = [
"tests",
]
3 changes: 2 additions & 1 deletion shr_parser/__init__.py → src/shr_parser/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from .shr_parser import ShrSweep, ShrFileParser
from .enumerations import ShrScale, ShrWindow, ShrDecimationDetector, ShrVideoDetector, ShrVideoUnits, ShrDecimationType, ShrChannelizerOutputUnits
from .exceptions import ShrFileParserException, FileNotOpenError
from .exceptions import ShrFileParserException, FileNotOpenError, ShrFileParserWarning
from .metadata import ShrSweepHeader, ShrFileHeader
from .version import __version__
File renamed without changes.
5 changes: 5 additions & 0 deletions shr_parser/exceptions.py → src/shr_parser/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,8 @@ class FileNotOpenError(ShrFileParserException):

def __init__(self):
super().__init__("File not open")


class ShrFileParserWarning(RuntimeWarning):
"""Warning indicating that further parsing may result in an error"""
pass
File renamed without changes.
32 changes: 24 additions & 8 deletions shr_parser/shr_parser.py → src/shr_parser/shr_parser.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from .enumerations import *
from .metadata import ShrFileHeader, ShrSweepHeader
from .exceptions import ShrFileParserException, FileNotOpenError
from .exceptions import ShrFileParserException, FileNotOpenError, ShrFileParserWarning
import struct
from io import TextIOWrapper
from io import BufferedReader
import numpy as np
from pathlib import Path

SHR_FILE_SIGNATURE = 0xAA10
SHR_FILE_VERSION = 0x2
Expand Down Expand Up @@ -35,6 +36,7 @@ class ShrSweep:
"""
Frequency sweep information.
"""

def __init__(self, header: ShrSweepHeader, sweep: np.array, n: int, file_header: ShrFileHeader):
"""
Initializer.
Expand Down Expand Up @@ -114,13 +116,14 @@ class ShrFileParser:
:raises FileNotFoundError: If unable to open file for reading.
:raises FileNotOpenError: If the file is not open.
"""

def __init__(self, fname: str):
"""
Initializer.
:param fname: The name of the file to parse.
"""
self.__fname = fname
self.__f: TextIOWrapper[bytes] | None = None
self.__f: BufferedReader | None = None
self.__header = ShrFileHeader()

def _open_file(self, fname: str):
Expand All @@ -131,24 +134,37 @@ def _open_file(self, fname: str):

bytes_read = self.__f.read(FILE_HEADER_SIZE)
if len(bytes_read) != FILE_HEADER_SIZE:
self.__f.close()
self.__f = None
raise ShrFileParserException("Unable to read header")

self.__header.from_tuple(struct.unpack(FILE_HEADER_PACK, bytes_read))

if self.__header.signature != SHR_FILE_SIGNATURE:
self.__f.close()
self.__f = None
raise ShrFileParserException("Invalid SHR file")

if self.__header.version > SHR_FILE_VERSION:
self.__f.close()
self.__f = None
raise ShrFileParserException(
f"Tried parsing SHR file with version {self.__header.version}. Version {SHR_FILE_VERSION} "
f"and lower is supported.")

sweep_data_size = Path(fname).stat().st_size - FILE_HEADER_SIZE
sz_per_sweep = (4 * self.__header.sweep_length) + SWEEP_HEADER_SIZE

if sweep_data_size != (sz_per_sweep * self.__header.sweep_count):
raise ShrFileParserWarning(f"{fname} reported {self.__header.sweep_count} sweeps in the file. "
f"Found {sweep_data_size / sz_per_sweep} sweeps instead!")

def __enter__(self):
self._open_file(self.__fname)
return self

def __exit__(self, exc_type, exc_val, exc_tb):
self.__f.close()
self.close()

def open(self):
"""
Expand Down Expand Up @@ -201,15 +217,15 @@ def get_sweep_n(self, n: int) -> ShrSweep:
header_bytes: bytes = self.__f.read(SWEEP_HEADER_SIZE)
sweep_bytes: bytes = self.__f.read(4 * self.__header.sweep_length)

header = ShrSweepHeader()
header.from_tuple(struct.unpack(SWEEP_HEADER_PACK, header_bytes))
sweep = np.frombuffer(sweep_bytes, dtype=np.float32)

if len(header_bytes) != SWEEP_HEADER_SIZE:
raise ShrFileParserException("Invalid sweep header size")
if len(sweep_bytes) != sweep_size:
raise ShrFileParserException("Invalid sweep size")

header = ShrSweepHeader()
header.from_tuple(struct.unpack(SWEEP_HEADER_PACK, header_bytes))
sweep = np.frombuffer(sweep_bytes, dtype=np.float32)

return ShrSweep(header, sweep, n, self.__header)

def get_all_sweeps(self) -> list[ShrSweep]:
Expand Down
3 changes: 3 additions & 0 deletions src/shr_parser/version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import importlib.metadata

__version__ = importlib.metadata.version("py-shr-parser")
Empty file added tests/__init__.py
Empty file.
Binary file added tests/test_files/sweep0v2.shr
Binary file not shown.
Loading