Source code for RsWaveform
"""Load, manipulate and save R&S waveform files."""
from __future__ import annotations
import io
import typing
from importlib.metadata import PackageNotFoundError, version
import numpy as np
from . import iqtar, iqw, wv
from .LoadInterface import LoadInterface
from .meta import Meta
from .ParentStorage import ParentStorage
from .SaveInterface import SaveInterface
from .utility.dsp import (
calculate_par,
calculate_peak,
calculate_rms,
convert_to_db,
normalize,
)
from .utility.integer_indexedproperty import IntegerIndexedProperty
if typing.TYPE_CHECKING:
from pathlib import Path
__version__ = "unknown"
try:
__version__ = version(__package__)
except PackageNotFoundError:
# package is not installed
pass
[docs]
class RsWaveform(object):
"""RsWaveform class for loading and saving IQ data."""
def __init__(
self,
load: typing.Type[LoadInterface] = wv.Load,
save: typing.Type[SaveInterface] = wv.Save,
file: typing.Union[None, str, typing.IO, Path] = None,
nr_samples: typing.Union[None, int] = None,
samples_offset: typing.Union[None, int] = None,
only_meta_data: bool = False,
) -> None:
"""Initialize an R&S Waveform class."""
if not issubclass(load, LoadInterface):
raise ValueError(f"Loader is not of type {type(LoadInterface)}")
if not issubclass(save, SaveInterface):
raise ValueError(f"Saver is not of type {type(SaveInterface)}")
self.loader = load()
self.saver = save()
self._parent_storage = ParentStorage()
if file:
if not only_meta_data:
if nr_samples is None and samples_offset is None:
self.load(file)
elif nr_samples is not None and samples_offset is not None:
self.load_in_chunks(file, nr_samples, samples_offset)
else:
raise ValueError(
"You need to define both nr_samples and "
"samples_offset to load chunks!"
)
else:
self.load_meta(file)
[docs]
def load(self, file: typing.Union[str, typing.IO, Path]) -> None:
"""Load a waveform file with a set loader class."""
self._parent_storage = self.loader.load(file)
[docs]
def load_in_chunks(
self,
file: typing.Union[str, typing.IO, Path],
nr_samples: int,
samples_offset: int,
) -> None:
"""Load a chunk of a waveform file with a set loader class."""
self._parent_storage = self.loader.load_in_chunks(
file, nr_samples, samples_offset
)
[docs]
def load_meta(self, file: typing.Union[str, typing.IO, Path]) -> None:
"""Load meta of a waveform file with a set loader class."""
self._parent_storage = self.loader.load_meta(file)
[docs]
def save(
self,
file: typing.Union[str, typing.IO, Path],
scale: float = np.power(2, np.iinfo(np.int16).bits - 1),
) -> None:
"""Saver a waveform file with a set saver class."""
self.saver.save(file, self._parent_storage, scale)
[docs]
def tobytes(self) -> bytes:
"""Get waveform file content as bytes."""
with io.BytesIO() as bio:
self.save(bio)
data = bio.getvalue()
return data
[docs]
def frombytes(self, binary_data: typing.Union[bytes, io.BytesIO]) -> None:
"""Load waveform from bytes file content."""
if isinstance(binary_data, bytes):
with io.BytesIO(binary_data) as bio:
self.load(bio)
else:
self.load(binary_data)
@property
def filename(self) -> str:
"""Get the waveform filename."""
return self._parent_storage.filename
@filename.setter
def filename(self, file_name: str) -> None:
"""Set the waveform filename."""
self._parent_storage.filename = file_name
[docs]
@IntegerIndexedProperty
def meta(self, index: int = 0) -> Meta:
"""Get the waveform storage metadata."""
return self._parent_storage.storages[index].meta
[docs]
@meta.setter
def meta_setter(self, index: int, meta: Meta) -> None:
"""Set the waveform storage metadata."""
self._parent_storage.storages[index].meta = meta
[docs]
@IntegerIndexedProperty
def data(self, index: int = 0) -> np.ndarray:
"""Get the waveform IQ data."""
return self._parent_storage.storages[index].data
[docs]
@data.setter
def data_setter(self, index: int, data: np.ndarray) -> None:
"""Set the waveform storage IQ data."""
self._parent_storage.storages[index].data = data
@property
def parent_storage(self) -> ParentStorage:
"""Get parent storage."""
return self._parent_storage
[docs]
class Iqw(RsWaveform, object):
"""Iqw class for loading and saving IQ data."""
def __init__(
self,
load: typing.Type[LoadInterface] = iqw.Load,
save: typing.Type[SaveInterface] = iqw.Save,
file: typing.Union[None, str, typing.IO, Path] = None,
nr_samples: typing.Union[None, int] = None,
samples_offset: typing.Union[None, int] = None,
) -> None:
"""Initialize a Iqw class."""
super().__init__(load, save, file, nr_samples, samples_offset, False)
[docs]
def save(
self, file: typing.Union[str, typing.IO, Path], scale: float = 1.0
) -> None:
"""Saver an iqw file with a set saver class."""
self.saver.save(file, self._parent_storage, scale)
[docs]
class IqTar(RsWaveform, object):
"""Iqtar class for loading and saving IQ data."""
def __init__(
self,
load: typing.Type[LoadInterface] = iqtar.Load,
save: typing.Type[SaveInterface] = iqtar.Save,
file: typing.Union[None, str, Path] = None,
nr_samples: typing.Union[None, int] = None,
samples_offset: typing.Union[None, int] = None,
only_meta_data: bool = False,
) -> None:
"""Initialize a IQtar class."""
super().__init__(load, save, file, nr_samples, samples_offset, only_meta_data)
[docs]
def save(
self, file: typing.Union[str, typing.IO, Path], scale: float = 1.0
) -> None:
"""Saver an iqw file with a set saver class."""
self.saver.save(file, self._parent_storage, scale)
__all__ = [
"RsWaveform",
"Iqw",
"IqTar",
"LoadInterface",
"SaveInterface",
"calculate_par",
"calculate_peak",
"calculate_rms",
"normalize",
"convert_to_db",
]