Skip to content

Commit a7f0e8d

Browse files
committed
Major changes
1 parent abe42c5 commit a7f0e8d

14 files changed

Lines changed: 854 additions & 509 deletions

File tree

.github/workflows/tests.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Tests
2+
3+
on:
4+
push:
5+
pull_request:
6+
workflow_dispatch:
7+
8+
jobs:
9+
pytest:
10+
name: Python ${{ matrix.python-version }}
11+
runs-on: ubuntu-latest
12+
strategy:
13+
fail-fast: false
14+
matrix:
15+
python-version: ["3.10", "3.11", "3.12", "3.13"]
16+
17+
steps:
18+
- name: Check out repository
19+
uses: actions/checkout@v4
20+
21+
- name: Set up Python
22+
uses: actions/setup-python@v5
23+
with:
24+
python-version: ${{ matrix.python-version }}
25+
cache: pip
26+
27+
- name: Install package and test dependencies
28+
run: |
29+
python -m pip install --upgrade pip
30+
python -m pip install -e . pytest
31+
32+
- name: Run tests
33+
run: python -m pytest tests --strict-markers

README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
This package offers the serial interface for communication with an EIT device from ScioSpec. Commands can be written serially and the system response can be read out. With the current version, it is possible to start and stop measurements with defined burst counts and to read out the measurement data. In addition, the measurement data is packed into a data class for better further processing.
44

5-
**WIP** Communication with ISX-3
5+
The package supports Sciospec EIT-16/32/64/128 instruments and the ISX-3 / ISX-3mini communication protocol.
66

77
## Installation
88

