From 73b773507d75f1e36f78a78e7ab0a54de29586df Mon Sep 17 00:00:00 2001 From: Mohab Ashraf <63811158+MohabASHRAF-byte@users.noreply.github.com> Date: Fri, 15 Nov 2024 17:04:47 +0200 Subject: [PATCH] Add files via upload --- EquationParser.py | 98 ++++++++++++++ RunAllTests.py | 12 ++ Signal.py | 123 +++++++++++++++++ Tests/Task1/Signal1.txt | 15 +++ Tests/Task1/Signal2.txt | 13 ++ Tests/Task1/__pycache__/code.cpython-312.pyc | Bin 0 -> 4604 bytes Tests/Task1/add.txt | 15 +++ Tests/Task1/advance3.txt | 15 +++ Tests/Task1/code.py | 72 ++++++++++ Tests/Task1/delay3.txt | 15 +++ Tests/Task1/folding.txt | 15 +++ Tests/Task1/mul5.txt | 15 +++ Tests/Task1/subtract.txt | 15 +++ Tests/Task3/Test1/in.txt | 14 ++ Tests/Task3/Test1/out.txt | 14 ++ Tests/Task3/Test2/in.txt | 12 ++ Tests/Task3/Test2/out.txt | 12 ++ Tests/Task3/__pycache__/code.cpython-312.pyc | Bin 0 -> 7537 bytes Tests/Task3/code.py | 135 +++++++++++++++++++ __init__.py | 4 + main.py | 30 +++++ task1.py | 134 ++++++++++++++++++ task2.py | 98 ++++++++++++++ task3.py | 70 ++++++++++ test.py | 31 +++++ utilities.py | 23 ++++ 26 files changed, 1000 insertions(+) create mode 100644 EquationParser.py create mode 100644 RunAllTests.py create mode 100644 Signal.py create mode 100644 Tests/Task1/Signal1.txt create mode 100644 Tests/Task1/Signal2.txt create mode 100644 Tests/Task1/__pycache__/code.cpython-312.pyc create mode 100644 Tests/Task1/add.txt create mode 100644 Tests/Task1/advance3.txt create mode 100644 Tests/Task1/code.py create mode 100644 Tests/Task1/delay3.txt create mode 100644 Tests/Task1/folding.txt create mode 100644 Tests/Task1/mul5.txt create mode 100644 Tests/Task1/subtract.txt create mode 100644 Tests/Task3/Test1/in.txt create mode 100644 Tests/Task3/Test1/out.txt create mode 100644 Tests/Task3/Test2/in.txt create mode 100644 Tests/Task3/Test2/out.txt create mode 100644 Tests/Task3/__pycache__/code.cpython-312.pyc create mode 100644 Tests/Task3/code.py create mode 100644 __init__.py create mode 100644 main.py create mode 100644 task1.py create mode 100644 task2.py create mode 100644 task3.py create mode 100644 test.py create mode 100644 utilities.py diff --git a/EquationParser.py b/EquationParser.py new file mode 100644 index 0000000..a52a9be --- /dev/null +++ b/EquationParser.py @@ -0,0 +1,98 @@ +import operator +import re + +ops = { + '+': (1, operator.add, 'left'), + '-': (1, operator.sub, 'left'), + '*': (2, operator.mul, 'left'), + '/': (2, operator.truediv, 'left'), +} + + +class EquationParser: + + def __init__(self, variables: dict = None): + self.variables = variables if variables else {} + + def add_variable(self, key: str, value: float): + """Add or update a variable in the dictionary.""" + self.variables[key] = value + + def remove_variable(self, key: str): + """Remove a variable from the dictionary.""" + if key in self.variables: + del self.variables[key] + + def clear_variables(self): + """Clear all variables from the dictionary.""" + self.variables.clear() + + @staticmethod + def __tokenize(expression: str) -> list[str]: + """Tokenize the input expression.""" + pattern = r'-?\d+\.\d+|-?\d+|[a-zA-Z_]\w*|[()+\-*/]' + tokens = re.findall(pattern, expression) + return tokens + + @staticmethod + def is_is(x): + try: + xx = float(x) + return True + except: + return False + + def __convert(self, tokens: list[str]) -> list[str]: + """Convert the token list into postfix notation using the Shunting-yard algorithm.""" + output = [] + operators = [] + + for token in tokens: + if token == '(': # Left parenthesis + operators.append(token) + elif token == ')': # Right parenthesis + while operators and operators[-1] != '(': + output.append(operators.pop()) + operators.pop() + elif self.is_is(token) or token in self.variables: + output.append(token) + else: + while operators and operators[-1] != '(' and ( + ops[token][0] < ops[operators[-1]][0] or + (ops[token][0] == ops[operators[-1]][0] and ops[token][2] == 'left') + ): + output.append(operators.pop()) + operators.append(token) + + # Append remaining operators + while operators: + output.append(operators.pop()) + + return output + + def __evaluate_postfix(self, postfix: list[str]) -> float: + """Evaluate the postfix expression.""" + stack = [] + + for token in postfix: + if self.is_is(token): + stack.append(float(token)) + elif token in ops: # Apply operators + b = stack.pop() + a = stack.pop() + result = ops[token][1](a, b) + stack.append(result) + else: # Handle variables (Signal or float) + variable = self.variables.get(token, 0) + stack.append(variable) + + return stack[0] + + def evaluate(self, expression: str): + """Public method to evaluate a mathematical expression.""" + tokens = self.__tokenize(expression) + postfix = self.__convert(tokens) + result = self.__evaluate_postfix(postfix) + return result + + # New methods for managing the variables dictionary diff --git a/RunAllTests.py b/RunAllTests.py new file mode 100644 index 0000000..54b2364 --- /dev/null +++ b/RunAllTests.py @@ -0,0 +1,12 @@ +from main import setup_gui +from test import test +from Tests.Task1.code import Task1Test +from Tests.Task3.code import QuantizationTest + +class RunAllTests: + @staticmethod + def Run(): + ob = Task1Test() + ob.Task1TestRunner() + ob2 = QuantizationTest() + ob2.RunAllTests() \ No newline at end of file diff --git a/Signal.py b/Signal.py new file mode 100644 index 0000000..6a50924 --- /dev/null +++ b/Signal.py @@ -0,0 +1,123 @@ +import pandas as pd +from math import log2 +class Signal: + def __init__(self, data: dict[int, float], offset: float = 0): + """Initialize the signal with a dictionary of index-value pairs and an offset.""" + self.data = dict(sorted(data.items())) + self.offset = offset + self.min_key = min(self.data.keys()) + self.max_key = max(self.data.keys()) + self.quantized_values = [] # Stores quantized values + self.errors = [] # Stores errors for each quantized value + self.levels = [] # Stores quantization levels + self.encoded_values = [] # Stores encoded binary values for each quantized value + + def get_signal_indexs(self): + return list(self.data.keys()) + + def get_signal_values(self): + return list(self.data.values()) + def do_work(self, other, op): + """Helper function to apply an operation to two Signal objects or a Signal and a scalar.""" + if isinstance(other, Signal): + idxs = sorted(set(self.data.keys()).union(other.data.keys())) + result = {} + for i in idxs: + num1 = self.data.get(i, 0) + num2 = other.data.get(i, 0) + result[i] = op(num1, num2) + return Signal(result, offset=self.offset) + elif isinstance(other, (int, float)): + # For scalar operation, apply it to all values in the Signal + result = {i: op(self.data.get(i, 0), other) for i in self.data.keys()} + return Signal(result, offset=self.offset) + else: + return NotImplemented + + def __add__(self, other): + return self.do_work(other, lambda x, y: x + y) + + def __sub__(self, other): + return self.do_work(other, lambda x, y: x - y) + + def __mul__(self, other): + return self.do_work(other, lambda x, y: x * y) + + def __truediv__(self, other): + return self.do_work(other, lambda x, y: x / y if y != 0 else float('inf')) + + def DelayingOrAdvancingSignalByK(self, k: int): + result = {} + for kk, v in self.data.items(): + result[kk + k] = v + self.data = result + + + + def mirror(self): + result = {} + for k, v in self.data.items(): + result[k * -1] = v + self.data = dict(sorted(result.items())) + + + def __repr__(self): + return f"Signal(data={self.data}, offset={self.offset})" + + def quantize_signal(self,perception=2, level=None, bits=None): + """Perform signal quantization and store results in the class.""" + if not self.data: + print("No signal data available.") + return + + # Convert data values to a list for easier processing + data_values = list(self.data.values()) + + # Calculate minimum and maximum values from data + min_val = min(data_values) + max_val = max(data_values) + + # Determine levels based on the input (either number of levels or bits) + if level is not None: + levels = level + elif bits is not None: + levels = (1 << bits) + else: + print("Provide either levels or bits for quantization.") + return + + # Calculate the delta and the actual quantization levels + delta = (max_val - min_val) / levels + self.levels = [round(min_val + i * delta + delta / 2,perception) for i in range(levels)] + + # Calculate binary representations of levels + level_bits = int(log2(levels)) + binary_reprs = [bin(i)[2:].zfill(level_bits) for i in range(levels)] + + # Initialize lists to store quantization outputs and errors + interval_indices = [] + encoded_values = [] + quantized_values = [] + errors = [] + output = [] + # Perform quantization + for point in data_values: + err = int(1e15) + x = 0 + for i in range(levels): + if round(abs(point - self.levels[i]), perception) < err: + x = i + err = round(abs(point - self.levels[i]), perception) + output.append([x + 1, binary_reprs[x], round(self.levels[x], perception), round(self.levels[x] - point, perception)]) + interval_indices.append(x + 1) + encoded_values.append(binary_reprs[x]) + quantized_values.append(round(self.levels[x], perception)) + errors.append(round(self.levels[x] - point, perception)) + self.interval_indices= interval_indices + self.quantized_values = quantized_values + self.errors = errors + self.levels = self.levels + self.encoded_values = encoded_values + # Output the quantization details + # output = list(zip(interval_indices, encoded_values, quantized_values, errors)) + diff --git a/Tests/Task1/Signal1.txt b/Tests/Task1/Signal1.txt new file mode 100644 index 0000000..d7b6cb8 --- /dev/null +++ b/Tests/Task1/Signal1.txt @@ -0,0 +1,15 @@ +0 +0 +12 +-4 -2 +-3 2 +-2 0 +-1 2 +0 4 +1 6 +2 3 +3 1 +4 -1 +5 -3 +6 0 +7 2 \ No newline at end of file diff --git a/Tests/Task1/Signal2.txt b/Tests/Task1/Signal2.txt new file mode 100644 index 0000000..4e20d4a --- /dev/null +++ b/Tests/Task1/Signal2.txt @@ -0,0 +1,13 @@ +0 +0 +10 +-3 1 +-2 0 +-1 -1 +0 3 +1 2 +2 1 +3 -3 +4 6 +5 8 +6 3 \ No newline at end of file diff --git a/Tests/Task1/__pycache__/code.cpython-312.pyc b/Tests/Task1/__pycache__/code.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dd30d0f57edf0874de7b5441375ccac33c9d6268 GIT binary patch literal 4604 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 literal 0 HcmV?d00001 diff --git a/Tests/Task3/code.py b/Tests/Task3/code.py new file mode 100644 index 0000000..4b80bd5 --- /dev/null +++ b/Tests/Task3/code.py @@ -0,0 +1,135 @@ +import utilities +from Signal import Signal +class QuantizationTest : + def __init__(self): + self.inputFilePath=[f"Tests/Task3/Test1/in.txt",f"Tests/Task3/Test2/in.txt"] + self.outputFilePath=[f"Tests/Task3/Test1/out.txt",f"Tests/Task3/Test2/out.txt"] + def GetTestSignal( self, index : int): + index -= 1 + if index >= len(self.inputFilePath): + return -1 + filePath = self.inputFilePath[index] + return utilities.ReadSignal(filePath) + def GetTestOutputPath( self, index : int): + index -= 1 + if index >= len(self.outputFilePath): + return -1 + return self.outputFilePath[index] + + def Test1(self,signal:Signal): + Your_EncodedValues = signal.encoded_values + Your_QuantizedValues = signal.quantized_values + file_name=self.outputFilePath[0] + expectedEncodedValues = [] + expectedQuantizedValues = [] + with open(file_name, 'r') as f: + line = f.readline() + line = f.readline() + line = f.readline() + line = f.readline() + while line: + # process line + L = line.strip() + if len(L.split(' ')) == 2: + L = line.split(' ') + V2 = str(L[0]) + V3 = float(L[1]) + expectedEncodedValues.append(V2) + expectedQuantizedValues.append(V3) + line = f.readline() + else: + break + if ((len(Your_EncodedValues) != len(expectedEncodedValues)) or ( + len(Your_QuantizedValues) != len(expectedQuantizedValues))): + print("QuantizationTest1 Test case failed, your signal have different length from the expected one") + return + for i in range(len(Your_EncodedValues)): + if (Your_EncodedValues[i] != expectedEncodedValues[i]): + print( + "QuantizationTest1 Test case failed, your EncodedValues have different EncodedValues from the expected one") + return + for i in range(len(expectedQuantizedValues)): + if abs(Your_QuantizedValues[i] - expectedQuantizedValues[i]) < 0.01: + continue + else: + print( + "QuantizationTest1 Test case failed, your QuantizedValues have different values from the expected one") + return + print("QuantizationTest1 Test case passed successfully") + + def Test2(self,signal:Signal): + Your_IntervalIndices = signal.interval_indices + Your_EncodedValues = signal.encoded_values + Your_QuantizedValues =signal.quantized_values + Your_SampledError = signal.errors + file_name = self.outputFilePath[1] + expectedIntervalIndices = [] + expectedEncodedValues = [] + expectedQuantizedValues = [] + expectedSampledError = [] + with open(file_name, 'r') as f: + line = f.readline() + line = f.readline() + line = f.readline() + line = f.readline() + while line: + # process line + L = line.strip() + if len(L.split(' ')) == 4: + L = line.split(' ') + V1 = int(L[0]) + V2 = str(L[1]) + V3 = float(L[2]) + V4 = float(L[3]) + expectedIntervalIndices.append(V1) + expectedEncodedValues.append(V2) + expectedQuantizedValues.append(V3) + expectedSampledError.append(V4) + line = f.readline() + else: + break + if (len(Your_IntervalIndices) != len(expectedIntervalIndices) + or len(Your_EncodedValues) != len(expectedEncodedValues) + or len(Your_QuantizedValues) != len(expectedQuantizedValues) + or len(Your_SampledError) != len(expectedSampledError)): + print("QuantizationTest2 Test case failed, your signal have different length from the expected one") + return + + for i in range(len(Your_IntervalIndices)): + if (Your_IntervalIndices[i] != expectedIntervalIndices[i]): + print("QuantizationTest2 Test case failed, your signal have different indicies from the expected one") + return + for i in range(len(Your_EncodedValues)): + if (Your_EncodedValues[i] != expectedEncodedValues[i]): + print( + "QuantizationTest2 Test case failed, your EncodedValues have different EncodedValues from the expected one") + return + + for i in range(len(expectedQuantizedValues)): + if abs(Your_QuantizedValues[i] - expectedQuantizedValues[i]) < 0.01: + continue + else: + print( + "QuantizationTest2 Test case failed, your QuantizedValues have different values from the expected one") + return + for i in range(len(expectedSampledError)): + if abs(Your_SampledError[i] - expectedSampledError[i]) < 0.01: + continue + else: + print( + "QuantizationTest2 Test case failed, your SampledError have different values from the expected one") + return + print("QuantizationTest2 Test case passed successfully") + def RunAllTests(self): + print("Run Task 3 Tests") + print(".......................") + signal1 = self.GetTestSignal(1) + signal1.quantize_signal(level=8) + signal2 = self.GetTestSignal(2) + signal2.quantize_signal(level=4) + Tester = QuantizationTest() + Tester.Test1(signal=signal1) + Tester.Test2(signal=signal2) + print("*"*50) + + diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..3249aae --- /dev/null +++ b/__init__.py @@ -0,0 +1,4 @@ +from main import setup_gui +from RunAllTests import RunAllTests +if __name__ == "__main__": + RunAllTests.Run() \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..c10ee96 --- /dev/null +++ b/main.py @@ -0,0 +1,30 @@ +import tkinter +from task1 import Task1 +from task2 import Task2 +from task3 import Task3 # Import Task3 + +def setup_gui(): + root = tkinter.Tk() + root.geometry("870x600") + root.minsize(width=800, height=800) + custom_font = tkinter.font.Font(family="Arial", size=12, weight="bold") + style = tkinter.ttk.Style() + style.configure('W.TButton', font=('Arial', 12, 'bold')) + + root.title("Signal Operations") + + notebook = tkinter.ttk.Notebook(root) + notebook.pack(fill='both', expand=True) + + # Add task tabs + task1_frame = Task1(root) + notebook.add(task1_frame, text="Task 1: Signal Operations") + + task2_frame = Task2(root) + notebook.add(task2_frame, text="Task 2: Sine & Cosine") + + task3_frame = Task3(root) # Add Task3 + notebook.add(task3_frame, text="Task 3: Signal Quantization") + + root.mainloop() + diff --git a/task1.py b/task1.py new file mode 100644 index 0000000..cf25595 --- /dev/null +++ b/task1.py @@ -0,0 +1,134 @@ +from tkinter import ttk, filedialog +from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg +import matplotlib.pyplot as plt +from Signal import Signal +from EquationParser import EquationParser + + +class Task1(ttk.Frame): + def __init__(self, master): + super().__init__(master) + self.signals = [] + self.variables = {} + self.ep = EquationParser(self.variables) + self.plot_frame = None + self.result_frame = None + self.setup_gui() + + def setup_gui(self): + button_frame = ttk.Frame(self) + button_frame.grid(row=0, column=0, sticky="ew", padx=10, pady=5) + + equation_frame = ttk.Frame(self, borderwidth=2) + equation_frame.grid(row=1, column=0, sticky="nsew", padx=10, pady=5) + + self.plot_frame = ttk.Frame(self, borderwidth=2, relief="solid") + self.plot_frame.grid(row=2, column=0, sticky="nsew", padx=10, pady=5) + + self.result_frame = ttk.Frame(self, borderwidth=2, relief="solid") + self.result_frame.grid(row=3, column=0, sticky="nsew", padx=10, pady=5) + + self.create_widgets(button_frame, equation_frame) + + def create_widgets(self, button_frame, equation_frame): + equation_label = ttk.Label(equation_frame, text="Enter Equation (e.g., s1 + s2):") + equation_label.grid(row=0, column=0, padx=5, pady=5) + + self.equation_entry = ttk.Entry(equation_frame, width=40) + self.equation_entry.grid(row=0, column=1, padx=5, pady=5) + + # Create and add buttons + self.add_button(button_frame, 0, 0, "Open File", self.open_file) + self.add_button(button_frame, 0, 2, "S1 + S2", lambda: self.evaluate_equation("s1 + s2", "Addition Result")) + self.add_button(button_frame, 0, 3, "Subtract Signals", + lambda: self.evaluate_equation("s1 - s2", "Subtraction Result")) + self.add_button(button_frame, 0, 4, "Multiply S1 by 5", + lambda: self.evaluate_equation("s1 * 5", "Multiply S1 by 5 Result")) + self.add_button(button_frame, 0, 5, "Delay S1 by 3", self.delay_signals) + self.add_button(button_frame, 0, 6, "Advance S1 by 3", self.advance_signals) + + def add_button(self, frame, row, column, text, command): + button = ttk.Button(frame, text=text, command=command) + button.grid(row=row, column=column, padx=5, pady=7.5) + + def open_file(self): + file_paths = filedialog.askopenfilenames(title="Select Text Files", filetypes=[("Text files", "*.txt")]) + if file_paths: + for file_path in file_paths: + with open(file_path, 'r') as file: + data = {} + for line in file: + parts = line.strip().split() + if len(parts) == 2: + index = int(parts[0]) + value = float(parts[1]) + data[index] = value + self.signals.append(Signal(data)) + self.update_variables() + self.plot_signals() + + def update_variables(self): + self.variables.clear() + for idx, signal in enumerate(self.signals): + self.variables[f"s{idx + 1}"] = signal + self.ep = EquationParser(self.variables) + + def plot_signals(self): + fig, axs = plt.subplots(1, len(self.signals), figsize=(8, 3)) + if len(self.signals) == 1: + axs = [axs] # Make iterable if only one signal + + for ax, signal in zip(axs, self.signals): + ax.stem(signal.data.keys(), signal.data.values(), use_line_collection=True) + ax.set_title(f"Signal {self.signals.index(signal) + 1}") + + for i in range(len(self.signals), len(axs)): + axs[i].axis('off') # Hide unused subplots + + for widget in self.plot_frame.winfo_children(): + widget.destroy() + + canvas = FigureCanvasTkAgg(fig, master=self.plot_frame) + canvas.get_tk_widget().grid(row=0, column=0, sticky="nsew") + canvas.draw() + + def advance_signals(self): + result = self.signals[0].DelayingOrAdvancingSignalByK(3) + self.plot_result(result, "Advance S1 by 3 Result") + + def delay_signals(self): + result = self.signals[0].DelayingOrAdvancingSignalByK(-3) + self.plot_result(result, "Delay S1 by 3 Result") + + def mirror_signals(self): + result = self.signals[0].mirror() + self.plot_result(result, "Fold S1") + + def plot_result(self, result, title): + fig, ax = plt.subplots(figsize=(8, 3)) + ax.stem(result.data.keys(), result.data.values(), use_line_collection=True) + ax.set_title(title) + + for widget in self.result_frame.winfo_children(): + widget.destroy() + + canvas = FigureCanvasTkAgg(fig, master=self.result_frame) + canvas.get_tk_widget().grid(row=0, column=0, sticky="nsew") + canvas.draw() + + self.export_result(result, title) + + def export_result(self, result, title): + file_path = filedialog.asksaveasfilename( + title=f"Save {title}", defaultextension=".txt", filetypes=[("Text files", "*.txt")]) + if file_path: + with open(file_path, 'w') as file: + file.write("0\n") + file.write("0\n") + file.write("12\n") + for index, value in result.data.items(): + file.write(f"{index} {value}\n") + + def evaluate_equation(self, equation, plot_title): + result = self.ep.evaluate(equation) + self.plot_result(result, plot_title) \ No newline at end of file diff --git a/task2.py b/task2.py new file mode 100644 index 0000000..68dca0e --- /dev/null +++ b/task2.py @@ -0,0 +1,98 @@ +from tkinter import ttk +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg + +class Task2(ttk.Frame): + def __init__(self, master): + super().__init__(master) + self.plot_frame = None + self.setup_gui() + + def setup_gui(self): + # Create and organize input widgets for Task 2 + ttk.Label(self, text="Amplitude:").grid(row=0, column=0, padx=5, pady=5) + self.amplitude_entry = ttk.Entry(self) + self.amplitude_entry.grid(row=0, column=1, padx=5, pady=5) + + ttk.Label(self, text="Theta (degrees):").grid(row=1, column=0, padx=5, pady=5) + self.theta_entry = ttk.Entry(self) + self.theta_entry.grid(row=1, column=1, padx=5, pady=5) + + ttk.Label(self, text="Frequency (Hz):").grid(row=2, column=0, padx=5, pady=5) + self.frequency_entry = ttk.Entry(self) + self.frequency_entry.grid(row=2, column=1, padx=5, pady=5) + + ttk.Label(self, text="Sampling Frequency (Hz):").grid(row=3, column=0, padx=5, pady=5) + self.fs_entry = ttk.Entry(self) + self.fs_entry.grid(row=3, column=1, padx=5, pady=5) + + # Dropdown for selecting wave type (Sine or Cosine) + self.wave_type = ttk.Combobox(self, values=["Sine Wave", "Cosine Wave"]) + self.wave_type.grid(row=4, column=0, columnspan=2, padx=5, pady=5) + self.wave_type.set("Select Wave Type") + + # Dropdown for selecting plot type (Discrete, Continuous, or Both) + self.plot_type = ttk.Combobox(self, values=["Discrete", "Continuous", "Both"]) + self.plot_type.grid(row=5, column=0, columnspan=2, padx=5, pady=5) + self.plot_type.set("Select Plot Type") + + # Button to generate the signal + generate_button = ttk.Button(self, text="Generate Signal", command=self.generate_signal) + generate_button.grid(row=6, column=0, columnspan=2, padx=5, pady=10) + + # Frame for displaying the plot + self.plot_frame = ttk.Frame(self, borderwidth=2, relief="solid") + self.plot_frame.grid(row=7, column=0, columnspan=2, sticky="nsew", padx=10, pady=10) + + def generate_signal(self): + try: + amplitude = float(self.amplitude_entry.get()) + theta = np.radians(float(self.theta_entry.get())) # Convert degrees to radians + frequency = float(self.frequency_entry.get()) + fs = float(self.fs_entry.get()) + + # Time array for both continuous and discrete signals + t_continuous = np.linspace(0, 4, 1000) # Fine sampling for continuous signal + t_discrete = np.arange(0, 4, 1/fs) # Discrete time steps based on fs + + # Generate the signal based on the selected wave type + if self.wave_type.get() == "Sine Wave": + continuous_signal = amplitude * np.sin(2 * np.pi * frequency * t_continuous + theta) + discrete_signal = amplitude * np.sin(2 * np.pi * frequency * t_discrete + theta) + elif self.wave_type.get() == "Cosine Wave": + continuous_signal = amplitude * np.cos(2 * np.pi * frequency * t_continuous + theta) + discrete_signal = amplitude * np.cos(2 * np.pi * frequency * t_discrete + theta) + else: + raise ValueError("Invalid wave type selected") + + self.plot_generated_signal(t_continuous, continuous_signal, t_discrete, discrete_signal) + + except ValueError as e: + print(f"Error: {e}") + + def plot_generated_signal(self, t_continuous, continuous_signal, t_discrete, discrete_signal): + fig, ax = plt.subplots(figsize=(8, 3)) + + # Determine which plot types to show (discrete, continuous, or both) + plot_type = self.plot_type.get() + if plot_type == "Discrete": + ax.stem(t_discrete, discrete_signal, use_line_collection=True, label="Discrete") + elif plot_type == "Continuous": + ax.plot(t_continuous, continuous_signal, label="Continuous") + elif plot_type == "Both": + ax.plot(t_continuous, continuous_signal, label="Continuous") + ax.stem(t_discrete, discrete_signal, use_line_collection=True, label="Discrete") + + ax.set_title(f"{self.wave_type.get()} - {plot_type}") + ax.set_xlabel("Time (s)") + ax.set_ylabel("Amplitude") + # ax.legend() + + # Clear any previous plots + for widget in self.plot_frame.winfo_children(): + widget.destroy() + + canvas = FigureCanvasTkAgg(fig, master=self.plot_frame) + canvas.get_tk_widget().grid(row=0, column=0, sticky="nsew") + canvas.draw() \ No newline at end of file diff --git a/task3.py b/task3.py new file mode 100644 index 0000000..30a4f15 --- /dev/null +++ b/task3.py @@ -0,0 +1,70 @@ +from tkinter import ttk, filedialog +from Signal import Signal +from Tests.Task3.code import QuantizationTest + +class Task3(ttk.Frame): + def __init__(self, master): + super().__init__(master) + self.plot_frame = None + self.signal_data = {} + self.setup_gui() + + def setup_gui(self): + ttk.Label(self, text="Select Input Type:").grid(row=0, column=0, padx=5, pady=5) + self.input_type = ttk.Combobox(self, values=["Levels", "Bits"]) + self.input_type.grid(row=0, column=1, padx=5, pady=5) + self.input_type.set("Select Input Type") + + ttk.Label(self, text="Enter Value:").grid(row=1, column=0, padx=5, pady=5) + self.value_entry = ttk.Entry(self) + self.value_entry.grid(row=1, column=1, padx=5, pady=5) + + ttk.Label(self, text="Select test:").grid(row=2, column=0, padx=5, pady=5) + self.test_value = ttk.Combobox(self, values=["test-1", "test-2"]) + self.test_value.grid(row=2, column=1, padx=5, pady=5) + self.test_value.set("Select test") + + # open_file_button = ttk.Button(self, text="Open File", command=Read()) + # open_file_button.grid(row=3, column=0, padx=5, pady=10) + + generate_button = ttk.Button(self, text="Quantize Signal", command=self.quantize_signal) + generate_button.grid(row=3, column=1, padx=5, pady=10) + def quantize_signal(self): + """ + Perform signal quantization based on input type and value. + """ + + Tester = QuantizationTest() + Tester.RunAllTests() + if self.test_value.get() == "test-1": + testNumber = 1 + elif self.test_value.get() == "test-2": + testNumber = 2 + + # Read the input file using relative path + signal = Tester.GetTestSignal(testNumber) + + if signal is None: + print("No signal data available.") + return + + # Get the Levels + LevelsOrBits = self.input_type.get() + value = int(self.value_entry.get()) + + # Set up perception + perception = 2 if not testNumber == 2 else 3 + + # call quantaize the signal + if LevelsOrBits == "Levels" : + signal.quantize_signal(level = value, perception = perception) + if LevelsOrBits == "Bits" : + signal.quantize_signal(bits = value, perception = perception) + + # Run the testing + if testNumber == 1 : + Tester.Test1(signal) + else: + Tester.Test2(signal) + + diff --git a/test.py b/test.py new file mode 100644 index 0000000..e389d79 --- /dev/null +++ b/test.py @@ -0,0 +1,31 @@ +from EquationParser import EquationParser +from Signal import Signal +def test(): + dic = { + 0 :0.387, + 1: 0.430, + 2 :0.478, + 3: 0.531, + 4: 0.590, + 5 :0.6561, + 6 :0.729, + 7 :0.81, + 8 :0.9, + 9 :1, + 10: 0.2, + } + dic1={ + 0 :-1.22, + 1 :1.5, + 2: 3.24, + 3 :3.94, + 4 :2.20, + 5 :-1.1, + 6 :-2.26, + 7 :-1.88, + 8 :-1.2, + } + s1 = Signal(data=dic1) + s1.quantize_signal(bits=2) + print(s1.quantized_values) + print(s1.errors) diff --git a/utilities.py b/utilities.py new file mode 100644 index 0000000..237113b --- /dev/null +++ b/utilities.py @@ -0,0 +1,23 @@ +from Signal import Signal +def ReadSignal(file_path:str=None): + """ + Open a file and read a single signal from it. + """ + + if not file_path: + return None + + with open(file_path, 'r') as file: + # Read the first three numbers + temp = int(file.readline().strip()) + offest = int(file.readline().strip()) + num_points = int(file.readline().strip()) + # Read the points + signal_data:dict[int, float] ={} + for line in file: + parts = line.strip().split() + index = int(parts[0]) + value = float(parts[1]) + signal_data[index] = value + signal = Signal(data=signal_data,offset=offest) + return signal \ No newline at end of file