Commit 9245ec8da86ed48de1a7bb70cd322f2abfca8301

Authored by bdubois
0 parents
Exists in master

first commit

Showing 3 changed files with 650 additions and 0 deletions Side-by-side Diff

... ... @@ -0,0 +1 @@
  1 +Handle PNA-X, N522xA or N523x device from Keysight. Tested on N5234 device.
n523x_utils/extract_peak.py
... ... @@ -0,0 +1,286 @@
  1 +#!/usr/bin/python3
  2 +# -*- coding: utf-8 -*-
  3 +
  4 +"""package n523xA_utils
  5 +author Benoit Dubois
  6 +copyright FEMTO ENGINEERING
  7 +license GPL v3.0+
  8 +brief Acquire data trace from N5234A or N5230A device.
  9 + Try to detrend trace in order to extract mode shape.
  10 +"""
  11 +
  12 +# Ctrl-c closes the application
  13 +import signal
  14 +signal.signal(signal.SIGINT, signal.SIG_DFL)
  15 +
  16 +import sys
  17 +import os.path as path
  18 +import logging
  19 +from pyqtgraph.parametertree import Parameter, ParameterTree
  20 +import pyqtgraph as pg
  21 +import numpy as np
  22 +import numpy.polynomial.polynomial as poly
  23 +import scipy.signal as scs
  24 +from PyQt4.QtCore import pyqtSlot, pyqtSignal, QDir, QFileInfo
  25 +from PyQt4.QtGui import QApplication, QMainWindow, QWidget, QVBoxLayout, \
  26 + QHBoxLayout, QMessageBox, QFileDialog
  27 +
  28 +import n523x as vna
  29 +
  30 +CONSOLE_LOG_LEVEL = logging.DEBUG
  31 +FILE_LOG_LEVEL = logging.WARNING
  32 +
  33 +APP_NAME = "ExtractPeak"
  34 +
  35 +#===============================================================================
  36 +class MyVna(vna.N523x):
  37 +
  38 + def connect(self, try_=3):
  39 + """Overloaded vna.Vna method beacause, VNA seems to refuse connection
  40 + without reasons.
  41 + :param try_: number of connection attempt (int).
  42 + """
  43 + for i in range(try_):
  44 + if super().connect() is True:
  45 + break
  46 +
  47 +#===============================================================================
  48 +PARAMS = [
  49 + {'name': 'Load data', 'type': 'group', 'children': [
  50 + {'name': 'VNA', 'type': 'group', 'children': [
  51 + {'name': 'IP', 'type': 'str', 'value': vna.DEFAULT_IP},
  52 + {'name': 'Port', 'type': 'int', 'value': vna.DEFAULT_PORT},
  53 + {'name': 'Acquisition', 'type': 'action'},
  54 + ]},
  55 + {'name': 'File', 'type': 'group', 'children': [
  56 + {'name': 'Filename', 'type': 'str'},
  57 + {'name': 'Open', 'type': 'action'},
  58 + ]},
  59 + ]},
  60 + {'name': 'Fit', 'type': 'group', 'children': [
  61 + {'name': 'Order', 'type': 'int', 'value': 3, 'limits': (1, 15)},
  62 + {'name': 'Filtering', 'type': 'bool', 'value': False},
  63 + {'name': 'Run fit', 'type': 'action'},
  64 + ]},
  65 + {'name': 'Plot', 'type': 'group', 'children': []},
  66 +]
  67 +
  68 +class PeakUi(QMainWindow):
  69 + """Ui of extract peak application.
  70 + """
  71 +
  72 + def __init__(self):
  73 + """Constructor.
  74 + :returns: None
  75 + """
  76 + super().__init__()
  77 + self.setWindowTitle("Fit Peak")
  78 + self.setCentralWidget(self._central_widget())
  79 + self.region = pg.LinearRegionItem()
  80 + self.region.setZValue(10)
  81 + # Add the LinearRegionItem to the ViewBox, but tell the ViewBox to
  82 + # exclude this item when doing auto-range calculations.
  83 + self.mplot1.addItem(self.region, ignoreBounds=True)
  84 +
  85 + def _central_widget(self):
  86 + """Generates central widget.
  87 + :returns: central widget of UI (QWidget)
  88 + """
  89 + self.p = Parameter.create(name='params', type='group', children=PARAMS)
  90 + self.ptree = ParameterTree()
  91 + self.ptree.setParameters(self.p, showTop=False)
  92 + self.mplot1 = pg.PlotWidget()
  93 + self.mplot2 = pg.PlotWidget()
  94 + plot_lay = QVBoxLayout()
  95 + plot_lay.addWidget(self.mplot1)
  96 + plot_lay.addWidget(self.mplot2)
  97 + main_lay = QHBoxLayout()
  98 + main_lay.addWidget(self.ptree)
  99 + main_lay.addLayout(plot_lay)
  100 + main_lay.setStretchFactor(plot_lay, 2)
  101 + central_widget = QWidget()
  102 + central_widget.setLayout(main_lay)
  103 + return central_widget
  104 +
  105 +#===============================================================================
  106 +class PeakApp(QApplication):
  107 + """Peak application.
  108 + """
  109 +
  110 + # Emit data key value
  111 + acquire_done = pyqtSignal()
  112 + fit_done = pyqtSignal(object, object)
  113 +
  114 + def __init__(self, args):
  115 + """Constructor.
  116 + :returns: None
  117 + """
  118 + super().__init__(args)
  119 + self._ui = PeakUi()
  120 + self._data = None
  121 + self._cdata_name = None
  122 + self._dplot = None # Data plot
  123 + self._fdplot = None # Fitted data plot
  124 + self._pplot = None # (Extracted) Peak plot
  125 + self._ui.p.param(
  126 + 'Load data', 'VNA', 'Acquisition').sigActivated.connect(
  127 + self.acquire_data_from_device)
  128 + self._ui.p.param('Load data', 'File', 'Open').sigActivated.connect(
  129 + self.acquire_data_from_file)
  130 + self._ui.p.param('Fit', 'Run fit').sigActivated.connect(
  131 + lambda: self.fit_peak(self._ui.p.param('Fit', 'Order').value(),
  132 + self._ui.p.param('Fit', 'Filtering').value()))
  133 + self.acquire_done.connect(self.handle_acq_data)
  134 + self.fit_done.connect(self.display_fit)
  135 + self._ui.show()
  136 +
  137 + @pyqtSlot()
  138 + def acquire_data_from_device(self):
  139 + """Acquire data process.
  140 + :returns: None
  141 + """
  142 + ip = self._ui.p.param('Load data', 'VNA', 'IP').value()
  143 + port = self._ui.p.param('Load data', 'VNA', 'Port').value()
  144 + # Acquisition itself
  145 + dev = MyVna(ip, port)
  146 + dev.connect()
  147 + dev.write("FORMAT:DATA REAL,64")
  148 + try:
  149 + datas = dev.get_measurements()
  150 + except Exception as ex:
  151 + logging.error("Problem during acquisition: %s", str(ex))
  152 + QMessageBox.warning(self._ui, "Acquisition problem",
  153 + "Problem during acquisition: {}".format(ex),
  154 + QMessageBox.Ok)
  155 + return
  156 + self._data = {dev.measurement_number_to_name(idx+1):
  157 + data for idx, data in enumerate(datas)}
  158 + self.acquire_done.emit()
  159 +
  160 + @pyqtSlot()
  161 + def acquire_data_from_file(self):
  162 + filename = QFileDialog().getOpenFileName(
  163 + parent=None,
  164 + caption="Choose .s2p file to load",
  165 + directory=QDir.currentPath(),
  166 + filter="s2p files (*.s2p);;Any files (*)")
  167 + if filename == '':
  168 + return
  169 + try:
  170 + data = np.transpose(np.loadtxt(filename, comments=('!', '#')))
  171 + except Exception as ex:
  172 + logging.error("Problem when reading file: %s", str(ex))
  173 + QMessageBox.warning(self._ui, "Acquisition problem",
  174 + "Problem when reading file: {}".format(ex),
  175 + QMessageBox.Ok)
  176 + return
  177 + self._data = {QFileInfo(filename).baseName(): data}
  178 + self.acquire_done.emit()
  179 +
  180 + @pyqtSlot()
  181 + def handle_acq_data(self):
  182 + self._ui.p.param('Plot').clearChildren()
  183 + for name_, data in self._data.items():
  184 + self._ui.p.param('Plot').addChild(
  185 + Parameter.create(name=name_, type='action'))
  186 + for child in self._ui.p.param('Plot').children():
  187 + child.sigActivated.connect(self.set_cdata)
  188 + self.set_cdata(self._ui.p.param('Plot').children()[0])
  189 +
  190 + @pyqtSlot(object)
  191 + def set_cdata(self, param):
  192 + """
  193 + :param param: Parameter object
  194 + """
  195 + self._cdata_name = param.name()
  196 + self.display_data()
  197 +
  198 + @pyqtSlot()
  199 + def display_data(self):
  200 + if self._dplot is not None:
  201 + self._ui.mplot1.removeItem(self._dplot)
  202 + if self._fdplot is not None:
  203 + self._ui.mplot1.removeItem(self._fdplot)
  204 + if self._pplot is not None:
  205 + self._ui.mplot2.removeItem(self._pplot)
  206 + self._dplot = self._ui.mplot1.plot(
  207 + self._data[self._cdata_name][0, :],
  208 + self._data[self._cdata_name][1, :], pen="w")
  209 + self._ui.region.setRegion([self._data[self._cdata_name][0, 0],
  210 + self._data[self._cdata_name][0, -1]])
  211 +
  212 + @pyqtSlot(int, bool)
  213 + def fit_peak(self, order, filter_=False):
  214 + name_ = self._cdata_name
  215 + x_min, x_max = self._ui.region.getRegion()
  216 + x_min_id = np.where(self._data[name_][0, :] >= x_min)[0][0]
  217 + x_max_id = np.where(self._data[name_][0, :] <= x_max)[0][-1]
  218 + x_n = np.concatenate([self._data[name_][0, 1:x_min_id],
  219 + self._data[name_][0, x_max_id:-1]])
  220 + # Suppress mean to get better fitting:
  221 + x_n0 = x_n - self._data[name_][0, :].mean()
  222 + y_n = np.concatenate([self._data[name_][1, 1:x_min_id],
  223 + self._data[name_][1, x_max_id:-1]])
  224 + try:
  225 + coefs = poly.polyfit(x_n0, y_n, order)
  226 + except TypeError: # when user has not selected region to exclude
  227 + QMessageBox.information(self._ui,
  228 + "ROI omited",
  229 + "Select region to exclude before fit")
  230 + return
  231 + xfit = self._data[name_][0, :] - self._data[name_][0, :].mean()
  232 + yfit = poly.polyval(xfit, coefs)
  233 + res = self._data[name_][1, :] - yfit
  234 + if filter_ is True:
  235 + b, a = scs.butter(3, 0.005)
  236 + res = scs.filtfilt(b, a, res, padlen=150)
  237 + self.fit_done.emit(yfit, res)
  238 +
  239 + @pyqtSlot(object, object)
  240 + def display_fit(self, yfit, res):
  241 + name_ = self._cdata_name
  242 + if self._fdplot is not None:
  243 + self._ui.mplot1.removeItem(self._fdplot)
  244 + if self._pplot is not None:
  245 + self._ui.mplot2.removeItem(self._pplot)
  246 + self._fdplot = self._ui.mplot1.plot(
  247 + self._data[name_][0, :], yfit, pen='g')
  248 + self._pplot = self._ui.mplot2.plot(
  249 + self._data[name_][0, :]-self._data[name_][0, :].mean(),
  250 + res, pen='g')
  251 +
  252 +#==============================================================================
  253 +def configure_logging():
  254 + """Configures logs.
  255 + """
  256 + home = path.expanduser("~")
  257 + log_file = "." + APP_NAME + ".log"
  258 + abs_log_file = path.join(home, log_file)
  259 + date_fmt = "%d/%m/%Y %H:%M:%S"
  260 + log_format = "%(asctime)s %(levelname) -8s %(filename)s " + \
  261 + " %(funcName)s (%(lineno)d): %(message)s"
  262 + logging.basicConfig(level=FILE_LOG_LEVEL, \
  263 + datefmt=date_fmt, \
  264 + format=log_format, \
  265 + filename=abs_log_file, \
  266 + filemode='w')
  267 + console = logging.StreamHandler()
  268 + # define a Handler which writes messages to the sys.stderr
  269 + console.setLevel(CONSOLE_LOG_LEVEL)
  270 + # set a format which is simpler for console use
  271 + console_format = '%(levelname) -8s %(filename)s (%(lineno)d): %(message)s'
  272 + formatter = logging.Formatter(console_format)
  273 + # tell the handler to use this format
  274 + console.setFormatter(formatter)
  275 + # add the handler to the root logger
  276 + logging.getLogger('').addHandler(console)
  277 +
  278 +#==============================================================================
  279 +def main():
  280 + configure_logging()
  281 + app = PeakApp(sys.argv)
  282 + sys.exit(app.exec_())
  283 +
  284 +#==============================================================================
  285 +if __name__ == '__main__':
  286 + main()