@@ -45,9 +45,10 @@ conda activate sciopy
4545
pip install -e .
4646
```
4747

48-
49-
50-
48+
Running the tests
49+
```bash
50+
python -m pytest
51+
```
5152

5253
## Contact
5354

@@ -57,4 +58,4 @@ Email: jacob.thoenes@uni-rostock.de
5758

5859
___
5960

60-
- FTDI Driver installation: https://www.ftdichip.com/old2020/Drivers/D2XX.htm
61+
- FTDI Driver installation: https://www.ftdichip.com/old2020/Drivers/D2XX.htm

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
project = "sciopy"
99
author = "Jacob P. Thönes"
10-
release = "0.8.2.3"
10+
release = "0.9.1"
1111

1212
extensions = [
1313
"sphinx.ext.autodoc",

examples/ISX-3.ipynb

Lines changed: 106 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -2,142 +2,180 @@
22
"cells": [
33
{
44
"cell_type": "markdown",
5-
"id": "2727ee29",
5+
"id": "intro",
66
"metadata": {},
77
"source": [
8-
"# Example code for connecting the Sciospec ISX-3 device"
8+
"# ISX-3 / ISX-3mini example\n",
9+
"\n",
10+
"This notebook configures and measures one impedance spectrum using the USB full-speed serial interface. Connect the instrument, close the official Sciospec software, and select the correct serial port below. On Windows, make sure the USB driver described in the project README is installed."
911
]
1012
},
1113
{
1214
"cell_type": "code",
13-
"execution_count": 1,
14-
"id": "18bd09f0",
15+
"execution_count": null,
16+
"id": "imports",
1517
"metadata": {},
1618
"outputs": [],
1719
"source": [
1820
"import matplotlib.pyplot as plt\n",
19-
"import numpy as np\n",
21+
"from serial.tools import list_ports\n",
2022
"\n",
21-
"from sciopy import ISX_3, EisMeasurementSetup"
23+
"from sciopy import EisMeasurementSetup, ISX_3"
2224
]
2325
},
2426
{
25-
"cell_type": "code",
26-
"execution_count": 2,
27-
"id": "88778348",
27+
"cell_type": "markdown",
28+
"id": "port-help",
2829
"metadata": {},
29-
"outputs": [],
3030
"source": [
31-
"isx = ISX_3()"
31+
"## Select and connect the device\n",
32+
"\n",
33+
"Run the next cell to display available ports. Then set `PORT` to the ISX-3 port, for example `\"COM3\"` on Windows or `\"/dev/ttyUSB0\"` on Linux."
3234
]
3335
},
3436
{
3537
"cell_type": "code",
36-
"execution_count": 3,
37-
"id": "c21b7642",
38+
"execution_count": null,
39+
"id": "ports",
3840
"metadata": {},
39-
"outputs": [
40-
{
41-
"ename": "SerialException",
42-
"evalue": "[Errno 2] could not open port COM1: [Errno 2] No such file or directory: 'COM1'",
43-
"output_type": "error",
44-
"traceback": [
45-
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
46-
"\u001b[0;31mFileNotFoundError\u001b[0m Traceback (most recent call last)",
47-
"File \u001b[0;32m~/miniconda3/lib/python3.12/site-packages/serial/serialposix.py:322\u001b[0m, in \u001b[0;36mSerial.open\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 321\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 322\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mfd \u001b[38;5;241m=\u001b[39m \u001b[43mos\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mopen\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mportstr\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mos\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mO_RDWR\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m|\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mos\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mO_NOCTTY\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m|\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mos\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mO_NONBLOCK\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 323\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mOSError\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m msg:\n",
48-
"\u001b[0;31mFileNotFoundError\u001b[0m: [Errno 2] No such file or directory: 'COM1'",
49-
"\nDuring handling of the above exception, another exception occurred:\n",
50-
"\u001b[0;31mSerialException\u001b[0m Traceback (most recent call last)",
51-
"Cell \u001b[0;32mIn[3], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# connect to the device (adjust the port as necessary)\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m \u001b[43misx\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mconnect_device_USB2\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mCOM1\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n",
52-
"File \u001b[0;32m~/Schreibtisch/Uni/Forschung/PyPI_Packages/sciopy/sciopy/ISX_3.py:63\u001b[0m, in \u001b[0;36mISX_3.connect_device_USB2\u001b[0;34m(self, port, baudrate, timeout)\u001b[0m\n\u001b[1;32m 61\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 62\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mserial_protocol \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mUSB-FS\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m---> 63\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mdevice \u001b[38;5;241m=\u001b[39m \u001b[43mserial\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mSerial\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 64\u001b[0m \u001b[43m \u001b[49m\u001b[43mport\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mport\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 65\u001b[0m \u001b[43m \u001b[49m\u001b[43mbaudrate\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mbaudrate\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 66\u001b[0m \u001b[43m \u001b[49m\u001b[43mtimeout\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtimeout\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 67\u001b[0m \u001b[43m \u001b[49m\u001b[43mparity\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mserial\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mPARITY_NONE\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 68\u001b[0m \u001b[43m \u001b[49m\u001b[43mstopbits\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mserial\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mSTOPBITS_ONE\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 69\u001b[0m \u001b[43m \u001b[49m\u001b[43mbytesize\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mserial\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mEIGHTBITS\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 70\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 71\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mConnection to\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mdevice\u001b[38;5;241m.\u001b[39mname, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mis established.\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n",
53-
"File \u001b[0;32m~/miniconda3/lib/python3.12/site-packages/serial/serialutil.py:244\u001b[0m, in \u001b[0;36mSerialBase.__init__\u001b[0;34m(self, port, baudrate, bytesize, parity, stopbits, timeout, xonxoff, rtscts, write_timeout, dsrdtr, inter_byte_timeout, exclusive, **kwargs)\u001b[0m\n\u001b[1;32m 241\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124munexpected keyword arguments: \u001b[39m\u001b[38;5;132;01m{!r}\u001b[39;00m\u001b[38;5;124m'\u001b[39m\u001b[38;5;241m.\u001b[39mformat(kwargs))\n\u001b[1;32m 243\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m port \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[0;32m--> 244\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mopen\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n",
54-
"File \u001b[0;32m~/miniconda3/lib/python3.12/site-packages/serial/serialposix.py:325\u001b[0m, in \u001b[0;36mSerial.open\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 323\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mOSError\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m msg:\n\u001b[1;32m 324\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mfd \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[0;32m--> 325\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m SerialException(msg\u001b[38;5;241m.\u001b[39merrno, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mcould not open port \u001b[39m\u001b[38;5;132;01m{}\u001b[39;00m\u001b[38;5;124m: \u001b[39m\u001b[38;5;132;01m{}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;241m.\u001b[39mformat(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_port, msg))\n\u001b[1;32m 326\u001b[0m \u001b[38;5;66;03m#~ fcntl.fcntl(self.fd, fcntl.F_SETFL, 0) # set blocking\u001b[39;00m\n\u001b[1;32m 328\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mpipe_abort_read_r, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mpipe_abort_read_w \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m, \u001b[38;5;28;01mNone\u001b[39;00m\n",
55-
"\u001b[0;31mSerialException\u001b[0m: [Errno 2] could not open port COM1: [Errno 2] No such file or directory: 'COM1'"
56-
]
57-
}
58-
],
41+
"outputs": [],
5942
"source": [
60-
"# connect to the device (adjust the port as necessary)\n",
61-
"isx.connect_device_USB2(\"COM1\")"
43+
"ports = list(list_ports.comports())\n",
44+
"if ports:\n",
45+
" for port in ports:\n",
46+
" print(f\"{port.device}: {port.description}\")\n",
47+
"else:\n",
48+
" print(\"No serial ports found. Check the connection and USB driver.\")"
6249
]
6350
},
6451
{
6552
"cell_type": "code",
6653
"execution_count": null,
67-
"id": "fa54e5a6",
54+
"id": "connect",
6855
"metadata": {},
6956
"outputs": [],
7057
"source": [
71-
"# reset the system (clear previous settings)\n",
72-
"isx.ResetSystem()"
58+
"PORT = \"COM3\" # Change this to the port shown above.\n",
59+
"\n",
60+
"isx = ISX_3()\n",
61+
"isx.connect_device_USB2(PORT, timeout=1)\n",
62+
"print(f\"Connected to {PORT}\")"
63+
]
64+
},
65+
{
66+
"cell_type": "markdown",
67+
"id": "configuration-help",
68+
"metadata": {},
69+
"source": [
70+
"## Configure the sweep\n",
71+
"\n",
72+
"Amplitude is specified as peak amplitude in millivolts. The example uses a four-point measurement on the BNC port with automatic current ranging."
7373
]
7474
},
7575
{
7676
"cell_type": "code",
77-
"execution_count": 4,
78-
"id": "28c54b12",
77+
"execution_count": null,
78+
"id": "setup",
7979
"metadata": {},
8080
"outputs": [],
8181
"source": [
8282
"setup = EisMeasurementSetup(\n",
83-
" start=10,\n",
84-
" stop=100000,\n",
85-
" step=20,\n",
86-
" stepmode=\"log\",\n",
87-
" avg=1,\n",
88-
" amplitude=100,\n",
83+
" start=10, # Hz\n",
84+
" stop=100_000, # Hz\n",
85+
" step=20, # number of frequency points\n",
86+
" stepmode=\"log\", # \"lin\" or \"log\"\n",
87+
" avg=1, # spectra acquired by StartMeasure()\n",
88+
" amplitude=100, # mV peak amplitude\n",
8989
" precision=1,\n",
90-
" measurement_time=1,\n",
91-
")"
90+
" measurement_time=1, # reserved for API compatibility\n",
91+
")\n",
92+
"\n",
93+
"isx.SetMeasurementSetup(setup)\n",
94+
"isx.ClearFE_Settings()\n",
95+
"isx.SetFE_Settings(\n",
96+
" PP=0x02, # four-point configuration\n",
97+
" CH=0x01, # BNC port / ISX-3mini port 1\n",
98+
" RA=0x00, # automatic current range\n",
99+
")\n",
100+
"\n",
101+
"print(\"Frontend:\", isx.GetFE_Settings())\n",
102+
"print(\"Frequencies [Hz]:\", isx.GetSetup())"
92103
]
93104
},
94105
{
95-
"cell_type": "code",
96-
"execution_count": null,
97-
"id": "e8cf6f6b",
106+
"cell_type": "markdown",
107+
"id": "measurement-help",
98108
"metadata": {},
99-
"outputs": [],
100109
"source": [
101-
"isx.SetMeasurementSetup(setup)"
110+
"## Measure and plot one spectrum\n",
111+
"\n",
112+
"`StartMeasure()` uses `setup.avg` when no spectrum count is passed explicitly. Each result contains the frequency-row ID and the complex impedance in ohms."
102113
]
103114
},
104115
{
105116
"cell_type": "code",
106117
"execution_count": null,
107-
"id": "2a8c956e",
118+
"id": "measure",
108119
"metadata": {},
109120
"outputs": [],
110121
"source": [
111-
"# disconnect device from serial port\n",
112-
"isx.disconnect_device_USB2()"
122+
"points = isx.StartMeasure()\n",
123+
"if not points:\n",
124+
" raise RuntimeError(\n",
125+
" \"No measurement points received. Check the frontend, sample, and serial timeout.\"\n",
126+
" )\n",
127+
"\n",
128+
"row_ids = [point.frequency_id for point in points]\n",
129+
"impedances = [point.impedance for point in points]\n",
130+
"\n",
131+
"for point in points:\n",
132+
" print(\n",
133+
" f\"row={point.frequency_id:3d}, \"\n",
134+
" f\"Z={point.impedance.real:.6g} {point.impedance.imag:+.6g}j ohm\"\n",
135+
" )\n",
136+
"\n",
137+
"fig, ax = plt.subplots()\n",
138+
"ax.plot(row_ids, [abs(value) for value in impedances], \"o-\")\n",
139+
"ax.set(\n",
140+
" xlabel=\"Frequency-row ID\",\n",
141+
" ylabel=\"|Z| [ohm]\",\n",
142+
" title=\"ISX-3 impedance spectrum\",\n",
143+
")\n",
144+
"ax.grid(True)\n",
145+
"plt.show()"
146+
]
147+
},
148+
{
149+
"cell_type": "markdown",
150+
"id": "disconnect-help",
151+
"metadata": {},
152+
"source": [
153+
"## Disconnect\n",
154+
"\n",
155+
"Always close the connection before reconnecting with this notebook or the official software."
113156
]
114157
},
115158
{
116159
"cell_type": "code",
117160
"execution_count": null,
118-
"id": "1cf1c447",
161+
"id": "disconnect",
119162
"metadata": {},
120163
"outputs": [],
121-
"source": []
164+
"source": [
165+
"isx.disconnect_device_USB2()\n",
166+
"print(\"Disconnected\")"
167+
]
122168
}
123169
],
124170
"metadata": {
125171
"kernelspec": {
126-
"display_name": "base",
172+
"display_name": "Python 3",
127173
"language": "python",
128174
"name": "python3"
129175
},
130176
"language_info": {
131-
"codemirror_mode": {
132-
"name": "ipython",
133-
"version": 3
134-
},
135-
"file_extension": ".py",
136-
"mimetype": "text/x-python",
137177
"name": "python",
138-
"nbconvert_exporter": "python",
139-
"pygments_lexer": "ipython3",
140-
"version": "3.12.2"
178+
"version": "3.10"
141179
}
142180
},
143181
"nbformat": 4,

0 commit comments

Comments
 (0)