From df08aa6b2cf8e17cb97a5092edefb20419d5fa67 Mon Sep 17 00:00:00 2001 From: ym13n22 <ym13n22@soton.ac.uk> Date: Tue, 16 Jul 2024 21:21:57 +0100 Subject: [PATCH] done --- integration/Window.py | 434 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 434 insertions(+) create mode 100644 integration/Window.py diff --git a/integration/Window.py b/integration/Window.py new file mode 100644 index 0000000..5f2ff01 --- /dev/null +++ b/integration/Window.py @@ -0,0 +1,434 @@ +import threading +import tkinter as tk +from matplotlib.figure import Figure +from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg +from mpl_toolkits.mplot3d.art3d import Poly3DCollection +import numpy as np +import serial +import serial.tools.list_ports +from time import sleep +import math + +class Window: + def __init__(self, root): + self.root = root + self.root.title("Integration") + self.ports = [port.device for port in serial.tools.list_ports.comports()] + + # Set the initial size and position of the popup window + self.width = 1000 + self.height = 600 + screen_width = self.root.winfo_screenwidth() + screen_height = self.root.winfo_screenheight() + x = (screen_width // 2) - (self.width // 2) + y = (screen_height // 2) - (self.height // 2) + self.root.geometry(f"{self.width}x{self.height}+{x}+{y}") + + # Configure the grid to be expandable + self.root.columnconfigure(0, weight=1) + self.root.columnconfigure(1, weight=1) + self.root.rowconfigure(0, weight=1) + self.root.rowconfigure(1, weight=1) + + # Create a frame + self.frame1 = tk.Frame(self.root, borderwidth=1, relief="solid", width=self.width / 3, height=self.height / 2) + self.frame1.grid(row=0, column=0, padx=10, pady=10, sticky="nsew") + + self.frame2 = tk.Frame(self.root, borderwidth=1, relief="solid", width=self.width * 2 / 3, height=self.height / 2) + self.frame2.grid(row=0, column=1, padx=10, pady=10, sticky="nsew") + + + self.frame3 = tk.Frame(self.root, borderwidth=1, relief="solid", width=self.width / 3, height=self.height / 2) + self.frame3.grid(row=1, column=0, padx=10, pady=10, sticky="nsew") + + self.frame4 = tk.Frame(self.root, borderwidth=1, relief="solid", width=self.width * 2 / 3, height=self.height / 2) + self.frame4.grid(row=1, column=1, padx=10, pady=10, sticky="nsew") + self.frame4.grid_propagate(False) + label4 = tk.Label(self.frame4, text="Section 4") + label4.place(relx=0.5, rely=0.5, anchor='center') + + #self.imu_thread = threading.Thread(target=self.initial_IMU) + #self.emg_thread = threading.Thread(target=self._initialise_EMG_graph) + #self.emg_thread.start() + #self.imu_thread.start() + + self.emg_data_1 = [-1] * 41 + self.emg_data_2 = [-1] * 41 + + self.initial_IMU() + self._initialise_EMG_graph() + + + + def initial_IMU(self): + # Serial Port Setup + if'COM6' in self.ports:#port maybe different on different laptop + self.arduino = serial.Serial('COM6', 115200) + self.label2 = tk.Label(self.frame2, text="Port: COM6 ") + self.label2.place(relx=0.35, rely=0.8, anchor='center') + self.label1 = tk.Label(self.frame2, + text="click the Connect button to see the animation", + wraplength=self.width / 2) + self.label1.place(relx=0.5, rely=0.9, anchor='center') + # Add a button to start data transmission + self.start_button = tk.Button(self.frame2, text="connect", command=self.start_data_transmission) + self.start_button.place(relx=0.5, rely=0.8, anchor='center') + + self.start_button = tk.Button(self.frame2, text="Disconnect", command=self.disconnect) + self.start_button.place(relx=0.7, rely=0.8, anchor='center') + + else: + print("IMU is not connected") + self.label2 = tk.Label(self.frame2, text="Port: None ") + self.label2.place(relx=0.35, rely=0.8, anchor='center') + self.label1 = tk.Label(self.frame2, + text="Please check the IUM connection", + wraplength=self.width / 2) + self.label1.place(relx=0.5, rely=0.9, anchor='center') + + sleep(1) + + # Conversions + self.toRad = 2 * np.pi / 360 + self.toDeg = 1 / self.toRad + + # Initialize Parameters + self.count = 0 + self.averageroll = 0 + self.averageyaw = 0 + self.averagepitch = 0 + self.averageemg = 0 + self.iterations = 10 # EMG measurements to get average + + # Create a figure for the 3D plot + self.fig = Figure(figsize=((self.width / 300), (self.height / 200))) + self.ax = self.fig.add_subplot(111, projection='3d') + + # Set Limits + self.ax.set_xlim(-2, 2) + self.ax.set_ylim(-2, 2) + self.ax.set_zlim(-2, 2) + + # Set labels + self.ax.set_xlabel('X') + self.ax.set_ylabel('Y') + self.ax.set_zlabel('Z',labelpad=0) + + # Draw Axes + self.ax.quiver(0, 0, 0, 2, 0, 0, color='red', label='X-Axis', arrow_length_ratio=0.1) # X Axis (Red) + self.ax.quiver(0, 0, 0, 0, -2, 0, color='green', label='Y-Axis', arrow_length_ratio=0.1) # Y Axis (Green) + self.ax.quiver(0, 0, 0, 0, 0, 4, color='blue', label='Z-Axis', arrow_length_ratio=0.1) # Z Axis (Blue) + + # Draw the board as a rectangular prism (solid) + self.prism_vertices = np.array([ + [-1.5, -1, 0], [1.5, -1, 0], [1.5, 1, 0], [-1.5, 1, 0], # bottom vertices + [-1.5, -1, 0.1], [1.5, -1, 0.1], [1.5, 1, 0.1], [-1.5, 1, 0.1] + # top vertices (height=0.1 for visual thickness) + ]) + + self.prism_faces = [ + [self.prism_vertices[j] for j in [0, 1, 2, 3]], # bottom face + [self.prism_vertices[j] for j in [4, 5, 6, 7]], # top face + [self.prism_vertices[j] for j in [0, 1, 5, 4]], # side face + [self.prism_vertices[j] for j in [1, 2, 6, 5]], # side face + [self.prism_vertices[j] for j in [2, 3, 7, 6]], # side face + [self.prism_vertices[j] for j in [3, 0, 4, 7]] # side face + ] + + self.prism_collection = Poly3DCollection(self.prism_faces, facecolors='gray', linewidths=1, edgecolors='black', + alpha=0.25) + self.ax.add_collection3d(self.prism_collection) + + # Front Arrow (Purple) + self.front_arrow, = self.ax.plot([0, 2], [0, 0], [0, 0], color='purple', marker='o', markersize=10, + label='Front Arrow') + + # Up Arrow (Magenta) + self.up_arrow, = self.ax.plot([0, 0], [0, -1], [0, 1], color='magenta', marker='o', markersize=10, + label='Up Arrow') + + # Side Arrow (Orange) + self.side_arrow, = self.ax.plot([0, 1], [0, -1], [0, 1], color='orange', marker='o', markersize=10, + label='Side Arrow') + + # Create a canvas to draw on + self.canvas = FigureCanvasTkAgg(self.fig, master=self.frame1) + self.canvas.draw() + self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) + + # Create a label for average EMG + # self.emg_label = tk.Label(self.frame1, text="Average EMG: 0", font=("Arial", 14)) + # self.emg_label.pack(pady=10) + + + + # Initialize the data transmission flag + self.transmitting = False + + + + def _initialise_EMG_graph(self): + if 'COM5' in self.ports:#port maybe different on different laptop + self.arduino_EMG = serial.Serial('COM5', 9600,timeout=1) + self.label2 = tk.Label(self.frame3, text="Port: COM5 ") + self.label2.place(relx=0.23, rely=0.8, anchor='center') + self.label1 = tk.Label(self.frame3, + text="click the Connect button to see the animation", + wraplength=self.width / 2) + self.label1.place(relx=0.5, rely=0.9, anchor='center') + # Add a button to start data transmission + self.start_button = tk.Button(self.frame3, text="connect", command=self.start_EMG_data_transmission) + self.start_button.place(relx=0.45, rely=0.8, anchor='center') + + self.start_button = tk.Button(self.frame3, text="Disconnect", command=self.EMG_disconnect) + self.start_button.place(relx=0.7, rely=0.8, anchor='center') + + else: + print("EMG is not connected") + self.label2 = tk.Label(self.frame3, text="Port: None ") + self.label2.place(relx=0.35, rely=0.8, anchor='center') + self.label1 = tk.Label(self.frame3, + text="Please check the IUM connection", + wraplength=self.width / 2) + self.label1.place(relx=0.5, rely=0.9, anchor='center') + + # Create a figure and axis + fig = Figure(figsize=((self.width / 200), (self.height / 200))) # Adjusting figsize based on frame size + self.ax1 = fig.add_subplot(111) + + self.ax1.set_title("Electromyography Envelope", fontsize=14, pad=0) + + + self.ax1.set_xlim(0, 5) + self.ax1.set_ylim(0, 5) + + self.ax1.set_xlabel("Sample(20 samples per second)",fontsize=8,labelpad=-2) + self.ax1.set_ylabel("Magnitude",labelpad=0) + + self.ax1.set_xticks(np.arange(0, 41, 8)) + self.ax1.set_yticks(np.arange(0, 1001, 200)) + + for x_tick in self.ax1.get_xticks(): + self.ax1.axvline(x_tick, color='gray', linestyle='--', linewidth=0.5) + for y_tick in self.ax1.get_yticks(): + self.ax1.axhline(y_tick, color='gray', linestyle='--', linewidth=0.5) + + + + + # Plot two lines + self.line1, = self.ax1.plot([], [], color='red', label='Outer Wrist Muscle (Extensor Carpi Ulnaris)') + self.line2, = self.ax1.plot([], [], color='blue', label='Inner Wrist Muscle (Flexor Carpi Radialis)') + self.ax1.legend(fontsize=9, loc='upper right') + + + + + # Embed the plot in the tkinter frame + self.canvas1 = FigureCanvasTkAgg(fig, master=self.frame4) + self.canvas1.draw() + self.canvas1.get_tk_widget().pack(fill=tk.BOTH, expand=True) + + self.EMG_transmitting = False + + + def start_data_transmission(self): + # Set the transmitting flag to True and start the update loop + self.transmitting = True + self.update_display() + + def start_EMG_data_transmission(self): + # Set the transmitting flag to True and start the update loop + self.EMG_transmitting = True + self.EMG_Display() + + def disconnect(self): + self.transmitting = False + + def EMG_disconnect(self): + self.EMG_transmitting = False + + + + def update_display(self): + if self.transmitting: + try: + while self.arduino.inWaiting() > 0: + dataPacket = self.arduino.readline() + dataPacket = dataPacket.decode() + cleandata = dataPacket.replace("\r\n", "") + row = cleandata.strip().split(',') + + if len(row) == 9: + splitPacket = cleandata.split(',') + + emg = float(splitPacket[0]) # EMG sensor data + q0 = float(splitPacket[1]) # qw + q1 = float(splitPacket[2]) # qx + q2 = float(splitPacket[3]) # qy + q3 = float(splitPacket[4]) # qz + + # Calculate Angles + roll = math.atan2(2 * (q0 * q1 + q2 * q3), 1 - 2 * (q1 * q1 + q2 * q2)) + pitch = -math.asin(2 * (q0 * q2 - q3 * q1)) + yaw = -math.atan2(2 * (q0 * q3 + q1 * q2), 1 - 2 * (q2 * q2 + q3 * q3)) + + roll_label=tk.Label(self.frame2, text="roll is : "+str(roll)) + roll_label.config(font=("Arial", 12)) + roll_label.place(relx=0.2, rely=0.3, anchor='w') + pitch_label=tk.Label(self.frame2, text="pitch is : "+str(pitch)) + pitch_label.config(font=("Arial", 12)) + pitch_label.place(relx=0.2, rely=0.4, anchor='w') + yaw_label=tk.Label(self.frame2, text="yaw is : "+str(yaw)) + yaw_label.config(font=("Arial", 12)) + yaw_label.place(relx=0.2, rely=0.5, anchor='w') + + # Rotation matrices + Rz = np.array([ + [np.cos(yaw), -np.sin(yaw), 0], + [np.sin(yaw), np.cos(yaw), 0], + [0, 0, 1] + ]) + + Ry = np.array([ + [np.cos(pitch), 0, np.sin(pitch)], + [0, 1, 0], + [-np.sin(pitch), 0, np.cos(pitch)] + ]) + + Rx = np.array([ + [1, 0, 0], + [0, np.cos(roll), -np.sin(roll)], + [0, np.sin(roll), np.cos(roll)] + ]) + + R = Rz @ Ry @ Rx # Combined rotation matrix + + # Apply the rotation + rotated_vertices = (R @ self.prism_vertices.T).T + + prism_faces_rotated = [ + [rotated_vertices[j] for j in [0, 1, 2, 3]], # bottom face + [rotated_vertices[j] for j in [4, 5, 6, 7]], # top face + [rotated_vertices[j] for j in [0, 1, 5, 4]], # side face + [rotated_vertices[j] for j in [1, 2, 6, 5]], # side face + [rotated_vertices[j] for j in [2, 3, 7, 6]], # side face + [rotated_vertices[j] for j in [3, 0, 4, 7]] # side face + ] + + # Update the collection + self.prism_collection.set_verts(prism_faces_rotated) + + # Update Arrows + k = np.array([np.cos(yaw) * np.cos(pitch), np.sin(pitch), np.sin(yaw) * np.cos(pitch)]) # X vector + y = np.array([0, 1, 0]) # Y vector: pointing down + s = np.cross(k, y) # Side vector + v = np.cross(s, k) # Up vector + vrot = v * np.cos(roll) + np.cross(k, v) * np.sin(roll) # Rotated Up vector + + self.front_arrow.set_data([0, k[0] * 2], [0, k[1] * 2]) + self.front_arrow.set_3d_properties([0, k[2] * 2]) + self.up_arrow.set_data([0, vrot[0] * 1], [0, vrot[1] * 1]) + self.up_arrow.set_3d_properties([0, vrot[2] * 1]) + self.side_arrow.set_data([0, s[0] * 1], [0, s[1] * 1]) + self.side_arrow.set_3d_properties([0, s[2] * 1]) + + # Update canvas + self.canvas.draw() + + self.averageroll += roll * self.toDeg + self.averageyaw += yaw * self.toDeg + self.averagepitch += pitch * self.toDeg + self.averageemg += emg + + if self.count == self.iterations: + self.averageroll = self.averageroll / self.iterations + self.averageyaw = self.averageyaw / self.iterations + self.averagepitch = self.averagepitch / self.iterations + self.averageemg = self.averageemg / self.iterations + + self.averageroll = round(self.averageroll) + self.averageyaw = round(self.averageyaw) + self.averagepitch = round(self.averagepitch) + + # Print the averaged results + print("iterations:", self.iterations) + print("averageroll is", self.averageroll) + print("averageyaw is", self.averageyaw) + print("averagepitch is", self.averagepitch) + print("averageemg=", self.averageemg) + + self.count = 0 + + self.averageyaw = 0 + self.averageroll = 0 + self.averagepitch = 0 + self.averageemg = 0 + else: + self.count += 1 + + # Update EMG Label + #self.emg_label.config(text=f"Average EMG: {self.averageemg:.2f}") + + except Exception as e: + print(f"An error occurred: {e}") + + # Call update_display() again after 50 milliseconds + self.root.after(50, self.update_display) + + def EMG_Display(self): + if self.EMG_transmitting: + try: + data = self.arduino_EMG.readline() + emg_data = self._decode(data) + if emg_data is not None: + print(f"EMG 1: {emg_data[0]} , EMG 2: {emg_data[1]}") + + # Append the new data to the lists + + self.emg_data_1.append(emg_data[0]) + self.emg_data_1.pop(0) + self.emg_data_2.append(emg_data[1]) + self.emg_data_2.pop(0) + + # Update the line data to shift the line from right to left + self.line1.set_data(range(len(self.emg_data_1)), self.emg_data_1) + self.line2.set_data(range(len(self.emg_data_2)), self.emg_data_2) + + # Redraw the canvas + self.canvas1.draw() # Redraw the canvas + + except Exception as e: + print(f"An error occurred: {e}") + + + # Call update_display() again after 50 milliseconds + self.root.after(50, self.EMG_Display) + + def _decode(self, serial_data): + serial_string = serial_data.decode(errors="ignore") + adc_string_1 = "" + adc_string_2 = "" + self.adc_values = [0, 0] + if '\n' in serial_string: + # remove new line character + serial_string = serial_string.replace("\n", "") + if serial_string != '': + # Convert number to binary, placing 0s in empty spots + serial_string = format(int(serial_string, 10), "024b") + + # Separate the input number from the data + for i0 in range(0, 12): + adc_string_1 += serial_string[i0] + for i0 in range(12, 24): + adc_string_2 += serial_string[i0] + + self.adc_values[0] = int(adc_string_1, base=2) + self.adc_values[1] = int(adc_string_2, base=2) + + return self.adc_values + +if __name__ == "__main__": + root = tk.Tk() + app = Window(root) + root.mainloop() \ No newline at end of file -- GitLab