Source code for pylablib.core.devio.data_format
"""
Library for binary data encoding/decoding for device communication
and dealing with different data format representations in different contexts (numpy, SCPI, etc.).
"""
import numpy as np
import re
import sys
from ..utils import py3
[docs]class DataFormat:
"""
Describes data encoding for device communications.
Args:
size (int): Size of a single element (in bytes).
kind (str): Kind of the element. Can be
``'i'`` (integer), ``'u'`` (unsigned integer), ``'f'`` (floating point) or ``'ascii'`` (text representation).
byteorder (str): Byte order: ``'>'`` is big-endian (MSB first), ``'<'`` is little-endian (LSB first).
"""
_native_byteorder="<" if sys.byteorder=="little" else ">"
def __init__(self, size, kind, byteorder):
self.size=size and int(size)
self.kind=kind
self.byteorder=byteorder
[docs] def flip_byteorder(self):
"""Flip byteorder of the description"""
if self.byteorder is not None:
self.byteorder="<" if self.byteorder==">" else ">"
[docs] @staticmethod
def from_desc(desc, str_type="numpy"):
"""
Build the format from the string description.
`str_type` is the description format. Can be
``'numpy'`` (numpy dtype description),
``'struct'`` (:mod:`struct` description) or
``'SCPI'`` (the standard SCPI description).
"""
if isinstance(desc, DataFormat):
return desc
if desc.lower()=="ascii":
return DataFormat(None,"ascii",None)
if str_type=="numpy":
dtype=np.dtype(desc)
if not dtype.kind in "uif":
raise ValueError("can't describe data format: {0}".format(desc))
byteorder=DataFormat._native_byteorder if dtype.byteorder in "=|" else dtype.byteorder
return DataFormat(dtype.itemsize,dtype.kind,byteorder)
elif str_type=="struct":
if desc[:1]=="@":
desc="="+desc[1:]
return DataFormat.from_desc(desc,"numpy")
elif str_type=="SCPI":
return DataFormat.from_desc_SCPI(desc)
else:
raise ValueError("unrecognized format type: {0}".format(str_type))
[docs] @staticmethod
def from_desc_SCPI(desc, border="norm"):
"""
Build the format from the string SCPI description.
`border` describes byte order (either ``'norm'`` or ``'swap'``).
"""
desc=desc.lower().strip()
border=border.lower().strip()[:4]
if border=="norm":
byteorder=">"
elif border=="swap":
byteorder="<"
else:
raise ValueError("unrecognized SCPI border: {0}".format(border))
if desc[:3]=="asc":
return DataFormat(None,"ascii",None)
split_desc=desc.split(",")
if len(split_desc)<2:
raise ValueError("can't determine SCPI format: {0}".format(desc))
bitlen=int(split_desc[1])
if bitlen%8!=0 or bitlen==0:
raise ValueError("wrong bitlength in SCPI format: {0}".format(desc))
if desc[:4]=="real":
kind="f"
elif desc[:3]=="int":
kind="i"
else:
raise ValueError("can't determine SCPI format: {0}".format(desc))
return DataFormat(bitlen//8,kind,byteorder)
_struct_descriptors={("i",1):"b",("i",2):"h",("i",4):"i",("i",8):"q",
("u",1):"B",("u",2):"H",("u",4):"I",("u",8):"Q",
("f",4):"f",("f",8):"d"}
_SCPI_descriptors={"i":"int","u":"int","f":"real"}
[docs] def to_desc(self, str_type="auto"):
"""
Build a description string of this format.
`str_type` can be ``'auto'`` (similar to ``'numpy'``, but also accepts ``'ascii'``),
``'numpy'``, ``'struct'`` or ``'SCPI'`` (return tuple ``(desc, border)``).
"""
if self.is_ascii():
if str_type=="auto":
return "ascii"
elif str_type=="SCPI":
return "ascii", "norm"
else:
raise ValueError("can't represent format 'ascii' as {0}".format(str_type))
if str_type in ["numpy","auto"]:
return self.byteorder+self.kind+str(self.size)
elif str_type=="struct":
if (self.kind,self.size) in self._struct_descriptors:
desc=self._struct_descriptors[(self.kind,self.size)]
else:
raise ValueError("can't represent format {0} as struct".format(self))
return self.byteorder+desc
elif str_type=="SCPI":
desc=self._SCPI_descriptors[self.kind]
border="norm" if self.byteorder==">" else "swap"
return "{0},{1}".format(desc,self.size*8), border
else:
raise ValueError("unrecognized format type: {0}".format(str_type))
def __str__(self):
return self.to_desc()
def __repr__(self):
return "{0}('{1}')".format(self.__class__.__name__,self.__str__())
[docs] def convert_from_str(self, data):
"""Convert the string data into an array"""
if self.is_ascii():
if isinstance(data,py3.bytestring):
data=[e.strip() for e in re.split(br"\s*,\s*|\s+",data)]
else:
data=[e.strip() for e in re.split(r"\s*,\s*|\s+",data)]
return np.array([float(e) for e in data if e])
else:
return np.require(np.frombuffer(data,dtype=self.to_desc("numpy")),requirements="W")
[docs] def convert_to_str(self, data, ascii_format=".5f"):
"""
Convert the array into a string data.
`ascii_format` is the :meth:`str.format` string for textual representation.
"""
if self.is_ascii():
fmt="{:"+ascii_format+"}"
return ",".join([fmt.format(e) for e in data])
else:
return np.asarray(data).astype(self.to_desc("numpy")).tostring()