n523x_utils/n523x.py
... ... @@ -0,0 +1,363 @@
  1 +# -*- coding: utf-8 -*-
  2 +
  3 +"""package n523x_utils
  4 +author Benoit Dubois
  5 +copyright FEMTO ENGINEERING
  6 +license GPL v3.0+
  7 +brief Handle PNA-X, N522xA or N523x device from Keysight.
  8 + Tested on N5234 device.
  9 +"""
  10 +
  11 +import time
  12 +import socket
  13 +import struct
  14 +import logging
  15 +import numpy as np
  16 +
  17 +DEFAULT_IP = "192.168.0.11"
  18 +DEFAULT_PORT = 5025
  19 +
  20 +#==============================================================================
  21 +class N523x(object):
  22 + """Handle PNA-X, N522xA or N523x device from Keysight.
  23 + """
  24 +
  25 + IFBW = (1, 2, 3, 5, 7, 10, 15, 20, 30, 50, 70, 100, 150, 200, 300, 500, 700,
  26 + 1e3, 1.5e3, 2e3, 3e3, 5e3, 7e3, 10e3, 15e3, 20e3, 30e3, 50e3, 70e3,
  27 + 100e3, 150e3, 200e3, 280e3, 360e3, 600e3)
  28 +
  29 + def __init__(self, ip, port=DEFAULT_PORT, timeout=1.0):
  30 + """Constructor.
  31 + :param ip: IP address of device (str)
  32 + :param port: Ethernet port of device (int)
  33 + :param timeout: Timeout on connection instance (float)
  34 + :returns: None
  35 + """
  36 + self.ip = ip
  37 + self.port = port
  38 + self._timeout = timeout
  39 + self._sock = None
  40 +
  41 + def connect(self):
  42 + """Connect to device.
  43 + """
  44 + try:
  45 + self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  46 + self._sock.settimeout(self._timeout)
  47 + self._sock.connect((self.ip, self.port))
  48 + except ValueError as ex:
  49 + logging.error("Connection parameters out of range: %s", str(ex))
  50 + return False
  51 + except socket.timeout:
  52 + logging.error("Timeout on connection")
  53 + return False
  54 + except Exception as ex:
  55 + logging.error("Unexpected exception during connection with " + \
  56 + "VNA: %s", str(ex))
  57 + return False
  58 + else:
  59 + logging.debug("Connected to VNA")
  60 + return True
  61 +
  62 + def write(self, data):
  63 + """"Ethernet writing process.
  64 + :param data: data writes to device (str)
  65 + :returns: None
  66 + """
  67 + try:
  68 + self._sock.send((data + "\n").encode('utf-8'))
  69 + except socket.timeout:
  70 + logging.error("Timeout")
  71 + except Exception as ex:
  72 + logging.error(str(ex))
  73 + logging.debug("write " + data.strip('\n'))
  74 +
  75 + def read(self, length=100):
  76 + """Read process.
  77 + :param length: length of message to read (int)
  78 + :returns: Message reads from device (str)
  79 + """
  80 + try:
  81 + retval = self._sock.recv(length).decode('utf-8').strip('\n')
  82 + except socket.timeout:
  83 + logging.error("Timeout")
  84 + return ''
  85 + except Exception as ex:
  86 + logging.error(str(ex))
  87 + raise ex
  88 + logging.debug("read: " + retval)
  89 + return retval
  90 +
  91 + def raw_read(self, length=512):
  92 + """Raw read process.
  93 + :param length: length of message to read (int)
  94 + :returns: Message reads from device (bytes)
  95 + """
  96 + try:
  97 + data = self._sock.recv(length)
  98 + except socket.timeout:
  99 + logging.error("Timeout")
  100 + return bytes()
  101 + except Exception as ex:
  102 + logging.error(str(ex))
  103 + raise ex
  104 + return data
  105 +
  106 + def bin_read(self):
  107 + """Read binary data then decode them to ascii.
  108 + The reading process is specific to the transfert of binary data
  109 + with these VNA devices: <header><data><EOT>, with:
  110 + - <header>: #|lenght of bytes_count (one byte)|bytes_count
  111 + - <data>: "REAL,64" (float64) binary data
  112 + - <EOT>: '\n' character.
  113 + Note: The data transfert format must be selected to "REAL,64"" before
  114 + using this method
  115 + """
  116 + header_max_length = 11
  117 + raw_data = self.raw_read(header_max_length)
  118 + if raw_data.find(b'#') != 0:
  119 + logging.error("Data header not valid")
  120 + return
  121 + byte_count_nb = int(raw_data[1:2])
  122 + byte_count = int(raw_data[2:2+byte_count_nb])
  123 + # Note : Read 'byte_count' bytes but only
  124 + # 2 + byte_count_nb + byte_count - header_max_length
  125 + # needs to be readen.
  126 + # This tip can be used because EOF ('\n') is transmited at the end of
  127 + # the message and thus stop reception of data.
  128 + while len(raw_data) < byte_count:
  129 + raw_data += self.raw_read(byte_count)
  130 + nb_elem = int(byte_count / 8)
  131 + data = np.asarray(struct.unpack(">{:d}d".format(nb_elem),
  132 + raw_data[2+byte_count_nb:-1]))
  133 + return data
  134 +
  135 + def query(self, msg, length=100):
  136 + """Basic query process: write query then read response.
  137 + """
  138 + self.write(msg)
  139 + return self.read(length)
  140 +
  141 + def reset(self):
  142 + """Reset device.
  143 + """
  144 + self.write("*RST")
  145 +
  146 + @property
  147 + def id(self):
  148 + """Return ID of device.
  149 + """
  150 + return self.query("*IDN?")
  151 +
  152 + def get_span(self, cnum=1):
  153 + return self.query("SENS{}:FOM:RANG:SEGM:FREQ:SPAN?".format(cnum))
  154 +
  155 + def set_span(self, value, cnum=1):
  156 + self.write("SENS{}:FREQ:SPAN {}".format(cnum, value))
  157 +
  158 + def get_start(self, cnum=1):
  159 + return self.query("SENS{}:FREQ:START?".format(cnum))
  160 +
  161 + def set_start(self, value, cnum=1):
  162 + self.write("SENS{}:FREQ:START {}".format(cnum, value))
  163 +
  164 + def get_stop(self, cnum=1):
  165 + return self.query("SENS{}:FREQ:STOP?".format(cnum))
  166 +
  167 + def set_stop(self, value, cnum=1):
  168 + self.write("SENS{}:FREQ:STOP {}".format(cnum, value))
  169 +
  170 + def get_center_freq(self, cnum=1):
  171 + return self.query("SENS{}:FREQ:CENT?".format(cnum))
  172 +
  173 + def set_center_freq(self, value, cnum=1):
  174 + self.write("SENS{}:FREQ:CENT {}".format(cnum, value))
  175 +
  176 + def get_points(self, cnum=1):
  177 + return self.query("SENS{}:SWE:POINTS?".format(cnum))
  178 +
  179 + def set_points(self, value, cnum=1):
  180 + self.write("SENS{}:SWE:POINTS {}".format(cnum, value))
  181 +
  182 + def get_sweep_type(self, cnum=1):
  183 + return self.query("SENS{}:SWE:TYPE?".format(cnum))
  184 +
  185 + def set_sweep_type(self, value, cnum=1):
  186 + self.write("SENS{}:SWE:TYPE {}".format(cnum, value))
  187 +
  188 + @staticmethod
  189 + def read_s2p(filename):
  190 + return np.loadtxt(filename, comments=['!', '#'])
  191 +
  192 + def get_window_numbers(self):
  193 + """Return the number of existing windows.
  194 + """
  195 + data = self.query("DISP:CAT?")
  196 + if data is None:
  197 + return []
  198 + return [int(x) for x in data.replace('\"', '').split(',')]
  199 +
  200 + def get_measurement_catalog(self, channel=''):
  201 + """Returns ALL measurement numbers, or measurement numbers
  202 + from a specified channel
  203 + :param channel: Channel number to catalog. If not specified,
  204 + all measurement numbers are returned.
  205 + :returns: ALL measurement numbers, or measurement numbers
  206 + from a specified channel
  207 + """
  208 + data = self.query("SYST:MEAS:CAT? {}".format(channel))
  209 + if data is None:
  210 + return []
  211 + return [int(x) for x in data.replace('\"', '').split(',')]
  212 +
  213 + def measurement_number_to_trace(self, nb=None):
  214 + """Returns the trace number of the specified measurement number.
  215 + Trace numbers restart for each window while measurement numbers are
  216 + always unique.
  217 + :param n: Measurement number for which to return the trace number.
  218 + If unspecified, value is set to 1.
  219 + """
  220 + return self.query("SYST:MEAS{}:TRAC?".format(nb))
  221 +
  222 + def measurement_number_to_name(self, nb=None):
  223 + """Returns the name of the specified measurement.
  224 + :param n: Measurement number for which to return the measurement name.
  225 + If unspecified, value is set to 1.
  226 + """
  227 + return self.query("SYST:MEAS{}:NAME?".format(nb)).replace('\"', '')
  228 +
  229 + def set_measurement(self, name, fast=True):
  230 + """ Sets the selected measurement. Most CALC: commands require that
  231 + this command be sent before a setting change is made. One measurement
  232 + on each channel can be selected at the same time.
  233 + :param name: Name of the measurement. CASE-SENSITIVE. Do NOT include
  234 + the parameter name that is returned with Calc:Par:Cat?
  235 + :param fast: Optional. The PNA display is NOT updated. Therefore,
  236 + do not use this argument when an operator is using the
  237 + PNA display. Otherwise, sending this argument results
  238 + in much faster sweep speeds. There is NO other reason
  239 + to NOT send this argument.
  240 + """
  241 + if name is None:
  242 + logging.error("Requiered name parameter")
  243 + raise ValueError("Requiered name parameter")
  244 + cnum = int(name[2])
  245 + self.write("CALC{}:PAR:SEL {}{}"
  246 + .format(cnum, name, ",fast" if fast is True else None))
  247 +
  248 + def get_measurement(self, name):
  249 + """Get a data measurement.
  250 + Note that VNA must be configured to transfer data in float 64 format
  251 + before using the method.
  252 + :param name: Name of the measurement. CASE-SENSITIVE. Do NOT include
  253 + the parameter name that is returned with Calc:Par:Cat?
  254 + :return: Array of measurement data.
  255 + """
  256 + cnum = int(name[2])
  257 + self.set_measurement(name)
  258 + if self.get_sweep_type() != "LIN":
  259 + raise NotImplementedError
  260 + datax = np.linspace(float(self.get_start(cnum)),
  261 + float(self.get_stop(cnum)),
  262 + int(self.get_points(cnum)))
  263 + self.write("CALC{}:DATA? FDATA".format(cnum))
  264 + datay = self.bin_read()
  265 + retval = np.asarray([datax, datay])
  266 + return retval
  267 +
  268 + def get_snp(self, cnum=1):
  269 + """Get snp data.
  270 + :param cnum: Channel number of the measurement. There must be a selected
  271 + measurement on that channel. If unspecified, <cnum> is set to 1.
  272 + :return: Array of data.
  273 + """
  274 + self.write("CALC{}:DATA:SNP:PORT? \"1,2\"".format(cnum))
  275 + return np.asarray(self.bin_read()).reshape(9, -1)
  276 +
  277 + def get_measurements(self):
  278 + """Find current measurements, get data then prepare a list of array
  279 + [freq, data] for each measurement.
  280 + :returns: list of measurements
  281 + """
  282 + datas = []
  283 + meas_nb = self.get_measurement_catalog()
  284 + for nb in meas_nb:
  285 + name = self.measurement_number_to_name(nb)
  286 + datas.append(self.get_measurement(name))
  287 + return datas
  288 +
  289 + def get_snps(self):
  290 + """Get all snp traces.
  291 + """
  292 + meas_nb = self.get_measurement_catalog()
  293 + datas = []
  294 + for measurement in meas_nb:
  295 + datas.append(self.get_snp(measurement))
  296 + return datas
  297 +
  298 + def write_snps(self, filename=None):
  299 + """Write all snp traces.
  300 + """
  301 + if filename is None:
  302 + filename = time.strftime("%Y%m%d-%H%M%S", time.gmtime())
  303 + meas_nb = self.get_measurement_catalog()
  304 + for measurement in meas_nb:
  305 + data = np.asarray(self.get_snp(measurement)).reshape(9, -1)
  306 + np.savetxt(filename + '_' + str(measurement) + '.s2p',
  307 + data,
  308 + delimiter='\t',
  309 + header="#f(Hz)\tReal(S11)\tImag(S11)" +
  310 + "\tReal(S21)\tImag(S21)" +
  311 + "\tReal(S12)\tImag(S12)" +
  312 + "\tReal(S22)\tImag(S22)")
  313 +
  314 +
  315 +#==============================================================================
  316 +def main():
  317 + """Main of test program
  318 + :returns: None
  319 + """
  320 + import matplotlib.pyplot as plt
  321 +
  322 + acp_type = 'data' # 'data' or 'snp'
  323 +
  324 + dev = N523x(DEFAULT_IP, timeout=0.5)
  325 +
  326 + if dev.connect() is False:
  327 + print("Connection error")
  328 + return
  329 +
  330 + print("Connected to", dev.id)
  331 +
  332 + dev.write("FORMAT:DATA REAL,64")
  333 +
  334 + if acp_type == 'snp':
  335 + dev.write("MMEM:STOR:TRAC:FORM:SNP MA")
  336 + datas = dev.get_snp()
  337 + plt.figure(1)
  338 + nb_rows = datas.shape[0]
  339 + for idx, data in enumerate(datas):
  340 + plt.subplot(nb_rows*100+10+idx)
  341 + plt.plot(datas[0], data, 'b')
  342 + plt.show()
  343 +
  344 + else:
  345 + datas = dev.get_measurements()
  346 + plt.figure(1)
  347 + nb_rows = len(datas)
  348 + for idx, data in enumerate(datas):
  349 + plt.subplot(nb_rows*100+10+idx+1)
  350 + plt.ylabel(dev.measurement_number_to_name(idx+1))
  351 + try:
  352 + plt.plot(data[0], data[1], 'b', )
  353 + except Exception:
  354 + pass
  355 + plt.show()
  356 +
  357 +#==============================================================================
  358 +if __name__ == '__main__':
  359 + CONSOLE_LOG_LEVEL = logging.DEBUG
  360 + CONSOLE_FORMAT = '%(levelname) -8s %(filename)s (%(lineno)d): %(message)s'
  361 + logging.basicConfig(format=CONSOLE_FORMAT, level=CONSOLE_LOG_LEVEL)
  362 +
  363 + main()