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] def is_ascii(self): """Check of the format is textual""" return self.kind=="ascii"
[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()