Commit 9245ec8da86ed48de1a7bb70cd322f2abfca8301
0 parents
Exists in
master
first commit
Showing 3 changed files with 650 additions and 0 deletions Inline Diff
README
File was created | 1 | Handle PNA-X, N522xA or N523x device from Keysight. Tested on N5234 device. |
n523x_utils/extract_peak.py
File was created | 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" |
n523x_utils/n523x.py
File was created | 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 | #============================================================================== |