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 0000000..dd30d0f Binary files /dev/null and b/Tests/Task1/__pycache__/code.cpython-312.pyc differ diff --git a/Tests/Task1/add.txt b/Tests/Task1/add.txt new file mode 100644 index 0000000..3100199 --- /dev/null +++ b/Tests/Task1/add.txt @@ -0,0 +1,15 @@ +0 +0 +12 +-4 -2 +-3 3 +-2 0 +-1 1 +0 7 +1 8 +2 4 +3 -2 +4 5 +5 5 +6 3 +7 2 \ No newline at end of file diff --git a/Tests/Task1/advance3.txt b/Tests/Task1/advance3.txt new file mode 100644 index 0000000..7c79560 --- /dev/null +++ b/Tests/Task1/advance3.txt @@ -0,0 +1,15 @@ +0 +0 +12 +-7 -2 +-6 2 +-5 0 +-4 2 +-3 4 +-2 6 +-1 3 +0 1 +1 -1 +2 -3 +3 0 +4 2 \ No newline at end of file diff --git a/Tests/Task1/code.py b/Tests/Task1/code.py new file mode 100644 index 0000000..2ad71f9 --- /dev/null +++ b/Tests/Task1/code.py @@ -0,0 +1,72 @@ +from utilities import ReadSignal +def check_signal(signal3 ,Expected ,Message): + Your_indices,Your_samples = signal3.get_signal_indexs(),signal3.get_signal_values() + expected_indices,expected_samples=Expected.get_signal_indexs(),Expected.get_signal_values() + if (len(expected_samples)!=len(Your_samples)) and (len(expected_indices)!=len(Your_indices)): + return Message+" Test case failed, your signal have different length from the expected one" + + for i in range(len(Your_indices)): + if(Your_indices[i]!=expected_indices[i]): + return Message + " Test case failed, your signal have different indicies from the expected one" + for i in range(len(expected_samples)): + if abs(Your_samples[i] - expected_samples[i]) < 0.01: + continue + else: + return Message +" Test case failed, your signal have different values from the expected one" + + return Message +" Test case passed successfully" + +class Task1Test: + def AddationTest (self): + signal1 = ReadSignal("Tests/Task1/Signal1.txt") + signal2 = ReadSignal("Tests/Task1/Signal2.txt") + signal3 = signal1 + signal2 + addSignal = ReadSignal("Tests/Task1/add.txt") + print(check_signal(signal3,addSignal,"Addation")) + + def SubtractiaonTest (self): + signal1 = ReadSignal("Tests/Task1/Signal1.txt") + signal2 = ReadSignal("Tests/Task1/Signal2.txt") + signal3 = signal1 - signal2 + SubSignal = ReadSignal("Tests/Task1/subtract.txt") + result = check_signal(signal3 , SubSignal , "Subtraction") + print(result) + + def MulTest(self): + signal1 = ReadSignal("Tests/Task1/Signal1.txt") + signal3 = signal1 * 5 + mulBy5Signal = ReadSignal("Tests/Task1/mul5.txt") + result = check_signal(signal3 , mulBy5Signal , "Mul5") + print(result) + + def AdavnceTest(self): + signal1 = ReadSignal("Tests/Task1/Signal1.txt") + advance5 = ReadSignal("Tests/Task1/advance3.txt") + signal1.DelayingOrAdvancingSignalByK(-3) + result = check_signal(signal1,advance5,"Advacning By 5 ") + print(result) + + def DelayTest(self): + signal1 = ReadSignal("Tests/Task1/Signal1.txt") + delay5 = ReadSignal("Tests/Task1/delay3.txt") + signal1.DelayingOrAdvancingSignalByK(3) + result = check_signal(signal1,delay5,"Delay By 5 ") + print(result) + + def FoldingTest(self): + signal1 = ReadSignal("Tests/Task1/Signal1.txt") + mirror = ReadSignal("Tests/Task1/folding.txt") + signal1.mirror() + result = check_signal(signal1,mirror,"Folding") + print(result) + + def Task1TestRunner(self): + print("Run Task 1 Tests ") + print(".......................") + self.AddationTest() + self.SubtractiaonTest() + self.MulTest() + self.AdavnceTest() + self.DelayTest() + self.FoldingTest() + print("*"*50) diff --git a/Tests/Task1/delay3.txt b/Tests/Task1/delay3.txt new file mode 100644 index 0000000..37d7272 --- /dev/null +++ b/Tests/Task1/delay3.txt @@ -0,0 +1,15 @@ +0 +0 +12 +-1 -2 +0 2 +1 0 +2 2 +3 4 +4 6 +5 3 +6 1 +7 -1 +8 -3 +9 0 +10 2 \ No newline at end of file diff --git a/Tests/Task1/folding.txt b/Tests/Task1/folding.txt new file mode 100644 index 0000000..50fa727 --- /dev/null +++ b/Tests/Task1/folding.txt @@ -0,0 +1,15 @@ +0 +0 +12 +-7 2 +-6 0 +-5 -3 +-4 -1 +-3 1 +-2 3 +-1 6 +0 4 +1 2 +2 0 +3 2 +4 -2 \ No newline at end of file diff --git a/Tests/Task1/mul5.txt b/Tests/Task1/mul5.txt new file mode 100644 index 0000000..83f8a47 --- /dev/null +++ b/Tests/Task1/mul5.txt @@ -0,0 +1,15 @@ +0 +0 +12 +-4 -10 +-3 10 +-2 0 +-1 10 +0 20 +1 30 +2 15 +3 5 +4 -5 +5 -15 +6 0 +7 10 \ No newline at end of file diff --git a/Tests/Task1/subtract.txt b/Tests/Task1/subtract.txt new file mode 100644 index 0000000..62b2291 --- /dev/null +++ b/Tests/Task1/subtract.txt @@ -0,0 +1,15 @@ +0 +0 +12 +-4 -2 +-3 1 +-2 0 +-1 3 +0 1 +1 4 +2 2 +3 4 +4 -7 +5 -11 +6 -3 +7 2 \ No newline at end of file diff --git a/Tests/Task3/Test1/in.txt b/Tests/Task3/Test1/in.txt new file mode 100644 index 0000000..e4a9b1f --- /dev/null +++ b/Tests/Task3/Test1/in.txt @@ -0,0 +1,14 @@ +0 +0 +11 +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 \ No newline at end of file diff --git a/Tests/Task3/Test1/out.txt b/Tests/Task3/Test1/out.txt new file mode 100644 index 0000000..50bf62f --- /dev/null +++ b/Tests/Task3/Test1/out.txt @@ -0,0 +1,14 @@ +0 +0 +11 +001 0.35 +010 0.45 +010 0.45 +011 0.55 +011 0.55 +100 0.65 +101 0.75 +110 0.85 +110 0.85 +111 0.95 +000 0.25 \ No newline at end of file diff --git a/Tests/Task3/Test2/in.txt b/Tests/Task3/Test2/in.txt new file mode 100644 index 0000000..e634f6d --- /dev/null +++ b/Tests/Task3/Test2/in.txt @@ -0,0 +1,12 @@ +0 +0 +9 +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 \ No newline at end of file diff --git a/Tests/Task3/Test2/out.txt b/Tests/Task3/Test2/out.txt new file mode 100644 index 0000000..1779871 --- /dev/null +++ b/Tests/Task3/Test2/out.txt @@ -0,0 +1,12 @@ +0 +0 +9 +1 00 -1.485 -0.265 +3 10 1.615 0.115 +4 11 3.165 -0.075 +4 11 3.165 -0.775 +3 10 1.615 -0.585 +1 00 -1.485 -0.385 +1 00 -1.485 0.775 +1 00 -1.485 0.395 +1 00 -1.485 -0.285 \ No newline at end of file diff --git a/Tests/Task3/__pycache__/code.cpython-312.pyc b/Tests/Task3/__pycache__/code.cpython-312.pyc new file mode 100644 index 0000000..b64981b Binary files /dev/null and b/Tests/Task3/__pycache__/code.cpython-312.pyc differ 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