diff --git a/src/visualising/communication/illustration/__init__.py b/src/visualising/communication/animation/__init__.py similarity index 100% rename from src/visualising/communication/illustration/__init__.py rename to src/visualising/communication/animation/__init__.py diff --git a/src/visualising/communication/illustration/animation.py b/src/visualising/communication/animation/animation.py similarity index 100% rename from src/visualising/communication/illustration/animation.py rename to src/visualising/communication/animation/animation.py diff --git a/src/visualising/communication/illustration/color/__init__.py b/src/visualising/communication/animation/color/__init__.py similarity index 100% rename from src/visualising/communication/illustration/color/__init__.py rename to src/visualising/communication/animation/color/__init__.py diff --git a/src/visualising/communication/illustration/color/color.py b/src/visualising/communication/animation/color/color.py similarity index 100% rename from src/visualising/communication/illustration/color/color.py rename to src/visualising/communication/animation/color/color.py diff --git a/src/visualising/communication/illustration/color/rgb.py b/src/visualising/communication/animation/color/rgb.py similarity index 100% rename from src/visualising/communication/illustration/color/rgb.py rename to src/visualising/communication/animation/color/rgb.py diff --git a/src/visualising/communication/animation/ensemble.py b/src/visualising/communication/animation/ensemble.py new file mode 100644 index 0000000000000000000000000000000000000000..96b41ce71f7d240e09810687bab307e9dc51fd89 --- /dev/null +++ b/src/visualising/communication/animation/ensemble.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python + +import re +import importlib.resources + +from visualising.illustration.animation.color.rgb import RGB +from visualising.illustration.animation.pixel import Pixel +from visualising.illustration.animation.frame import Frame + + +class Ensemble: + def __init__(self, l_frame, r_frame): + self.l_frame = l_frame + self.r_frame = r_frame + + def illuminated(self, index): + if not 0 <= index <= 31: + raise ValueError("The parameter index must be an integer between 0 and 31!") + if index < 16: + return self.l_frame.illuminated(index) + else: + return self.r_frame.illuminated(index - 16) + + def replace_pixel(self, index, pixel): + if not 0 <= index <= 31: + raise ValueError("The parameter index must be an integer between 0 and 31!") + if index < 16: + self.l_frame.replace_pixel(index, pixel) + else: + self.r_frame.replace_pixel(index - 16, pixel) + + @staticmethod + def empty(): + l_pixels = [] + r_pixels = [] + for _ in range(16): + l_pixels.append(Pixel(RGB(0, 0, 0))) + r_pixels.append(Pixel(RGB(0, 0, 0))) + + return Ensemble(Frame(l_pixels), Frame(r_pixels)) diff --git a/src/visualising/communication/illustration/frame.py b/src/visualising/communication/animation/frame.py similarity index 88% rename from src/visualising/communication/illustration/frame.py rename to src/visualising/communication/animation/frame.py index 315ea427daf34fd1a02ea612d6c79921746509f0..1c5fe2560257a2b968e1fa618376623f07c720ed 100644 --- a/src/visualising/communication/illustration/frame.py +++ b/src/visualising/communication/animation/frame.py @@ -17,9 +17,9 @@ class Frame: def illuminated(self, index): if not 0 <= index <= 15: raise ValueError("The parameter index must be an integer between 0 and 15!") - return self._pixels[index].color.illuminated + return self.pixels[index].color.illuminated def replace_pixel(self, index, pixel): if not 0 <= index <= 15: raise ValueError("The parameter index must be an integer between 0 and 15!") - self._pixels[index] = pixel + self.pixels[index] = pixel diff --git a/src/visualising/communication/illustration/pixel.py b/src/visualising/communication/animation/pixel.py similarity index 100% rename from src/visualising/communication/illustration/pixel.py rename to src/visualising/communication/animation/pixel.py diff --git a/src/visualising/communication/arduino.py b/src/visualising/communication/arduino.py index fecbec8c8d2e1c08bb3a701f0524c353eb99bbea..735b3244892e0c2fcb8439e387929cd806d9757d 100755 --- a/src/visualising/communication/arduino.py +++ b/src/visualising/communication/arduino.py @@ -2,7 +2,6 @@ import time -from visualising.communication.channel.connection import Connection from visualising.communication.channel.message.msg_frame import MsgFrame from visualising.communication.channel.message.msg_instr import MsgInstr from visualising.communication.channel.message.response import Response @@ -11,53 +10,43 @@ from visualising.communication.illustration.animation import Animation class Arduino: def __init__(self, connection): - self._connection = connection - - @property - def connection(self): - return self._connection - - @connection.setter - def connection(self, connection): - if not isinstance(connection, Connection): - raise TypeError("The parameter connection must be an object of the Connection class!") - self._connection = connection + self.connection = connection # Creates instruction message and sends it to the Arduino, # to play a loaded animation. def start_playback(self): - self._connection.confirm_msg(MsgInstr('B'), [Response(b'\x1f')], 5) + self.connection.confirm_msg(MsgInstr('B'), [Response(b'\x1f')], 5) # Creates instruction message and sends it to the Arduino, # to pause a playing animation. def pause_playback(self): - self._connection.confirm_msg(MsgInstr('C'), [Response(b'\x5f')], 5) + self.connection.confirm_msg(MsgInstr('C'), [Response(b'\x5f')], 5) # Creates instruction message and sends it to the Arduino, # to resume playback of a loaded animation. def resume_playback(self): - self._connection.confirm_msg(MsgInstr('D'), [Response(b'\x6f')], 5) + self.connection.confirm_msg(MsgInstr('D'), [Response(b'\x6f')], 5) # Creates instruction message and sends it to the Arduino, # to clear the NeoPixel rings. def reset_display(self): - self._connection.confirm_msg(MsgInstr('E'), [Response(b'\x7f')], 5) + self.connection.confirm_msg(MsgInstr('E'), [Response(b'\x7f')], 5) # Creates one instruction messages and multiple frame messages to load an animation # onto the Arduino. def load_animation(self, animation): - if not isinstance(animation, Animation) or len(animation.ensembles) > 16: - raise ValueError("The animation parameter must be an object of the Animation class, which may contain a " - "maximum of 16 ensembles!") + if len(animation.ensembles) > 16: + raise ValueError("The animation parameter must be an object of the Animation class, " + "which may contain a maximum of 16 ensembles!") - self._connection.confirm_msg(MsgInstr('A', animation), [Response(b'\x0f')], 5) + self.connection.confirm_msg(MsgInstr('A', animation), [Response(b'\x0f')], 5) for ensemble in animation.ensembles: l_frame = ensemble.l_frame r_frame = ensemble.r_frame - self._connection.confirm_msg(MsgFrame(l_frame), [Response(b'\x2f'), Response(b'\x3f')], 10) - self._connection.confirm_msg(MsgFrame(r_frame), [Response(b'\x2f'), Response(b'\x3f')], 10) + self.connection.confirm_msg(MsgFrame(l_frame), [Response(b'\x2f'), Response(b'\x3f')], 10) + self.connection.confirm_msg(MsgFrame(r_frame), [Response(b'\x2f'), Response(b'\x3f')], 10) # First, an animation is loaded onto the Arduino. The playback of this # animation is then started. In addition, it is checked whether the @@ -74,7 +63,7 @@ class Arduino: # Current time in seconds. curr_time = time.time() - while not Response(b'\x4f').compare(self._connection.receive_msg()): + while not Response(b'\x4f').compare(self.connection.receive_msg()): # Wait 10 times the estimated animation playback time for a response. if time.time() > curr_time + playback_time * 10: raise ArduinoException("Successful playback of animation can't be confirmed!") @@ -91,6 +80,7 @@ class Arduino: for ensemble in animation.ensembles: self.play_animation(Animation([ensemble], animation.ensemble_time, 1)) + # Streams multiple animations in succession to the Arduino. def stream_animations(self, animations): for animation in animations: self.stream_animation(animation) diff --git a/src/visualising/communication/channel/message/msg_instr.py b/src/visualising/communication/channel/message/msg_instr.py index 495e2f9a72d31541c855052f437ef29ff90269f3..14b0a1f2301f4447cc6254d2d46159a464190b9a 100644 --- a/src/visualising/communication/channel/message/msg_instr.py +++ b/src/visualising/communication/channel/message/msg_instr.py @@ -22,7 +22,7 @@ class MsgInstr(Message): # actual message, most of the message consists only of zeros. The first and last bit # are intended for authentication as an instruction message. def export_message(self): - message = [bytes('[', 'ascii'), bytes(self._instr, 'ascii')] + message = [bytes('[', 'ascii'), bytes(self.instr, 'ascii')] if self.animation is not None: num_frames = len(self.animation.ensembles) * 2 diff --git a/src/visualising/communication/illustration/ensemble.py b/src/visualising/communication/illustration/ensemble.py deleted file mode 100644 index 69f3c53a6d70a467f1ee03ff5b27bbbc8cdda6a2..0000000000000000000000000000000000000000 --- a/src/visualising/communication/illustration/ensemble.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env python - -class Ensemble: - def __init__(self, l_frame, r_frame): - self.l_frame = l_frame - self.r_frame = r_frame - - def replace_pixel(self, index, pixel): - if not 0 <= index <= 31: - raise ValueError("The parameter index must be an integer between 0 and 31!") - if index < 16: - self.l_frame.replace_pixel(index, pixel) - else: - self.r_frame.replace_pixel(index - 16, pixel) diff --git a/src/visualising/expression/emotion.py b/src/visualising/expression/emotion.py index b514670e8fc2da191e752dc40b60f0a08080eb0c..079df3722a56797c6fc52571155b67668108c295 100644 --- a/src/visualising/expression/emotion.py +++ b/src/visualising/expression/emotion.py @@ -1,7 +1,7 @@ import time -from visualising.communication.arduino import Arduino -from visualising.communication.illustration.animation import Animation +from visualising.expression.library.raw import Raw +from visualising.communication.illustration.ensemble import Ensemble from visualising.communication.illustration.color.rgb import RGB from visualising.expression.expression import Expression from visualising.expression.tool import Tool @@ -9,7 +9,7 @@ from visualising.expression.tool import Tool class Emotion(Expression): def __init__(self, arduino): - self._arduino = arduino + self.arduino = arduino self.playback_start = 0 self.refresh = 5 @@ -17,16 +17,6 @@ class Emotion(Expression): self.ok = "expression_ok.txt" self.angry = "expression_angry.txt" - @property - def arduino(self): - return self._arduino - - @arduino.setter - def arduino(self, arduino): - if not isinstance(arduino, Arduino): - raise TypeError("The parameter arduino must be an object of the Arduino class!") - self._arduino = arduino - def react(self, state): if time.time() - self.playback_start > self.refresh: color = Tool.state_to_color(state[0], RGB(25, 25, 25), RGB(25, 0, 0)) @@ -39,53 +29,36 @@ class Emotion(Expression): self.visualise(self.happy, color) def visualise(self, filepath, color): - ensembles = Tool.create_ensembles(filepath, "emotion") - self.arduino.stream_animation(Emotion.build_emotion_parallel(ensembles[0], 50, color)) + still = Raw.to_ensemble_list(filepath, "emotion") + buildup = Tool.build_emotion(still[0], color) + animation = Animation(buildup, 50, 1) + self.arduino.stream_animation(animation) self.playback_start = time.time() @staticmethod - def build_emotion(blueprint, time, color): + def build_emotion(still, color): ensembles = [] - for i in range(32): - ensemble = Tool.create_empty_ensemble() + for i in range(32 + 1): + empty = Ensemble.empty() for j in range(i + 1): - if j < 16: - illuminated = blueprint.l_frame.illuminated(j) - else: - illuminated = bluepring.r_frame.illuminated(j % 16) - - if illuminated or j == i: - ensemble = Tool.set_pixel_color(ensemble, j, color) - - ensembles.append(ensemble) - - ensemble = Tool.create_empty_ensemble() - for k in range(32): - if k < 16: - illuminated = blueprint.l_frame.illuminated(k) - else: - illuminated = blueprint.r_frame.illuminated(k % 16) - - if illuminated: - ensemble = Tool.set_pixel_color(ensemble, k, color) - - ensembles.append(ensemble) - - return Animation(ensembles, time, 1) + if still.illuminated(j) or (j == i and i != 32): + empty.replace_pixel(j, Pixel(color)) + ensembles.append(empty) + return ensembles @staticmethod - def build_emotion_parallel(blueprint, time, color): + def build_emotion_parallel(still, color): ensembles = [] - for i in range(16): - ensemble = Tool.create_empty_ensemble() + for i in range(16 + 1): + empty = Ensemble.empty() for j in range(i + 1): - if blueprint.l_frame.illuminated(j): - ensemble = Tool.set_pixel_color(ensemble, j, color) - if blueprint.r_frame.illuminated(j): - ensemble = Tool.set_pixel_color(ensemble, 16 + j, color) + if still.l_frame.illuminated(j): + empty.replace_pixel(j, Pixel(color)) + if still.r_frame.illuminated(j): + empty.replace_pixel(j, Pixel(color)) if j == i: - ensemble = Tool.set_pixel_color(ensemble, j, color) - ensemble = Tool.set_pixel_color(ensemble, 16 + j, color) + empty.replace_pixel(j, Pixel(color)) + empty.replace_pixel(16 + j, Pixel(color)) ensembles.append(ensemble) diff --git a/src/visualising/expression/library/emotion/__init__.py b/src/visualising/expression/library/expression/__init__.py similarity index 100% rename from src/visualising/expression/library/emotion/__init__.py rename to src/visualising/expression/library/expression/__init__.py diff --git a/src/visualising/expression/library/emotion/expression_angry.txt b/src/visualising/expression/library/expression/expression_angry.txt similarity index 100% rename from src/visualising/expression/library/emotion/expression_angry.txt rename to src/visualising/expression/library/expression/expression_angry.txt diff --git a/src/visualising/expression/library/emotion/expression_clean.txt b/src/visualising/expression/library/expression/expression_clear.txt similarity index 100% rename from src/visualising/expression/library/emotion/expression_clean.txt rename to src/visualising/expression/library/expression/expression_clear.txt diff --git a/src/visualising/expression/library/emotion/expression_happy.txt b/src/visualising/expression/library/expression/expression_happy.txt similarity index 100% rename from src/visualising/expression/library/emotion/expression_happy.txt rename to src/visualising/expression/library/expression/expression_happy.txt diff --git a/src/visualising/expression/library/emotion/expression_ok.txt b/src/visualising/expression/library/expression/expression_ok.txt similarity index 100% rename from src/visualising/expression/library/emotion/expression_ok.txt rename to src/visualising/expression/library/expression/expression_ok.txt diff --git a/src/visualising/expression/library/raw.py b/src/visualising/expression/library/raw.py new file mode 100644 index 0000000000000000000000000000000000000000..4cb1bcd03b532566c15fed4ced324995cfd7184d --- /dev/null +++ b/src/visualising/expression/library/raw.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python + +import re +import importlib.resources + +from visualising.illustration.animation.color.rgb import RGB +from visualising.illustration.animation.pixel import Pixel +from visualising.illustration.animation.frame import Frame +from visualising.illustration.animation.ensemble import Ensemble +from visualising.illustration.animation.animation import Animation +from visualising.expression.library import alphabet +from visualising.expression.library import expression + + +class Raw: + def __init__(self): + pass + + @staticmethod + def to_ensemble_list(filename, directory): + def load(): + if directory != "expression" and directory != "alphabet": + raise ValueError("The parameter directory describes a unknown directory!") + if directory == "expression": + return importlib.resources.open_text(emotion, filename) + if directory == "alphabet": + return importlib.resources.open_text(alphabet, filename) + + def to_pixel_list(line): + pixels = [] + for (r, g, b) in line.split(","): + pixels.append( + Pixel( + RGB( + int(re.search(r'\d+', r).group()), + int(re.search(r'\d+', g).group()), + int(re.search(r'\d+', b).group()) + ) + ) + ) + return pixels + + file = load() + ensembles = [] + while True: + l_line = file.readline() + r_line = file.readline() + + if bool(l_line) != bool(r_line): + raise ValueError("Animation must have an even number of frames!") + if not l_line and not r_line: + break + + l_frame = Frame(to_pixel_list(l_line)) + r_frame = Frame(to_pixel_list(r_line)) + + ensembles.append(Ensemble(l_frame, r_frame)) + + return ensembles + + @staticmethod + def to_animation(filename, directory, ensemble_time, num_iter): + ensemble_list = Raw.to_ensemble_list(filename, directory) + return Animation(ensemble_list, ensemble_time, num_iter) diff --git a/src/visualising/expression/tool.py b/src/visualising/expression/tool.py index 284b29d5ed62a5ac3db00a56f100a247653d9aba..b38bad83e3b6df9f4342911b318cd9b4b3ac06ef 100644 --- a/src/visualising/expression/tool.py +++ b/src/visualising/expression/tool.py @@ -1,117 +1,14 @@ #!/usr/bin/env python -import re import numpy as np -import importlib.resources -from visualising.communication.illustration.animation import Animation -from visualising.communication.illustration.ensemble import Ensemble -from visualising.communication.illustration.frame import Frame -from visualising.communication.illustration.pixel import Pixel from visualising.communication.illustration.color.rgb import RGB -from visualising.expression.library import alphabet -from visualising.expression.library import emotion class Tool: def __init__(self): pass - @staticmethod - def read_raw_ensembles(filename, directory): - def read_raw_frame(line): - raw_frame = [] - for value in line.split(","): - raw_frame.append( - int(re.search(r'\d+', value).group()) - ) - return raw_frame - - if directory == "emotion": - file = importlib.resources.open_text(emotion, filename) - elif directory == "alphabet": - file = importlib.resources.open_text(alphabet, filename) - else: - raise ValueError("The parameter directory describes a unknown directory!") - - raw_ensembles = [] - while True: - l_line = file.readline() - r_line = file.readline() - - if bool(l_line) != bool(r_line): - raise ValueError("Animation must have an even number of frames!") - if not l_line and not r_line: - break - - l_raw_frame = read_raw_frame(l_line) - r_raw_frame = read_raw_frame(r_line) - - raw_ensembles.append([l_raw_frame, r_raw_frame]) - - return raw_ensembles - - @staticmethod - def create_ensembles(filename, directory): - def create_frame(raw_pixels): - if len(raw_pixels) != 48: - raise ValueError("Frame must have exactly 48 values!") - - pixels = [] - - i = 0 - while i < 48: - r = raw_pixels[i + 0] - g = raw_pixels[i + 1] - b = raw_pixels[i + 2] - - pixels.append(Pixel(RGB(r, g, b))) - i = i + 3 - - return Frame(pixels) - - raw_ensembles = Tool.read_raw_ensembles(filename, directory) - - ensembles = [] - for raw_ensemble in raw_ensembles: - if len(raw_ensemble) != 2: - raise ValueError("Ensemble must have exactly two frames!") - - l_frame = create_frame(raw_ensemble[0]) - r_frame = create_frame(raw_ensemble[1]) - - ensembles.append(Ensemble(l_frame, r_frame)) - - return ensembles - - @staticmethod - def create_animation(filename, directory, ensemble_time, iterations): - ensembles = Tool.create_ensembles(filename, directory) - return Animation(ensembles, ensemble_time, iterations) - - @staticmethod - def create_empty_ensemble(): - l_pixels = [] - r_pixels = [] - for _ in range(16): - l_pixels.append(Pixel(RGB(0, 0, 0))) - r_pixels.append(Pixel(RGB(0, 0, 0))) - - return Ensemble(Frame(l_pixels), Frame(r_pixels)) - - @staticmethod - def set_pixel_color(ensemble, index, color): - if not isinstance(index, int) or not 0 <= index <= 31: - raise ValueError("The parameter index must be an integer between 0 and 31!") - - if index < 16: - ensemble.l_frame.replace_pixel(index, Pixel(color)) - else: - index = index - 16 - ensemble.r_frame.replace_pixel(index, Pixel(color)) - - return ensemble - @staticmethod def state_to_color(state, start_color, end_color): if not 0 <= state <= 1: