"""
.. module:: skrf.io.general
========================================
general (:mod:`skrf.io.general`)
========================================
General input/output functions for reading and writing skrf objects
Pickle functions
------------------
The read/write methods use the pickle module. These should only be used
for temporary storage.
.. autosummary::
:toctree: generated/
read
read_all
read_all_networks
write
write_all
save_sesh
Spreadsheets
-----------------------------
.. autosummary::
:toctree: generated/
network_2_spreadsheet
networkset_2_spreadsheet
Pandas dataframe
----------------------------------
.. autosummary::
:toctree: generated/
network_2_dataframe
Statistics
----------
.. autosummary::
:toctree: generated/
statistical_2_touchstone
JSON
-------
.. autosummary::
:toctree: generated/
TouchstoneEncoder
to_json_string
from_json_string
"""
from __future__ import annotations
import glob
import inspect
import json
import os
import pickle
import sys
import warnings
from io import StringIO
from pickle import UnpicklingError
from typing import Any
import numpy as npy
from pandas import DataFrame, ExcelWriter, Series
from ..frequency import Frequency
from ..network import Network
from ..networkSet import NetworkSet
from ..util import get_extn, get_fid
def _get_extension(inst: Any) -> str:
"""File extension conventions for skrf objects.
"""
from ..calibration.calibration import Calibration
from ..media import Media
extensions = [
(Frequency, "freq"),
(Network, "ntwk"),
(NetworkSet, "ns"),
(Calibration, "cal"),
(Media, "med"),
]
for cls, ext in extensions:
print(cls, ext)
if isinstance(inst, cls):
return ext
return "p"
[docs]
def read(file, *args, **kwargs):
r"""
Read skrf object[s] from a pickle file.
Reads a skrf object that is written with :func:`write`, which uses
the :mod:`pickle` module.
Parameters
----------
file : str or file-object
name of file, or a file-object
\*args, \*\*kwargs : arguments and keyword arguments
passed through to pickle.load
.. note::
If `file` is a:
* a file-object, it is left open
* a filename, then a file-object is opened and closed.
* a file-object and reading fails, then the position is reset back to 0 using seek if possible.
Examples
--------
>>> n = rf.Network(f=[1,2,3],s=[1,1,1],z0=50)
>>> n.write('my_ntwk.ntwk')
>>> n_2 = rf.read('my_ntwk.ntwk')
See Also
--------
read : read a skrf object
write : write skrf object[s]
read_all : read all skrf objects in a directory
write_all : write dictionary of skrf objects to a directory
"""
fid = get_fid(file, mode='rb')
try:
obj = pickle.load(fid, *args, **kwargs)
except (UnpicklingError, UnicodeDecodeError):
# if fid is seekable then reset to beginning of file
fid.seek(0)
if isinstance(file, str):
# we created the fid so close it
fid.close()
raise
if isinstance(file, str):
# we created the fid so close it
fid.close()
return obj
[docs]
def write(file, obj, overwrite = True):
"""
Write skrf object[s] to a file.
This uses the :mod:`pickle` module to write skrf objects to a file.
Note that you can write any pickl-able python object. For example,
you can write a list or dictionary of :class:`~skrf.network.Network`
objects
or :class:`~skrf.calibration.calibration.Calibration` objects. This
will write out a single file. If you would like to write out a
separate file for each object, use :func:`write_all`.
Parameters
----------
file : file or string
File or filename to which the data is saved. If file is a
file-object, then the filename is unchanged. If file is a
string, an appropriate extension will be appended to the file
name if it does not already have an extension.
obj : an object, or list/dict of objects
object or list/dict of objects to write to disk
overwrite : Boolean
if file exists, should it be overwritten?
.. note::
If `file` is a string, but doesnt contain a suffix, one is chosen
automatically. Here are the extensions:
==================================================== ===============
skrf object extension
==================================================== ===============
:class:`~skrf.frequency.Frequency` '.freq'
:class:`~skrf.network.Network` '.ntwk'
:class:`~skrf.networkSet.NetworkSet` '.ns'
:class:`~skrf.calibration.calibration.Calibration` '.cal'
:class:`~skrf.media.media.Media` '.med'
other '.p'
==================================================== ===============
.. note::
To make the file written by this method cross-platform, the pickling
protocol 2 is used. See :mod:`pickle` for more info.
Examples
--------
Convert a touchstone file to a pickled Network,
>>> n = rf.Network('my_ntwk.s2p')
>>> rf.write('my_ntwk',n)
>>> n_red = rf.read('my_ntwk.ntwk')
Writing a list of different objects
>>> n = rf.Network('my_ntwk.s2p')
>>> ns = rf.NetworkSet([n,n,n])
>>> rf.write('out',[n,ns])
>>> n_red = rf.read('out.p')
See Also
--------
read : read a skrf object
write : write skrf object[s]
read_all : read all skrf objects in a directory
write_all : write dictionary of skrf objects to a directory
skrf.network.Network.write : write method of Network
skrf.calibration.calibration.Calibration.write : write method of Calibration
"""
if isinstance(file, str):
extn = get_extn(file)
if extn is None:
# if there is not extension add one
file += f".{_get_extension(obj)}"
if os.path.exists(file):
if not overwrite:
warnings.warn('file exists, and overwrite option is False. Not writing.', stacklevel=2)
return
with open(file, 'wb') as fid:
pickle.dump(obj, fid, protocol=2)
else:
fid = file
pickle.dump(obj, fid, protocol=2)
fid.close()
[docs]
def read_all(dir: str ='.', sort = True, contains = None, f_unit = None,
obj_type=None, files: list=None, recursive=False) -> dict:
"""
Read all skrf objects in a directory.
Attempts to load all files in `dir`, using :func:`read`. Any file
that is not readable by skrf is skipped. Optionally, simple filtering
can be achieved through the use of `contains` argument.
Parameters
----------
dir : str, optional
the directory to load from, default \'.\'
sort: boolean, default is True
filenames sorted by https://docs.python.org/3/library/stdtypes.html#list.sort without arguements
contains : str, optional
if not None, only files containing this substring will be loaded
f_unit : ['hz','khz','mhz','ghz','thz']
for all :class:`~skrf.network.Network` objects, set their
frequencies's :attr:`~skrf.frequency.Frequency.f_unit`
obj_type : str
Name of skrf object types to read (ie 'Network')
files : list, optional
list of files to load, bypasses dir parameter.
recursive : bool, optional
If True, search in the specified directory and all other nested directories
Returns
-------
out : dictionary
dictionary containing all loaded skrf objects. keys are the
filenames without extensions, and the values are the objects
Examples
--------
>>> rf.read_all('skrf/data/')
{'delay_short': 1-Port Network: 'delay_short', 75-110 GHz, 201 pts, z0=[ 50.+0.j],
'line': 2-Port Network: 'line', 75-110 GHz, 201 pts, z0=[ 50.+0.j 50.+0.j],
'ntwk1': 2-Port Network: 'ntwk1', 1-10 GHz, 91 pts, z0=[ 50.+0.j 50.+0.j],
'one_port': one port Calibration: 'one_port', 500-750 GHz, 201 pts, 4-ideals/4-measured,
...
>>> rf.read_all('skrf/data/', obj_type = 'Network')
{'delay_short': 1-Port Network: 'delay_short', 75-110 GHz, 201 pts, z0=[ 50.+0.j],
'line': 2-Port Network: 'line', 75-110 GHz, 201 pts, z0=[ 50.+0.j 50.+0.j],
'ntwk1': 2-Port Network: 'ntwk1', 1-10 GHz, 91 pts, z0=[ 50.+0.j 50.+0.j],
>>> rf.read_all(files = ['skrf/data/delay_short.s1p', 'skrf/data/line.s2p'], obj_type = 'Network')
{'delay_short': 1-Port Network: 'delay_short', 75-110 GHz, 201 pts, z0=[ 50.+0.j],
'line': 2-Port Network: 'line', 75-110 GHz, 201 pts, z0=[ 50.+0.j 50.+0.j]}
See Also
----------
read : read a skrf object
write : write skrf object[s]
read_all : read all skrf objects in a directory
write_all : write dictionary of skrf objects to a directory
"""
out={}
filelist = []
if files is None:
if recursive:
if not dir.endswith(os.path.sep):
dir += os.path.sep
dir += '**'
for filename in glob.iglob(os.path.join(dir, '*.s*p'), recursive=recursive):
filelist.append(filename)
else:
filelist.extend(files)
if sort is True:
filelist.sort()
for filename in filelist:
if contains is not None and contains not in filename:
continue
fullname = filename
keyname = os.path.splitext(filename.split(os.path.sep)[-1])[0]
try:
out[keyname] = read(fullname)
continue
except Exception:
pass
try:
out[keyname] = Network(fullname)
continue
except Exception:
pass
if f_unit is not None:
for keyname in out:
try:
out[keyname].frequency.unit = f_unit
except Exception:
pass
if obj_type is not None:
out = {k: out[k] for k in out if
isinstance(out[k],sys.modules[__name__].__dict__[obj_type])}
return out
[docs]
def read_all_networks(*args, **kwargs):
"""
Read all networks in a directory.
This is a convenience function. It just calls::
read_all(*args,obj_type='Network', **kwargs)
See Also
--------
read_all
"""
return read_all(*args,obj_type='Network', **kwargs)
ran = read_all_networks
[docs]
def write_all(dict_objs, dir='.', *args, **kwargs):
r"""
Write a dictionary of skrf objects individual files in `dir`.
Each object is written to its own file. The filename used for each
object is taken from its key in the dictionary. If no extension
exists in the key, then one is added. See :func:`write` for a list
of extensions. If you would like to write the dictionary to a single
output file use :func:`write`.
.. note::
Any object in dict_objs that is pickl-able will be written.
Parameters
----------
dict_objs : dict
dictionary of skrf objects
dir : str
directory to save skrf objects into
\*args, \*\*kwargs :
passed through to :func:`~skrf.io.general.write`. `overwrite`
option may be of use.
See Also
--------
read : read a skrf object
write : write skrf object[s]
read_all : read all skrf objects in a directory
write_all : write dictionary of skrf objects to a directory
Examples
--------
Writing a diction of different skrf objects
>>> from skrf.data import line, short
>>> d = {'ring_slot':ring_slot, 'one_port_cal':one_port_cal}
>>> rf.write_all(d)
"""
if not os.path.exists('.'):
raise OSError('No such directory: %s'%dir)
for k in dict_objs:
filename = k
obj = dict_objs[k]
extn = get_extn(filename)
if extn is None:
# if there is not extension add one
filename += f".{_get_extension(obj)}"
try:
with open(os.path.join(dir+'/', filename), 'wb') as fid:
write(fid, obj,*args, **kwargs)
except Exception as inst:
print(inst)
warnings.warn(f'couldnt write {k}: {inst}', stacklevel=2)
pass
[docs]
def save_sesh(dict_objs, file='skrfSesh.p', module='skrf', exclude_prefix='_'):
"""
Save all `skrf` objects in the local namespace.
This is used to save current workspace in a hurry, by passing it the
output of :func:`locals` (see Examples). Note this can be
used for other modules as well by passing a different `module` name.
Parameters
----------
dict_objs : dict
dictionary containing `skrf` objects. See the Example.
file : str or file-object, optional
the file to save all objects to
module : str, optional
the module name to grep for.
exclude_prefix: str, optional
dont save objects which have this as a prefix.
See Also
--------
read : read a skrf object
write : write skrf object[s]
read_all : read all skrf objects in a directory
write_all : write dictionary of skrf objects to a directory
Examples
--------
Write out all skrf objects in current namespace.
>>> rf.write_all(locals(), 'mysesh.p')
"""
objects = {}
print('pickling: ')
for k in dict_objs:
try:
if module in inspect.getmodule(dict_objs[k]).__name__:
try:
pickle.dumps(dict_objs[k])
if k[0] != '_':
objects[k] = dict_objs[k]
print(k+', ')
finally:
pass
except(AttributeError, TypeError):
pass
if len (objects ) == 0:
print('nothing')
write(file, objects)
def load_all_touchstones(dir = '.', contains=None, f_unit=None):
"""
Loads all touchtone files in a given dir into a dictionary.
Notes
-------
Parameters
-----------
dir : string
the path
contains : string
a string the filenames must contain to be loaded.
f_unit : ['hz','mhz','ghz']
the frequency unit to assign all loaded networks. see
:attr:`frequency.Frequency.unit`.
Returns
---------
ntwkDict : a dictionary with keys equal to the file name (without
a suffix), and values equal to the corresponding ntwk types
Examples
----------
>>> ntwk_dict = rf.load_all_touchstones('.', contains ='20v')
See Also
-----------
read_all
"""
ntwkDict = {}
for f in os.listdir (dir):
if contains is not None and contains not in f:
continue
keyname,extn = os.path.splitext(f)
extn = extn.lower()
try:
if extn[1]== 's' and extn[-1]=='p':
ntwkDict[keyname]=(Network(dir +'/'+f))
if f_unit is not None:
ntwkDict[keyname].frequency.unit=f_unit
except Exception:
pass
return ntwkDict
def write_dict_of_networks(ntwkDict, dir='.'):
"""
Saves a dictionary of networks touchstone files in a given directory
The filenames assigned to the touchstone files are taken from
the keys of the dictionary.
Parameters
-----------
ntwkDict : dictionary
dictionary of :class:`Network` objects
dir : string
directory to write touchstone file to
"""
warnings.warn('Deprecated. use write_all.', DeprecationWarning, stacklevel=2)
for ntwkKey in ntwkDict:
ntwkDict[ntwkKey].write_touchstone(filename = dir+'/'+ntwkKey)
def read_csv(filename):
"""
Read a 2-port s-parameter data from a csv file.
Specifically, this reads a two-port csv file saved from a Rohde Schwarz
ZVA-40, and possibly other network analyzers. It returns into a
:class:`Network` object.
Parameters
------------
filename : str
name of file
Returns
--------
ntwk : :class:`Network` object
the network representing data in the csv file
"""
ntwk = Network(name=filename[:-4])
try:
data = npy.loadtxt(filename, skiprows=3,delimiter=',',\
usecols=range(9))
s11 = data[:,1] +1j*data[:,2]
s21 = data[:,3] +1j*data[:,4]
s12 = data[:,5] +1j*data[:,6]
s22 = data[:,7] +1j*data[:,8]
ntwk.s = npy.array([[s11, s21],[s12,s22]]).transpose().reshape(-1,2,2)
except(IndexError):
data = npy.loadtxt(filename, skiprows=3,delimiter=',',\
usecols=range(3))
ntwk.s = data[:,1] +1j*data[:,2]
ntwk.frequency.f = data[:,0]
return ntwk
## file conversion
[docs]
def statistical_2_touchstone(file_name, new_file_name=None,\
header_string='# GHz S RI R 50.0'):
"""
Converts Statistical file to a touchstone file.
Converts the file format used by Statistical and other Dylan Williams
software to standard touchstone format.
Parameters
----------
file_name : string
name of file to convert
new_file_name : string
name of new file to write out (including extension)
header_string : string
touchstone header written to first beginning of file
"""
remove_tmp_file = new_file_name is None
if remove_tmp_file:
new_file_name = 'tmp-'+file_name
# This breaks compatibility with python 2.6 and older
with open(file_name) as old_file, open(new_file_name, 'w') as new_file:
new_file.write('%s\n'%header_string)
for line in old_file:
new_file.write(line)
if remove_tmp_file:
os.rename(new_file_name,file_name)
[docs]
def network_2_spreadsheet(ntwk: Network, file_name: str = None,
file_type: str = 'excel', form: str ='db', *args, **kwargs):
r"""
Write a Network object to a spreadsheet, for your boss.
Write the s-parameters of a network to a spreadsheet, in a variety
of forms. This functions makes use of the pandas module, which in
turn makes use of the xlrd module. These are imported during this
function call. For more details about the file-writing functions
see the `pandas.DataFrom.to_???` functions.
.. note::
The frequency unit used in the spreadsheet is take from
`ntwk.frequency.unit`
Parameters
----------
ntwk : :class:`~skrf.network.Network` object
the network to write
file_name : str, None
the file_name to write. if None, ntwk.name is used.
file_type : ['csv','excel','html']
the type of file to write. See `pandas.DataFrame.to_???` functions.
form : 'db','ma','ri'
format to write data,
* db = db, deg
* ma = mag, deg
* ri = real, imag
\*args, \*\*kwargs :
passed to `pandas.DataFrame.to_???` functions.
See Also
--------
networkset_2_spreadsheet : writes a spreadsheet for many networks
"""
file_extns = {'csv':'csv','excel':'xls','html':'html'}
form = form.lower()
if form not in ['db','ri','ma']:
raise ValueError('`form` must be either `db`,`ma`,`ri`')
file_type = file_type.lower()
if file_type not in file_extns.keys():
raise ValueError('file_type must be `csv`,`html`,`excel` ')
if ntwk.name is None and file_name is None:
raise ValueError('Either ntwk must have name or give a file_name')
if file_name is None and 'excel_writer' not in kwargs.keys():
file_name = ntwk.name + '.'+file_extns[file_type]
d = {}
index =ntwk.frequency.f
if form =='db':
for m,n in ntwk.port_tuples:
d[f'S{ntwk._fmt_trace_name(m,n)} Log Mag(dB)'] = \
Series(ntwk.s_db[:,m,n], index = index)
d[f'S{ntwk._fmt_trace_name(m,n)} Phase(deg)'] = \
Series(ntwk.s_deg[:,m,n], index = index)
elif form =='ma':
for m,n in ntwk.port_tuples:
d[f'S{ntwk._fmt_trace_name(m,n)} Mag(lin)'] = \
Series(ntwk.s_mag[:,m,n], index = index)
d[f'S{ntwk._fmt_trace_name(m,n)} Phase(deg)'] = \
Series(ntwk.s_deg[:,m,n], index = index)
elif form =='ri':
for m,n in ntwk.port_tuples:
d[f'S{ntwk._fmt_trace_name(m,n)} Real'] = \
Series(ntwk.s_re[:,m,n], index = index)
d[f'S{ntwk._fmt_trace_name(m,n)} Imag'] = \
Series(ntwk.s_im[:,m,n], index = index)
df = DataFrame(d)
df.__getattribute__('to_%s'%file_type)(file_name,
index_label='Freq(%s)'%ntwk.frequency.unit, **kwargs)
[docs]
def network_2_dataframe(ntwk: Network, attrs: list[str] =None,
ports: list[tuple[int, int]] = None, port_sep: str | None = None):
"""
Convert one or more attributes of a network to a pandas DataFrame.
Parameters
----------
ntwk : :class:`~skrf.network.Network` object
the network to write
attrs : list Network attributes
like ['s_db','s_deg']
ports : list of tuples
list of port pairs to write. defaults to ntwk.port_tuples
(like [(0,0)])
port_sep : string
defaults to None, which means a empty string "" is used for
networks with lower than 10 ports. (s_db 11, s_db 21)
For more than ten ports a "_" is used to avoid ambiguity.
(s_db 1_1, s_db 2_1)
Returns
-------
df : pandas DataFrame Object
"""
if attrs is None:
attrs = ["s_db"]
if ports is None:
ports = ntwk.port_tuples
if port_sep is None:
port_sep = "_" if ntwk.nports > 10 else ""
d = {}
for attr in attrs:
attr_array = getattr(ntwk, attr)
for m, n in ports:
d[f'{attr} {m+1}{port_sep}{n+1}'] = attr_array[:, m, n]
return DataFrame(d, index=ntwk.frequency.f)
[docs]
def networkset_2_spreadsheet(ntwkset: NetworkSet, file_name: str = None, file_type: str = 'excel',
*args, **kwargs):
r"""
Write a NetworkSet object to a spreadsheet, for your boss.
Write the s-parameters of a each network in the networkset to a
spreadsheet. If the `excel` file_type is used, then each network,
is written to its own sheet, with the sheetname taken from the
network `name` attribute.
This functions makes use of the pandas module, which in turn makes
use of the xlrd module. These are imported during this function.
.. note::
The frequency unit used in the spreadsheet is take from
`ntwk.frequency.unit`
Parameters
----------
ntwkset : :class:`~skrf.networkSet.NetworkSet` object
the network to write
file_name : str, None
the file_name to write. if None, ntwk.name is used.
file_type : ['csv','excel','html']
the type of file to write. See `pandas.DataFrame.to_???` functions.
form : 'db','ma','ri'
format to write data,
* db = db, deg
* ma = mag, deg
* ri = real, imag
\*args, \*\*kwargs :
passed to `pandas.DataFrame.to_???` functions.
See Also
--------
networkset_2_spreadsheet : writes a spreadsheet for many networks
"""
if ntwkset.name is None and file_name is None:
raise(ValueError('Either ntwkset must have name or give a file_name'))
if file_name is None:
file_name = ntwkset.name
if file_type == 'excel':
# add file extension if missing
if not file_name.endswith('.xlsx'):
file_name += '.xlsx'
with ExcelWriter(file_name) as writer:
[network_2_spreadsheet(k, writer, sheet_name=k.name, **kwargs) for k in ntwkset]
else:
[network_2_spreadsheet(k,*args, **kwargs) for k in ntwkset]
StringBuffer = StringIO
[docs]
class TouchstoneEncoder(json.JSONEncoder):
"""
Serializes Network object by converting arrays to lists,
splitting complex numbers into real and imaginary,
and breaking down frequency objects into dicts.
"""
[docs]
def default(self, obj):
if isinstance(obj, npy.ndarray):
return obj.tolist()
if isinstance(obj, complex):
return npy.real(obj), npy.imag(obj) # split into [real, im]
if isinstance(obj, Frequency):
return {'flist': obj.f_scaled.tolist(), 'funit': obj.unit}
return json.JSONEncoder.default(self, obj)
[docs]
def to_json_string(network):
"""
Dumps Network to JSON string. Faster than converting and saving as touchstone.
Safer than pickling (no arbitrary code execution on load).
:param network: :class:`~skrf.network.Network` object
A Network object to be serialized and returned as a JSON string.
:return: str
JSON string representation of a network object.
"""
return json.dumps(network.__dict__, cls=TouchstoneEncoder)
[docs]
def from_json_string(obj_string):
"""
Loads network object from JSON string representation.
:param obj_string: str
JSON string representation of a network object.
:return: :class:`~skrf.network.Network` object
A Network object, rebuilt from JSON.
"""
obj = json.loads(obj_string)
ntwk = Network()
ntwk.variables = obj['variables']
ntwk.name = obj['name']
ntwk.comments = obj['comments']
ntwk.port_names = obj['port_names']
ntwk.z0 = npy.array(obj['_z0'])[..., 0] + npy.array(obj['_z0'])[..., 1] * 1j # recreate complex numbers
ntwk.s = npy.array(obj['_s'])[..., 0] + npy.array(obj['_s'])[..., 1] * 1j
ntwk.frequency = Frequency.from_f(npy.array(obj['_frequency']['flist']),
unit=obj['_frequency']['funit'])
return ntwk