From a1af997ae529accee29a4d475d4ca70e9af1e75c Mon Sep 17 00:00:00 2001 From: mc-so Date: Tue, 12 Dec 2023 19:29:51 +0800 Subject: [PATCH 1/4] Added CN0565 Support Signed-off-by: Michelle Claire So --- adi/__init__.py | 1 + adi/cn0565.py | 120 +++++++++++++++++++++++++++++++++++++++++++++ supported_parts.md | 1 + 3 files changed, 122 insertions(+) create mode 100644 adi/cn0565.py diff --git a/adi/__init__.py b/adi/__init__.py index 3fe376fbd..57ead3e42 100644 --- a/adi/__init__.py +++ b/adi/__init__.py @@ -77,6 +77,7 @@ from adi.cn0511 import cn0511 from adi.cn0532 import cn0532 from adi.cn0554 import cn0554 +from adi.cn0565 import cn0565 from adi.cn0566 import CN0566 from adi.cn0575 import cn0575 from adi.cn0579 import cn0579 diff --git a/adi/cn0565.py b/adi/cn0565.py new file mode 100644 index 000000000..5486518c4 --- /dev/null +++ b/adi/cn0565.py @@ -0,0 +1,120 @@ +# Copyright (C) 2023-2024 Analog Devices, Inc. +# +# SPDX short identifier: ADIBSD +# Author: Ivan Gil Mercano + +import numpy as np +from adi.ad5940 import ad5940 +from adi.adg2128 import adg2128 +from adi.context_manager import context_manager + + +class cn0565(ad5940, adg2128, context_manager): + + """The CN0565 class inherits features from both the AD5940 (providing high + precision in impedance and electrochemical frontend) and the ADG2128 + (enabling arbitrary assignment of force and sense electrodes). ' + These combined functionalities are utilized for Electrical + Impedance Tomography. + + parameters: + uri: type=string + URI of the platform + """ + + _device_name = "cn0565" + + def __init__(self, uri=""): + context_manager.__init__(self, uri, self._device_name) + ad5940.__init__(self) + adg2128.__init__(self) + self._switch_sequence = None + self.add(0x71) + self.add(0x70) + self._electrode_count = 8 + self._force_distance = 1 + self._sense_distance = 1 + self.excitation_frequency = 10000 + + @property + def electrode_count(self): + """electrode_count: Number of electrodes""" + return self._electrode_count + + @electrode_count.setter + def electrode_count(self, value): + self._electrode_count = value + + @property + def force_distance(self): + """force_distance: Number of electrodes between forcing electrodes. 1 means they are adjacent""" + return self._force_distance + + @force_distance.setter + def force_distance(self, value): + self._force_distance = value + + @property + def sense_distance(self): + """sense_distance: Number of electrodes between sensing electrodes. 1 means they are adjacent""" + return self._sense_distance + + @sense_distance.setter + def sense_distance(self, value): + self._sense_distance = value + + @property + def switch_sequence(self): + """switch_sequence: type=np.array + Sequence of combinations of forcing electrodes and sensing electrodes in the form of + f+, s+, s-, s+ + """ + seq = 0 + ret = [] + for i in range(self.electrode_count): + f_plus = i + f_minus = (i + self.force_distance) % self.electrode_count + for j in range(self.electrode_count): + s_plus = j % self.electrode_count + if s_plus == f_plus or s_plus == f_minus: + continue + s_minus = (s_plus + self.sense_distance) % self.electrode_count + if s_minus == f_plus or s_minus == f_minus: + continue + ret.append((f_plus, s_plus, s_minus, f_minus)) + seq += 1 + + return np.array(ret) + + @property + def all_voltages(self): + """all_voltages: type=np.array + Voltage readings from different electrode combinations + """ + if self._switch_sequence is None: + self._switch_sequence = self.switch_sequence + + self.impedance_mode = False + ret = [] + for seq in self._switch_sequence: + # reset cross point switch + self.gpio1_toggle = True + + # set new cross point switch configuration from pregenerated sequence + self._xline[seq[0]][0] = True + self._xline[seq[1]][1] = True + self._xline[seq[2]][2] = True + self._xline[seq[3]][3] = True + + # read impedance + s = self.channel["voltage0"].raw + ret.append([s.real, s.imag]) + + return np.array(ret).reshape(len(ret), 2) + + @property + def electrode_count_available(self): + """electrode_count_available: type=np.array + Supported Electrode Counts + """ + return np.array([8, 16, 32]) diff --git a/supported_parts.md b/supported_parts.md index c612e0846..0674ae8c1 100644 --- a/supported_parts.md +++ b/supported_parts.md @@ -141,6 +141,7 @@ - CN0540 - CN0554 - CN0549 +- CN0565 - CN0566 - CN0575 - CN0579 From 2574841bad25e0ef57d1d208fdb307797d9f277c Mon Sep 17 00:00:00 2001 From: mc-so Date: Fri, 15 Dec 2023 16:39:52 +0800 Subject: [PATCH 2/4] Add documentation for CN0565 Signed-off-by: Michelle Claire So --- doc/source/devices/adi.cn0565.rst | 7 +++++++ doc/source/devices/index.rst | 1 + 2 files changed, 8 insertions(+) create mode 100644 doc/source/devices/adi.cn0565.rst diff --git a/doc/source/devices/adi.cn0565.rst b/doc/source/devices/adi.cn0565.rst new file mode 100644 index 000000000..fc29942f7 --- /dev/null +++ b/doc/source/devices/adi.cn0565.rst @@ -0,0 +1,7 @@ +cn0565 +================= + +.. automodule:: adi.cn0565 + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/devices/index.rst b/doc/source/devices/index.rst index 3cd2dc02c..94d211097 100644 --- a/doc/source/devices/index.rst +++ b/doc/source/devices/index.rst @@ -86,6 +86,7 @@ Supported Devices adi.cn0532 adi.cn0540 adi.cn0554 + adi.cn0565 adi.cn0566 adi.cn0575 adi.cn0579 From 26a9621bd509929a8f7d2f3f26c569918317658b Mon Sep 17 00:00:00 2001 From: mso Date: Wed, 21 Dec 2022 14:46:16 +0800 Subject: [PATCH 3/4] Added cn0565 pyadi-iio examples Signed off by: Michelle Claire So --- examples/cn0565/README.md | 54 +++ examples/cn0565/cn0565_back_projection.py | 60 ++++ examples/cn0565/cn0565_example.py | 235 +++++++++++++ examples/cn0565/cn0565_example_single.py | 90 +++++ examples/cn0565/cn0565_greit.py | 59 ++++ examples/cn0565/cn0565_jacobian.py | 64 ++++ examples/cn0565/cn0565_prod_tst.py | 297 +++++++++++++++++ examples/cn0565/cn0565_sample_plot.py | 98 ++++++ examples/cn0565/cn0565_test_iio.py | 120 +++++++ examples/cn0565/cn0565_worker.py | 176 ++++++++++ examples/cn0565/main.py | 227 +++++++++++++ examples/cn0565/realtimeEITUI.py | 155 +++++++++ examples/cn0565/realtimeEITUI.ui | 388 ++++++++++++++++++++++ examples/cn0565/requirements.txt | 8 + 14 files changed, 2031 insertions(+) create mode 100644 examples/cn0565/README.md create mode 100644 examples/cn0565/cn0565_back_projection.py create mode 100644 examples/cn0565/cn0565_example.py create mode 100644 examples/cn0565/cn0565_example_single.py create mode 100644 examples/cn0565/cn0565_greit.py create mode 100644 examples/cn0565/cn0565_jacobian.py create mode 100644 examples/cn0565/cn0565_prod_tst.py create mode 100644 examples/cn0565/cn0565_sample_plot.py create mode 100644 examples/cn0565/cn0565_test_iio.py create mode 100644 examples/cn0565/cn0565_worker.py create mode 100644 examples/cn0565/main.py create mode 100644 examples/cn0565/realtimeEITUI.py create mode 100644 examples/cn0565/realtimeEITUI.ui create mode 100644 examples/cn0565/requirements.txt diff --git a/examples/cn0565/README.md b/examples/cn0565/README.md new file mode 100644 index 000000000..e5005f143 --- /dev/null +++ b/examples/cn0565/README.md @@ -0,0 +1,54 @@ +# CN0565 +## 1. Install Prerequisite: + +```cmd +pip install -r requirements.txt +pip install -e . +``` +From the pyadi-iio main folder, install the prerequisites. +```cmd +pip install -r requirements.txt +``` +After running this command, pip will read the requirements.txt file and install the specified packages along with their dependencies. + +## 2. Run Example Script: +### Single Query +This will perform a single impedance measurement across a given electrode combinations. +``` +python cn0565_example_single.py + +``` +For example: +```cmd +python cn0565_example_single.py 0 3 1 2 +``` +This will use electrodes 0 and 3 for excitation and electrodes 1 and 2 for sensing. +### Running Multiple Measurements +This will perform the impedance across different possible electrode combinations. +```cmd +python cn0565_example.py +``` +### Running EIT Examples +These examples uses [PyEIT](https://github.com/eitcom/pyEIT) +#### Back Projection +```cmd +python cn0565_back_projection.py +``` +#### Jacobian +```cmd +python cn0565_jacobian.py +``` +#### GREIT +```cmd +python cn0565_greit.py +``` +#### All EIT Plots +```cmd +python cn0565_sample_plot.py +``` + +## 3. To run the GUI: +```cmd +python main.py +``` +This will start the GUI for the Electrical Impedance Tomography Measurement System. diff --git a/examples/cn0565/cn0565_back_projection.py b/examples/cn0565/cn0565_back_projection.py new file mode 100644 index 000000000..8a0c16874 --- /dev/null +++ b/examples/cn0565/cn0565_back_projection.py @@ -0,0 +1,60 @@ +# Copyright (C) 2022 Analog Devices, Inc. + +# SPDX short identifier: ADIBSD + +from __future__ import absolute_import, division, print_function + +import matplotlib.pyplot as plt +import numpy as np +import pyeit.eit.bp as bp +import pyeit.eit.protocol as protocol +import pyeit.mesh as mesh +from adi import cn0565 +from pyeit.eit.fem import EITForward +from pyeit.mesh.shape import thorax +from pyeit.mesh.wrapper import PyEITAnomaly_Circle + +# variable/board declaration +value_type = "re" # re, im, others -> magnitude +n_el = 16 # no of electrodes +port = "COM6" +baudrate = 230400 + +# mesh and protocol creation +mesh = mesh.create(n_el, h0=0.08) +protocol = protocol.create(n_el, dist_exc=1, step_meas=1, parser_meas="std") + +# board initialization +eit_board = cn0565(uri=f"serial:{port},{baudrate},8n1") +eit_board.excitation_frequency = 10000 +eit_board.electrode_count = n_el +eit_board.force_distance = 1 +eit_board.sense_distance = 1 + +# boundary voltage reading +voltages = eit_board.all_voltages +if value_type == "re": + current_data = voltages[:, 0] +elif value_type == "im": + current_data = voltages[:, 1] +else: + current_data = np.sqrt((voltages ** 2).sum(axis=1)) + +# Resistor array board is fixed. Use this to get absolute impedance +v0 = np.full_like(current_data, 1) +v1 = current_data + + +eit = bp.BP(mesh, protocol) +eit.setup(weight="none") +ds = 192.0 * eit.solve(v1, v0, normalize=True) +points = mesh.node +triangle = mesh.element + +# Plot +fig, ax = plt.subplots() +im = ax.tripcolor(points[:, 0], points[:, 1], triangle, ds) +ax.set_title(r"Impedance Measurement Using Back Projection") +ax.axis("equal") +fig.colorbar(im, ax=ax) +plt.show() diff --git a/examples/cn0565/cn0565_example.py b/examples/cn0565/cn0565_example.py new file mode 100644 index 000000000..8453da6c2 --- /dev/null +++ b/examples/cn0565/cn0565_example.py @@ -0,0 +1,235 @@ +# Copyright (C) 2022 Analog Devices, Inc. + +# SPDX short identifier: ADIBSD + +import cmath +import sys +import time +from datetime import datetime +from math import sqrt + +import adi +import openpyxl +import pandas as pd + +# Electrode Names Dictionary +electrode_name = [ + "R26_C56_C57", # Electrode 0 + "R24_C54_C56", # Electrode 1 + "R22_C52_C54", # Electrode 2 + "R18_C50_C52", # Electrode 3 + "R16_C48_C50", # Electrode 4 + "R14_C46_C48", # Electrode 5 + "R12_C44_C46", # Electrode 6 + "R10_C42_C44", # Electrode 7 + "R8_C40_C42", # Electrode 8 + "R9_C38_C40", # Electrode 9 + "R4_C36_C38", # Electrode 10 + "R2_C29_C36", # Electrode 11 + "R1_C29_C33", # Electrode 12 + "R3_C33_C37", # Electrode 13 + "R5_C37_C39", # Electrode 14 + "R7_C39_C41", +] # Electrode 15 + +# Function to export impedance measurements in excel file +def create_file(freq, frequency, amplitude, baudrate, real_impedance, imag_impedance): + create = "" + while create != "Y" or create != "N": + create = input( + "Would you like to generate a csv file? [Y/N] " + ) # ask user input + + if create == "Y": + First_E = [] # 1st of Electrode pair array + Sec_E = [] # 2nd of Electrode pair array + + Fpos = [] # Position of Positive Forcing Lead + Fneg = [] # Position of Negative Forcing Lead + Spos = [] # Position of Positive Sensing Lead + Sneg = [] # Position of Negative Sensing Lead + + item_num = [] + item = 0 + + for neg_e in range(1, 16): + for pos_e in range(0, neg_e): + ############################################################# + # For generation of excel file containing impedance readings + # Array storing assignment: + First_E.append("E" + str(pos_e)) + First_E.append(electrode_name[pos_e]) + Sec_E.append("E" + str(neg_e)) + Sec_E.append(electrode_name[neg_e]) + Fpos.append(pos_e) + Fpos.append("") + Fneg.append(neg_e) + Fneg.append("") + Spos.append(neg_e) + Spos.append("") + Sneg.append(pos_e) + Sneg.append("") + item = item + 1 + item_num.append(item) + item_num.append("") + + results = [] + results.append("") + results.append("Amplitude") + results.append(str(amplitude) + " mV") + results.append("Frequency") + results.append(str(frequency) + " Hz") + results.append("Baud Rate") + results.append(str(baudrate)) + + for q in range(0, 233): + results.append("") + + electrode_data = { + "1st Electrode": First_E, + "2nd Electrode": Sec_E, + "F+": Fpos, + "F-": Fneg, + "S+": Spos, + "S-": Sneg, + "Real 1": array(real_impedance), + "Imaginary 1": array(imag_impedance), + "Results": results, + } + + DF = pd.DataFrame.from_dict(electrode_data).set_index([item_num]) + + file_name = "cn0565_example_data.csv" + DF.to_csv(file_name) + print("File created! Filename: " + file_name) + print("End!") + break + + elif create == "N": + print("End!") + break + else: + print("Invalid input.") + + +def array(x): # Function to convert array size to maximum size of data frame + + total_pairs = len(x) + new_array = [] + + for a in range(0, total_pairs): + new_array.append(x[a]) + new_array.append("") + return new_array + + +def main(): + # ---------------------------------------------------------------------------------------------------- + # DEVICE SETTINGS + # ---------------------------------------------------------------------------------------------------- + cn0565 = adi.cn0565(uri="serial:COM7,230400,8n1n") + # reset the cross point switch + + amplitude = 100 + frequency = 10000 + baudrate = 230400 + + cn0565.gpio1_toggle = True + cn0565.excitation_amplitude = amplitude # Set amplitude + cn0565.excitation_frequency = frequency # Hz # Set frequency between 10kHz to 80kHz + cn0565.magnitude_mode = False + cn0565.impedance_mode = True + + print("--------------------------------------------------------------") + print("Amplitude: " + str(amplitude) + "mV") + print("Frequency: " + str(frequency) + " Hz") + print("Baud Rate: " + str(baudrate)) + print("--------------------------------------------------------------\n") + + cn0565.immediate = True + + cn0565.add(0x71) + cn0565.add(0x70) + + fplus = 1 + splus = 4 + fminus = 4 + sminus = 1 + + cn0565[fplus][0] = True + cn0565[splus][1] = True + cn0565[sminus][2] = True + cn0565[fminus][3] = True + + # Array for Real & Imaginary values per iteration + real_impedance = [] + imag_impedance = [] + + for neg_e in range(1, 16): # setting neg_e = Electrode 1 to Electrode 15 + + for pos_e in range(0, neg_e): # setting pos_e = Electrode 0 to neg_e + + if pos_e == neg_e: + print("Forcing electrodes are shorted in the given assignments.") + return False + + cn0565.open_all() + cn0565[pos_e][0] = True + cn0565[pos_e][1] = True + cn0565[neg_e][2] = True + cn0565[neg_e][3] = True + + # Command CN0565 to measure impedance at specified electrodes using specified frequency + res = cn0565.channel["voltage0"].raw + + print("--------------------------------------------------------------") + print("Electrode " + str(neg_e) + " - Electrode " + str(pos_e)) + print(electrode_name[neg_e] + " - " + electrode_name[pos_e]) + print("--------------------------------------------------------------") + print("Rectangular: " + str(res)) + (mag, radph) = cmath.polar(res) + degph = radph * 180 / cmath.pi + degphb = 360 + degph + print(f"Polar: Magnitude:{mag} Phase(degrees): {degph} or {degphb}") + + print("Real Impedance: " + str(res.real)) + print("Imaginary Impedance: " + str(res.imag)) + print("--------------------------------------------------------------") + print("\n") + + # store impedance reading of each pair to array + real_impedance.append(res.real) + imag_impedance.append(res.imag) + + """ + # average of real impedance values + ave_real = sum(real_impedance)/len(real_impedance) + + print('===========================================================') + print("*** Real values average: " + str(ave_real)) + print('-----------------------------------------------------------') + + # average of imaginary impedance values + ave_imag = sum(imag_impedance)/len(imag_impedance) + + print("*** Imaginary values average: " + str(ave_imag)) + print('===========================================================') + + Vmag = sqrt((ave_imag**2) + (ave_real**2)) + print("*** |Impedance|: " + str(Vmag)) + print('===========================================================\n') + print("END") + """ + + # function to ask user if excel file containing impedance data is to be generated + create_file( + cn0565.excitation_frequency, + frequency, + amplitude, + baudrate, + real_impedance, + imag_impedance, + ) + + +main() diff --git a/examples/cn0565/cn0565_example_single.py b/examples/cn0565/cn0565_example_single.py new file mode 100644 index 000000000..1eaddb0c9 --- /dev/null +++ b/examples/cn0565/cn0565_example_single.py @@ -0,0 +1,90 @@ +# Copyright (C) 2022 Analog Devices, Inc. + +# SPDX short identifier: ADIBSD + + +import cmath +import sys +import time + +import adi + +# Electrode Names Dictionary +electrode_name = [ + "R26_C56_C57", # Electrode 0 + "R24_C54_C56", # Electrode 1 + "R22_C52_C54", # Electrode 2 + "R18_C50_C52", # Electrode 3 + "R16_C48_C50", # Electrode 4 + "R14_C46_C48", # Electrode 5 + "R12_C44_C46", # Electrode 6 + "R10_C42_C44", # Electrode 7 + "R8_C40_C42", # Electrode 8 + "R9_C38_C40", # Electrode 9 + "R4_C36_C38", # Electrode 10 + "R2_C29_C36", # Electrode 11 + "R1_C29_C33", # Electrode 12 + "R3_C33_C37", # Electrode 13 + "R5_C37_C39", # Electrode 14 + "R7_C39_C41", # Electrode 15 +] + +cn0565 = adi.cn0565(uri="serial:COM7,230400,8n1n") +# reset the cross point switch + +amplitude = 100 +frequency = 10000 + +cn0565.gpio1_toggle = True +cn0565.excitation_amplitude = amplitude +cn0565.excitation_frequency = frequency +cn0565.magnitude_mode = False +cn0565.impedance_mode = True + +cn0565.add(0x71) +cn0565.add(0x70) + +cn0565.immediate = True + +fplus = 1 +splus = 4 +fminus = 4 +sminus = 1 + +if len(sys.argv) > 1: + fplus = int(sys.argv[1]) + fminus = int(sys.argv[2]) + splus = int(sys.argv[3]) + sminus = int(sys.argv[4]) + +cn0565[fplus][0] = True +cn0565[splus][1] = True +cn0565[sminus][2] = True +cn0565[fminus][3] = True + +print("--------------------------------------------------------------") +print("Amplitude: " + str(amplitude) + "mV") +print("Frequency: " + str(frequency) + " Hz") +print("Baud Rate: 230400") +print("--------------------------------------------------------------") +print("Electrode " + str(fplus) + " - Electrode " + str(fminus)) +print(electrode_name[fplus] + " - " + electrode_name[fminus]) +print("--------------------------------------------------------------") + +cn0565.open_all() +cn0565[fminus][0] = True +cn0565[fminus][1] = True +cn0565[fplus][2] = True +cn0565[fplus][3] = True +res = cn0565.channel["voltage0"].raw + +# print("Rectangular: " + str(res)) +(mag, radph) = cmath.polar(res) +degph = radph * 180 / cmath.pi +degphb = 360 + degph +print("Rectangular: " + str(res)) +print(f"Polar: Magnitude:{mag} Phase(degrees): {degph} or {degphb}") +print("Real Impedance: " + str(res.real)) +print("Imaginary Impedance: " + str(res.imag)) +print("--------------------------------------------------------------") +print("END") diff --git a/examples/cn0565/cn0565_greit.py b/examples/cn0565/cn0565_greit.py new file mode 100644 index 000000000..cc7ca5210 --- /dev/null +++ b/examples/cn0565/cn0565_greit.py @@ -0,0 +1,59 @@ +# Copyright (C) 2022 Analog Devices, Inc. + +# SPDX short identifier: ADIBSD + +from __future__ import absolute_import, division, print_function + +import matplotlib.pyplot as plt +import numpy as np +import pyeit.eit.greit as greit +import pyeit.eit.protocol as protocol +import pyeit.mesh as mesh +from adi import cn0565 +from pyeit.eit.fem import EITForward +from pyeit.mesh.shape import thorax +from pyeit.mesh.wrapper import PyEITAnomaly_Circle + +# variable and parameter declaration +value_type = "re" # re, im, others -> magnitude +n_el = 16 # no of electrodes +port = "COM6" +baudrate = 230400 + +# board initialization +eit_board = cn0565(uri=f"serial:{port},{baudrate},8n1") +eit_board.excitation_frequency = 10000 +eit_board.electrode_count = n_el +eit_board.force_distance = 1 +eit_board.sense_distance = 1 + +# mesh and protocol creation +mesh = mesh.create(n_el, h0=0.08) +protocol = protocol.create(n_el=n_el, dist_exc=1, step_meas=1, parser_meas="std") + +# Read actual boundary voltages from CN0565 +voltages = eit_board.all_voltages +if value_type == "re": + current_data = voltages[:, 0] +elif value_type == "im": + current_data = voltages[:, 1] +else: + current_data = np.sqrt((voltages ** 2).sum(axis=1)) + +# Resistor array board is fixed. Use this to get absolute impedance +v0 = np.full_like(current_data, 1) +v1 = current_data + +eit = greit.GREIT(mesh, protocol) +eit.setup(p=0.50, lamb=0.01, perm=1, jac_normalized=True) +# the normalize for Greit when dist_exc>4 should always be True +ds = eit.solve(v1, v0, normalize=True) +x, y, ds = eit.mask_value(ds, mask_value=np.NAN) + +# Plot +fig, ax = plt.subplots() +im = ax.imshow(np.real(ds)) +ax.set_title(r"Impedance Measurement Using GREIT Matrix") +ax.axis("equal") +fig.colorbar(im, ax=ax) +plt.show() diff --git a/examples/cn0565/cn0565_jacobian.py b/examples/cn0565/cn0565_jacobian.py new file mode 100644 index 000000000..6bc70ab85 --- /dev/null +++ b/examples/cn0565/cn0565_jacobian.py @@ -0,0 +1,64 @@ +# Copyright (C) 2022 Analog Devices, Inc. + +# SPDX short identifier: ADIBSD + +from __future__ import absolute_import, division, print_function + +import matplotlib.pyplot as plt +import numpy as np +import pyeit.eit.jac as jac +import pyeit.eit.protocol as protocol +import pyeit.mesh as mesh +from adi import cn0565 +from pyeit.eit.fem import EITForward +from pyeit.eit.interp2d import sim2pts +from pyeit.mesh.shape import thorax +from pyeit.mesh.wrapper import PyEITAnomaly_Circle + +# variable and parameter declaration +value_type = "re" # re, im, others -> magnitude +n_el = 16 # no of electrodes +port = "COM6" +baudrate = 230400 + +# board initialization +eit_board = cn0565(uri=f"serial:{port},{baudrate},8n1") +eit_board.excitation_frequency = 10000 +eit_board.electrode_count = n_el +eit_board.force_distance = 1 +eit_board.sense_distance = 1 + +# mesh and protocol creation +mesh = mesh.create(n_el, h0=0.08) +protocol = protocol.create(n_el, dist_exc=1, step_meas=1, parser_meas="std") + +# Read actual boundary voltages from CN0565 +voltages = eit_board.all_voltages +if value_type == "re": + current_data = voltages[:, 0] +elif value_type == "im": + current_data = voltages[:, 1] +else: + current_data = np.sqrt((voltages ** 2).sum(axis=1)) + +# Resistor array board is fixed. Use this to get absolute impedance +v0 = np.full_like(current_data, 1) +v1 = current_data + +# naive inverse solver using jacobian +eit = jac.JAC(mesh, protocol) +eit.setup(p=0.5, lamb=0.01, method="kotre", perm=1, jac_normalized=True) +# the normalize for BP when dist_exc>4 should always be True +ds = eit.solve(v1, v0, normalize=True) +points = mesh.node +triangle = mesh.element +x, y = points[:, 0], points[:, 1] +ds_n = sim2pts(points, triangle, np.real(ds)) + +# plot +fig, ax = plt.subplots() +im = ax.tripcolor(x, y, triangle, ds_n, shading="flat") +ax1.set_title(r"Reconstituted $\Delta$ Conductivities") +ax1.axis("equal") +fig.colorbar(im, ax=ax1) +plt.show() diff --git a/examples/cn0565/cn0565_prod_tst.py b/examples/cn0565/cn0565_prod_tst.py new file mode 100644 index 000000000..860709bd8 --- /dev/null +++ b/examples/cn0565/cn0565_prod_tst.py @@ -0,0 +1,297 @@ +# Copyright (C) 2022 Analog Devices, Inc. + +# SPDX short identifier: ADIBSD + +import cmath +import sys +import time +from datetime import datetime +from pprint import pprint + +import adi +import openpyxl +import pandas as pd + +# Electrode Names Dictionary +electrode_name = [ + "R26_C56_C57", # Electrode 0 + "R24_C54_C56", # Electrode 1 + "R22_C52_C54", # Electrode 2 + "R18_C50_C52", # Electrode 3 + "R16_C48_C50", # Electrode 4 + "R14_C46_C48", # Electrode 5 + "R12_C44_C46", # Electrode 6 + "R10_C42_C44", # Electrode 7 + "R8_C40_C42", # Electrode 8 + "R9_C38_C40", # Electrode 9 + "R4_C36_C38", # Electrode 10 + "R2_C29_C36", # Electrode 11 + "R1_C29_C33", # Electrode 12 + "R3_C33_C37", # Electrode 13 + "R5_C37_C39", # Electrode 14 + "R7_C39_C41", +] # Electrode 15 + + +def main(): + # ---------------------------------------------------------------------------------------------------- + # DEVICE SETTINGS + # ---------------------------------------------------------------------------------------------------- + cn0565 = adi.cn0565(uri="serial:COM5,230400,8n1n") + # reset the cross point switch + + cn0565.gpio1_toggle = True + cn0565.excitation_amplitude = 100 # Set amplitude + cn0565.excitation_frequency = 10000 # Hz # Set frequency between 10kHz to 80kHz + cn0565.magnitude_mode = False + cn0565.impedance_mode = True + + cn0565.immediate = True + + cn0565.add(0x71) + cn0565.add(0x70) + + fplus = 1 + splus = 4 + fminus = 4 + sminus = 1 + + cn0565[fplus][0] = True + cn0565[splus][1] = True + cn0565[sminus][2] = True + cn0565[fminus][3] = True + + # Array for Real & Imaginary values per iteration + real_impedance = [] + imag_impedance = [] + + # Pair Count + pair = 1 + + passed = 0 + + Electrode_Iter = iter(range(0, 16)) + for neg_e in Electrode_Iter: + pos_e = next(Electrode_Iter) # setting pos_e = Electrode 0 to neg_e + cn0565.open_all() + cn0565[pos_e][0] = True + cn0565[pos_e][1] = True + cn0565[neg_e][2] = True + cn0565[neg_e][3] = True + + # Command CN0565 to measure impedance at specified electrodes using specified frequency + res = cn0565.channel["voltage0"].raw + + print("Electrode " + str(neg_e) + " - Electrode " + str(pos_e)) + print(electrode_name[neg_e] + " - " + electrode_name[pos_e]) + print("Rectangular: " + str(res)) + (mag, radph) = cmath.polar(res) + degph = radph * 180 / cmath.pi + degphb = 360 + degph + print(f"Polar: Magnitude:{mag} Phase(degrees): {degph} or {degphb}") + + print("Real Impedance: " + str(res.real)) + print("Imaginary Impedance: " + str(res.imag)) + # print('\n') + + # store impedance reading of each pair to array + real_impedance.append(res.real) + imag_impedance.append(res.imag) + + if pair == 1: + if res.real >= 26000 and res.real <= 54000: + print("---------------------------------------") + print( + "Electrode " + + str(neg_e) + + " - Electrode " + + str(pos_e) + + " - PASSED!" + ) + print("---------------------------------------\n") + passed = passed + 1 + else: + print("---------------------------------------") + print( + "Electrode " + + str(neg_e) + + " - Electrode " + + str(pos_e) + + " - FAILED!" + ) + print("---------------------------------------\n") + + elif pair == 2: + if abs(res.imag) >= 110 and abs(res.imag) <= 230: + print("---------------------------------------") + print( + "Electrode " + + str(neg_e) + + " - Electrode " + + str(pos_e) + + " - PASSED!" + ) + print("---------------------------------------\n") + passed = passed + 1 + else: + print("---------------------------------------") + print( + "Electrode " + + str(neg_e) + + " - Electrode " + + str(pos_e) + + " - FAILED!" + ) + print("---------------------------------------\n") + + elif pair == 3: + if abs(res.imag) >= 15210 and abs(res.imag) <= 31600: + print("---------------------------------------") + print( + "Electrode " + + str(neg_e) + + " - Electrode " + + str(pos_e) + + " - PASSED!" + ) + print("---------------------------------------\n") + passed = passed + 1 + else: + print("---------------------------------------") + print( + "Electrode " + + str(neg_e) + + " - Electrode " + + str(pos_e) + + " - FAILED!" + ) + print("---------------------------------------\n") + + elif pair == 4: + if res.real >= 13000 and res.real <= 27000: + print("---------------------------------------") + print( + "Electrode " + + str(neg_e) + + " - Electrode " + + str(pos_e) + + " - PASSED!" + ) + print("---------------------------------------\n") + passed = passed + 1 + else: + print("---------------------------------------") + print( + "Electrode " + + str(neg_e) + + " - Electrode " + + str(pos_e) + + " - FAILED!" + ) + print("---------------------------------------\n") + + elif pair == 5: + if abs(res.imag) >= 517 and abs(res.imag) <= 1075: + print("---------------------------------------") + print( + "Electrode " + + str(neg_e) + + " - Electrode " + + str(pos_e) + + " - PASSED!" + ) + print("---------------------------------------\n") + passed = passed + 1 + else: + print("---------------------------------------") + print( + "Electrode " + + str(neg_e) + + " - Electrode " + + str(pos_e) + + " - FAILED!" + ) + print("---------------------------------------\n") + + elif pair == 6: + if res.real >= 6487 and res.real <= 13475: + print("---------------------------------------") + print( + "Electrode " + + str(neg_e) + + " - Electrode " + + str(pos_e) + + " - PASSED!" + ) + print("---------------------------------------\n") + passed = passed + 1 + else: + print("---------------------------------------") + print( + "Electrode " + + str(neg_e) + + " - Electrode " + + str(pos_e) + + " - FAILED!" + ) + print("---------------------------------------\n") + + elif pair == 7: + if abs(res.imag) >= 22010 and abs(res.imag) <= 45715: + print("---------------------------------------") + print( + "Electrode " + + str(neg_e) + + " - Electrode " + + str(pos_e) + + " - PASSED!" + ) + print("---------------------------------------\n") + passed = passed + 1 + else: + print("---------------------------------------") + print( + "Electrode " + + str(neg_e) + + " - Electrode " + + str(pos_e) + + " - FAILED!" + ) + print("---------------------------------------\n") + + elif pair == 8: + if abs(res.imag) >= 5170 and abs(res.imag) <= 10800: + print("---------------------------------------") + print( + "Electrode " + + str(neg_e) + + " - Electrode " + + str(pos_e) + + " - PASSED!" + ) + print("---------------------------------------\n") + passed = passed + 1 + else: + print("---------------------------------------") + print( + "Electrode " + + str(neg_e) + + " - Electrode " + + str(pos_e) + + " - FAILED!" + ) + print("---------------------------------------\n") + + pair = pair + 1 + + if passed == 8: + print("-----------------------") + print("BOARD STATUS: PASSED") + print("-----------------------\n") + else: + print("-----------------------") + print("BOARD STATUS: FAILED") + print("-----------------------\n") + + +main() diff --git a/examples/cn0565/cn0565_sample_plot.py b/examples/cn0565/cn0565_sample_plot.py new file mode 100644 index 000000000..fd5d3f082 --- /dev/null +++ b/examples/cn0565/cn0565_sample_plot.py @@ -0,0 +1,98 @@ +# Copyright (C) 2022 Analog Devices, Inc. + +# SPDX short identifier: ADIBSD + +from __future__ import absolute_import, division, print_function + +import matplotlib.pyplot as plt +import numpy as np +import pyeit.eit.bp as bp +import pyeit.eit.greit as greit +import pyeit.eit.jac as jac +import pyeit.eit.protocol as protocol +import pyeit.mesh as mesh +from adi import cn0565 +from pyeit.eit.fem import EITForward +from pyeit.eit.interp2d import sim2pts +from pyeit.mesh.shape import thorax +from pyeit.mesh.wrapper import PyEITAnomaly_Circle + +print(" 1. build mesh, protocol and setup board ") +value_type = "re" # re, im, others -> magnitude +n_el = 16 # no of electrodes +mesh_obj = mesh.create(n_el, h0=0.08) +port = "COM7" +baudrate = 230400 + +protocol_obj = protocol.create(n_el, dist_exc=1, step_meas=1, parser_meas="std") + +eit_board = cn0565(uri=f"serial:{port},{baudrate},8n1") +eit_board.excitation_frequency = 10000 +eit_board.electrode_count = n_el +eit_board.force_distance = 1 +eit_board.sense_distance = 1 + +print("2. Read actual boundary voltages from CN0565 ") +voltages = eit_board.all_voltages + +if value_type == "re": + current_data = voltages[:, 0] +elif value_type == "im": + current_data = voltages[:, 1] +else: + current_data = np.sqrt((voltages ** 2).sum(axis=1)) + +# Resistor array board is fixed. Use this to get absolute impedance +v0 = np.full_like(current_data, 1) + +v1 = current_data + +# 3.1 naive inverse solver using back-projection +eit = bp.BP(mesh_obj, protocol_obj) +eit.setup(weight="none") +# the normalize for BP when dist_exc>4 should always be True +ds = 192.0 * eit.solve(v1, v0, normalize=True) + +# extract node, element, alpha +pts = mesh_obj.node +tri = mesh_obj.element +x, y = pts[:, 0], pts[:, 1] + +# draw +fig, axes = plt.subplots(3, 1, constrained_layout=True, figsize=(6, 12)) +# reconstructed +ax = axes[0] +im = ax.tripcolor(pts[:, 0], pts[:, 1], tri, ds) +ax.set_title(r"Object Impedance using Back Projection") +ax.axis("equal") +fig.colorbar(im, ax=ax) + +# 3.2 JAC solver +# Note: if the jac and the real-problem are generated using the same mesh, +# then, data normalization in solve are not needed. +# However, when you generate jac from a known mesh, but in real-problem +# (mostly) the shape and the electrode positions are not exactly the same +# as in mesh generating the jac, then data must be normalized. +eit = jac.JAC(mesh_obj, protocol_obj) +eit.setup(p=0.5, lamb=0.01, method="kotre", perm=1, jac_normalized=True) +ds = eit.solve(v1, v0, normalize=True) +ds_n = sim2pts(pts, tri, np.real(ds)) + +ax1 = axes[1] +ax1.set_title(r"Object Impedance using Gauss-Newton solver (JAC)") +im = ax1.tripcolor(x, y, tri, ds_n, shading="flat") +ax1.axis("equal") +fig.colorbar(im, ax=ax1) + +# 3.3 Construct using GREIT """ +eit = greit.GREIT(mesh_obj, protocol_obj) +eit.setup(p=0.50, lamb=0.01, perm=1, jac_normalized=True) +ds = eit.solve(v1, v0, normalize=True) +x, y, ds = eit.mask_value(ds, mask_value=np.NAN) + +ax2 = axes[2] +ax2.set_title(r"Object Impedance using 2D GREIT") +im = ax2.imshow(np.real(ds), interpolation="none", cmap=plt.cm.viridis) +fig.colorbar(im, ax=ax2) +ax2.axis("equal") +plt.show() diff --git a/examples/cn0565/cn0565_test_iio.py b/examples/cn0565/cn0565_test_iio.py new file mode 100644 index 000000000..16c8a20bf --- /dev/null +++ b/examples/cn0565/cn0565_test_iio.py @@ -0,0 +1,120 @@ +import cmath +import csv +import sys +import time + +import adi +import numpy as np +import serial.tools.list_ports +from EitSerialReaderProtocol import EIT, EIT_Interface + + +def main(): + # Electrodes + # Values can be from 0 to 23 + f_plus = 0 + f_minus = 1 + s_plus = 2 + s_minus = 3 + + # Testing + ref_res = 2000 + + # Measurement + mode = "Z" + + # Frequency + freq = 10 + + # Serial Port Settings + cmprt = list(serial.tools.list_ports.comports()) + active_port = cmprt[0] + string_port = str(active_port) + port = string_port.split()[0] + print("ACTIVE PORT : " + str(port)) + baudrate = 230400 + bIIO = True + + if freq > 80 or freq < 10: + print("Provide a frequency between 10 and 80 kHz") + return False + + # Open CN0565 + intf = EIT_Interface(port, baudrate, bIIO) + + with open("cn0565_test.csv", "w", newline="") as csvfile: + + z_writer = csv.writer( + csvfile, delimiter=",", quotechar="|", quoting=csv.QUOTE_MINIMAL + ) + good = 0 + + r_array = [] + std_array = [] + + for i in range(1): + + z_val = [] + Z_sum = [] + + # neg_e = negative electrode + # pos_e = positive electrode + + for neg_e in range(1, 16): + + z_sub = [] + # z_sub.append(neg_e) + + for pos_e in range(0, neg_e): + if pos_e == neg_e: + print( + "Forcing electrodes are shorted in the given assignments." + ) + return False + + # Command CN0565 to measure impedance at specified electrodes using specified frequency + res = intf.query(freq, pos_e, neg_e, neg_e, pos_e, mode) + z_sub.append(res.real) + Z_sum.append(res.real) + time.sleep(0.1) + + """ + # Check if measurement is close to reference resistance + if (res.real > ref_res * 1.05) or (res.real < ref_res * 0.95): + print("Board failed at (" + str(pos_e) + ", " + str(neg_e) + ") with " + str(res.real)) + return False + else: + print("(" + str(pos_e) + ", " + str(neg_e) + "): " + str(res.real)) + """ + + z_writer.writerow(z_sub) + z_val.append(z_sub) + + std = np.std(Z_sum) + + std_array.append(std) + + if std < 100: + Trial = "Trial " + str_i = str(i + 1) + colon = ": " + Passed = "Board Passed!" + result = Trial + str_i + colon + Passed + good = good + 1 + + else: + Trial = "Trial " + str_i = str(i + 1) + colon = ": " + Passed = "Board Failed!" + result = Trial + str_i + colon + Passed + + r_array.append(result) + + print("================================================") + print(Passed) + print(std) + print("================================================") + + +main() diff --git a/examples/cn0565/cn0565_worker.py b/examples/cn0565/cn0565_worker.py new file mode 100644 index 000000000..0e602258a --- /dev/null +++ b/examples/cn0565/cn0565_worker.py @@ -0,0 +1,176 @@ +# Copyright (C) 2022 Analog Devices, Inc. + +# SPDX short identifier: ADIBSD + +import sys + +import matplotlib.pyplot as plt +import numpy as np +import pyeit.eit.bp as bp +import pyeit.eit.greit as greit +import pyeit.eit.jac as jac +import pyeit.eit.protocol as protocol +import pyeit.mesh as mesh +import serial +import serial.threaded +from pyeit.eit.interp2d import sim2pts +from PyQt5.QtCore import * +from PyQt5.QtGui import * +from PyQt5.QtWidgets import * + + +class CN0565_Worker(QThread): + doneCompute = pyqtSignal() + # signal for getting the electrode + doneGetSupportedElectrodeCount = pyqtSignal(np.ndarray) + # signal for computing + + # initialization for the QThread + def __init__(self, parent=None, figure=None): + super(CN0565_Worker, self).__init__(parent) + self.exiting = False # keeps the run running + self.set_baseline = True + self.pVal = 0.5 + self.lambdaVal = 0.5 + self.reconstruction = "greit" + # declaration for interface (intf) + self.freq = 10000 + self.el = 16 + self.h0 = 0.08 + self.force_distance = 1 + self.sense_distance = 1 + self.fixed_ref = True + self.figure = figure + self.intf = None + self.value_type = "re" + + # declaration for protocol + self.dist_exc = 1 + self.step_meas = 1 + self.parser_meas = "std" + # plot + self.mesh_obj = [] + self.ds = [] + self.eit = None + self.electrode_count_available = np.ndarray([8, 16, 32]) + self.doneGetSupportedElectrodeCount.emit(self.electrode_count_available) + print("Worker Created") + + def plot(self, figure, mesh_obj, ds): + """ Setup display""" + axis_size = [-1.2, 1.2, -1.2, 1.2] + self.figure.clear() + self.ax = figure.add_subplot() + pts = self.mesh_obj.node + tri = self.mesh_obj.element + x, y = pts[:, 0], pts[:, 1] + + if self.reconstruction == "bp": + im = self.ax.tripcolor(pts[:, 0], pts[:, 1], tri, ds) + self.ax.set_title(r"BP") + self.ax.axis("equal") + self.figure.colorbar(im) + + elif self.reconstruction == "jac": + im = self.ax.tripcolor(x, y, tri, ds, shading="flat") + self.ax.set_title(r"JAC") + self.ax.axis("equal") + self.figure.colorbar(im) + + elif self.reconstruction == "greit": + im = self.ax.imshow(np.real(ds)) + self.ax.set_title(r"GREIT") + self.ax.axis("equal") + self.figure.colorbar(im) + + def updatePvalue(self, p=0.5): + self.pVal = p + self.reSetup = True + + def updateLambdaValue(self, lamb=0.5): + self.lambdaVal = lamb + self.reSetup = True + + def updateReconstructionMethod(self, reconstruction="jac"): + self.reconstruction = reconstruction + self.reSetup = True + + def setValueType(self, value_type="re"): + self.value_type = value_type + + def setBaseline(self): + self.set_baseline = True + + def run(self): + self.exiting = False + # self.electrode_count_available = self.intf.electrode_count_available() + # self.doneGetSupportedElectrodeCount.emit(self.electrode_count_available) + + print("Electrode count is:", self.el) + self.intf.excitation_frequency = self.freq + self.intf.electrode_count = self.el + self.intf.force_distance = self.force_distance + self.intf.sense_distance = self.sense_distance + self.mesh_obj = mesh.create(n_el=self.el, h0=self.h0) + protocol_obj = protocol.create( + self.el, self.dist_exc, self.step_meas, self.parser_meas + ) + + print( + "Mesh built: " + + str(self.el) + + " " + + str(self.h0) + + " " + + str(self.force_distance) + + " " + + str(self.sense_distance) + ) + while not self.exiting: + voltages = self.intf.all_voltages + + if self.value_type == "re": + current_data = voltages[:, 0] + elif self.value_type == "im": + current_data = voltages[:, 1] + else: + current_data = np.sqrt((voltages ** 2).sum(axis=1)) + + if self.set_baseline: + v0 = current_data + self.set_baseline = False + print("Done baseline!") + else: + v1 = current_data + self.solver(v0, v1, protocol_obj) + print("Plotting... ") + + def solver(self, v0, v1, protocol_obj): + if self.reconstruction == "bp": + self.eit = bp.BP(self.mesh_obj, protocol_obj) + self.eit.setup(weight="none") + self.ds = self.eit.solve(v1, v0, normalize=True) + self.plot(self.figure, self.mesh_obj, self.ds) + self.doneCompute.emit() + + elif self.reconstruction == "jac": + self.eit = jac.JAC(self.mesh_obj, protocol_obj) + self.eit.setup( + p=0.5, lamb=0.01, method="kotre", perm=1, jac_normalized=True + ) + # the normalize for BP when dist_exc>4 should always be True + self.ds = self.eit.solve(v1, v0, normalize=True) + self.ds = sim2pts( + self.mesh_obj.node, self.mesh_obj.element, self.ds + ) # changes here + self.plot(self.figure, self.mesh_obj, self.ds) + self.doneCompute.emit() + + elif self.reconstruction == "greit": + self.eit = greit.GREIT(self.mesh_obj, protocol_obj) + self.eit.setup(p=0.50, lamb=0.01, perm=1, jac_normalized=True) + # the normalize for Greit when dist_exc>4 should always be True + self.ds = self.eit.solve(v1, v0, normalize=True) + x, y, self.ds = self.eit.mask_value(self.ds, mask_value=np.NAN) + self.plot(self.figure, self.mesh_obj, self.ds) + self.doneCompute.emit() diff --git a/examples/cn0565/main.py b/examples/cn0565/main.py new file mode 100644 index 000000000..19bf78c1a --- /dev/null +++ b/examples/cn0565/main.py @@ -0,0 +1,227 @@ +# Copyright (C) 2022 Analog Devices, Inc. + +# SPDX short identifier: ADIBSD + +import argparse +import os +import os.path +import sys +from types import MethodType + +import adi +import realtimeEITUI +import serial +import serial.tools.list_ports +from cn0565_worker import CN0565_Worker +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas +from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar +from matplotlib.figure import Figure +from PyQt5 import QtCore, QtGui, QtWidgets + +__tool_name__ = "Real Time Electrical Impedance Tomography" +__banner__ = "Analog Devices" +__version__ = "0.0.2.0" +__release_date__ = "Oct-2023" + + +def rebinder(f): + if not isinstance(f, MethodType): + raise TypeError("rebinder was intended for rebinding methods") + + def wrapper(*args, **kw): + return f(*args, **kw) + + return wrapper + + +class RealtimeEIT(QtWidgets.QMainWindow, realtimeEITUI.Ui_MainWindow): + def __init__(self, port, baudrate, iio, el, parent=None): + super(RealtimeEIT, self).__init__(parent) + self.port = port + self.baudrate = baudrate + self.iio = iio + self.intf = None + self.setupUi(self) + self.setFixedSize(self.size()) + sizeX = 3.7 # 1151/96 + sizeY = 1.9 # 401/96 + size = (sizeX, sizeY) + self.figure = Figure(size) + self.canvas = FigureCanvas(self.figure) + self.toolbar = NavigationToolbar(self.canvas, self) + self.vLayout_plot.addWidget(self.toolbar) + self.vLayout_plot.addWidget(self.canvas) + self.connected = False + + # functionality + self.sldr_freq.valueChanged.connect(self.freqValueChanged) + self.sldr_p.valueChanged.connect(self.pValueChanged) + self.sldr_lambda.valueChanged.connect(self.lambdaValueChanged) + self.btn_refresh_comm.clicked.connect(self.update_cmb_comm_select) + self.btn_baseline.clicked.connect(self.setBaseline) + self.btn_connect.clicked.connect(self.comm_connect) + self.rbtn_real.toggled.connect(lambda: self.btnState(self.rbtn_real)) + self.rbtn_imaginary.toggled.connect(lambda: self.btnState(self.rbtn_imaginary)) + self.rbtn_magnitude.toggled.connect(lambda: self.btnState(self.rbtn_magnitude)) + self.rbtn_bp.toggled.connect(lambda: self.btnState(self.rbtn_bp)) + self.rbtn_jac.toggled.connect(lambda: self.btnState(self.rbtn_jac)) + self.rbtn_greit.toggled.connect(lambda: self.btnState(self.rbtn_greit)) + + # workers + self.worker = CN0565_Worker(figure=self.figure) + self.worker.doneCompute.connect(self.updatePlot) + self.worker.doneGetSupportedElectrodeCount.connect(self.updateElectrodeCount) + + # function declaration + self.freqValueChanged() + self.pValueChanged() + self.lambdaValueChanged() + self.rbtn_bp.toggle() + self.rbtn_real.toggle() + self.update_cmb_comm_select() + + self.updateElectrodeCount(el) + + index = self.cmb_supported_electrode_count_select.findText( + str(el), QtCore.Qt.MatchFixedString + ) + if index >= 0: + self.cmb_supported_electrode_count_select.setCurrentIndex(index) + + def updatePlot(self): + self.canvas.draw() + + def comm_connect(self): + if self.btn_connect.text() == "Connect": + self.btn_connect.setText("Disconnect") + self.port = self.cmb_comm_select.currentData().strip() + if self.connected == True: + self.worker.intf = self.intf + self.worker.start() + else: + try: + print(f"Serial: {self.port} baudrate: {self.baudrate}") + self.intf = adi.cn0565( + uri=f"serial:{self.port},{self.baudrate},8n1" + ) + self.worker.intf = self.intf + self.worker.start() + except Exception as e: + print(f"Serial Connection Error! {e}") + self.btn_connect.setText("Connect") + del self.intf + self.connected = True + + else: + # TODO: stop hardware first + self.btn_connect.setText("Connect") + print("Disconnected!") + print("Ready for another run...") + self.worker.exiting = True + self.worker.quit() + + def btnState(self, btn): + if btn.isChecked(): + print(btn.text() + " is chosen") + if btn.text() == "Real": + self.worker.setValueType("re") + if btn.text() == "Imaginary": + self.worker.setValueType("im") + if btn.text() == "Magnitude": + self.worker.setValueType("mag") + if btn.text() == "BP": + self.worker.updateReconstructionMethod("bp") + if btn.text() == "JAC": + self.worker.updateReconstructionMethod("jac") + if btn.text() == "GREIT": + self.worker.updateReconstructionMethod("greit") + + def setBaseline(self): + self.worker.setBaseline() + + def updatePlot(self): + self.canvas.draw() + + def updateElectrodeCount(self, supported_electrode_count): + """ + TODO: Update the Electrode counts supported + """ + self.cmb_supported_electrode_count_select.clear() + + if isinstance(supported_electrode_count, int): + supported_electrode_count = [supported_electrode_count] + + for electrode_count in supported_electrode_count: + self.cmb_supported_electrode_count_select.addItem(str(electrode_count)) + + def freqValueChanged(self): + self.freqVal = int(self.sldr_freq.value()) + self.sbox_freq.setProperty("value", self.freqVal) + self.worker.freq = self.freqVal + + def update_cmb_comm_select(self): + self.cmb_comm_select.clear() + ports = serial.tools.list_ports.comports() + for port in ports: + self.cmb_comm_select.addItem( + port.device + ": " + port.description, port.device + ) + + def pValueChanged(self): + self.pVal = int(self.sldr_p.value()) / 100.0 + self.sbox_p.setProperty("value", self.pVal) + self.worker.updatePvalue(p=self.pVal) + + def lambdaValueChanged(self): + self.lambdaVal = int(self.sldr_lambda.value()) / 100.0 + self.sbox_lambda.setProperty("value", self.lambdaVal) + self.worker.updateLambdaValue(lamb=self.lambdaVal) + + def baselineDragEnterEvent(self, event): + event.accept() + + def inputDragEnterEvent(self, event): + event.accept() + + +def main(argv): + ap = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter) + + ap.add_argument( + "-b", + "--baudrate", + action="store", + nargs="?", + help="Serial port baudrate.", + default=230400, + ) + + ap.add_argument( + "-e", + "--el", + action="store", + nargs="?", + help="Number of electrodes.", + default=16, + ) + + ap.add_argument( + "-i", + "--iio", + action="store_true", + help="Use libiio instead of custom serial protocol.", + default=False, + ) + args = ap.parse_args() + + el = int(args.el) + if el not in [8, 16, 32]: + el = 16 + app = QtWidgets.QApplication(sys.argv) + form = RealtimeEIT(None, args.baudrate, args.iio, el) + form.show() + sys.exit(app.exec()) + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/examples/cn0565/realtimeEITUI.py b/examples/cn0565/realtimeEITUI.py new file mode 100644 index 000000000..f738c102e --- /dev/null +++ b/examples/cn0565/realtimeEITUI.py @@ -0,0 +1,155 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'realtimeEITUI.ui' +# +# Created by: PyQt5 UI code generator 5.9.2 +# +# WARNING! All changes made in this file will be lost! + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_MainWindow(object): + def setupUi(self, MainWindow): + MainWindow.setObjectName("MainWindow") + MainWindow.resize(540, 638) + self.centralwidget = QtWidgets.QWidget(MainWindow) + self.centralwidget.setObjectName("centralwidget") + self.cmb_comm_select = QtWidgets.QComboBox(self.centralwidget) + self.cmb_comm_select.setGeometry(QtCore.QRect(10, 10, 351, 22)) + self.cmb_comm_select.setObjectName("cmb_comm_select") + self.btn_connect = QtWidgets.QPushButton(self.centralwidget) + self.btn_connect.setGeometry(QtCore.QRect(400, 10, 81, 23)) + self.btn_connect.setObjectName("btn_connect") + self.grp_dataset = QtWidgets.QGroupBox(self.centralwidget) + self.grp_dataset.setGeometry(QtCore.QRect(10, 40, 91, 91)) + self.grp_dataset.setObjectName("grp_dataset") + self.rbtn_real = QtWidgets.QRadioButton(self.grp_dataset) + self.rbtn_real.setGeometry(QtCore.QRect(10, 20, 82, 17)) + self.rbtn_real.setObjectName("rbtn_real") + self.rbtn_imaginary = QtWidgets.QRadioButton(self.grp_dataset) + self.rbtn_imaginary.setGeometry(QtCore.QRect(10, 40, 82, 17)) + self.rbtn_imaginary.setObjectName("rbtn_imaginary") + self.rbtn_magnitude = QtWidgets.QRadioButton(self.grp_dataset) + self.rbtn_magnitude.setGeometry(QtCore.QRect(10, 60, 82, 17)) + self.rbtn_magnitude.setObjectName("rbtn_magnitude") + self.sldr_freq = QtWidgets.QSlider(self.centralwidget) + self.sldr_freq.setGeometry(QtCore.QRect(380, 50, 91, 22)) + self.sldr_freq.setMinimum(10) + self.sldr_freq.setMaximum(80) + self.sldr_freq.setOrientation(QtCore.Qt.Horizontal) + self.sldr_freq.setObjectName("sldr_freq") + self.sldr_p = QtWidgets.QSlider(self.centralwidget) + self.sldr_p.setGeometry(QtCore.QRect(380, 80, 91, 22)) + self.sldr_p.setMinimum(1) + self.sldr_p.setProperty("value", 50) + self.sldr_p.setOrientation(QtCore.Qt.Horizontal) + self.sldr_p.setObjectName("sldr_p") + self.sldr_lambda = QtWidgets.QSlider(self.centralwidget) + self.sldr_lambda.setGeometry(QtCore.QRect(380, 110, 91, 22)) + self.sldr_lambda.setMinimum(1) + self.sldr_lambda.setProperty("value", 50) + self.sldr_lambda.setOrientation(QtCore.Qt.Horizontal) + self.sldr_lambda.setObjectName("sldr_lambda") + self.sbox_p = QtWidgets.QDoubleSpinBox(self.centralwidget) + self.sbox_p.setGeometry(QtCore.QRect(480, 80, 51, 22)) + self.sbox_p.setMinimum(0.01) + self.sbox_p.setMaximum(0.99) + self.sbox_p.setSingleStep(0.01) + self.sbox_p.setProperty("value", 0.5) + self.sbox_p.setObjectName("sbox_p") + self.sbox_lambda = QtWidgets.QDoubleSpinBox(self.centralwidget) + self.sbox_lambda.setGeometry(QtCore.QRect(480, 110, 51, 22)) + self.sbox_lambda.setMinimum(0.01) + self.sbox_lambda.setMaximum(0.99) + self.sbox_lambda.setSingleStep(0.01) + self.sbox_lambda.setProperty("value", 0.5) + self.sbox_lambda.setObjectName("sbox_lambda") + self.label = QtWidgets.QLabel(self.centralwidget) + self.label.setGeometry(QtCore.QRect(290, 50, 81, 16)) + self.label.setObjectName("label") + self.label_2 = QtWidgets.QLabel(self.centralwidget) + self.label_2.setGeometry(QtCore.QRect(360, 80, 31, 20)) + self.label_2.setObjectName("label_2") + self.label_3 = QtWidgets.QLabel(self.centralwidget) + self.label_3.setGeometry(QtCore.QRect(330, 110, 41, 16)) + self.label_3.setObjectName("label_3") + self.btn_baseline = QtWidgets.QPushButton(self.centralwidget) + self.btn_baseline.setGeometry(QtCore.QRect(484, 10, 51, 23)) + self.btn_baseline.setObjectName("btn_baseline") + self.sbox_freq = QtWidgets.QSpinBox(self.centralwidget) + self.sbox_freq.setGeometry(QtCore.QRect(480, 50, 51, 22)) + self.sbox_freq.setMinimum(10) + self.sbox_freq.setMaximum(80) + self.sbox_freq.setObjectName("sbox_freq") + self.verticalLayoutWidget = QtWidgets.QWidget(self.centralwidget) + self.verticalLayoutWidget.setGeometry(QtCore.QRect(10, 140, 521, 451)) + self.verticalLayoutWidget.setObjectName("verticalLayoutWidget") + self.vLayout_plot = QtWidgets.QVBoxLayout(self.verticalLayoutWidget) + self.vLayout_plot.setContentsMargins(0, 0, 0, 0) + self.vLayout_plot.setObjectName("vLayout_plot") + self.btn_refresh_comm = QtWidgets.QPushButton(self.centralwidget) + self.btn_refresh_comm.setGeometry(QtCore.QRect(370, 10, 21, 21)) + self.btn_refresh_comm.setText("") + icon = QtGui.QIcon() + icon.addPixmap( + QtGui.QPixmap("refresh.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off + ) + self.btn_refresh_comm.setIcon(icon) + self.btn_refresh_comm.setObjectName("btn_refresh_comm") + self.grp_reconstruction = QtWidgets.QGroupBox(self.centralwidget) + self.grp_reconstruction.setGeometry(QtCore.QRect(110, 40, 91, 91)) + self.grp_reconstruction.setObjectName("grp_reconstruction") + self.rbtn_bp = QtWidgets.QRadioButton(self.grp_reconstruction) + self.rbtn_bp.setGeometry(QtCore.QRect(10, 20, 82, 17)) + self.rbtn_bp.setObjectName("rbtn_bp") + self.rbtn_jac = QtWidgets.QRadioButton(self.grp_reconstruction) + self.rbtn_jac.setGeometry(QtCore.QRect(10, 40, 82, 17)) + self.rbtn_jac.setObjectName("rbtn_jac") + self.rbtn_greit = QtWidgets.QRadioButton(self.grp_reconstruction) + self.rbtn_greit.setGeometry(QtCore.QRect(10, 60, 82, 17)) + self.rbtn_greit.setObjectName("rbtn_greit") + self.grp_electro_num = QtWidgets.QGroupBox(self.centralwidget) + self.grp_electro_num.setGeometry(QtCore.QRect(210, 40, 71, 51)) + self.grp_electro_num.setObjectName("grp_electro_num") + self.cmb_supported_electrode_count_select = QtWidgets.QComboBox( + self.grp_electro_num + ) + self.cmb_supported_electrode_count_select.setGeometry( + QtCore.QRect(10, 20, 51, 22) + ) + self.cmb_supported_electrode_count_select.setMaxVisibleItems(5) + self.cmb_supported_electrode_count_select.setMaxCount(25) + self.cmb_supported_electrode_count_select.setObjectName( + "cmb_supported_electrode_count_select" + ) + MainWindow.setCentralWidget(self.centralwidget) + self.menubar = QtWidgets.QMenuBar(MainWindow) + self.menubar.setGeometry(QtCore.QRect(0, 0, 540, 21)) + self.menubar.setObjectName("menubar") + MainWindow.setMenuBar(self.menubar) + self.statusbar = QtWidgets.QStatusBar(MainWindow) + self.statusbar.setObjectName("statusbar") + MainWindow.setStatusBar(self.statusbar) + + self.retranslateUi(MainWindow) + self.cmb_supported_electrode_count_select.setCurrentIndex(-1) + QtCore.QMetaObject.connectSlotsByName(MainWindow) + + def retranslateUi(self, MainWindow): + _translate = QtCore.QCoreApplication.translate + MainWindow.setWindowTitle(_translate("MainWindow", "Realtime EIT")) + self.btn_connect.setText(_translate("MainWindow", "Connect")) + self.grp_dataset.setTitle(_translate("MainWindow", "Dataset")) + self.rbtn_real.setText(_translate("MainWindow", "Real")) + self.rbtn_imaginary.setText(_translate("MainWindow", "Imaginary")) + self.rbtn_magnitude.setText(_translate("MainWindow", "Magnitude")) + self.label.setText(_translate("MainWindow", "Frequency (kHz)")) + self.label_2.setText(_translate("MainWindow", "p")) + self.label_3.setText(_translate("MainWindow", "lambda")) + self.btn_baseline.setText(_translate("MainWindow", "Baseline")) + self.grp_reconstruction.setTitle(_translate("MainWindow", "Reconstruction")) + self.rbtn_bp.setText(_translate("MainWindow", "BP")) + self.rbtn_jac.setText(_translate("MainWindow", "JAC")) + self.rbtn_greit.setText(_translate("MainWindow", "GREIT")) + self.grp_electro_num.setTitle(_translate("MainWindow", "Electrodes")) diff --git a/examples/cn0565/realtimeEITUI.ui b/examples/cn0565/realtimeEITUI.ui new file mode 100644 index 000000000..c2fe00530 --- /dev/null +++ b/examples/cn0565/realtimeEITUI.ui @@ -0,0 +1,388 @@ + + + MainWindow + + + + 0 + 0 + 540 + 638 + + + + Realtime EIT + + + + + + 10 + 10 + 351 + 22 + + + + + + + 400 + 10 + 81 + 23 + + + + Connect + + + + + + 10 + 40 + 91 + 91 + + + + Dataset + + + + + 10 + 20 + 82 + 17 + + + + Real + + + + + + 10 + 40 + 82 + 17 + + + + Imaginary + + + + + + 10 + 60 + 82 + 17 + + + + Magnitude + + + + + + + 380 + 50 + 91 + 22 + + + + 10 + + + 80 + + + Qt::Horizontal + + + + + + 380 + 80 + 91 + 22 + + + + 1 + + + 50 + + + Qt::Horizontal + + + + + + 380 + 110 + 91 + 22 + + + + 1 + + + 50 + + + Qt::Horizontal + + + + + + 480 + 80 + 51 + 22 + + + + 0.010000000000000 + + + 0.990000000000000 + + + 0.010000000000000 + + + 0.500000000000000 + + + + + + 480 + 110 + 51 + 22 + + + + 0.010000000000000 + + + 0.990000000000000 + + + 0.010000000000000 + + + 0.500000000000000 + + + + + + 290 + 50 + 81 + 16 + + + + Frequency (kHz) + + + + + + 360 + 80 + 31 + 20 + + + + p + + + + + + 330 + 110 + 41 + 16 + + + + lambda + + + + + + 484 + 10 + 51 + 23 + + + + Baseline + + + + + + 480 + 50 + 51 + 22 + + + + 10 + + + 80 + + + + + + 10 + 140 + 521 + 451 + + + + + + + + 370 + 10 + 21 + 21 + + + + + + + + refresh.pngrefresh.png + + + + + + 110 + 40 + 91 + 91 + + + + Reconstruction + + + + + 10 + 20 + 82 + 17 + + + + BP + + + + + + 10 + 40 + 82 + 17 + + + + JAC + + + + + + 10 + 60 + 82 + 17 + + + + GREIT + + + + + + + 210 + 40 + 71 + 51 + + + + Electrodes + + + + + 10 + 20 + 51 + 22 + + + + -1 + + + 5 + + + 25 + + + + + + + + 0 + 0 + 540 + 21 + + + + + + + + diff --git a/examples/cn0565/requirements.txt b/examples/cn0565/requirements.txt new file mode 100644 index 000000000..1ca2b06d6 --- /dev/null +++ b/examples/cn0565/requirements.txt @@ -0,0 +1,8 @@ +numpy +scipy +matplotlib +pyserial +PyQt5 +pyeit +openpyxl +pandas From 39b52d8547dd2ab0c6b732baa983ce7ea8266cc2 Mon Sep 17 00:00:00 2001 From: mc-so Date: Wed, 20 Dec 2023 14:56:40 +0800 Subject: [PATCH 4/4] Added test for CN0565 Signed-off-by: mc-so --- test/emu/devices/cn0565.xml | 1 + test/emu/hardware_map.yml | 11 +++++++++++ test/test_cn0565.py | 31 +++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+) create mode 100644 test/emu/devices/cn0565.xml create mode 100644 test/test_cn0565.py diff --git a/test/emu/devices/cn0565.xml b/test/emu/devices/cn0565.xml new file mode 100644 index 000000000..41aeccb6f --- /dev/null +++ b/test/emu/devices/cn0565.xml @@ -0,0 +1 @@ +]> \ No newline at end of file diff --git a/test/emu/hardware_map.yml b/test/emu/hardware_map.yml index 0c684db37..df43cdcc3 100644 --- a/test/emu/hardware_map.yml +++ b/test/emu/hardware_map.yml @@ -177,6 +177,17 @@ cn0554: - iio:device0 - iio:device1 +cn0565: + - ad5940 + - adg2128 + - pyadi_iio_class_support: + - cn0565 + - emulate: + - filename: cn0565.xml + - data_devices: + - iio:device0 + - iio:device1 + cn0566: - ad7291 - pyadi_iio_class_support: diff --git a/test/test_cn0565.py b/test/test_cn0565.py new file mode 100644 index 000000000..900b3e290 --- /dev/null +++ b/test/test_cn0565.py @@ -0,0 +1,31 @@ +import pytest + +hardware = ["cn0565"] +classname = "adi.cn0565" + +######################################### +@pytest.mark.iio_hardware(hardware) +@pytest.mark.parametrize("classname", [(classname)]) +@pytest.mark.parametrize( + "attr, start, stop, step, tol, repeats", + [ + ("electrode_count", 0, 64, 8, 1, 1), + ("force_distance", 0, 8, 1, 0, 0), + ("sense_distance", 0, 8, 1, 0, 0), + ], +) +def test_cn0565_attrs( + test_attribute_single_value, + iio_uri, + classname, + attr, + start, + stop, + step, + tol, + repeats, +): + + test_attribute_single_value( + iio_uri, classname, attr, start, stop, step, tol, repeats + )