Repository: AaltoPML/VAI-Lab (language: python)

Issue:
Css core UI
# Purpose 
Add a GUI to the framework initialisation.

## In brief
- Added a new front page that asks for a data path and a pipeline definition.
- Adapted the Core functions to be called by the GUI on button press.
- Restructured files to have all GUIs for every module in the same folder.

## New GUI for the framework initialisation
![AIFRED](https://user-images.githubusercontent.com/22427519/165497546-00236f6d-d72a-43e0-a123-4ca340a4fbf1.png)

When opening the core program it now launches this GUI to facilitate the user interaction with the framework. The objective is to request the location of the data to be used by the model and define the pipeline to be followed. To do so, the GUI checks for:
- `Data folder`. Asks for the data files location and stores the path for the subsequent data loading.
- **Pipeline definition**. Offers two different options to define the pipeline:
  - `Upload XML file`. Asks for an XML file containing the pipeline structure.
  - `Interact with canvas`. Opens an interactive canvas to design the pipeline. On `save`, it creates an XML file with the pipeline definition. `Upload` allows to load a predefined pipeline on an XML file for edition.

Once both things are indicated, it launches the specified modules.


## File restructuring
Now, all GUI files are stored in the same folder and are accesed by the different modules to require specific displays. 
This requires some discussion and agreement in will likey be changed.


Discussion / hints:

Fix patch:
diff --git a/aidesign/Core/AI_design_core.py b/aidesign/Core/AI_design_core.py
new file mode 100644
index 00000000..3dd83f52
--- /dev/null
+++ b/aidesign/Core/AI_design_core.py
@@ -0,0 +1,92 @@
+# from importlib import import_module
+from aidesign.utils.import_helper import import_module
+from .. import Settings
+from .. import GUI
+
+
+class Core(Settings):
+    def __init__(self) -> None:
+        super().__init__()
+        self.loop_level = 0
+
+    def launch(self):
+        gui_app = GUI.GUI()
+        gui_app.set_plugin_name('main')
+        gui_output = gui_app.launch()
+        try:
+            self.load_config_file(gui_output["xml_filename"])
+        except:
+            raise Exception("No XML File Selected. Cannot Run Pipeline")
+        self.run()
+
+    def load_config_file(self, filename: str):
+        self.load_XML(filename)
+
+    def _execute_module(self, specs):
+        """Executes named module with given options
+        Imports, instantiates, sets params, then launches module
+
+        :param specs: dict of module to be executed
+        """
+        mod = import_module(globals(), specs["module_type"])()
+        mod.set_options(specs)
+        print("\t"*self.loop_level
+                + specs["module_type"]
+                + " module: \"{}\"".format(specs["name"])
+                + "processing..."
+              )
+        mod.launch()
+
+    def _execute_loop(self, specs):
+        try:
+            print("\t"*self.loop_level
+                  + specs["type"]
+                  + " loop: "
+                    + "\"{}\"".format(specs["name"])
+                    + " starting...")
+            self.loop_level += 1
+            getattr(self, "_execute_{}_loop".format(specs["type"]))(specs)
+            self.loop_level -= 1
+        except KeyError:
+            print("\nError: Invalid Loop Type.")
+            print("Loop \"{0}\" with type \"{1}\" not recognised".format(
+                specs.key, specs["type"]))
+
+    def _execute_entry_point(self, specs):
+        """Placeholder: Will parse the initialiser module when ready"""
+        pass
+
+    def _execute_exit_point(self, specs):
+        """Placeholder: Will parse the Output module when ready"""
+        pass
+
+    def _parse_condition(self, condition):
+        try:
+            condition = int(condition)
+            if isinstance(condition, int):
+                return range(0, condition)
+        except:
+            print("Condition \"{0}\" cannot be parsed".format(condition))
+            print(
+                "Other formats in in development. Only ranged for loops are working currently")
+
+    def _execute_for_loop(self, specs):
+        condition = self._parse_condition(specs["condition"])
+        for c in condition:
+            self._execute(specs)
+
+    def _execute(self, specs):
+        """Run elements within a given dictionary.
+        Only interates over dict values that are dicts themselves.
+        Non-dict elements cannot contain modules or loops
+
+        :param specs: dict of elements to be executed
+        """
+        for key in [key for key, val in specs.items() if type(val) == dict]:
+            getattr(self, "_execute_{}".format(
+                specs[key]["class"]))(specs[key])
+
+    def run(self):
+        print("Running pipeline...")
+        self._execute(self.loaded_modules)
+        print("Pipeline Complete")
diff --git a/aidesign/Core/Assets/UFAIcon.ico b/aidesign/Core/Assets/UFAIcon.ico
new file mode 100644
index 00000000..60f8034e
Binary files /dev/null and b/aidesign/Core/Assets/UFAIcon.ico differ
diff --git a/aidesign/Core/Assets/UFAIcon_name.png b/aidesign/Core/Assets/UFAIcon_name.png
new file mode 100644
index 00000000..fb03392b
Binary files /dev/null and b/aidesign/Core/Assets/UFAIcon_name.png differ
diff --git a/aidesign/Core/__init__.py b/aidesign/Core/__init__.py
new file mode 100644
index 00000000..169fa711
--- /dev/null
+++ b/aidesign/Core/__init__.py
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+
+"""Import all submodules inside this folder and expose them with their respective names
+
+Let's look for a more automatic way of doing this when we have more modules
+"""
+from .AI_design_core import Core
+from .. import *
\ No newline at end of file
diff --git a/aidesign/GUI/GUI_core.py b/aidesign/GUI/GUI_core.py
index ba76783a..02ff7f72 100644
--- a/aidesign/GUI/GUI_core.py
+++ b/aidesign/GUI/GUI_core.py
@@ -1,11 +1,6 @@
 # -*- coding: utf-8 -*-
 import tkinter as tk
-from tkinter import font as tkfont
-from sys import modules as get_module_name
-from .plugins import PageCanvas
-from .plugins import PageManual
-from .plugins import StartPage
-
+from aidesign.utils.import_helper import import_plugin
 
 class GUI(tk.Tk):
     """
@@ -17,77 +12,79 @@ class GUI(tk.Tk):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
 
-        self.title_font = tkfont.Font(family='Helvetica',
+        self.title_font = tk.font.Font(family='Helvetica',
                                       size=14,
                                       weight="bold")
-        self.pages_font = tkfont.nametofont("TkDefaultFont")
-        self.current_module_name = get_module_name[__name__]
-
-        self.desired_ui_types = []
-        self.top_ui_layer = None
-        self.startpage_exist = False
-        self.available_ui_types = {
-            "StartPage": {
-                "name": "startpage",
+        self.pages_font = tk.font.nametofont("TkDefaultFont")
+
+
+        self._desired_ui_types = []
+        self._top_ui_layer = None
+        self._module_config = None
+        self.output = {}
+
+        self._available_ui_types = {
+            "MainPage": {
+                "name": "main",
+                "layer_priority": 1,
+                "required_children": ['aidCanvas']
+            },
+            "aidCanvas": {
+                "name": "aidCanvas",
                 "layer_priority": 1,
-                "required_children": ["PageManual", "PageCanvas"]
+                "required_children": []
             },
-            "PageManual": {
+            "ManualInput": {
                 "name": "manual",
                 "layer_priority": 2,
                 "required_children": None,
-                # temporary fix so startpage still works:
-                "default_class_list": ['Atelectasis',
-                                       'Cardiomelagy',
-                                       'Effusion',
-                                       'Infiltration',
-                                       'Mass',
-                                       'Nodule',
-                                       'Pneumonia',
-                                       'Pneumothorax'
-                                       ]
             },
-            "PageCanvas": {
+            "CanvasInput": {
                 "name": "canvas",
                 "layer_priority": 2,
                 "required_children": None,
-                # temporary fix so startpage still works:
-                "default_class_list": [['State_a', 'Action_a'],
-                                       ['State_x', 'State_y',
-                                        'Action_x', 'Action_y'],
-                                       ['State_x', 'State_y', 'Action_x', 'Action_y']]
             }
         }
 
-    def compare_layer_priority(self, ui_name):
+    def _compare_layer_priority(self, ui_name):
         """Check if a new module should have higher layer priority than the existing one
 
         :param ui_name: name of the UI method being compared
         :type ui_name: str
         """
-        if self.top_ui_layer == None:
-            self.top_ui_layer = ui_name
+        if self._top_ui_layer == None:
+            self._top_ui_layer = ui_name
         else:
-            current_top_layer = self.available_ui_types[self.top_ui_layer]["layer_priority"]
-            candidate_layer = self.available_ui_types[ui_name]["layer_priority"]
-            self.top_ui_layer = candidate_layer \
+            current_top_layer = self._available_ui_types[self._top_ui_layer]["layer_priority"]
+            candidate_layer = self._available_ui_types[ui_name]["layer_priority"]
+            self._top_ui_layer = candidate_layer \
                 if candidate_layer < current_top_layer \
-                else self.top_ui_layer
+                else self._top_ui_layer
 
-    def add_UI_type_to_frames(self, ui_name):
+    def _add_UI_type_to_frames(self, ui_name):
         """Add user defined UI method to list of frames to be loaded
 
         :param ui_name: name of the UI method being loaded
         :type ui_name: str 
         """
-        self.desired_ui_types.append(
-            getattr(self.current_module_name, ui_name))
-        self.compare_layer_priority(ui_name)
-        if self.available_ui_types[ui_name]["required_children"] != None:
-            for children in self.available_ui_types[ui_name]["required_children"]:
-                self.add_UI_type_to_frames(children)
-
-    def plugin_name(self, ui_type):
+        try:
+            plugin = import_plugin(globals(),ui_name)
+        except:
+            from sys import exit
+            print(
+                "Error: User Interface \"{0}\" not recognised. \
+                \nAvailable methods are: \
+                \n  - {1}"\
+                .format(ui_name, ",\n  - ".join(
+                    [i["name"]for i in self._available_ui_types.values()])))
+            exit(1)
+        self._desired_ui_types.append(plugin)
+        self._compare_layer_priority(ui_name)
+        if self._available_ui_types[ui_name]["required_children"] != None:
+            for children in self._available_ui_types[ui_name]["required_children"]:
+                self._add_UI_type_to_frames(children)
+
+    def set_plugin_name(self, ui_type: list):
         """"Given user input, create a list of classes of the corresponding User Interface Type 
 
         :param ui_name: name of the desired User Interface Method
@@ -95,55 +92,47 @@ def plugin_name(self, ui_type):
         """
         ui_type = ui_type\
             if isinstance(ui_type, list)\
-            else [ui_type]  # put ui_type in list if not already
+            else [ui_type]
 
         for ui in ui_type:
-            ui_name = ''.join(kn for kn in self.available_ui_types.keys()
-                              if ui.lower() == self.available_ui_types[kn]["name"])
-            try:
-                self.add_UI_type_to_frames(ui_name)
-                self.startpage_exist = 1 if ui_name == "StartPage" else self.startpage_exist
-            except:
-                from sys import exit
-                print(
-                    "Error: User Interface \"{0}\" not recognised.".format(ui))
-                print("Available methods are:")
-                print(
-                    "   - {}".format(",\n   - ".join([i["name"] for i in self.available_ui_types.values()])))
-                exit(1)
-
-    def set_class_list(self, class_list):
-        self._class_list = class_list
-
-    def show_frame(self, page_name):
+            ui_name = ''.join(kn for kn in self._available_ui_types.keys()
+                              if ui.lower() == self._available_ui_types[kn]["name"])
+            self._add_UI_type_to_frames(ui_name)
+
+    def _append_to_output(self, key:str, value:any):
+        self.output[key] = value
+
+    def set_options(self, module_config: dict):
+        """Send configuration arguments to GUI
+
+        :param module_config: dict of settings to congfigure the plugin
+        """
+        self._module_config = module_config
+        self.set_plugin_name(self._module_config["plugin"]["plugin_name"])
+
+    def _show_frame(self, page_name):
         '''Show a frame for the given page name'''
         frame = self.frames[page_name]
         frame.tkraise()
 
     def launch(self):
-        # the container is where we'll stack a bunch of frames
-        # on top of each other, then the one we want visible
-        # will be raised above the others
+        """Runs UserInterface Plugin. 
+        If multiple frames exist, they are stacked
+        """
         container = tk.Frame(self, bg='#19232d')
         container.pack(side="top", fill="both", expand=True)
         container.grid_rowconfigure(0, weight=1)
         container.grid_columnconfigure(0, weight=1)
 
         self.frames = {}
-        for F in self.desired_ui_types:
+        for F in self._desired_ui_types:
             page_name = F.__name__
-            frame = F(parent=container, controller=self)
-            if not self.startpage_exist:
-                frame.class_list(self._class_list)
-            elif self.startpage_exist and page_name != "StartPage":
-                default_class_list = self.available_ui_types[page_name]["default_class_list"]
-                frame.class_list(default_class_list)
+            frame = F(parent=container, controller=self,
+                      config=self._module_config)
             self.frames[page_name] = frame
 
-            # put all of the pages in the same location;
-            # the one on the top of the stacking order
-            # will be the one that is visible.
             frame.grid(row=0, column=0, sticky="nsew")
 
-        self.show_frame(self.top_ui_layer)
+        self._show_frame(self._top_ui_layer)
         self.mainloop()
+        return self.output
diff --git a/aidesign/GUI/UserInterfaceClass.py b/aidesign/GUI/User_Feedback_template.py
similarity index 100%
rename from aidesign/GUI/UserInterfaceClass.py
rename to aidesign/GUI/User_Feedback_template.py
diff --git a/aidesign/GUI/__init__.py b/aidesign/GUI/__init__.py
index c2a82c8b..c05d8468 100644
--- a/aidesign/GUI/__init__.py
+++ b/aidesign/GUI/__init__.py
@@ -4,5 +4,7 @@
 
 Let's look for a more automatic way of doing this when we have more modules
 """
+from .User_Feedback_template import UI
 from .GUI_core import GUI
-from .UserInterfaceClass import UI
+
+from .. import *
\ No newline at end of file
diff --git a/aidesign/GUI/plugins/canvas_input.py b/aidesign/GUI/plugins/CanvasInput.py
similarity index 95%
rename from aidesign/GUI/plugins/canvas_input.py
rename to aidesign/GUI/plugins/CanvasInput.py
index 9063e7e2..3d052d3c 100644
--- a/aidesign/GUI/plugins/canvas_input.py
+++ b/aidesign/GUI/plugins/CanvasInput.py
@@ -1,627 +1,628 @@
-from . import UI
-import tkinter as tk                # python 3
-import os
-from PIL import Image, ImageTk
-
-from tkinter import messagebox, ttk
-
-from tkinter.filedialog import asksaveasfile, askopenfile, askopenfilename
-import numpy as np
-import pandas as pd
-import math
-
-class PageCanvas(tk.Frame):
-    def __init__(self, parent, controller):
-        self.parent = parent
-        super().__init__(self.parent, bg = self.parent['bg'])
-        self.controller = controller
-        
-        self.controller.title('Canvas Input')
-        self.notebook = ttk.Notebook(self)
-        self.notebook.grid(row=0, column=0, columnspan=7, 
-                           rowspan = 12, pady = 15)
-        self.frame = []
-        self.tree = []
-        self.canvas = []
-        self.draw = []
-        self.state = []
-        self.type = []
-        self.clock = []
-
-        self.save_path = ''
-        self.saved = True
-    
-    def class_list(self):
-        """Getter for required _class_list variable
-        
-        :return: list of class labels
-        :rtype: list of strings
-        """
-        return self._class_list
-
-    def class_list(self,value):
-        """Setter for required _class_list variable
-        
-        :param value: labels for state-action pair headers
-        :type value: list of strings
-        """
-        self._class_list = value
-        # self.out_data = {'State_x': [], 'State_y': [], 'Action_x': [], 'Action_y': []} #coordinates
-        # self.out_data = {out: [] for out in self._class_list} #coordinates
-        #Tree defintion. Output display
-        self.out_data = {}
-        for ii in np.arange(len(self._class_list)):
-            self.out_data[ii] = {}
-            for key in self._class_list[ii]:
-                self.out_data[ii][key] = []
-
-        for ii in np.arange(len(self._class_list)):
-            self.frame.append(tk.Frame(self, bg = self.parent['bg']))
-            self.frame[-1].grid(row=0, column=0, sticky="nsew", pady = 15)
-            if self._class_list[ii][0].split('_')[1] == 'a':
-                self.type.append('Rotating')
-                self.clock.append(tk.StringVar())
-                self.clock[ii].set('clock')
-            else:
-                self.type.append('Sliding')
-                self.clock.append(-1)
-            self.notebook.add(
-                self.frame[-1], 
-                text = 'Object '+ str(ii) + ' - ' + self.type[-1])
-                
-            
-            # Create a canvas widget
-            self.width, self.height = 600, 600
-            self.canvas.append(tk.Canvas(
-                self.frame[-1], width=self.width, 
-                height=self.height, background="white"))
-            self.canvas[-1].grid(row=0, column=0, columnspan=4, rowspan = 2)
-            self.canvas[-1].bind('<Button-1>', self.draw_dot)
-            self.canvas[-1].bind("<B1-Motion>", self.on_drag)
-            # self.canvas[-1].bind('<Motion>', self.motion)
-            
-            # Booleans to identify what the user wants to do
-            self.draw.append(tk.StringVar())
-            self.draw[ii].set('draw')
-            self.state.append(tk.StringVar())
-            self.state[ii].set('state')    
-            # Buttons under the canvas
-            self.button_draw = tk.Radiobutton(
-                self.frame[-1], text = 'Draw', fg = 'white', bg = self.parent['bg'],
-                height = 3, width = 20, var = self.draw[ii], 
-                selectcolor = 'black', value = 'draw').grid(column = 4, row = 3)
-            self.button_drag = tk.Radiobutton(
-                self.frame[-1], text = 'Move', fg = 'white', bg = self.parent['bg'],
-                height = 3, width = 20, var = self.draw[ii], 
-                selectcolor = 'black', value = 'drag').grid(column = 5, row = 3)
-            self.clock_arc = tk.Radiobutton(
-                self.frame[-1], text = 'Clockwise', fg = 'white', bg = self.parent['bg'],
-                height = 3, width = 20, var = self.clock[ii], 
-                selectcolor = 'black', value = 'clock').grid(column = 6, row = 3)            
-            self.countclock_arc = tk.Radiobutton(
-                self.frame[-1], text = 'Counterclockwise', fg = 'white', bg = self.parent['bg'],
-                height = 3, width = 20, var = self.clock[ii], 
-                selectcolor = 'black', value = 'countclock').grid(column = 7, row = 3)
-            # self.button_edit = tk.Radiobutton(
-            #     self.frame[-1], text = 'Edit', fg = 'white', bg = self.parent['bg'],
-            #     height = 3, width = 20, var = self.draw[ii], 
-            #     selectcolor = 'black', value = 'edit').grid(column = 6,row = 3)
-            self.button_save = tk.Button(
-                self.frame[-1], text = 'Save', fg = 'white', bg = self.parent['bg'],
-                height = 3, width = 20, 
-                command = self.save_file).grid(column = 1, row = 3)
-            self.button_upload = tk.Button(
-                self.frame[-1], text = 'Upload coordinates', fg = 'white', 
-                bg = self.parent['bg'], height = 3, width = 20, 
-                command = self.upload_sa).grid(column = 0, row = 3)
-            self.button_reset = tk.Button(
-                self.frame[-1], text = 'Reset', fg = 'white', 
-                bg = self.parent['bg'], height = 3, width = 20, 
-                command = self.reset).grid(column = 2, row = 3)
-            button_main = tk.Button(
-                self.frame[-1], text="Go to the main page", fg = 'white', 
-                bg = self.parent['bg'], height = 3, width = 20,
-                command = self.check_quit).grid(column = 3, row = 3)
-            style = ttk.Style()
-            style.configure(
-                "Treeview", background = 'white', foreground = 'white', 
-                rowheight = 25, fieldbackground = 'white', 
-                font = self.controller.pages_font)
-            style.configure("Treeview.Heading", 
-                            font = self.controller.pages_font)
-            style.map('Treeview', background = [('selected', 'grey')])
-            
-
-            tree_frame = tk.Frame(self.frame[-1])
-            tree_frame.grid(row = 0, column = 4, columnspan = 3, rowspan = 10)
-            
-            tree_scrollx = tk.Scrollbar(tree_frame, orient = 'horizontal')
-            tree_scrollx.pack(side = tk.BOTTOM, fill = tk.X)
-            tree_scrolly = tk.Scrollbar(tree_frame)
-            tree_scrolly.pack(side = tk.RIGHT, fill = tk.Y)
-            
-            self.tree.append(ttk.Treeview(
-                tree_frame, 
-                yscrollcommand = tree_scrolly.set, 
-                xscrollcommand = tree_scrollx.set))
-            self.tree[-1].pack()
-            
-            tree_scrollx.config(command = self.tree[-1].xview)
-            tree_scrolly.config(command = self.tree[-1].yview)
-            
-            if self.type[-1] == 'Rotating':
-                self.x_ini, self.y_ini = int(self.width/2), int(self.height/2)
-                self.r = int(np.min([self.x_ini, self.y_ini])/1.5)
-                self.create_circle(self.x_ini, self.y_ini, 4, 
-                                    fill="#DDD", outline="", width=4)
-                self.create_circle(self.x_ini, self.y_ini, self.r, 
-                                    fill="", outline="#DDD", width=4)
-                
-            self.tree[-1]['columns'] = self._class_list[ii]
-            
-            # Format columns
-            self.tree[-1].column("#0", width = 50)
-            for n, cl in enumerate(self._class_list[ii]):
-                self.tree[-1].column(
-                    cl, width = int(
-                        self.controller.pages_font.measure(str(cl)))+20, 
-                    minwidth = 50, anchor = tk.CENTER)
-                    
-            # Headings
-            self.tree[-1].heading("#0", text = "Sample", anchor = tk.CENTER)
-            for cl in self._class_list[ii]:
-                self.tree[-1].heading(cl, text = cl, anchor = tk.CENTER)
-            
-            # for nn in np.arange(N):
-            #     self.tree[-1].insert(
-            #                 parent = '', index = 'end', iid = (nn+1)*1000, text = 'Trial '+str(nn), 
-            #                 values = tuple())
-            
-            self.tree[-1].tag_configure('odd', foreground = 'black', 
-                                        background='#E8E8E8')
-            self.tree[-1].tag_configure('even', foreground = 'black', 
-                                        background='#DFDFDF')
-        
-            # Define double-click on row action
-            self.tree[-1].bind("<Double-1>", self.OnDoubleClick)
-
-    def draw_dot(self, event):
-        
-        ii = self.notebook.index(self.notebook.select())
-        if self.draw[ii].get() == 'drag':
-            self.selected = self.canvas[ii].find_overlapping(
-                event.x-10, event.y-10, event.x+10, event.y+10)
-            if self.selected:
-                if len(self.selected) > 2:
-                    self.canvas[ii].selected = self.selected[-2]
-                else:
-                    self.canvas[ii].selected = self.selected[-1]
-                self.canvas[ii].startxy = (event.x, event.y)
-            else:
-                self.canvas[ii].selected = None
-
-        if self.draw[ii].get()  == 'draw':
-            # Draw an oval in the given coordinates
-            self.saved = False
-            # Angle of the selected point with respect to the circle center
-            if self.type[ii] == 'Rotating':
-                alpha, x_o, y_o = self.angle_calc(event.x, event.y, 
-                                                  circxy=True)
-                if self.state[ii].get()  == 'state': # Write state coordinates
-                    self.canvas[ii].create_oval(
-                        x_o-3, y_o-3, x_o+3, y_o+3, fill="black", width=0, 
-                        tags=("state"+str(ii)+'-'
-                              + str(len(self.out_data[ii]['State_a']))))
-                    self.out_data[ii]['State_a'].append((
-                        -np.rad2deg(alpha)+360)%360)
-                    self.state[ii].set('action')
-                    if self.tree[ii].selection(): # Update coordinates in corresponding row if exists.
-                        n = int(self.tree[ii].selection()[0]) + 1
-                        if n%2 == 0:
-                            tag = ('even',)
-                        else:
-                            tag = ('odd',)
-                        self.tree[ii].insert(
-                            parent = '', index = 'end', iid = n, text = n+1, 
-                            values = tuple(self.dict2mat(
-                                self.out_data[ii])[n,:].astype(int)), 
-                            tags = tag)
-                        self.tree[ii].selection_set(str(n))
-                    else:
-                        self.tree[ii].insert(
-                            parent = '', index = 'end', iid = 0, text = 1, 
-                            values = tuple(self.dict2mat(
-                                self.out_data[ii])[0,:].astype(int)), 
-                                tags = ('even',))
-                        self.tree[ii].selection_set(str(0))
-                    
-                elif self.state[ii].get()  == 'action':
-                    if self.clock[ii].get()  == 'clock':
-                        start = self.out_data[ii]['State_a'][-1]
-                        end = np.rad2deg(-alpha)
-                    else:
-                        start = self.out_data[ii]['State_a'][-1] - 360
-                        end = 360 - np.rad2deg(alpha)
-                    self.create_circle_arc(
-                        self.x_ini, self.y_ini, self.r, fill = "", 
-                        outline = "red", start = start, end = end, width=2, 
-                        style = tk.ARC, tags = ("action"+str(ii)+'-' + str(len(
-                            self.out_data[ii]['Action_a']))))              
-                    self.out_data[ii]['Action_a'].append((
-                        -np.rad2deg(alpha)+360)%360)
-                    self.state[ii].set('state')
-                    n = int(self.tree[ii].selection()[0])
-                    self.tree[ii].item(
-                        self.tree[ii].get_children()[n], text = n+1, 
-                        values = tuple(self.dict2mat(
-                            self.out_data[ii])[n,:].astype(int)))
-            else:
-                if self.state[ii].get()  == 'state': # Write state coordinates
-                    self.canvas[ii].create_oval(
-                        event.x-3, event.y-3, event.x+3, event.y+3, 
-                        fill="black", width=0, 
-                        tags=("state"+str(ii)+'-'+ str(len(
-                            self.out_data[ii]['State_x']))))
-                    self.out_data[ii]['State_x'].append(event.x)
-                    self.out_data[ii]['State_y'].append(event.y)
-                    self.state[ii].set('action')
-                    if self.tree[ii].selection(): # Update coordinates in corresponding row if exists.
-                        n = int(self.tree[ii].selection()[0]) + 1
-                        if n%2 == 0:
-                            tag = ('even',)
-                        else:
-                            tag = ('odd',)
-                        self.tree[ii].insert(
-                            parent = '', index = 'end', iid = n, text = n+1, 
-                            values = tuple(self.dict2mat(
-                                self.out_data[ii])[n,:].astype(int)), 
-                            tags = tag)
-                        self.tree[ii].selection_set(str(n))
-                    else:
-                        self.tree[ii].insert(
-                            parent = '', index = 'end', iid = 0, text = 1, 
-                            values = tuple(self.dict2mat(
-                                self.out_data[ii])[0,:].astype(int)), 
-                                tags = ('even',))
-                        self.tree[ii].selection_set(str(0))
-                    
-                elif self.state[ii].get()  == 'action':
-                    self.canvas[ii].create_line(
-                        self.out_data[ii]['State_x'][-1], 
-                        self.out_data[ii]['State_y'][-1], event.x, event.y, 
-                        fill="red", arrow=tk.LAST, 
-                        tags=("action"+str(ii)+'-' + str(len(
-                            self.out_data[ii]['Action_x']))))
-                    self.out_data[ii]['Action_x'].append(event.x)
-                    self.out_data[ii]['Action_y'].append(event.y)
-                    self.state[ii].set('state')
-                    n = int(self.tree[ii].selection()[0])
-                    self.tree[ii].item(
-                        self.tree[ii].get_children()[n], text = n+1, 
-                        values = tuple(self.dict2mat(
-                            self.out_data[ii])[n,:].astype(int)))
-        
-    def on_drag(self, event):
-        
-        ii = self.notebook.index(self.notebook.select())
-        if self.draw[ii].get()  == 'drag' and self.canvas[ii].selected:
-            # move the selected item
-            n = int(self.canvas[ii].gettags("current")[0].split('-')[1])
-            
-            if self.type[ii] == 'Rotating':
-                alpha, dx, dy = self.angle_calc(event.x, event.y, circxy=True)
-                _, dx_pr, dy_pr = self.angle_calc(
-                    self.canvas[ii].startxy[0], self.canvas[ii].startxy[1], 
-                    circxy=True)
-                xa = self.r * np.cos(
-                    np.deg2rad(self.out_data[ii]['Action_a'][n])) + self.x_ini
-                ya = self.r * np.sin(
-                    np.deg2rad(self.out_data[ii]['Action_a'][n])) + self.y_ini
-                if self.canvas[ii].gettags(
-                        "current")[0].split('-')[0] == 'state'+str(ii): # If state, updates both the state placement and the arrow
-                    self.canvas[ii].move(
-                        self.canvas[ii].selected, dx-dx_pr, 
-                        dy-dy_pr)
-                    self.out_data[ii]['State_a'][n] = (360-np.rad2deg(alpha))%360
-
-                    if self.clock[ii].get()  == 'clock':
-                        start = self.out_data[ii]['Action_a'][n]
-                        end = np.rad2deg(-alpha)
-                    else:
-                        start = self.out_data[ii]['Action_a'][n] - 360
-                        end = 360 - np.rad2deg(alpha)
-
-                    self.canvas[ii].itemconfigure(
-                        "action"+str(ii)+'-'+str(n), start = start, 
-                        extent = end - start)
-                    
-                elif self.canvas[ii].gettags(
-                        "current")[0].split('-')[0] == 'action'+str(ii): # If action, updates the arrow end of the line position
-                    
-                    self.out_data[ii]['Action_a'][n] = (360-np.rad2deg(alpha))%360
-
-                    if self.clock[ii].get()  == 'clock':
-                        start = self.out_data[ii]['State_a'][n]
-                        end = np.rad2deg(-alpha)
-                    else:
-                        start = self.out_data[ii]['State_a'][n] - 360
-                        end = 360 - np.rad2deg(alpha)
-                    
-                    self.canvas[ii].itemconfigure(
-                        "action"+str(ii)+'-'+str(n), start = start, 
-                        extent = end - start)
-
-            else:
-                # calculate distance moved from last position
-                dx = event.x - self.canvas[ii].startxy[0]
-                dy = event.y - self.canvas[ii].startxy[1]
-                if self.canvas[ii].gettags(
-                        "current")[0].split('-')[0] == 'state'+str(ii): # If state, updates both the state placement and the arrow
-                    self.canvas[ii].move(self.canvas[ii].selected, dx, dy)
-                    self.canvas[ii].coords(
-                        "action"+str(ii)+'-'+str(n), 
-                        (event.x, event.y, self.out_data[ii]['Action_x'][n], 
-                        self.out_data[ii]['Action_y'][n]))
-                    self.out_data[ii]['State_x'][n] = event.x
-                    self.out_data[ii]['State_y'][n] = event.y  
-                elif self.canvas[ii].gettags(
-                        "current")[0].split('-')[0] == 'action'+str(ii): # If action, updates the arrow end of the line position
-                    self.canvas[ii].coords(
-                        self.canvas[ii].gettags("current")[0], 
-                        (self.out_data[ii]['State_x'][n], 
-                         self.out_data[ii]['State_y'][n], event.x, event.y))
-                    self.out_data[ii]['Action_x'][n] = event.x
-                    self.out_data[ii]['Action_y'][n] = event.y
-            # update last position
-            self.canvas[ii].startxy = (event.x, event.y)                
-            self.tree[ii].item(self.tree[ii].get_children()[n], text = n+1, 
-                           values = tuple(self.dict2mat(
-                               self.out_data[ii])[n,:].astype(int)))
-            self.tree[ii].selection_set(str(n))
-
-    def checkered(self, line_distance):
-        
-        ii = self.notebook.index(self.notebook.select()) 
-        # vertical lines at an interval of "line_distance" pixel
-        for x in range(line_distance, self.width, line_distance):
-            self.canvas[ii].create_line(x, 0, x, self.height, fill="#476042")
-        # horizontal lines at an interval of "line_distance" pixel
-        for y in range(line_distance, self.height, line_distance):
-            self.canvas[ii].create_line(0, y, self.width, y, fill="#476042")   
-            
-    def check_quit(self):
-        
-        # Should it also reset the canvas?
-        
-        if self.saved:
-            self.controller.show_frame("StartPage")
-        else:
-            response = messagebox.askokcancel("Are you sure you want to leave?", "Do you want to leave the program without saving?")
-            if response:
-                self.controller.show_frame("StartPage")
-            
-    def save_file_as(self):
-        
-        self.save_path = asksaveasfile(mode='w')
-        self.save_file()
-        
-    def save_file(self):
-        
-        if self.save_path == '':
-            self.save_path = asksaveasfile(defaultextension = '.txt', filetypes = 
-                                      [('Text file', '.txt'), 
-                                       ('CSV file', '.csv'), 
-                                       ('All Files', '*.*')])
-        if self.save_path is not None: # asksaveasfile return `None` if dialog closed with "cancel".
-            # Transform input for output file    
-            data = {}
-            samples = np.max([len(self.out_data[element][
-                list(self.out_data[element].keys())[0]]) 
-                for element in self.out_data])
-            for ii in self.out_data.keys():
-                for c in self.out_data[ii].keys():
-                    a, b = c.split('_')
-                    aux = self.out_data[ii][c]
-                    aux.extend([-1]*(samples - len(self.out_data[ii][c])))
-                    data[a+str(ii)+'_'+b] = aux
-                    
-            filedata = pd.DataFrame(data, columns = data.keys()).to_string()
-            self.save_path.seek(0) # Move to the first row to overwrite it
-            self.save_path.write(filedata)
-            self.save_path.flush() # Save without closing
-            # typically the above line would do. however this is used to ensure that the file is written
-            os.fsync(self.save_path.fileno())
-            self.saved = True
-         
-    def on_return(self, event):
-        ii = self.notebook.index(self.notebook.select())
-        val = self.tree[ii].item(self.treerow)['values']
-        val = [float(i) for i in val]
-        val[int(self.treecol[1:])-1] = float(self.entry.get())
-        self.tree[ii].item(self.treerow, values = val)
-        self.entry.destroy()        
-        self.saved = False
-        
-        if self.type[ii] == 'Rotating':
-            dx, dy = self.coord_calc(np.deg2rad(360 - val[0]))
-            dx_pr, dy_pr = self.coord_calc(np.deg2rad(360 - self.out_data[ii]['State_a'][self.treerow]))            
-            self.out_data[ii]['State_a'][self.treerow] = float(val[0])
-            self.canvas[ii].move("state"+str(ii)+'-'+str(self.treerow), dx-dx_pr, 
-                dy-dy_pr)
-            
-            self.out_data[ii]['Action_a'][self.treerow] = float(val[1])
-            if self.clock[ii].get()  == 'clock':
-                start = self.out_data[ii]['State_a'][self.treerow]
-                end = self.out_data[ii]['Action_a'][self.treerow]
-            else:
-                start = self.out_data[ii]['State_a'][self.treerow] - 360
-                end = self.out_data[ii]['Action_a'][self.treerow]
-            
-            self.canvas[ii].itemconfigure(
-                "action"+str(ii)+'-'+str(self.treerow), start = start, 
-                extent = end - start)
-        else:
-            # calculate distance moved from last position
-            dx_s = val[0] - self.out_data[ii]['State_x'][self.treerow]
-            dy_s = val[1] - self.out_data[ii]['State_y'][self.treerow]
-
-            self.canvas[ii].move("state"+str(ii)+'-'+str(self.treerow), dx_s, dy_s)
-
-            self.out_data[ii]['State_x'][self.treerow] = val[0]
-            self.out_data[ii]['State_y'][self.treerow] = val[1]
-
-            self.canvas[ii].coords(
-                "action"+str(ii)+'-'+str(self.treerow), 
-                (self.out_data[ii]['State_x'][self.treerow], 
-                  self.out_data[ii]['State_y'][self.treerow], val[2], val[3]))
-            self.out_data[ii]['Action_x'][self.treerow] = val[2]
-            self.out_data[ii]['Action_y'][self.treerow] = val[3]
-            
-    def OnDoubleClick(self, event):
-        
-        """ Executed when a row is double clicked.
-        Opens an entry box to edit a cell and updates the canvas and the 
-        stored data. """
-        
-        ii = self.notebook.index(self.notebook.select())
-        self.treerow = int(self.tree[ii].identify_row(event.y))
-        self.treecol = self.tree[ii].identify_column(event.x)
-        
-        # get column position info
-        x, y, width, height = self.tree[ii].bbox(self.treerow, self.treecol)
-    
-        # y-axis offset
-        pady = height // 2
-        # pady = 0
-        
-        if hasattr(self, 'entry'):
-            self.entry.destroy()
-            
-        self.entry = tk.Entry(self.tree[ii], justify='center') 
-        
-        if int(self.treecol[1:]) > 0:
-            self.entry.insert(
-                0, self.tree[ii].item(self.treerow)['values'][int(str(self.treecol[1:]))-1])
-            # self.entry['selectbackground'] = '#123456'
-            self.entry['exportselection'] = False
-    
-            self.entry.focus_force()
-            self.entry.bind("<Return>", self.on_return)
-            self.entry.bind("<Escape>", lambda *ignore: self.entry.destroy())
-            
-            self.entry.place(x = x, 
-                             y = y + pady, 
-                             anchor = tk.W, width = width)
-
-    def upload_sa(self):
-        
-        ii = self.notebook.index(self.notebook.select())
-        filename = askopenfilename(initialdir = os.getcwd(), 
-                                   title = 'Select a file', 
-                                   defaultextension = '.txt', 
-                                   filetypes = [('Text file', '.txt'), 
-                                                ('CSS file', '.css'), 
-                                                ('All Files', '*.*')])
-        if filename is not None:
-            data = open(filename,'r')
-            read = False
-            self.draw[ii].set('drag')
-            for n, point in enumerate(data):
-                if read: # Not elegant at all, just to omit the header.
-                    i, sx, sy, ax, ay = point.split()
-                    # Draw an oval in the given coordinates
-                    self.canvas[ii].create_oval(
-                        float(sx)-3, float(sy)-3, float(sx)+3, float(sy)+3, 
-                        fill="black", width=0, 
-                        tags=("state-" + str(len(
-                            self.out_data[ii]['State_x']))))
-                    self.canvas[ii].create_line(
-                        float(sx), float(sy), float(ax), float(ay), 
-                        fill="red", arrow=tk.LAST, 
-                        tags=("action"+str(ii)+"-"+ str(len(
-                            self.out_data[ii]['Action_x']))))
-                    self.out_data[ii]['State_x'].append(sx)
-                    self.out_data[ii]['State_y'].append(sy)
-                    self.out_data[ii]['Action_x'].append(ax)
-                    self.out_data[ii]['Action_y'].append(ay)
-                    self.tree.insert(
-                        parent = '', index = 'end', 
-                        iid = len(self.out_data[ii]['Action_x'])-1, 
-                        text = len(self.out_data[ii]['Action_x']), 
-                        values = tuple(self.dict2mat(
-                            self.out_data[ii])
-                            [len(self.out_data[ii]
-                                 ['Action_x'])-1,:].astype(int)))
-                    self.tree.selection_set(str(len(
-                        self.out_data[ii]['Action_x'])-1))
-                else:
-                    read = True
-    
-    def reset(self):
-        
-        ii = self.notebook.index(self.notebook.select())
-        msg = messagebox.askyesnocancel(
-            'Info', 'Are you sure you want to reset the canvas?')
-        if msg:
-            self.canvas[ii].delete(tk.ALL) # Reset canvas
-            self.state[ii].set('state')
-            self.out_data[ii] = {}
-            for key in self._class_list[ii]:
-                self.out_data[ii][key] = []
-                    
-            for record in self.tree[ii].get_children(): #Reset treeview
-                self.tree[ii].delete(record)
-            if self.type[ii] == 'Rotating': 
-                self.create_circle(self.x_ini, self.y_ini, 4, 
-                                   fill="#DDD", outline="", width=4)
-                self.create_circle(self.x_ini, self.y_ini, self.r, 
-                                   fill="", outline="#DDD", width=4)
-    
-    def dict2mat(self, X):
-        
-        max_size = 0
-        for key in X:
-            if len(X[key]) > max_size:
-                max_size = len(X[key])
-
-        result = -np.ones((max_size, len(X)))
-        for k, key in enumerate(X):
-            result[:len(X[key]), k]  = X[key]
-            
-        return result
-    
-    def angle_calc(self, x, y, circxy = False):
-        if (x == self.x_ini) & (y < self.y_ini):
-            alpha = math.pi/2
-        elif (x == self.x_ini) & (y > self.y_ini):
-            alpha = 3*math.pi/2
-        else:
-            alpha = np.arctan((y-self.y_ini)/(x-self.x_ini))
-            alpha += ((x-self.x_ini) < 0) * math.pi
-            alpha += (alpha < 0) * 2*math.pi
-        if circxy:
-            cx, cy = self.coord_calc(alpha)
-            return alpha, cx, cy
-        else:
-            return alpha
-        
-    def coord_calc(self, alpha):
-        cx = self.r * np.cos(alpha) + self.x_ini
-        cy = self.r * np.sin(alpha) + self.y_ini
-        return cx, cy
-
-        
-    def create_circle(self, x, y, r, **kwargs):
-        
-        return self.canvas[self.notebook.index(
-            self.notebook.select())].create_oval(x-r, y-r, x+r, y+r, **kwargs)
-    
-    def create_circle_arc(self, x, y, r, **kwargs):
-        
-        if "start" in kwargs and "end" in kwargs:
-            kwargs["extent"] = kwargs["end"] - kwargs["start"]
-            # if self.clock == 'countclock':
-            #     kwargs["extent"] += (kwargs["extent"] < 0) * 360
-            del kwargs["end"]
-        return self.canvas[self.notebook.index(
+from . import UI
+import tkinter as tk
+import os
+
+from tkinter import messagebox, ttk
+
+from tkinter.filedialog import asksaveasfile, askopenfile, askopenfilename
+import numpy as np
+import pandas as pd
+import math
+
+class CanvasInput(tk.Frame,UI):
+    def __init__(self, parent, controller, config:dict):
+        self.parent = parent
+        super().__init__(self.parent, bg = self.parent['bg'])
+        self.controller = controller
+        
+        self.controller.title('Canvas Input')
+        self.notebook = ttk.Notebook(self)
+        self.notebook.grid(row=0, column=0, columnspan=7, 
+                           rowspan = 12, pady = 15)
+        self.frame = []
+        self.tree = []
+        self.canvas = []
+        self.draw = []
+        self.state = []
+        self.type = []
+        self.clock = []
+        self._parse_config(config)
+        self.save_path = ''
+        self.saved = True
+    
+    def _parse_config(self, config):
+        self.config = config
+        self.class_list(self.config["plugin"]["options"]["class_list"])
+
+    def class_list(self):
+        """Getter for required _class_list variable
+        
+        :return: list of class labels
+        :rtype: list of strings
+        """
+        return self._class_list
+
+    def class_list(self, value):
+        """Setter for required _class_list variable
+        
+        :param value: labels for state-action pair headers
+        :type value: list of strings
+        """
+        self._class_list = value
+        self.out_data = {}
+        for ii in np.arange(len(self._class_list)):
+            self.out_data[ii] = {}
+            for key in self._class_list[ii]:
+                self.out_data[ii][key] = []
+
+        for ii in np.arange(len(self._class_list)):
+            self.frame.append(tk.Frame(self, bg = self.parent['bg']))
+            self.frame[-1].grid(row=0, column=0, sticky="nsew", pady = 15)
+            if self._class_list[ii][0].split('_')[1] == 'a':
+                self.type.append('Rotating')
+                self.clock.append(tk.StringVar())
+                self.clock[ii].set('clock')
+            else:
+                self.type.append('Sliding')
+                self.clock.append(-1)
+            self.notebook.add(
+                self.frame[-1], 
+                text = 'Object '+ str(ii) + ' - ' + self.type[-1])
+                
+            
+            # Create a canvas widget
+            self.width, self.height = 600, 600
+            self.canvas.append(tk.Canvas(
+                self.frame[-1], width=self.width, 
+                height=self.height, background="white"))
+            self.canvas[-1].grid(row=0, column=0, columnspan=4, rowspan = 2)
+            self.canvas[-1].bind('<Button-1>', self.draw_dot)
+            self.canvas[-1].bind("<B1-Motion>", self.on_drag)
+            # self.canvas[-1].bind('<Motion>', self.motion)
+            
+            # Booleans to identify what the user wants to do
+            self.draw.append(tk.StringVar())
+            self.draw[ii].set('draw')
+            self.state.append(tk.StringVar())
+            self.state[ii].set('state')    
+            # Buttons under the canvas
+            self.button_draw = tk.Radiobutton(
+                self.frame[-1], text = 'Draw', fg = 'white', bg = self.parent['bg'],
+                height = 3, width = 20, var = self.draw[ii], 
+                selectcolor = 'black', value = 'draw').grid(column = 4, row = 3)
+            self.button_drag = tk.Radiobutton(
+                self.frame[-1], text = 'Move', fg = 'white', bg = self.parent['bg'],
+                height = 3, width = 20, var = self.draw[ii], 
+                selectcolor = 'black', value = 'drag').grid(column = 5, row = 3)
+            self.clock_arc = tk.Radiobutton(
+                self.frame[-1], text = 'Clockwise', fg = 'white', bg = self.parent['bg'],
+                height = 3, width = 20, var = self.clock[ii], 
+                selectcolor = 'black', value = 'clock').grid(column = 6, row = 3)            
+            self.countclock_arc = tk.Radiobutton(
+                self.frame[-1], text = 'Counterclockwise', fg = 'white', bg = self.parent['bg'],
+                height = 3, width = 20, var = self.clock[ii], 
+                selectcolor = 'black', value = 'countclock').grid(column = 7, row = 3)
+            # self.button_edit = tk.Radiobutton(
+            #     self.frame[-1], text = 'Edit', fg = 'white', bg = self.parent['bg'],
+            #     height = 3, width = 20, var = self.draw[ii], 
+            #     selectcolor = 'black', value = 'edit').grid(column = 6,row = 3)
+            self.button_save = tk.Button(
+                self.frame[-1], text = 'Save', fg = 'white', bg = self.parent['bg'],
+                height = 3, width = 20, 
+                command = self.save_file).grid(column = 1, row = 3)
+            self.button_upload = tk.Button(
+                self.frame[-1], text = 'Upload coordinates', fg = 'white', 
+                bg = self.parent['bg'], height = 3, width = 20, 
+                command = self.upload_sa).grid(column = 0, row = 3)
+            self.button_reset = tk.Button(
+                self.frame[-1], text = 'Reset', fg = 'white', 
+                bg = self.parent['bg'], height = 3, width = 20, 
+                command = self.reset).grid(column = 2, row = 3)
+            button_main = tk.Button(
+                self.frame[-1], text="Done", fg = 'white', 
+                bg = self.parent['bg'], height = 3, width = 20,
+                command = self.check_quit).grid(column = 3, row = 3)
+            style = ttk.Style()
+            style.configure(
+                "Treeview", background = 'white', foreground = 'white', 
+                rowheight = 25, fieldbackground = 'white', 
+                font = self.controller.pages_font)
+            style.configure("Treeview.Heading", 
+                            font = self.controller.pages_font)
+            style.map('Treeview', background = [('selected', 'grey')])
+            
+
+            tree_frame = tk.Frame(self.frame[-1])
+            tree_frame.grid(row = 0, column = 4, columnspan = 3, rowspan = 10)
+            
+            tree_scrollx = tk.Scrollbar(tree_frame, orient = 'horizontal')
+            tree_scrollx.pack(side = tk.BOTTOM, fill = tk.X)
+            tree_scrolly = tk.Scrollbar(tree_frame)
+            tree_scrolly.pack(side = tk.RIGHT, fill = tk.Y)
+            
+            self.tree.append(ttk.Treeview(
+                tree_frame, 
+                yscrollcommand = tree_scrolly.set, 
+                xscrollcommand = tree_scrollx.set))
+            self.tree[-1].pack()
+            
+            tree_scrollx.config(command = self.tree[-1].xview)
+            tree_scrolly.config(command = self.tree[-1].yview)
+            
+            if self.type[-1] == 'Rotating':
+                self.x_ini, self.y_ini = int(self.width/2), int(self.height/2)
+                self.r = int(np.min([self.x_ini, self.y_ini])/1.5)
+                self.create_circle(self.x_ini, self.y_ini, 4, 
+                                    fill="#DDD", outline="", width=4)
+                self.create_circle(self.x_ini, self.y_ini, self.r, 
+                                    fill="", outline="#DDD", width=4)
+                
+            self.tree[-1]['columns'] = self._class_list[ii]
+            
+            # Format columns
+            self.tree[-1].column("#0", width = 50)
+            for n, cl in enumerate(self._class_list[ii]):
+                self.tree[-1].column(
+                    cl, width = int(
+                        self.controller.pages_font.measure(str(cl)))+20, 
+                    minwidth = 50, anchor = tk.CENTER)
+                    
+            # Headings
+            self.tree[-1].heading("#0", text = "Sample", anchor = tk.CENTER)
+            for cl in self._class_list[ii]:
+                self.tree[-1].heading(cl, text = cl, anchor = tk.CENTER)
+            
+            # for nn in np.arange(N):
+            #     self.tree[-1].insert(
+            #                 parent = '', index = 'end', iid = (nn+1)*1000, text = 'Trial '+str(nn), 
+            #                 values = tuple())
+            
+            self.tree[-1].tag_configure('odd', foreground = 'black', 
+                                        background='#E8E8E8')
+            self.tree[-1].tag_configure('even', foreground = 'black', 
+                                        background='#DFDFDF')
+        
+            # Define double-click on row action
+            self.tree[-1].bind("<Double-1>", self.OnDoubleClick)
+
+    def draw_dot(self, event):
+        
+        ii = self.notebook.index(self.notebook.select())
+        if self.draw[ii].get() == 'drag':
+            self.selected = self.canvas[ii].find_overlapping(
+                event.x-10, event.y-10, event.x+10, event.y+10)
+            if self.selected:
+                if len(self.selected) > 2:
+                    self.canvas[ii].selected = self.selected[-2]
+                else:
+                    self.canvas[ii].selected = self.selected[-1]
+                self.canvas[ii].startxy = (event.x, event.y)
+            else:
+                self.canvas[ii].selected = None
+
+        if self.draw[ii].get()  == 'draw':
+            # Draw an oval in the given coordinates
+            self.saved = False
+            # Angle of the selected point with respect to the circle center
+            if self.type[ii] == 'Rotating':
+                alpha, x_o, y_o = self.angle_calc(event.x, event.y, 
+                                                  circxy=True)
+                if self.state[ii].get()  == 'state': # Write state coordinates
+                    self.canvas[ii].create_oval(
+                        x_o-3, y_o-3, x_o+3, y_o+3, fill="black", width=0, 
+                        tags=("state"+str(ii)+'-'
+                              + str(len(self.out_data[ii]['State_a']))))
+                    self.out_data[ii]['State_a'].append((
+                        -np.rad2deg(alpha)+360)%360)
+                    self.state[ii].set('action')
+                    if self.tree[ii].selection(): # Update coordinates in corresponding row if exists.
+                        n = int(self.tree[ii].selection()[0]) + 1
+                        if n%2 == 0:
+                            tag = ('even',)
+                        else:
+                            tag = ('odd',)
+                        self.tree[ii].insert(
+                            parent = '', index = 'end', iid = n, text = n+1, 
+                            values = tuple(self.dict2mat(
+                                self.out_data[ii])[n,:].astype(int)), 
+                            tags = tag)
+                        self.tree[ii].selection_set(str(n))
+                    else:
+                        self.tree[ii].insert(
+                            parent = '', index = 'end', iid = 0, text = 1, 
+                            values = tuple(self.dict2mat(
+                                self.out_data[ii])[0,:].astype(int)), 
+                                tags = ('even',))
+                        self.tree[ii].selection_set(str(0))
+                    
+                elif self.state[ii].get()  == 'action':
+                    if self.clock[ii].get()  == 'clock':
+                        start = self.out_data[ii]['State_a'][-1]
+                        end = np.rad2deg(-alpha)
+                    else:
+                        start = self.out_data[ii]['State_a'][-1] - 360
+                        end = 360 - np.rad2deg(alpha)
+                    self.create_circle_arc(
+                        self.x_ini, self.y_ini, self.r, fill = "", 
+                        outline = "red", start = start, end = end, width=2, 
+                        style = tk.ARC, tags = ("action"+str(ii)+'-' + str(len(
+                            self.out_data[ii]['Action_a']))))              
+                    self.out_data[ii]['Action_a'].append((
+                        -np.rad2deg(alpha)+360)%360)
+                    self.state[ii].set('state')
+                    n = int(self.tree[ii].selection()[0])
+                    self.tree[ii].item(
+                        self.tree[ii].get_children()[n], text = n+1, 
+                        values = tuple(self.dict2mat(
+                            self.out_data[ii])[n,:].astype(int)))
+            else:
+                if self.state[ii].get()  == 'state': # Write state coordinates
+                    self.canvas[ii].create_oval(
+                        event.x-3, event.y-3, event.x+3, event.y+3, 
+                        fill="black", width=0, 
+                        tags=("state"+str(ii)+'-'+ str(len(
+                            self.out_data[ii]['State_x']))))
+                    self.out_data[ii]['State_x'].append(event.x)
+                    self.out_data[ii]['State_y'].append(event.y)
+                    self.state[ii].set('action')
+                    if self.tree[ii].selection(): # Update coordinates in corresponding row if exists.
+                        n = int(self.tree[ii].selection()[0]) + 1
+                        if n%2 == 0:
+                            tag = ('even',)
+                        else:
+                            tag = ('odd',)
+                        self.tree[ii].insert(
+                            parent = '', index = 'end', iid = n, text = n+1, 
+                            values = tuple(self.dict2mat(
+                                self.out_data[ii])[n,:].astype(int)), 
+                            tags = tag)
+                        self.tree[ii].selection_set(str(n))
+                    else:
+                        self.tree[ii].insert(
+                            parent = '', index = 'end', iid = 0, text = 1, 
+                            values = tuple(self.dict2mat(
+                                self.out_data[ii])[0,:].astype(int)), 
+                                tags = ('even',))
+                        self.tree[ii].selection_set(str(0))
+                    
+                elif self.state[ii].get()  == 'action':
+                    self.canvas[ii].create_line(
+                        self.out_data[ii]['State_x'][-1], 
+                        self.out_data[ii]['State_y'][-1], event.x, event.y, 
+                        fill="red", arrow=tk.LAST, 
+                        tags=("action"+str(ii)+'-' + str(len(
+                            self.out_data[ii]['Action_x']))))
+                    self.out_data[ii]['Action_x'].append(event.x)
+                    self.out_data[ii]['Action_y'].append(event.y)
+                    self.state[ii].set('state')
+                    n = int(self.tree[ii].selection()[0])
+                    self.tree[ii].item(
+                        self.tree[ii].get_children()[n], text = n+1, 
+                        values = tuple(self.dict2mat(
+                            self.out_data[ii])[n,:].astype(int)))
+        
+    def on_drag(self, event):
+        
+        ii = self.notebook.index(self.notebook.select())
+        if self.draw[ii].get()  == 'drag' and self.canvas[ii].selected:
+            # move the selected item
+            n = int(self.canvas[ii].gettags("current")[0].split('-')[1])
+            
+            if self.type[ii] == 'Rotating':
+                alpha, dx, dy = self.angle_calc(event.x, event.y, circxy=True)
+                _, dx_pr, dy_pr = self.angle_calc(
+                    self.canvas[ii].startxy[0], self.canvas[ii].startxy[1], 
+                    circxy=True)
+                xa = self.r * np.cos(
+                    np.deg2rad(self.out_data[ii]['Action_a'][n])) + self.x_ini
+                ya = self.r * np.sin(
+                    np.deg2rad(self.out_data[ii]['Action_a'][n])) + self.y_ini
+                if self.canvas[ii].gettags(
+                        "current")[0].split('-')[0] == 'state'+str(ii): # If state, updates both the state placement and the arrow
+                    self.canvas[ii].move(
+                        self.canvas[ii].selected, dx-dx_pr, 
+                        dy-dy_pr)
+                    self.out_data[ii]['State_a'][n] = (360-np.rad2deg(alpha))%360
+
+                    if self.clock[ii].get()  == 'clock':
+                        start = self.out_data[ii]['Action_a'][n]
+                        end = np.rad2deg(-alpha)
+                    else:
+                        start = self.out_data[ii]['Action_a'][n] - 360
+                        end = 360 - np.rad2deg(alpha)
+
+                    self.canvas[ii].itemconfigure(
+                        "action"+str(ii)+'-'+str(n), start = start, 
+                        extent = end - start)
+                    
+                elif self.canvas[ii].gettags(
+                        "current")[0].split('-')[0] == 'action'+str(ii): # If action, updates the arrow end of the line position
+                    
+                    self.out_data[ii]['Action_a'][n] = (360-np.rad2deg(alpha))%360
+
+                    if self.clock[ii].get()  == 'clock':
+                        start = self.out_data[ii]['State_a'][n]
+                        end = np.rad2deg(-alpha)
+                    else:
+                        start = self.out_data[ii]['State_a'][n] - 360
+                        end = 360 - np.rad2deg(alpha)
+                    
+                    self.canvas[ii].itemconfigure(
+                        "action"+str(ii)+'-'+str(n), start = start, 
+                        extent = end - start)
+
+            else:
+                # calculate distance moved from last position
+                dx = event.x - self.canvas[ii].startxy[0]
+                dy = event.y - self.canvas[ii].startxy[1]
+                if self.canvas[ii].gettags(
+                        "current")[0].split('-')[0] == 'state'+str(ii): # If state, updates both the state placement and the arrow
+                    self.canvas[ii].move(self.canvas[ii].selected, dx, dy)
+                    self.canvas[ii].coords(
+                        "action"+str(ii)+'-'+str(n), 
+                        (event.x, event.y, self.out_data[ii]['Action_x'][n], 
+                        self.out_data[ii]['Action_y'][n]))
+                    self.out_data[ii]['State_x'][n] = event.x
+                    self.out_data[ii]['State_y'][n] = event.y  
+                elif self.canvas[ii].gettags(
+                        "current")[0].split('-')[0] == 'action'+str(ii): # If action, updates the arrow end of the line position
+                    self.canvas[ii].coords(
+                        self.canvas[ii].gettags("current")[0], 
+                        (self.out_data[ii]['State_x'][n], 
+                         self.out_data[ii]['State_y'][n], event.x, event.y))
+                    self.out_data[ii]['Action_x'][n] = event.x
+                    self.out_data[ii]['Action_y'][n] = event.y
+            # update last position
+            self.canvas[ii].startxy = (event.x, event.y)                
+            self.tree[ii].item(self.tree[ii].get_children()[n], text = n+1, 
+                           values = tuple(self.dict2mat(
+                               self.out_data[ii])[n,:].astype(int)))
+            self.tree[ii].selection_set(str(n))
+
+    def checkered(self, line_distance):
+        
+        ii = self.notebook.index(self.notebook.select()) 
+        # vertical lines at an interval of "line_distance" pixel
+        for x in range(line_distance, self.width, line_distance):
+            self.canvas[ii].create_line(x, 0, x, self.height, fill="#476042")
+        # horizontal lines at an interval of "line_distance" pixel
+        for y in range(line_distance, self.height, line_distance):
+            self.canvas[ii].create_line(0, y, self.width, y, fill="#476042")   
+            
+    def check_quit(self):
+        if not self.saved:
+            response = messagebox.askokcancel(
+                "Exit?", 
+                "Do you want to leave the program without saving?")
+            if response:
+                self.controller.destroy()
+        else:
+            response = messagebox.askokcancel(
+                "Exit?",
+                "Are you sure you are finished?")
+            self.controller.destroy()
+            
+    def save_file_as(self):
+        self.save_path = asksaveasfile(mode='w')
+        self.save_file()
+        
+    def save_file(self):
+        
+        if self.save_path == '':
+            self.save_path = asksaveasfile(defaultextension = '.txt', filetypes = 
+                                      [('Text file', '.txt'), 
+                                       ('CSV file', '.csv'), 
+                                       ('All Files', '*.*')])
+        if self.save_path is not None: # asksaveasfile return `None` if dialog closed with "cancel".
+            # Transform input for output file    
+            data = {}
+            samples = np.max([len(self.out_data[element][
+                list(self.out_data[element].keys())[0]]) 
+                for element in self.out_data])
+            for ii in self.out_data.keys():
+                for c in self.out_data[ii].keys():
+                    a, b = c.split('_')
+                    aux = self.out_data[ii][c]
+                    aux.extend([-1]*(samples - len(self.out_data[ii][c])))
+                    data[a+str(ii)+'_'+b] = aux
+                    
+            filedata = pd.DataFrame(data, columns = data.keys()).to_string()
+            self.save_path.seek(0) # Move to the first row to overwrite it
+            self.save_path.write(filedata)
+            self.save_path.flush() # Save without closing
+            # typically the above line would do. however this is used to ensure that the file is written
+            os.fsync(self.save_path.fileno())
+            self.saved = True
+         
+    def on_return(self, event):
+        ii = self.notebook.index(self.notebook.select())
+        val = self.tree[ii].item(self.treerow)['values']
+        val = [float(i) for i in val]
+        val[int(self.treecol[1:])-1] = float(self.entry.get())
+        self.tree[ii].item(self.treerow, values = val)
+        self.entry.destroy()        
+        self.saved = False
+        
+        if self.type[ii] == 'Rotating':
+            dx, dy = self.coord_calc(np.deg2rad(360 - val[0]))
+            dx_pr, dy_pr = self.coord_calc(np.deg2rad(360 - self.out_data[ii]['State_a'][self.treerow]))            
+            self.out_data[ii]['State_a'][self.treerow] = float(val[0])
+            self.canvas[ii].move("state"+str(ii)+'-'+str(self.treerow), dx-dx_pr, 
+                dy-dy_pr)
+            
+            self.out_data[ii]['Action_a'][self.treerow] = float(val[1])
+            if self.clock[ii].get()  == 'clock':
+                start = self.out_data[ii]['State_a'][self.treerow]
+                end = self.out_data[ii]['Action_a'][self.treerow]
+            else:
+                start = self.out_data[ii]['State_a'][self.treerow] - 360
+                end = self.out_data[ii]['Action_a'][self.treerow]
+            
+            self.canvas[ii].itemconfigure(
+                "action"+str(ii)+'-'+str(self.treerow), start = start, 
+                extent = end - start)
+        else:
+            # calculate distance moved from last position
+            dx_s = val[0] - self.out_data[ii]['State_x'][self.treerow]
+            dy_s = val[1] - self.out_data[ii]['State_y'][self.treerow]
+
+            self.canvas[ii].move("state"+str(ii)+'-'+str(self.treerow), dx_s, dy_s)
+
+            self.out_data[ii]['State_x'][self.treerow] = val[0]
+            self.out_data[ii]['State_y'][self.treerow] = val[1]
+
+            self.canvas[ii].coords(
+                "action"+str(ii)+'-'+str(self.treerow), 
+                (self.out_data[ii]['State_x'][self.treerow], 
+                  self.out_data[ii]['State_y'][self.treerow], val[2], val[3]))
+            self.out_data[ii]['Action_x'][self.treerow] = val[2]
+            self.out_data[ii]['Action_y'][self.treerow] = val[3]
+            
+    def OnDoubleClick(self, event):
+        
+        """ Executed when a row is double clicked.
+        Opens an entry box to edit a cell and updates the canvas and the 
+        stored data. """
+        
+        ii = self.notebook.index(self.notebook.select())
+        self.treerow = int(self.tree[ii].identify_row(event.y))
+        self.treecol = self.tree[ii].identify_column(event.x)
+        
+        # get column position info
+        x, y, width, height = self.tree[ii].bbox(self.treerow, self.treecol)
+    
+        # y-axis offset
+        pady = height // 2
+        # pady = 0
+        
+        if hasattr(self, 'entry'):
+            self.entry.destroy()
+            
+        self.entry = tk.Entry(self.tree[ii], justify='center') 
+        
+        if int(self.treecol[1:]) > 0:
+            self.entry.insert(
+                0, self.tree[ii].item(self.treerow)['values'][int(str(self.treecol[1:]))-1])
+            # self.entry['selectbackground'] = '#123456'
+            self.entry['exportselection'] = False
+    
+            self.entry.focus_force()
+            self.entry.bind("<Return>", self.on_return)
+            self.entry.bind("<Escape>", lambda *ignore: self.entry.destroy())
+            
+            self.entry.place(x = x, 
+                             y = y + pady, 
+                             anchor = tk.W, width = width)
+
+    def upload_sa(self):
+        
+        ii = self.notebook.index(self.notebook.select())
+        filename = askopenfilename(initialdir = os.getcwd(), 
+                                   title = 'Select a file', 
+                                   defaultextension = '.txt', 
+                                   filetypes = [('Text file', '.txt'), 
+                                                ('CSS file', '.css'), 
+                                                ('All Files', '*.*')])
+        if filename is not None:
+            data = open(filename,'r')
+            read = False
+            self.draw[ii].set('drag')
+            for n, point in enumerate(data):
+                if read: # Not elegant at all, just to omit the header.
+                    i, sx, sy, ax, ay = point.split()
+                    # Draw an oval in the given coordinates
+                    self.canvas[ii].create_oval(
+                        float(sx)-3, float(sy)-3, float(sx)+3, float(sy)+3, 
+                        fill="black", width=0, 
+                        tags=("state-" + str(len(
+                            self.out_data[ii]['State_x']))))
+                    self.canvas[ii].create_line(
+                        float(sx), float(sy), float(ax), float(ay), 
+                        fill="red", arrow=tk.LAST, 
+                        tags=("action"+str(ii)+"-"+ str(len(
+                            self.out_data[ii]['Action_x']))))
+                    self.out_data[ii]['State_x'].append(sx)
+                    self.out_data[ii]['State_y'].append(sy)
+                    self.out_data[ii]['Action_x'].append(ax)
+                    self.out_data[ii]['Action_y'].append(ay)
+                    self.tree.insert(
+                        parent = '', index = 'end', 
+                        iid = len(self.out_data[ii]['Action_x'])-1, 
+                        text = len(self.out_data[ii]['Action_x']), 
+                        values = tuple(self.dict2mat(
+                            self.out_data[ii])
+                            [len(self.out_data[ii]
+                                 ['Action_x'])-1,:].astype(int)))
+                    self.tree.selection_set(str(len(
+                        self.out_data[ii]['Action_x'])-1))
+                else:
+                    read = True
+    
+    def reset(self):
+        
+        ii = self.notebook.index(self.notebook.select())
+        msg = messagebox.askyesnocancel(
+            'Info', 'Are you sure you want to reset the canvas?')
+        if msg:
+            self.canvas[ii].delete(tk.ALL) # Reset canvas
+            self.state[ii].set('state')
+            self.out_data[ii] = {}
+            for key in self._class_list[ii]:
+                self.out_data[ii][key] = []
+                    
+            for record in self.tree[ii].get_children(): #Reset treeview
+                self.tree[ii].delete(record)
+            if self.type[ii] == 'Rotating': 
+                self.create_circle(self.x_ini, self.y_ini, 4, 
+                                   fill="#DDD", outline="", width=4)
+                self.create_circle(self.x_ini, self.y_ini, self.r, 
+                                   fill="", outline="#DDD", width=4)
+    
+    def dict2mat(self, X):
+        
+        max_size = 0
+        for key in X:
+            if len(X[key]) > max_size:
+                max_size = len(X[key])
+
+        result = -np.ones((max_size, len(X)))
+        for k, key in enumerate(X):
+            result[:len(X[key]), k]  = X[key]
+            
+        return result
+    
+    def angle_calc(self, x, y, circxy = False):
+        if (x == self.x_ini) & (y < self.y_ini):
+            alpha = math.pi/2
+        elif (x == self.x_ini) & (y > self.y_ini):
+            alpha = 3*math.pi/2
+        else:
+            alpha = np.arctan((y-self.y_ini)/(x-self.x_ini))
+            alpha += ((x-self.x_ini) < 0) * math.pi
+            alpha += (alpha < 0) * 2*math.pi
+        if circxy:
+            cx, cy = self.coord_calc(alpha)
+            return alpha, cx, cy
+        else:
+            return alpha
+        
+    def coord_calc(self, alpha):
+        cx = self.r * np.cos(alpha) + self.x_ini
+        cy = self.r * np.sin(alpha) + self.y_ini
+        return cx, cy
+
+        
+    def create_circle(self, x, y, r, **kwargs):
+        
+        return self.canvas[self.notebook.index(
+            self.notebook.select())].create_oval(x-r, y-r, x+r, y+r, **kwargs)
+    
+    def create_circle_arc(self, x, y, r, **kwargs):
+        
+        if "start" in kwargs and "end" in kwargs:
+            kwargs["extent"] = kwargs["end"] - kwargs["start"]
+            # if self.clock == 'countclock':
+            #     kwargs["extent"] += (kwargs["extent"] < 0) * 360
+            del kwargs["end"]
+        return self.canvas[self.notebook.index(
             self.notebook.select())].create_arc(x-r, y-r, x+r, y+r, **kwargs)
\ No newline at end of file
diff --git a/aidesign/GUI/plugins/MainPage.py b/aidesign/GUI/plugins/MainPage.py
new file mode 100644
index 00000000..a300125e
--- /dev/null
+++ b/aidesign/GUI/plugins/MainPage.py
@@ -0,0 +1,157 @@
+import tkinter as tk
+import os
+from PIL import Image, ImageTk
+from tkinter import messagebox
+from tkinter.filedialog import askopenfilename, askdirectory
+
+class MainPage(tk.Frame):
+
+    def __init__(self, parent, controller, config:dict):
+        " Here we define the main frame displayed upon opening the program."
+        " This leads to the different methods to provide feedback."
+        super().__init__(parent, bg = parent['bg'])
+        self.controller = controller
+        self.controller.title('AI Assisted Framework Design')
+        
+        script_dir = os.path.dirname(__file__)
+        self.my_img1 = ImageTk.PhotoImage(
+                            Image.open(
+                                os.path.join(
+                                    script_dir,
+                                    'resources',
+                                    'Assets',
+                                    'AIFRED.png')
+                                    ).resize((600, 300))
+                                )
+        self.my_label = tk.Label(self, 
+                                    image = self.my_img1,
+                                    bg = parent['bg'])
+
+        self.my_label.grid(column = 0,
+                            row = 0,
+                            rowspan = 10,
+                            columnspan = 4)
+        
+        my_label = tk.Label(self, 
+                                text = 
+                                'Indicate the data folder and define the pipeline.',
+                                pady= 10,
+                                font = controller.title_font,
+                                bg = parent['bg'],
+                                fg = 'white')
+        my_label.grid(column = 0,
+                            row = 11,
+                            columnspan = 4)
+        
+        tk.Button(self,
+                    text = 'Data folder',
+                    fg = 'white',
+                    font = controller.title_font, 
+                    bg = parent['bg'],
+                    height = 3,
+                    width = 20, 
+                    command = self.upload_data,
+                    ).grid(column = 0, row = 12)
+        self.controller.Datalabel = tk.Label(self, 
+                                text = 'Incomplete',
+                                pady= 10,
+                                padx= 10,
+                                font = controller.title_font,
+                                bg = parent['bg'],
+                                fg = 'white')
+        self.controller.Datalabel.grid(column = 2,
+                            row = 12)
+        
+        tk.Button(self,
+                    text = 'Interact with canvas',
+                    fg = 'white',
+                    font = controller.title_font,
+                    bg = parent['bg'],
+                    height = 3,
+                    width = 20, 
+                    command = self.canvas
+                    ).grid(column = 0, row = 13)
+
+        tk.Button(self,
+                    text = 'Upoad XML file',
+                    fg = 'white',
+                    font = controller.title_font, 
+                    bg = parent['bg'],
+                    height = 3,
+                    width = 20, 
+                    command = self.upload_xml,
+                    ).grid(column = 1, row = 13)
+        
+        self.controller.XMLlabel = tk.Label(self, 
+                                text = 'Incomplete',
+                                pady= 10,
+                                padx= 10,
+                                font = controller.title_font,
+                                bg = parent['bg'],
+                                fg = 'white')
+        self.controller.XMLlabel.grid(column = 2,
+                            row = 13)
+        
+        self.RunButton = tk.Button(self,
+                    text = 'Run Pipeline',
+                    fg = 'white',
+                    font = controller.title_font, 
+                    bg = parent['bg'],
+                    height = 3,
+                    width = 20, 
+                    state = tk.DISABLED,
+                    command = self.controller.destroy,
+                    )
+        self.RunButton.grid(column = 0, row = 14)
+        
+        self.controller.XML = tk.IntVar()
+        self.controller.Data = tk.IntVar()
+        self.controller.XML.set(False)
+        self.controller.Data.set(False)
+        
+        self.controller.XML.trace('w', self.trace_XML)
+        self.controller.Data.trace('w', self.trace_Data)
+
+    # def light_theme(self):
+    #     listbox_tasks.config(bg="white", fg="black")
+    #     button_add_task.config(highlightbackground='white')
+    #     button_delete_task.config(highlightbackground='white')
+    #     button_load_tasks.config(highlightbackground='white')
+    #     button_save_tasks.config(highlightbackground='white')
+    #     entry_task.config(bg='white', fg='black')       
+    
+    def trace_XML(self,*args):
+        print('XML', self.controller.XML.get(), self.controller.Data.get())
+        if self.controller.XML.get():
+            self.controller.XMLlabel.config(text = 'Done!', fg = 'green')
+            if self.controller.Data.get():
+                self.RunButton.config(state = 'normal')
+
+    def trace_Data(self,*args):
+        print('Data', self.controller.XML.get(), self.controller.Data.get())
+        if self.controller.Data.get():
+            self.controller.Datalabel.config(text = 'Done!', fg = 'green')
+            if self.controller.XML.get():
+                self.RunButton.config(state = 'normal')
+
+    def canvas(self):
+        self.controller.show_frame("aidCanvas")
+    
+    def upload_xml(self):
+        filename = askopenfilename(initialdir = os.getcwd(), 
+                                   title = 'Select a file', 
+                                   defaultextension = '.xml', 
+                                   filetypes = [('XML file', '.xml'), 
+                                                ('All Files', '*.*')])
+        self.controller._append_to_output("xml_filename",filename)
+        self.controller.XML.set(True)
+        # self.controller.XMLlabel.config(text = 'Done!', fg = 'green')
+
+    def upload_data(self):
+        filename = askdirectory(initialdir = os.getcwd(),
+                                    title = 'Select a folder',
+                                    mustexist = True)
+        self.controller._append_to_output("data_filename",filename)
+        if filename is not None:
+            self.controller.Data.set(True)
+            # self.controller.Datalabel.config(text = 'Done!', fg = 'green')
\ No newline at end of file
diff --git a/aidesign/GUI/plugins/manual_input.py b/aidesign/GUI/plugins/ManualInput.py
similarity index 92%
rename from aidesign/GUI/plugins/manual_input.py
rename to aidesign/GUI/plugins/ManualInput.py
index 2f7e8640..5f1c50be 100644
--- a/aidesign/GUI/plugins/manual_input.py
+++ b/aidesign/GUI/plugins/ManualInput.py
@@ -1,35 +1,29 @@
 from . import UI
-import tkinter as tk                # python 3
+import tkinter as tk
 import os
 from PIL import Image, ImageTk
 from tkinter import messagebox, ttk
 from tkinter.filedialog import asksaveasfile, askopenfile, askopenfilename
 import numpy as np
 import pandas as pd
-import math
 
 
-class PageManual(tk.Frame):
-    def __init__(self, parent, controller):
+class ManualInput(tk.Frame,UI):
+    def __init__(self, parent, controller, config:dict):
         self.parent = parent    
         super().__init__(parent, bg = self.parent['bg'])
         self.controller = controller
         self.controller.title('Manual Input')
         
-        self.dirpath = os.getcwd()
-        self.assets_path = os.path.join(self.dirpath,'aidesign','GUI','plugins','resources','Assets')
-
-        # self._class_list = ['Atelectasis', 'Cardiomelagy', 'Effusion', 
-        #                    'Infiltration', 'Mass', 'Nodule', 'Pneumonia', 
-        #                    'Pneumothorax'
-        #                    ]
+        
+        self.dirpath = os.path.dirname(os.path.realpath(__file__))
+        self.assets_path = os.path.join(self.dirpath,'resources','Assets')
 
         self._class_list = None        
         pixels = 500
-        path = os.path.join(self.dirpath,'aidesign','GUI','plugins','resources','example_radiography_images')
+        path = os.path.join(self.dirpath,'resources','example_radiography_images')
         self.N = len(os.listdir(path))
         
-        # Create a list with all the available (I have tried reading the file when placing it but wasn't able to)
         self.image_list = []
         for f in os.listdir(path):
             self.image_list.append(
@@ -65,12 +59,17 @@ def __init__(self, parent, controller):
             command = lambda: self.forward_back(2)).grid(column = 2,row = 19)
         
         button_main = tk.Button(
-            self, text="Go to the main page", 
+            self, text="Done", 
             fg = 'white', bg = self.parent['bg'], height = 3, width = 20, 
             command = self.check_quit).grid(column = 4,row = 19)
+        self._parse_config(config)
         self.save_path = ''
         self.saved = True
         
+    def _parse_config(self, config):
+        self.config = config
+        self.class_list(self.config["plugin"]["options"]["class_list"])
+
     def class_list(self):
         """Getter for required _class_list variable
         
@@ -175,12 +174,15 @@ def check_quit(self):
         
         if not self.saved:
             response = messagebox.askokcancel(
-                "Are you sure you want to leave?", 
+                "Exit?", 
                 "Do you want to leave the program without saving?")
             if response:
-                self.controller.show_frame("StartPage")
+                self.controller.destroy()
         else:
-            self.controller.show_frame("StartPage")
+            response = messagebox.askokcancel(
+                "Exit?",
+                "Are you sure you are finished?")
+            self.controller.destroy()
     
     def open_file(self):
         
diff --git a/aidesign/GUI/plugins/__init__.py b/aidesign/GUI/plugins/__init__.py
index ea9d559e..7f700d67 100644
--- a/aidesign/GUI/plugins/__init__.py
+++ b/aidesign/GUI/plugins/__init__.py
@@ -4,7 +4,4 @@
 
 Let's look for a more automatic way of doing this when we have more modules
 """
-from ..UserInterfaceClass import UI
-from .canvas_input import PageCanvas
-from .manual_input import PageManual
-from .startpage import StartPage
\ No newline at end of file
+from ..User_Feedback_template import UI
diff --git a/aidesign/GUI/plugins/aidCanvas.py b/aidesign/GUI/plugins/aidCanvas.py
new file mode 100644
index 00000000..1649630f
--- /dev/null
+++ b/aidesign/GUI/plugins/aidCanvas.py
@@ -0,0 +1,855 @@
+# Import the required libraries
+import tkinter as tk
+from PIL import Image, ImageTk
+import os
+import numpy as np
+import pandas as pd
+from tkinter.filedialog import asksaveasfile, askopenfile, askopenfilename
+from tkinter import messagebox
+
+from aidesign.Settings.Settings_core import Settings
+
+class aidCanvas(tk.Frame):
+
+    """ Creates a frame with a canvas and allows to include different modules
+    for display which translate into new modules defined for the framework."""
+    
+    def __init__(self, parent, controller, config:dict):
+        
+        " Here we define the main frame displayed upon opening the program."
+        " This leads to the different methods to provide feedback."
+        
+        super().__init__(parent, bg = parent['bg'])
+        self.bg = parent['bg']
+        self.controller = controller
+        
+        script_dir = os.path.dirname(__file__)
+        self.tk.call('wm','iconphoto', self.controller._w, ImageTk.PhotoImage(
+            file = os.path.join(os.path.join(
+                script_dir, 
+                'resources', 
+                'Assets', 
+                'AIDIcon.ico'))))
+        self.my_img1 = ImageTk.PhotoImage(Image.open(os.path.join(
+                script_dir, 
+                'resources', 
+                'Assets', 
+                'AIDIcon_name.png')).resize((250, 200)))
+        
+        self.my_label = tk.Label(self, image = self.my_img1, bg = parent['bg'])
+        self.my_label.grid(column = 5, row = 0)
+        
+        # Create canvas
+        self.width, self.height = 600, 600
+        self.canvas = tk.Canvas(self, width=self.width, 
+            height=self.height, background="white")
+        self.canvas.grid(row=0, column=0, columnspan=4, rowspan = 10, 
+                         padx = 10, pady = 10)
+        
+        self.canvas.startxy = []
+        self.out_data = pd.DataFrame()
+        self.connections = {}
+        self.modules = 0
+        self.module_list = []
+        self.module_names = []
+        
+        # Create module
+        self.w, self.h = 100, 50
+        self.cr = 4
+        
+        #Initialiser module
+        self.add_module('Initialiser', self.width/2, self.h, ini = True)
+        self.add_module('Output', self.width/2, self.height - self.h, out = True)
+        
+        self.draw = False
+        self.canvas.bind("<B1-Motion>", self.on_drag)
+        self.canvas.bind('<Button-1>', self.select)
+        self.l = 0 #number of loops
+        self.drawLoop = False
+        self.loops = []
+        
+        # self.canvas.bind("<ButtonPress-1>", self.on_button_press)
+        self.canvas.bind("<ButtonRelease-1>", self.on_button_release)
+        
+        
+        # for m, module in enumerate(modules):
+        tk.Button(
+            self, text = 'Data processing', fg = 'white', bg = parent['bg'],
+            height = 3, width = 25, font = self.controller.pages_font,
+            command = lambda: self.add_module('DataProcessing', 
+                                              self.width/2, 
+                                              self.height/2)
+            ).grid(column = 5, row = 1)
+        tk.Button(
+            self, text = 'Modelling', fg = 'white', bg = parent['bg'],
+            height = 3, width = 25, font = self.controller.pages_font,
+            command = lambda: self.add_module('Modelling', 
+                                              self.width/2, 
+                                              self.height/2)
+            ).grid(column = 5, row = 2)
+        tk.Button(
+            self, text = 'Decision making', fg = 'white', bg = parent['bg'],
+            height = 3, width = 25, font = self.controller.pages_font,
+            command = lambda: self.add_module('DecisionMaking', 
+                                              self.width/2, 
+                                              self.height/2)
+            ).grid(column = 5, row = 3)
+        tk.Button(
+            self, text = 'User Feedback', fg = 'white', bg = parent['bg'],
+            height = 3, width = 25, font = self.controller.pages_font,
+            command = lambda: self.add_module('UserFeedback', 
+                                              self.width/2, 
+                                              self.height/2)
+            ).grid(column = 5, row = 4)
+        tk.Button(
+            self, text = 'Input data', fg = 'white', bg = parent['bg'],
+            height = 3, width = 25, font = self.controller.pages_font,
+            command = lambda: self.add_module('InputData', 
+                                              self.width/2, 
+                                              self.height/2)
+            ).grid(column = 5, row = 5)
+        tk.Button(
+            self, text = 'Delete selection', fg = 'white', bg = parent['bg'],
+            height = 3, width = 25, font = self.controller.pages_font,
+            command = self.delete_sel).grid(column = 5, row = 6)
+        tk.Button(
+            self, text = 'Upload', fg = 'white', bg = parent['bg'], 
+            height = 3, width = 20, font = self.controller.pages_font, 
+            command = self.upload).grid(column = 0, row = 10, padx = 10)
+        tk.Button(
+            self, text = 'Save', fg = 'white', bg = parent['bg'],
+            height = 3, width = 20, font = self.controller.pages_font, 
+            command = self.save_file).grid(column = 1, row = 10)
+        tk.Button(
+            self, text = 'Reset', fg = 'white', bg = parent['bg'], 
+            height = 3, width = 20, font = self.controller.pages_font, 
+            command = self.reset).grid(column = 2, row = 10)
+        tk.Button(
+            self, text = 'Done', fg = 'white', bg = parent['bg'], 
+            height = 3, width = 20, font = self.controller.pages_font, 
+            command = self.check_quit).grid(column = 3, row = 10)
+        
+        self.save_path = ''
+        self.saved = True
+
+    def class_list(self,value):
+        """ Temporary fix """
+        return value
+
+    def on_return_display(self, event):
+        """ Defines the type of loop and condition for the indicated loop
+        """
+        if self.loopDisp:
+            condition = self.entry1.get()
+            self.entry1.destroy()
+            if hasattr(self, 'entry2'):
+                self.entry2.focus()
+            key = 'type'
+            text_w = self.controller.pages_font.measure(condition) + 20
+            x = self.start_x + text_w/2
+        else:
+            condition = self.entry2.get()
+            self.entry2.destroy()
+            key = 'condition'
+            text_w = self.controller.pages_font.measure(condition) + 20
+            x = self.curX - text_w/2
+        self.loops[-1][key] = condition
+        self.canvas.create_text(
+            x, 
+            self.start_y + 20, 
+            font = self.controller.pages_font, 
+            text = condition, 
+            tags = ('loop-'+str(self.l-1), key+'-'+str(self.l-1)), 
+            justify = tk.CENTER)
+        self.canvas.tag_bind(key+'-'+str(self.l-1), 
+                         "<Double-1>", self.OnDoubleClick)
+        self.loopDisp = not self.loopDisp
+    
+    def on_button_release(self, event):
+        """ Finishes drawing the rectangle for loop definition. 
+            Once the rectangle is drawn, it stores the information of the 
+            modules inside the loop.
+        """
+        if self.drawLoop:
+            self.drawLoop = False
+            
+            if (abs(self.start_x - self.curX) > 10) and (
+                    abs(self.start_y - self.curY) > 10):
+                self.l += 1
+                # Identify modules in area
+                loopMod = self.canvas.find_enclosed(self.start_x, self.start_y, 
+                                                    self.curX, self.curY)
+                self.loops.append({'type': None,
+                                   'condition': None,
+                                   'mod': [], 
+                                   'coord': (self.start_x, self.start_y, 
+                                             self.curX, self.curY)})
+                for mod in loopMod:
+                    aux = self.canvas.itemcget(mod, 'tags').split(' ')[1]
+                    if len(aux) == 2 and aux[0] == 't':
+                        self.loops[-1]['mod'].append(self.canvas.itemcget(
+                            mod, 'text'))
+                
+                # Ask for loop definition and condition and display               
+                self.entry1 = tk.Entry(self.canvas, justify='center', 
+                                      font = self.controller.pages_font)
+                text = 'For/While'
+                self.loopDisp = True
+                self.entry1.insert(0, text)
+                # self.entry['selectbackground'] = '#d0d4d9'
+                self.entry1['exportselection'] = False
+        
+                self.entry1.focus()
+                self.entry1.bind("<Return>", self.on_return_display)
+                self.entry1.bind("<Escape>", lambda *ignore: self.entry1.destroy())
+                
+                self.entry1.place(
+                    x = self.start_x + 10,
+                    y = self.start_y + 20, 
+                    anchor = tk.W,
+                    width = self.controller.pages_font.measure(text)+10)
+                text = 'Condition'
+                self.entry2 = tk.Entry(self.canvas, justify='center', 
+                                      font = self.controller.pages_font)
+                self.entry2.insert(0, text)
+                self.entry2['exportselection'] = False
+        
+                self.entry2.bind("<Return>", self.on_return_display)
+                self.entry2.bind("<Escape>", lambda *ignore: self.entry.destroy())
+                
+                self.entry2.place(
+                    x = self.curX-self.controller.pages_font.measure(text)-20,
+                    y = self.start_y + 20, 
+                    anchor = tk.W,
+                    width = self.controller.pages_font.measure(text)+10)
+                
+                self.saved = False
+            else:
+                self.canvas.delete('loop-'+str(self.l))
+        else:
+            self.updateLoops()
+    
+    def updateLoops(self):
+        """ Checks if, after some movement, a module is included in an existing
+            loop
+        """
+        for l, loop in enumerate(self.loops):
+            coord = loop['coord']
+            loopMod = self.canvas.find_enclosed(coord[0], coord[1],
+                                                coord[2], coord[3])
+            self.loops[l]['mod'] = []
+            for mod in loopMod:
+                aux = self.canvas.itemcget(mod, 'tags').split(' ')[1]
+                if len(aux) == 2 and aux[0] == 't':
+                    self.loops[l]['mod'].append(self.canvas.itemcget(
+                        mod, 'text'))
+        print('Updated loop info:', self.loops)
+        
+    def select(self, event):
+        """ Selects the module at the mouse location. """
+        self.selected = self.canvas.find_overlapping(
+            event.x-5, event.y-5, event.x+5, event.y+5)
+        if self.selected:
+            if len(self.selected) > 2:
+                self.canvas.selected = self.selected[-2]
+            else:
+                self.canvas.selected = self.selected[-1]
+            # self.canvas.startxy = (event.x, event.y)
+        else:
+            self.canvas.selected = None
+            # save mouse drag start position
+            self.start_x = self.canvas.canvasx(event.x)
+            self.start_y = self.canvas.canvasy(event.y)
+            self.curX = self.start_x
+            self.curY = self.start_y
+            self.selected = self.canvas.find_overlapping(
+                        event.x-5, event.y-5, event.x+5, event.y+5)
+    
+            # create rectangle if not yet exist
+            if not self.selected:
+                self.rect = self.canvas.create_rectangle(event.x, event.y, 
+                                             event.x, event.y, 
+                                             outline = '#4ff07a',
+                                             tag = 'loop-'+str(self.l))
+                                             # ,fill = '#b3ffc7')
+                self.drawLoop = True
+                if self.l == 0:
+                    self.canvas.tag_lower('loop-'+str(self.l))
+                else:
+                    self.canvas.tag_raise('loop-'+str(self.l), 'loop-'+str(self.l-1))
+        
+        if len(self.canvas.gettags("current")) > 0:
+            if(len(self.canvas.gettags("current")[0].split('-')) > 1) and (
+                self.canvas.gettags("current")[0].split('-')[0] == 'loop'):
+                self.isLoop = True
+                self.m = int(self.canvas.gettags("current")[0].split('-')[1])
+            else:
+                self.isLoop = False
+                self.m = int(self.canvas.gettags("current")[0][1:])
+    
+    def on_drag(self, event):
+        """ Uses the mouse location to move the module and its text. 
+        At the same time, it looks up if there are any connection to this
+        module and subsequently moves the connection."""
+        
+        if self.drawLoop:
+            self.curX = self.canvas.canvasx(event.x)
+            self.curY = self.canvas.canvasy(event.y)
+    
+            # w, h = self.canvas.winfo_width(), self.canvas.winfo_height()
+            # if event.x > 0.9*w:
+            #     self.canvas.xview_scroll(1, 'units') 
+            # elif event.x < 0.1*w:
+            #     self.canvas.xview_scroll(-1, 'units')
+            # if event.y > 0.9*h:
+            #     self.canvas.yview_scroll(1, 'units') 
+            # elif event.y < 0.1*h:
+            #     self.canvas.yview_scroll(-1, 'units')
+    
+            # expand rectangle as you drag the mouse
+            self.canvas.coords('loop-'+str(self.l), self.start_x, 
+                               self.start_y, self.curX, self.curY)
+        else:
+            self.select(event)
+            
+            self.m = int(self.canvas.gettags("current")[0][1:])
+            dx = event.x - self.canvas.startxy[self.m][0]
+            dy = event.y - self.canvas.startxy[self.m][1]
+            # if self.isLoop:
+                # self.canvas.move('loop-'+str(self.m), dx, dy) 
+            # else:
+            # module
+            self.canvas.move('o'+str(self.m), dx, dy)
+            # self.updateLoops()
+            # Connections
+            if any(self.out_data.iloc[self.m].values) or any(
+                    self.out_data[self.out_data.columns[self.m]].values):
+                out = np.arange(self.out_data.values.shape[0])[
+                    self.out_data.values[self.m, :]==1]
+                for o in out:
+                    xycoord_i = self.canvas.coords(
+                        self.connections[o][self.m].split('-')[0])
+                    xycoord_o = self.canvas.coords(
+                        self.connections[o][self.m].split('-')[1])
+                    self.canvas.coords(self.connections[o][self.m], 
+                            (xycoord_i[0] + self.cr, 
+                             xycoord_i[1] + self.cr, 
+                             xycoord_o[0] + self.cr, 
+                             xycoord_o[1] + self.cr))
+                inp = np.arange(self.out_data.values.shape[0])[
+                    self.out_data.values[:, self.m]==1]
+                for i in inp:
+                    xycoord_i = self.canvas.coords(
+                        self.connections[self.m][i].split('-')[0])
+                    xycoord_o = self.canvas.coords(
+                        self.connections[self.m][i].split('-')[1])
+                    self.canvas.coords(self.connections[self.m][i], 
+                            (xycoord_i[0] + self.cr, 
+                             xycoord_i[1] + self.cr, 
+                             xycoord_o[0] + self.cr, 
+                             xycoord_o[1] + self.cr))
+            # Update module location
+            self.canvas.startxy[self.m] = (event.x, event.y)
+    
+    def module_out(self, name):
+        """ Updates the output DataFrame.
+        
+        :param name: name of the model
+        :type name: str
+        """
+        name_list = list(self.out_data.columns)
+        m_num = [n for n in name_list if (n.split('-')[0] == name)]
+        if len(m_num) > 0:
+            name_list.append(name + '-' + str(len(m_num)))
+        else:
+            name_list.append(name)
+        self.canvas.itemconfig('t'+str(self.modules), text = name_list[-1])
+        values = self.out_data.values
+        values = np.vstack((
+                    np.hstack((values, np.zeros((values.shape[0],1)))),
+                    np.zeros((1, values.shape[0]+1))))
+        self.out_data = pd.DataFrame(values, 
+                                     columns = name_list, 
+                                     index = name_list)
+        self.module_names.append(name_list[-1])
+
+    def add_module(self, boxName, x, y, ini = False, out = False):
+        """ Creates a rectangular module with the corresponding text inside.
+        
+        :param boxName: name of the model
+        :type boxName: str
+        """
+        if not ini and not out:
+            tag = ('o'+str(self.modules),)
+        else: #Make initialisation and output unmoveable
+            tag = ('n0',)
+        text_w = self.controller.pages_font.measure(boxName+'-00') + 10
+        self.canvas.create_rectangle(
+            x - text_w/2 , 
+            y - self.h/2, 
+            x + text_w/2, 
+            y + self.h/2, 
+            tags = tag + ('p'+str(self.modules),), 
+            fill = self.bg, width = 3,
+            activefill = '#dbaa21')
+        self.canvas.create_text(
+            x, 
+            y, 
+            font = self.controller.pages_font, 
+            text = boxName, 
+            tags = tag + ('t'+str(self.modules),), 
+            fill = '#d0d4d9', 
+            justify = tk.CENTER)
+        self.canvas.tag_bind('t'+str(self.modules), 
+                             "<Double-1>", self.OnDoubleClick)
+        if not out:
+            self.canvas.create_oval(
+                x - self.cr, 
+                y + self.h/2 - self.cr, 
+                x + self.cr, 
+                y + self.h/2 + self.cr, 
+                width = 2, 
+                fill = 'black', 
+                tags = tag + ('d'+str(self.modules),))
+            self.canvas.tag_bind('d'+str(self.modules), 
+                                 "<Button-1>", self.join_modules)
+            
+        if not ini:
+            self.canvas.create_oval(
+                x - self.cr, 
+                y - self.h/2 - self.cr, 
+                x + self.cr, 
+                y - self.h/2 + self.cr, 
+                width = 2, 
+                fill = 'black', 
+                tags = tag + ('u'+str(self.modules),))
+            self.canvas.tag_bind('u'+str(self.modules), 
+                                 "<Button-1>", self.join_modules)
+        
+        if not out and not ini:
+            self.canvas.create_oval(
+                x - text_w/2 - self.cr, 
+                y - self.cr, 
+                x - text_w/2 + self.cr, 
+                y + self.cr, 
+                width = 2, 
+                fill = 'black', 
+                tags = tag + ('l'+str(self.modules),))
+            self.canvas.tag_bind('l'+str(self.modules), 
+                                 "<Button-1>", self.join_modules)
+        
+            self.canvas.create_oval(
+                x + text_w/2 - self.cr, 
+                y - self.cr, 
+                x + text_w/2 + self.cr, 
+                y + self.cr, 
+                width = 2, 
+                fill = 'black', 
+                tags = tag + ('r'+str(self.modules),))
+            self.canvas.tag_bind('r'+str(self.modules), 
+                                 "<Button-1>", self.join_modules)
+        self.canvas.startxy.append((x, 
+                                    y))
+        self.connections[self.modules] = {}
+        self.module_out(boxName)
+        self.module_list.append(boxName)
+        self.modules += 1
+        self.saved = False
+    
+    def on_return_rename(self, event):
+        """ This function renames a module to the input specified through 
+        an entry widget. It then updates the corresponding rows and columns in
+        the stored data."""
+        
+        moduleName = self.entry.get()
+        if (moduleName in list(self.module_names)) and not(
+                moduleName == list(self.module_names)[self.m]):
+            messagebox.showwarning("Error", "This module already exists.")
+        else:
+            self.canvas.itemconfig('t'+str(self.m), text = moduleName)
+            x0, y0, x1, y1 = self.canvas.coords('p'+str(self.m))
+            text_w = self.controller.pages_font.measure(moduleName) + 20
+            shift = (text_w-x1+x0)/2
+            self.canvas.coords('p'+str(self.m), # Resize rectangle
+                               x0 - shift, 
+                               y0, 
+                               x1 + shift, 
+                               y1)
+            x0, y0, x1, y1 = self.canvas.coords('l'+str(self.m))
+            self.canvas.coords('l'+str(self.m), # Move left circle
+                                x0 - shift, 
+                                y0, 
+                                x1 - shift, 
+                                y1)
+            x0, y0, x1, y1 = self.canvas.coords('r'+str(self.m))
+            self.canvas.coords('r'+str(self.m), # Move left circle
+                                x0 + shift, 
+                                y0, 
+                                x1 + shift, 
+                                y1)
+            self.entry.destroy()
+            self.module_names[self.m] = moduleName
+            self.saved = False
+            
+    def on_return_editLoop(self, event):
+        """ This function renames loop conditions and updates the loop
+        information."""
+        
+        newText = self.entry.get()
+        conditions = self.canvas.itemcget(
+                        self.selected[0], 'tags').split(' ')[1].split('-')
+        self.canvas.itemconfig(self.selected[0], text = newText)
+        self.entry.destroy()
+        self.loops[int(conditions[1])][conditions[0]] = newText
+        self.saved = False
+        
+    def OnDoubleClick(self, event):
+        
+        """ Executed when text is double clicked.
+        Opens an entry box to edit the module name and updates the display and
+        the stored data. """
+        
+        self.selected = self.canvas.find_overlapping(
+            event.x-5, event.y-5, event.x+5, event.y+5)
+        
+        # If not loop text
+        if len(self.canvas.itemcget(
+                self.selected[0], 'tags').split(' ')[-2].split('-')) < 2:
+            
+            x0, y0, x1, y1 = self.canvas.coords(self.canvas.gettags("current")[0])
+            if hasattr(self, 'entry'):
+                self.entry.destroy()
+            
+            entryText = self.canvas.itemcget('t'+str(self.m), 'text')
+            
+            self.entry = tk.Entry(self.canvas, justify='center', 
+                                  font = self.controller.pages_font)
+    
+            self.entry.insert(
+                0, entryText)
+            self.entry['selectbackground'] = '#d0d4d9'
+            self.entry['exportselection'] = False
+    
+            self.entry.focus_force()
+            self.entry.bind("<Return>", self.on_return_rename)
+            self.entry.bind("<Escape>", lambda *ignore: self.entry.destroy())
+            
+            self.entry.place(x = x0, 
+                              y = y0 + (y1-y0)/2, 
+                              anchor = tk.W, width = x1 - x0)
+        # If loop text
+        else:
+            x0, y0 = self.canvas.coords(self.selected[0])
+            if hasattr(self, 'entry'):
+                self.entry.destroy()
+
+            # condition = self.canvas.itemcget(
+            #             self.selected[0], 'tags').split(' ')[-2]#.split('-')[0]
+            entryText = self.canvas.itemcget(self.selected[0], 'text')
+            
+            self.entry = tk.Entry(self.canvas, justify='center', 
+                                  font = self.controller.pages_font)
+            self.entry.insert(0, entryText)
+            # self.entry1['selectbackground'] = '#d0d4d9'
+            self.entry['exportselection'] = False
+    
+            self.entry.focus()
+            self.entry.bind("<Return>", self.on_return_editLoop)
+            self.entry.bind("<Escape>", lambda *ignore: self.entry.destroy())
+            text_w = self.controller.pages_font.measure(entryText) + 20
+            self.entry.place(x = x0 - text_w/2, 
+                          y = y0, 
+                          anchor = tk.W, 
+                          width = text_w)
+                
+    def delete_sel(self):
+        """ Deletes last selected module"""
+        if self.canvas.selected:
+            if self.isLoop:
+                self.canvas.delete('loop-'+str(self.m))
+                self.loops[self.m] = -1
+            elif not(self.m == 0):
+                self.canvas.delete('o'+str(self.m))
+                
+                col = self.out_data.columns[self.m]
+                self.out_data[col] = 0
+                self.out_data.loc[col] = 0
+                self.saved = False
+                self.module_names[self.m] = None
+                
+    def join_modules(self, event):
+        """ Draws a connecting line between two connecting circles. """
+        if self.draw:
+            tags = self.canvas.gettags(self.canvas.find_overlapping(
+            event.x-self.cr, event.y-self.cr, 
+            event.x+self.cr, event.y+self.cr)[-1])
+            tag2 = tags[-2] if tags[-1] == "current" else tags[-1]
+            if tag2[0] in ['d', 'l', 'u', 'r'] and int(
+                    tag2[1:]) != int(self.tag[1:]):
+                self.canvas.create_line(
+                            self.canvas.linestartxy[0] + self.cr, 
+                            self.canvas.linestartxy[1] + self.cr, 
+                            self.canvas.coords(tag2)[0] + self.cr, 
+                            self.canvas.coords(tag2)[1] + self.cr,
+                            fill = "red", 
+                            arrow = tk.LAST, 
+                            tags = ('o'+str(int(self.tag[1:])), 
+                                  'o'+str(int(tag2[1:])), self.tag + '-' + tag2))
+                self.out_data.iloc[int(self.tag[1:])][int(tag2[1:])] = 1
+                self.connections[int(tag2[1:])][
+                    int(self.tag[1:])] = self.tag + '-' + tag2
+            self.draw = False
+            self.saved = False
+        else:
+            tags = self.canvas.gettags(self.canvas.find_overlapping(
+            event.x-self.cr, event.y-self.cr, 
+            event.x+self.cr, event.y+self.cr)[-1])
+            self.tag = tags[-2] if tags[-1] == "current" else tags[-1]
+            self.canvas.linestartxy = self.canvas.coords(self.tag)#(event.x, event.y)
+            self.draw = True
+
+    def save_file_as(self):
+        
+        self.save_path = asksaveasfile(mode='w')
+        self.save_file()
+        
+    def save_file(self):
+        
+        if self.save_path == '':
+            self.save_path = asksaveasfile(defaultextension = '.xml', filetypes = 
+                                      [('XML file', '.xml'), 
+                                       ('All Files', '*.*')])
+        if self.save_path is not None: # asksaveasfile return `None` if dialog closed with "cancel".
+            # Transform input for output file
+            data = self.out_data.copy()
+            if np.sum(data.values[0,:]) == 0:
+                messagebox.showwarning(
+                    "Error", "Initialiser is not connected to any module.")
+            elif np.sum(data.values[:,1]) == 0:
+                messagebox.showwarning(
+                    "Error", "Output is not connected to any module.")
+            idx = (np.sum(data.values, axis = 0) 
+                   + np.sum(data.values, axis = 1)) < 1
+            col = np.array(data.columns)
+            for c in np.arange(len(idx))[idx]:
+                data = data.drop(columns=col[c], index=col[c])
+                
+            loop_modules = np.unique([v for a in self.loops for v in a['mod']])
+            out_loops = np.zeros_like(self.loops)
+            
+            values = data.values.astype(bool)
+            mn = np.array(self.module_names)
+            mn_id = [m is not None for m in mn]
+            mn = mn[mn_id]
+            s = Settings()
+            s.new_config_file(self.save_path.name)
+            s.filename = self.save_path.name
+            s.append_pipeline_module(self.module_list[0], # Initialiser
+                                  mn[0],
+                                  "",
+                                  {},
+                                  list(mn[values[:,0]]),
+                                  list(mn[values[0,:]]),
+                                  None,
+                                  [self.canvas.startxy[0], 0, self.connections[0]])
+            for i, mnn in enumerate(mn_id):
+                if (i > 1) and mnn:
+                    xml_parent = None
+                    parent_loops = []
+                    if mn[i] in loop_modules: # Model in any loop
+                        for x, loop in enumerate(self.loops):
+                            if mn[i] in loop['mod']: # Model in this loop
+                                if not out_loops[x]: # Loop already defined
+                                    s.append_pipeline_loop(self.loops[x]['type'],
+                                                self.loops[x]['condition'],
+                                                "loop"+str(x),
+                                                parent_loops,
+                                                list(loop['mod']),
+                                                xml_parent,
+                                                self.loops[x]['coord']
+                                                )
+                                    out_loops[x] = 1
+                                xml_parent = "loop"+str(x) # parent is the last loop
+                                parent_loops.append("loop"+str(x))
+
+                    s.append_pipeline_module(self.module_list[i],
+                      mn[i],
+                      "",
+                      {},
+                      list(mn[values[:,i]]),
+                      list(mn[values[i,:]]),
+                      xml_parent,
+                      [self.canvas.startxy[i], i, self.connections[i]])
+                
+            s.append_pipeline_module(self.module_list[1], # Out
+                      mn[1],
+                      "",
+                      {},
+                      list(mn[values[:,1]]),
+                      list(mn[values[1,:]]),
+                      None,
+                      [self.canvas.startxy[1], 1, self.connections[1]])
+            s.write_to_XML()
+            self.saved = True
+            self.controller.append_to_output("xml_filename",self.save_path.name)
+
+    def upload(self):
+        
+        filename = askopenfilename(initialdir = os.getcwd(), 
+                                   title = 'Select a file', 
+                                   defaultextension = '.xml', 
+                                   filetypes = [('XML file', '.xml'), 
+                                                ('All Files', '*.*')])
+        if filename is not None:
+            self.reset()
+
+            s = Settings()
+            s.load_XML(filename)
+            s._print_pretty(s.loaded_modules)
+            modules = s.loaded_modules
+            modout = modules['output']
+            del modules['Initialiser'], modules['output'] # They are generated when resetting
+            disp_mod = ['Initialiser', 'output']
+            id_mod = [0, 1]
+            
+            # Place the modules
+            disp_mod, id_mod = self.place_modules(modules, id_mod, disp_mod)
+            connect = list(modout['coordinates'][2].keys())
+            print(disp_mod, id_mod)
+            for p, parent in enumerate(modout['parents']):
+                    parent_id = id_mod[np.where(np.array(disp_mod) == parent)[0][0]]
+                    out, ins = modout['coordinates'][2][connect[p]].split('-')
+                    xout, yout, _ , _ = self.canvas.coords(out[0]+str(parent_id))
+                    xins, yins, _, _ =self.canvas.coords(ins[0]+str(1))
+                    self.canvas.create_line(
+                                xout + self.cr, 
+                                yout + self.cr, 
+                                xins + self.cr, 
+                                yins + self.cr,
+                                fill = "red", 
+                                arrow = tk.LAST, 
+                                tags = ('o'+str(parent_id), 
+                                      'o'+str(1), modout['coordinates'][2][connect[p]]))
+                    self.out_data.iloc[int(parent_id)][1] = 1
+                    self.connections[1][
+                        int(parent_id)] = out[0]+str(parent_id) + '-' + ins[0]+str(1)
+            self.controller.core.load_config_file(filename)
+
+    def place_modules(self, modules, id_mod, disp_mod):
+        # Place the modules
+        for key in [key for key, val in modules.items() if type(val) == dict]:
+            if modules[key]['class'] == 'loop':
+                # Extracts numbers from string
+                l = int(''.join(map(str, list(filter(str.isdigit, modules[key]['name'])))))
+                x0, y0, x1, y1 = modules[key]['coordinates']
+                self.canvas.create_rectangle(x0, y0, 
+                                             x1, y1, 
+                                             outline = '#4ff07a',
+                                             tag = 'loop-'+str(l))
+                text_w = self.controller.pages_font.measure(modules[key]['type']) + 20
+                self.canvas.create_text(
+                    x0 + text_w/2, 
+                    y0 + 20, 
+                    font = self.controller.pages_font, 
+                    text = modules[key]['type'], 
+                    tags = ('loop-'+str(l), 'type'+'-'+str(l)), 
+                    justify = tk.CENTER)
+                text_w = self.controller.pages_font.measure(modules[key]['condition']) + 20
+                self.canvas.create_text(
+                    x1 - text_w/2, 
+                    y0 + 20, 
+                    font = self.controller.pages_font, 
+                    text = modules[key]['condition'], 
+                    tags = ('loop-'+str(l), 'condition'+'-'+str(l)), 
+                    justify = tk.CENTER)
+                self.loops.append({'type': modules[key]['type'],
+                                   'condition': modules[key]['condition'],
+                                   'mod': [], 
+                                   'coord': (x0, y0, 
+                                             x1, y1)})
+                disp_mod, id_mod = self.place_modules(modules[key], id_mod, disp_mod)
+            else:
+                # Display module
+                self.add_module(key,
+                                modules[key]['coordinates'][0][0],
+                                modules[key]['coordinates'][0][1])
+                id_mod.append(modules[key]['coordinates'][1])
+                connect = list(modules[key]['coordinates'][2].keys())
+                
+                # Connect modules
+                for p, parent in enumerate(modules[key]['parents']):
+                    if not (parent[:4] == 'loop'):
+                        parent_id = id_mod[np.where(np.array(disp_mod) == parent)[0][0]]
+                        out, ins = modules[key]['coordinates'][2][connect[p]].split('-')
+                        xout, yout, _ , _ = self.canvas.coords(out[0]+str(parent_id))
+                        xins, yins, _, _ =self.canvas.coords(ins[0]+str(id_mod[-1]))
+                        self.canvas.create_line(
+                                    xout + self.cr, 
+                                    yout + self.cr, 
+                                    xins + self.cr, 
+                                    yins + self.cr,
+                                    fill = "red", 
+                                    arrow = tk.LAST, 
+                                    tags = ('o'+str(parent_id), 
+                                          'o'+str(id_mod[-1]), modules[key]['coordinates'][2][connect[p]]))
+                        self.out_data.iloc[int(parent_id)][int(id_mod[-1])] = 1
+                        self.connections[int(id_mod[-1])][
+                            int(parent_id)] = out[0]+str(parent_id) + '-' + ins[0]+str(id_mod[-1])
+                        print(self.connections)
+                    else:
+                        self.loops[-1]['mod'].append(key)
+                disp_mod.append(key)
+        return disp_mod, id_mod
+    
+    def reset(self):
+        
+        if not self.saved:
+            msg = messagebox.askyesnocancel(
+                'Info', 'Are you sure you want to reset the canvas?')
+        else:
+            msg = True
+        if msg:
+            self.canvas.delete(tk.ALL) # Reset canvas
+            
+            if hasattr(self, 'entry'):
+                self.entry.destroy()
+            if hasattr(self, 'entry1'):
+                self.entry1.destroy()
+            if hasattr(self, 'entry2'):
+                self.entry2.destroy()
+                
+            self.canvas.startxy = []
+            self.out_data = pd.DataFrame()
+            self.connections = {}
+            self.modules = 0
+            self.module_list = []
+            self.module_names = []
+            
+            self.add_module('Initialiser', self.width/2, self.h, ini = True)
+            self.add_module('Output', self.width/2, self.height - self.h, out = True)
+        
+            self.draw = False
+            self.loops = []
+            self.drawLoop = False
+            self.l = 0
+
+    def check_quit(self):
+        
+        if not self.saved:
+            response = messagebox.askokcancel(
+                "Are you sure you want to leave?", 
+                "Do you want to leave the program without saving?")
+            if response:
+                if self.save_path not in [None, '']:
+                    self.controller.XML.set(True)
+                    # self.controller.XMLlabel.config(text = 'Done!', fg = 'green')
+                self.controller.show_frame("MainPage")
+        else:
+            if self.save_path not in [None, '']:
+                    self.controller.XML.set(True)
+                    # self.controller.XMLlabel.config(text = 'Done!', fg = 'green')
+            self.controller.show_frame("MainPage")
+
+if __name__ == "__main__":
+    app = aidCanvas()
+    app.mainloop()
\ No newline at end of file
diff --git a/aidesign/GUI/plugins/resources/Assets/ADDIcon.ico b/aidesign/GUI/plugins/resources/Assets/ADDIcon.ico
new file mode 100644
index 00000000..9f78df37
Binary files /dev/null and b/aidesign/GUI/plugins/resources/Assets/ADDIcon.ico differ
diff --git a/aidesign/GUI/plugins/resources/Assets/ADDIcon_name.png b/aidesign/GUI/plugins/resources/Assets/ADDIcon_name.png
new file mode 100644
index 00000000..ba3d51d6
Binary files /dev/null and b/aidesign/GUI/plugins/resources/Assets/ADDIcon_name.png differ
diff --git a/aidesign/GUI/plugins/resources/Assets/AIDIcon.ico b/aidesign/GUI/plugins/resources/Assets/AIDIcon.ico
new file mode 100644
index 00000000..e3064fa8
Binary files /dev/null and b/aidesign/GUI/plugins/resources/Assets/AIDIcon.ico differ
diff --git a/aidesign/GUI/plugins/resources/Assets/AIDIcon_name.png b/aidesign/GUI/plugins/resources/Assets/AIDIcon_name.png
new file mode 100644
index 00000000..629fc3d0
Binary files /dev/null and b/aidesign/GUI/plugins/resources/Assets/AIDIcon_name.png differ
diff --git a/aidesign/GUI/plugins/resources/Assets/AIFRED.png b/aidesign/GUI/plugins/resources/Assets/AIFRED.png
new file mode 100644
index 00000000..3cd5a6ba
Binary files /dev/null and b/aidesign/GUI/plugins/resources/Assets/AIFRED.png differ
diff --git a/aidesign/GUI/plugins/resources/Hospital.xml b/aidesign/GUI/plugins/resources/Hospital.xml
new file mode 100644
index 00000000..1d3289ca
--- /dev/null
+++ b/aidesign/GUI/plugins/resources/Hospital.xml
@@ -0,0 +1,104 @@
+<Settings>
+    <pipeline>
+        <Initialiser name="Initialiser">
+            <plugin type="" />
+            <relationships>
+                <child name="Data preprocessing" />
+            </relationships>
+            <coordinates>
+                (314.5, 75.0)
+            </coordinates>
+        </Initialiser>
+        <Data processing name="Data preprocessing">
+            <plugin type="" />
+            <relationships>
+                <parent name="Initialiser" />
+                <child name="Modelling-0" />
+            </relationships>
+            <coordinates>
+                (117, 180)
+            </coordinates>
+        </Data processing>
+        <Modelling name="Modelling-0">
+            <plugin type="" />
+            <relationships>
+                <parent name="Data preprocessing" />
+                <child name="Modelling-1" />
+            </relationships>
+            <coordinates>
+                (117, 273)
+            </coordinates>
+        </Modelling>
+        <loop type="While" condition="Convergence" name="loop0">
+            <relationships>
+                <child name="Modelling-1" />
+                <child name="Input data" />
+                <child name="User Feedback Adaptation" />
+                <child name="Decision making" />
+                <child name="Modelling-1" />
+                <child name="Input data" />
+                <child name="User Feedback Adaptation" />
+                <child name="Decision making" />
+            </relationships>
+            <coordinates>
+                (202.0, 154.0, 585.0, 461.0)
+            </coordinates>
+            <Modelling name="Modelling-1">
+                <plugin type="" />
+                <relationships>
+                    <parent name="Modelling-0" />
+                    <parent name="Input data" />
+                    <parent name="User Feedback Adaptation" />
+                    <parent name="loop0" />
+                    <child name="Decision making" />
+                </relationships>
+                <coordinates>
+                    (257, 326)
+                </coordinates>
+            </Modelling>
+            <Input data name="Input data">
+                <plugin type="" />
+                <relationships>
+                    <parent name="loop0" />
+                    <child name="Modelling-1" />
+                </relationships>
+                <coordinates>
+                    (322, 207)
+                </coordinates>
+            </Input data>
+            <User Feedback Adaptation name="User Feedback Adaptation">
+                <plugin type="" />
+                <relationships>
+                    <parent name="Decision making" />
+                    <parent name="loop0" />
+                    <child name="Modelling-1" />
+                </relationships>
+                <coordinates>
+                    (454, 325)
+                </coordinates>
+            </User Feedback Adaptation>
+            <Decision making name="Decision making">
+                <plugin type="" />
+                <relationships>
+                    <parent name="Modelling-1" />
+                    <parent name="loop0" />
+                    <child name="Output" />
+                    <child name="User Feedback Adaptation" />
+                </relationships>
+                <coordinates>
+                    (354, 419)
+                </coordinates>
+            </Decision making>
+        </loop>
+        <Output name="Output">
+            <plugin type="" />
+            <relationships>
+                <parent name="Decision making" />
+            </relationships>
+            <coordinates>
+                (314.5, 525.0)
+            </coordinates>
+        </Output>
+    </pipeline>
+    <datastructure />
+</Settings>
diff --git a/aidesign/GUI/plugins/startpage.py b/aidesign/GUI/plugins/startpage.py
index c7fb8394..ac81b759 100644
--- a/aidesign/GUI/plugins/startpage.py
+++ b/aidesign/GUI/plugins/startpage.py
@@ -115,4 +115,4 @@ def upload_data(self):
                                             ('All Files', '*.*')
                                             ])
         if filename is not None:
-            messagebox.showinfo("Success", "Feedback correctly uploaded.")
+            messagebox.showinfo("Success", "Feedback correctly uploaded.")
\ No newline at end of file
diff --git a/aidesign/Modelling/__init__.py b/aidesign/Modelling/__init__.py
new file mode 100644
index 00000000..99e78a66
--- /dev/null
+++ b/aidesign/Modelling/__init__.py
@@ -0,0 +1,7 @@
+# -*- coding: utf-8 -*-
+
+"""Import all submodules inside this folder and expose them with their respective names
+
+Let's look for a more automatic way of doing this when we have more modules
+"""
+from .Modelling_core import Modelling
diff --git a/aidesign/Settings/Settings_core.py b/aidesign/Settings/Settings_core.py
index 1e22492d..0e51a2e1 100644
--- a/aidesign/Settings/Settings_core.py
+++ b/aidesign/Settings/Settings_core.py
@@ -17,7 +17,7 @@ def __init__(self, filename: str = None):
         """valid_tags lists the available XML tags and their function
         TODO: populate the modules in this list automatically
         """
-        self.valid_tags = {
+        self._valid_tags = {
             "pipeline": "declaration",
             "datastructure": "declaration",
             "relationships": "relationships",
@@ -25,11 +25,10 @@ def __init__(self, filename: str = None):
             "coordinates": "list",
             "Initialiser": "entry_point",
             "Output": "exit_point",
-            "GUI": "module",
+            "UserFeedback": "module",
             "DataProcessing": "module",
             "Modelling": "module",
             "InputData": "module",
-            "UserFeedbackAdaptation": "module",
             "DecisionMaking": "module",
             "loop": "loop"
         }
@@ -41,7 +40,7 @@ def set_filename(self, filename: str):
         """Converts relative paths into absolute before setting."""
         if filename[0] == ".":
             self.filename = path.join(path.dirname(__file__), filename)
-        elif filename[0] == "/":
+        elif filename[0] == "/" or (filename[0].isalpha() and filename[0].isupper()):
             self.filename = filename
 
     def new_config_file(self, filename: str = None):
@@ -59,17 +58,17 @@ def load_XML(self, filename: str):
         if filename != None:
             self.set_filename(filename)
         self.tree = ET.parse(self.filename)
-        self.parse_XML()
+        self._parse_XML()
 
-    def parse_XML(self):
+    def _parse_XML(self):
         self.root = self.tree.getroot()
 
         self.pipeline_tree = self.root.find("pipeline")
-        self.parse_tags(self.pipeline_tree, self.loaded_modules)
+        self._parse_tags(self.pipeline_tree, self.loaded_modules)
 
-        self.parse_data_structure()
+        self._parse_data_structure()
 
-    def parse_tags(self, element: ET.Element, parent:dict):
+    def _parse_tags(self, element: ET.Element, parent:dict):
         """Detect tags and send them to correct method for parsing
         Uses getattr to call the correct method
 
@@ -78,14 +77,14 @@ def parse_tags(self, element: ET.Element, parent:dict):
         """
         for child in element:
             try:
-                tag_type = self.valid_tags[child.tag]
-                getattr(self, "load_{}".format(tag_type))(child, parent)
+                tag_type = self._valid_tags[child.tag]
+                getattr(self, "_load_{}".format(tag_type))(child, parent)
             except KeyError:
                 print("\nError: Invalid XML Tag.")
                 print("XML tag \"{0}\" in \"{1}\" not found".format(child.tag,element.tag))
-                print("Valid tags are: \n\t- {}".format(",\n\t- ".join([*self.valid_tags])))
+                print("Valid tags are: \n\t- {}".format(",\n\t- ".join([*self._valid_tags])))
 
-    def load_module(self, element: ET.Element, parent:dict):
+    def _load_module(self, element: ET.Element, parent:dict):
         """Parses tags associated with modules and appends to parent dict
 
         :param elem: xml.etree.ElementTree.Element to be parsed
@@ -93,10 +92,12 @@ def load_module(self, element: ET.Element, parent:dict):
         """
         module_name = element.attrib["name"]
         module_type = element.tag
-        parent[module_name] = {"module_type": module_type}
-        self.parse_tags(element,parent[module_name])
+        parent[module_name] = {"name":module_name,
+                                "class":self._valid_tags[module_type],
+                                "module_type": module_type}
+        self._parse_tags(element,parent[module_name])
 
-    def load_plugin(self, element: ET.Element, parent:dict):
+    def _load_plugin(self, element: ET.Element, parent:dict):
         """Parses tags associated with plugins and appends to parent dict
 
         :param elem: xml.etree.ElementTree.Element to be parsed
@@ -107,32 +108,33 @@ def load_plugin(self, element: ET.Element, parent:dict):
         parent["plugin"]["options"] = {}
         for child in element:
             if child.text != None:
-                val = self.parse_text_to_list(child)
+                val = self._parse_text_to_list(child)
                 parent["plugin"]["options"][child.tag] = val
             for key in child.attrib:
                 parent["plugin"]["options"][child.tag] = {key:child.attrib[key]}
             
 
-    def load_entry_point(self, element: ET.Element, parent:dict):
+    def _load_entry_point(self, element: ET.Element, parent:dict):
         """Parses tags associated with initialiser and appends to parent dict
 
         :param elem: xml.etree.ElementTree.Element to be parsed
         :param parent: dict or dict fragment parsed tags will be appened to
         """
         initialiser_name = element.attrib["name"]
-        parent[initialiser_name] = {}
-        self.parse_tags(element,parent[initialiser_name])
+        parent[initialiser_name] = {"name":initialiser_name,
+                                    "class":self._valid_tags[element.tag]}
+        self._parse_tags(element,parent[initialiser_name])
 
-    def load_exit_point(self, element: ET.Element, parent:dict):
+    def _load_exit_point(self, element: ET.Element, parent:dict):
         """Parses tags associated with output and appends to parent dict
 
         :param elem: xml.etree.ElementTree.Element to be parsed
         :param parent: dict or dict fragment parsed tags will be appened to
         """
-        parent["output"] = {}
-        self.parse_tags(element,parent["output"])
+        parent["output"] = {"name":"output","class":self._valid_tags[element.tag]}
+        self._parse_tags(element,parent["output"])
 
-    def load_loop(self, element: ET.Element, parent:dict):
+    def _load_loop(self, element: ET.Element, parent:dict):
         """Parses tags associated with loops and appends to parent dict
 
         :param elem: xml.etree.ElementTree.Element to be parsed
@@ -140,12 +142,14 @@ def load_loop(self, element: ET.Element, parent:dict):
         """
         loop_name = element.attrib["name"]
         parent[loop_name] = {
-            "type": element.attrib["type"],
-            "condition": element.attrib["condition"]
+            "name":loop_name,
+            "class":self._valid_tags[element.tag],
+            "type": element.attrib["type"].lower(),
+            "condition": element.attrib["condition"],
         }
-        self.parse_tags(element, parent[loop_name])
+        self._parse_tags(element, parent[loop_name])
 
-    def load_relationships(self, element: ET.Element, parent:dict):
+    def _load_relationships(self, element: ET.Element, parent:dict):
         """Parses tags associated with relationships and adds to parent dict
 
         :param elem: xml.etree.ElementTree.Element to be parsed
@@ -159,25 +163,25 @@ def load_relationships(self, element: ET.Element, parent:dict):
             elif rel.tag == "child":
                 parent["children"].append(rel.attrib["name"])
 
-    def load_list(self, element: ET.Element, parent:dict):
+    def _load_list(self, element: ET.Element, parent:dict):
         """Parses elements consisting of lists, e.g. coordinates
 
         :param elem: xml.etree.ElementTree.Element to be parsed
         :param parent: dict or dict fragment parsed tags will be appened to
         """
         if element.text != None:
-            parent[element.tag] = self.parse_text_to_list(element)
+            parent[element.tag] = self._parse_text_to_list(element)
             if len(parent[element.tag]) == 1:
                 parent[element.tag] = parent[element.tag][0]
 
-    def parse_data_structure(self):
+    def _parse_data_structure(self):
         """Parses tags associated with data structure"""
         self.data_tree = self.root.find("datastructure")
         for child in self.data_tree:
             self.loaded_data_options[child.tag]\
-                = self.parse_text_to_list(child)
+                = self._parse_text_to_list(child)
 
-    def parse_text_to_list(self, element: ET.Element) -> list:
+    def _parse_text_to_list(self, element: ET.Element) -> list:
         """Formats raw text data
 
         :param elem: xml.etree.ElementTree.Element to be parsed
@@ -195,7 +199,7 @@ def parse_text_to_list(self, element: ET.Element) -> list:
         element.text = raw_elem_text
         return out
 
-    def print_pretty(self, element: ET.Element):
+    def _print_pretty(self, element: ET.Element):
         """Print indented dictionary to screen"""
         from pprint import PrettyPrinter
         pp = PrettyPrinter(sort_dicts=False, width=100)
@@ -203,20 +207,20 @@ def print_pretty(self, element: ET.Element):
 
     def print_loaded_modules(self):
         """Print indented pipeline specifications to screen"""
-        self.print_pretty(self.loaded_modules)
+        self._print_pretty(self.loaded_modules)
 
     def print_loaded_data_structure(self):
         """Print indented data structure specifications to screen"""
-        self.print_pretty(self.loaded_data_options)
+        self._print_pretty(self.loaded_data_options)
 
     def write_to_XML(self):
         """Formats XML Tree correctly, then writes to filename
         TODO: add overwrite check and alternate filename option
         """
-        self.indent(self.root)
+        self._indent(self.root)
         self.tree.write(self.filename)
 
-    def indent(self, elem: ET.Element, level: int = 0):
+    def _indent(self, elem: ET.Element, level: int = 0):
         """Formats XML tree to be human-readable before writing to file
 
         :param elem: xml.etree.ElementTree.Element to be indented
@@ -230,7 +234,7 @@ def indent(self, elem: ET.Element, level: int = 0):
             if not elem.tail or not elem.tail.strip():
                 elem.tail = i
             for elem in elem:
-                self.indent(elem, level+1)
+                self._indent(elem, level+1)
             if not elem.tail or not elem.tail.strip():
                 elem.tail = i
         else:
@@ -240,7 +244,7 @@ def indent(self, elem: ET.Element, level: int = 0):
                 elem.text = elem.text.replace("\n", ("\n" + (level+1)*sep))
                 elem.text = ("{0}{1}").format(elem.text, i)
 
-    def get_element_from_name(self, name: str):
+    def _get_element_from_name(self, name: str):
         """Find a module name and return its parent tags"""
         if name == None:
             return self.pipeline_tree
@@ -250,7 +254,7 @@ def get_element_from_name(self, name: str):
         assert len(unique_elem)>0, "Error: No element exists with name \"{0}\"".format(name)
         return unique_elem[0]
 
-    def get_all_elements_with_tag(self, tag: str):
+    def _get_all_elements_with_tag(self, tag: str):
         """Return all elements with a given tag
         
         :param tag: string with tag name
@@ -258,7 +262,7 @@ def get_all_elements_with_tag(self, tag: str):
         elems = self.root.findall(".//*{0}".format(tag))
         return elems
 
-    def loop_rels_autofill(self,
+    def _loop_rels_autofill(self,
                             elem:ET.Element,
                             xml_child:str):
         """Add the name of a new nested module to the relationships of a loop
@@ -269,9 +273,9 @@ def loop_rels_autofill(self,
         if elem.tag != "loop":
             return elem
         else:
-            return self.add_relationships(elem,[],[xml_child])
+            return self._add_relationships(elem,[],[xml_child])
 
-    def add_relationships(self, 
+    def _add_relationships(self, 
                             elem:ET.Element, 
                             parents:list, 
                             children:list):
@@ -293,7 +297,7 @@ def add_relationships(self,
                 new_child.set('name', c)
         return elem
 
-    def add_coords(self, 
+    def _add_coords(self, 
                     elem:ET.Element, 
                     coords:list=None):
         if coords == None:
@@ -314,16 +318,16 @@ def append_pipeline_module(self,
                                 ):
         """Append new pipeline module to existing XML elementTree to be written later
         
-        :param module_type: string declare type of module (GUI,data_processing etc)
+        :param module_type: string declare type of module (UserFeedback,data_processing etc)
         :param module_name: string give module a user-defined name
         :param plugin_type: string type of plugin to be loaded into module
         :param plugin_options: dict where keys & values are options & values
         :param parents: list of parent names for this module (can be empty)
         :param children: list of child names for this module (can be empty)
         :param xml_parent_element: str containing name of parent Element for new module
-        :param coords [optional]: list of coordinates for GUI canvas
+        :param coords [optional]: list of coordinates for UserFeedback canvas
         """
-        xml_parent_element = self.get_element_from_name(xml_parent_element)
+        xml_parent_element = self._get_element_from_name(xml_parent_element)
 
         new_mod = ET.Element(module_type.replace(" ", ""))
         new_mod.set('name', module_name)
@@ -343,10 +347,10 @@ def append_pipeline_module(self,
 
         if xml_parent_element.tag =="loop":
             parents.append(xml_parent_element.attrib["name"])
-        new_mod = self.add_relationships(new_mod,parents,children)
-        new_mod = self.add_coords(new_mod,coords)
+        new_mod = self._add_relationships(new_mod,parents,children)
+        new_mod = self._add_coords(new_mod,coords)
 
-        self.loop_rels_autofill(xml_parent_element, module_name)
+        self._loop_rels_autofill(xml_parent_element, module_name)
         xml_parent_element.append(new_mod)
 
     def append_pipeline_loop(self,
@@ -366,9 +370,9 @@ def append_pipeline_loop(self,
         :param parents: list of parent names for this module (can be empty)
         :param children: list of child names for this module (can be empty)
         :param xml_parent_element: str containing name of parent Element for new module
-        :param coords [optional]: list of coordinates for GUI canvas
+        :param coords [optional]: list of coordinates for UserFeedback canvas
         """
-        xml_parent_element = self.get_element_from_name(xml_parent_element)
+        xml_parent_element = self._get_element_from_name(xml_parent_element)
 
         new_loop = ET.Element("loop")
         new_loop.set('type', loop_type)
@@ -377,10 +381,10 @@ def append_pipeline_loop(self,
 
         if xml_parent_element.tag =="loop":
             parents.append(xml_parent_element.attrib["name"])
-        new_loop = self.add_relationships(new_loop,parents,children)
-        new_loop = self.add_coords(new_loop,coords)
+        new_loop = self._add_relationships(new_loop,parents,children)
+        new_loop = self._add_coords(new_loop,coords)
 
-        self.loop_rels_autofill(xml_parent_element, loop_name)
+        self._loop_rels_autofill(xml_parent_element, loop_name)
             
         xml_parent_element.append(new_loop)
 
@@ -409,7 +413,7 @@ def append_data_structure_field(self,
     # s = Settings("./resources/example_config.xml")
     # s = Settings()
     # s.new_config_file("./resources/example_config.xml")
-    # s.get_all_elements_with_tag("loop")
+    # s._get_all_elements_with_tag("loop")
     # s.load_XML("./resources/example_config.xml")
     # s.print_loaded_modules()
     # s.write_to_XML()
@@ -418,7 +422,7 @@ def append_data_structure_field(self,
     #                       "my_loop_3",
     #                       ["Init"],
     #                       [])
-    # s.append_pipeline_module("GUI thing",
+    # s.append_pipeline_module("UserFeedback thing",
     #                       "added_mod",
     #                       "startpage",
     #                       {"class_list":["test_1","test_2"],"class_list_2":["test_1","test_2"]},
diff --git a/aidesign/Settings/__init__.py b/aidesign/Settings/__init__.py
index 133906c9..a51c2a4a 100644
--- a/aidesign/Settings/__init__.py
+++ b/aidesign/Settings/__init__.py
@@ -4,4 +4,5 @@
 
 Let's look for a more automatic way of doing this when we have more modules
 """
-from .Settings_core import Settings
\ No newline at end of file
+from .Settings_core import Settings
+from .. import utils
\ No newline at end of file
diff --git a/aidesign/Settings/resources/basic_operation.xml b/aidesign/Settings/resources/basic_operation.xml
new file mode 100644
index 00000000..dc4bf3d7
--- /dev/null
+++ b/aidesign/Settings/resources/basic_operation.xml
@@ -0,0 +1,70 @@
+<Settings>
+    <pipeline>
+        <Initialiser name="Init">
+            <plugin type="">
+                <initial_data file="../resources/intial_data_test.csv" />
+                <goal file="../resources/design_goals_test.csv" />
+            </plugin>
+            <relationships>
+                <child name="My First UserFeedback Module" />
+            </relationships>
+        </Initialiser>
+        <loop type="for" condition="2" name="For Loop 1">
+            <UserFeedback name="My First UserFeedback Module">
+                <plugin type="manual">
+                    <class_list>
+                        Atelectasis
+                        Cardiomelagy
+                        Effusion
+                        Infiltration
+                        Mass
+                        Nodule
+                        Pneumonia
+                        Pneumothorax
+                    </class_list>
+                </plugin>
+                <relationships>
+                    <parent name="Init" />
+                    <child name="Data Processing 1" />
+                </relationships>
+        </UserFeedback>
+            <loop type="for" condition="1" name="For Loop 2">
+                <UserFeedback name="My Second UserFeedback Module">
+                    <plugin type="canvas">
+                        <class_list>
+                            ['State_a','Action_a']
+                            ['State_x','State_y','Action_x','Action_y']
+                            ['State_x','State_y','Action_x','Action_y']
+                        </class_list>
+                    </plugin>
+                    <relationships>
+                        <parent name="Data Processing 1" />
+                        <child name="Data Processing 1" />
+                        <child name="Output" />
+                    </relationships>
+                </UserFeedback>
+            </loop>
+        </loop>
+        <Output>
+            <plugin type="">
+                <out_data>
+                    all
+                </out_data>
+                <save_to file="../resources/output_test.csv" />
+            </plugin>
+            <relationships>
+                <parent name="My Second UserFeedback Module" />
+            </relationships>
+        </Output>
+    </pipeline>
+    <datastructure name="datatest">
+        <data_fields>
+            State_0
+            State_1
+            Action
+            Reward
+            Termination
+            Goal
+        </data_fields>
+    </datastructure>
+</Settings>
diff --git a/aidesign/Settings/resources/example_config.xml b/aidesign/Settings/resources/example_config.xml
index c7969db9..9bc55675 100644
--- a/aidesign/Settings/resources/example_config.xml
+++ b/aidesign/Settings/resources/example_config.xml
@@ -1,8 +1,10 @@
 <Settings>
     <pipeline>
         <Initialiser name="Init">
-            <initial_data file="../resources/intial_data_test.csv" />
-            <goal file="../resources/design_goals_test.csv" />&gt;
+            <plugin type="">
+                <initial_data file="../resources/intial_data_test.csv" />
+                <goal file="../resources/design_goals_test.csv" />
+            </plugin>
             <relationships>
                 <child name="My First GUI Module" />
             </relationships>
@@ -78,70 +80,23 @@
                         <child name="Output" />
                     </relationships>
                 </GUI>
+                <relationships>
+                        <child name="Data Processing 1" />
+                        <child name="Output" />
+                </relationships>
             </loop>
         </loop>
         <Output>
-            <out_data>
-                                all
-                            
-            </out_data>
-            <save_to file="../resources/output_test.csv" />
+            <plugin type="">
+                <out_data>
+                    all
+                </out_data>
+                <save_to file="../resources/output_test.csv" />
+            </plugin>
             <relationships>
                 <parent name="My Second GUI Module" />
             </relationships>
         </Output>
-        <loop type="for" condition="10" name="my_loop_3">
-            <relationships>
-                <parent name="Init" />
-            </relationships>
-        </loop>
-        <GUI name="added_mod">
-            <plugin type="startpage">
-                <class_list>
-                                        test_1
-                                        test_2
-                                    
-                </class_list>
-                <class_list_2>
-                                        test_1
-                                        test_2
-                                    
-                </class_list_2>
-            </plugin>
-            <relationships>
-                <parent name="For Loop 1" />
-                <child name="Output" />
-            </relationships>
-            <coordinates>
-                                                [2, 3, 4, 5]
-                                            
-                            
-            </coordinates>
-        </GUI>
-        <loop type="for" condition="10" name="my_loop_3">
-            <relationships>
-                <parent name="Init" />
-            </relationships>
-        </loop>
-        <GUI name="added_mod">
-            <plugin type="startpage">
-                <class_list>
-                    test_1
-                    test_2
-                </class_list>
-                <class_list_2>
-                    test_1
-                    test_2
-                </class_list_2>
-            </plugin>
-            <relationships>
-                <parent name="For Loop 1" />
-                <child name="Output" />
-            </relationships>
-            <coordinates>
-                [2, 3, 4, 5]
-            </coordinates>
-        </GUI>
     </pipeline>
     <datastructure name="datatest">
         <data_fields>
diff --git a/aidesign/UserFeedback/User_Feedback_core.py b/aidesign/UserFeedback/User_Feedback_core.py
new file mode 100644
index 00000000..f1a165e0
--- /dev/null
+++ b/aidesign/UserFeedback/User_Feedback_core.py
@@ -0,0 +1,124 @@
+# -*- coding: utf-8 -*-
+import tkinter as tk
+
+from aidesign.utils.import_helper import import_plugin
+
+class UserFeedback(tk.Tk):
+    """
+    TODO: This structure still needs serious overhaul. 
+
+    TODO: By having the TKinter controller as in the main GUI module, we are locked to using TKinter which defeats the purpose of being modular. This class needs to only be calling the plugins themselves, not acting as a controller.
+    """
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
+        self.title_font = tk.font.Font(family='Helvetica',
+                                      size=14,
+                                      weight="bold")
+        self.pages_font = tk.font.nametofont("TkDefaultFont")
+
+        self.desired_ui_types = []
+        self.top_ui_layer = None
+        # self.startpage_exist = False
+        self.available_ui_types = {
+            "ManualInput": {
+                "name": "manual",
+                "layer_priority": 2,
+                "required_children": None,
+            },
+            "CanvasInput": {
+                "name": "canvas",
+                "layer_priority": 2,
+                "required_children": None,
+            }
+        }
+
+    def _compare_layer_priority(self, ui_name):
+        """Check if a new module should have higher layer priority than the existing one
+
+        :param ui_name: name of the UI method being compared
+        :type ui_name: str
+        """
+        if self.top_ui_layer == None:
+            self.top_ui_layer = ui_name
+        else:
+            current_top_layer = self.available_ui_types[self.top_ui_layer]["layer_priority"]
+            candidate_layer = self.available_ui_types[ui_name]["layer_priority"]
+            self.top_ui_layer = candidate_layer \
+                if candidate_layer < current_top_layer \
+                else self.top_ui_layer
+
+    def _add_UI_type_to_frames(self, ui_name):
+        """Add user defined UI method to list of frames to be loaded
+
+        :param ui_name: name of the UI method being loaded
+        :type ui_name: str 
+        """
+        try:
+            plugin = import_plugin(globals(),ui_name)
+        except:
+            from sys import exit
+            print(
+                "Error: User Interface \"{0}\" not recognised. \
+                \nAvailable methods are: \
+                \n  - {1}"\
+                .format(ui_name, ",\n  - ".join(
+                    [i["name"]for i in self.available_ui_types.values()])))
+            exit(1)
+        self.desired_ui_types.append(plugin)
+        self._compare_layer_priority(ui_name)
+        if self.available_ui_types[ui_name]["required_children"] != None:
+            for children in self.available_ui_types[ui_name]["required_children"]:
+                self._add_UI_type_to_frames(children)
+
+    def _set_plugin_name(self, ui_type: list):
+        """"Given user input, create a list of classes of the corresponding User Interface Type 
+
+        :param ui_name: name of the desired User Interface Method
+        :type ui_name: str or list
+        """
+        ui_type = ui_type\
+            if isinstance(ui_type, list)\
+            else [ui_type]
+
+        for ui in ui_type:
+            ui_name = ''.join(kn for kn in self.available_ui_types.keys()
+                              if ui.lower() == self.available_ui_types[kn]["name"])
+            self._add_UI_type_to_frames(ui_name)
+
+
+    def _show_frame(self, page_name):
+        '''Show a frame for the given page name'''
+        frame = self.frames[page_name]
+        frame.tkraise()
+        
+    def set_options(self, module_config: dict):
+        """Send configuration arguments to GUI
+
+        :param module_config: dict of settings to congfigure the plugin
+        """
+        self.module_config = module_config
+        self._set_plugin_name(self.module_config["plugin"]["plugin_name"])
+
+
+    def launch(self):
+        """Runs UserInterface Plugin. 
+        If multiple frames exist, they are stacked
+        """
+        container = tk.Frame(self, bg='#19232d')
+        container.pack(side="top", fill="both", expand=True)
+        container.grid_rowconfigure(0, weight=1)
+        container.grid_columnconfigure(0, weight=1)
+
+        self.frames = {}
+        for F in self.desired_ui_types:
+            page_name = F.__name__
+            frame = F(parent=container, controller=self,
+                      config=self.module_config)
+            self.frames[page_name] = frame
+
+            frame.grid(row=0, column=0, sticky="nsew")
+
+        self._show_frame(self.top_ui_layer)
+        self.mainloop()
diff --git a/aidesign/UserFeedback/User_Feedback_template.py b/aidesign/UserFeedback/User_Feedback_template.py
new file mode 100644
index 00000000..aa6ee9b7
--- /dev/null
+++ b/aidesign/UserFeedback/User_Feedback_template.py
@@ -0,0 +1,30 @@
+from abc import ABC, abstractmethod
+
+"""Defines the parent class for all potential methods of user interface for the framework
+
+Todo: declare what is commonly needed for this module and add below (WIP)
+"""
+
+class UI(ABC):
+    @property
+    def class_list(self):
+        pass
+        # """Set the class list"""
+        
+    @class_list.setter
+    @abstractmethod
+    def class_list(self,value):
+        pass
+    
+
+    @abstractmethod
+    def save_file(self):
+        pass
+
+    @abstractmethod
+    def save_file_as(self):
+        pass
+
+    # @abstractmethod
+    # def on_press(self):
+    #     pass
diff --git a/aidesign/UserFeedback/__init__.py b/aidesign/UserFeedback/__init__.py
new file mode 100644
index 00000000..67725339
--- /dev/null
+++ b/aidesign/UserFeedback/__init__.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+
+"""Import all submodules inside this folder and expose them with their respective names
+
+Let's look for a more automatic way of doing this when we have more modules
+"""
+from .. import utils
+from .User_Feedback_core import UserFeedback
+from .User_Feedback_template import UI
diff --git a/aidesign/UserFeedback/plugins/CanvasInput.py b/aidesign/UserFeedback/plugins/CanvasInput.py
new file mode 100644
index 00000000..23e7ab0d
--- /dev/null
+++ b/aidesign/UserFeedback/plugins/CanvasInput.py
@@ -0,0 +1,628 @@
+from . import UI
+import tkinter as tk
+import os
+
+from tkinter import messagebox, ttk
+
+from tkinter.filedialog import asksaveasfile, askopenfile, askopenfilename
+import numpy as np
+import pandas as pd
+import math
+
+class CanvasInput(tk.Frame,UI):
+    def __init__(self, parent, controller, config:dict):
+        self.parent = parent
+        super().__init__(self.parent, bg = self.parent['bg'])
+        self.controller = controller
+        
+        self.controller.title('Canvas Input')
+        self.notebook = ttk.Notebook(self)
+        self.notebook.grid(row=0, column=0, columnspan=7, 
+                           rowspan = 12, pady = 15)
+        self.frame = []
+        self.tree = []
+        self.canvas = []
+        self.draw = []
+        self.state = []
+        self.type = []
+        self.clock = []
+        self._parse_config(config)
+        self.save_path = ''
+        self.saved = True
+    
+    def _parse_config(self, config):
+        self.config = config
+        self.class_list(self.config["plugin"]["options"]["class_list"])
+
+    def class_list(self):
+        """Getter for required _class_list variable
+        
+        :return: list of class labels
+        :rtype: list of strings
+        """
+        return self._class_list
+
+    def class_list(self, value):
+        """Setter for required _class_list variable
+        
+        :param value: labels for state-action pair headers
+        :type value: list of strings
+        """
+        self._class_list = value
+        self.out_data = {}
+        for ii in np.arange(len(self._class_list)):
+            self.out_data[ii] = {}
+            for key in self._class_list[ii]:
+                self.out_data[ii][key] = []
+
+        for ii in np.arange(len(self._class_list)):
+            self.frame.append(tk.Frame(self, bg = self.parent['bg']))
+            self.frame[-1].grid(row=0, column=0, sticky="nsew", pady = 15)
+            if self._class_list[ii][0].split('_')[1] == 'a':
+                self.type.append('Rotating')
+                self.clock.append(tk.StringVar())
+                self.clock[ii].set('clock')
+            else:
+                self.type.append('Sliding')
+                self.clock.append(-1)
+            self.notebook.add(
+                self.frame[-1], 
+                text = 'Object '+ str(ii) + ' - ' + self.type[-1])
+                
+            
+            # Create a canvas widget
+            self.width, self.height = 600, 600
+            self.canvas.append(tk.Canvas(
+                self.frame[-1], width=self.width, 
+                height=self.height, background="white"))
+            self.canvas[-1].grid(row=0, column=0, columnspan=4, rowspan = 2)
+            self.canvas[-1].bind('<Button-1>', self.draw_dot)
+            self.canvas[-1].bind("<B1-Motion>", self.on_drag)
+            # self.canvas[-1].bind('<Motion>', self.motion)
+            
+            # Booleans to identify what the user wants to do
+            self.draw.append(tk.StringVar())
+            self.draw[ii].set('draw')
+            self.state.append(tk.StringVar())
+            self.state[ii].set('state')    
+            # Buttons under the canvas
+            self.button_draw = tk.Radiobutton(
+                self.frame[-1], text = 'Draw', fg = 'white', bg = self.parent['bg'],
+                height = 3, width = 20, var = self.draw[ii], 
+                selectcolor = 'black', value = 'draw').grid(column = 4, row = 3)
+            self.button_drag = tk.Radiobutton(
+                self.frame[-1], text = 'Move', fg = 'white', bg = self.parent['bg'],
+                height = 3, width = 20, var = self.draw[ii], 
+                selectcolor = 'black', value = 'drag').grid(column = 5, row = 3)
+            self.clock_arc = tk.Radiobutton(
+                self.frame[-1], text = 'Clockwise', fg = 'white', bg = self.parent['bg'],
+                height = 3, width = 20, var = self.clock[ii], 
+                selectcolor = 'black', value = 'clock').grid(column = 6, row = 3)            
+            self.countclock_arc = tk.Radiobutton(
+                self.frame[-1], text = 'Counterclockwise', fg = 'white', bg = self.parent['bg'],
+                height = 3, width = 20, var = self.clock[ii], 
+                selectcolor = 'black', value = 'countclock').grid(column = 7, row = 3)
+            # self.button_edit = tk.Radiobutton(
+            #     self.frame[-1], text = 'Edit', fg = 'white', bg = self.parent['bg'],
+            #     height = 3, width = 20, var = self.draw[ii], 
+            #     selectcolor = 'black', value = 'edit').grid(column = 6,row = 3)
+            self.button_save = tk.Button(
+                self.frame[-1], text = 'Save', fg = 'white', bg = self.parent['bg'],
+                height = 3, width = 20, 
+                command = self.save_file).grid(column = 1, row = 3)
+            self.button_upload = tk.Button(
+                self.frame[-1], text = 'Upload coordinates', fg = 'white', 
+                bg = self.parent['bg'], height = 3, width = 20, 
+                command = self.upload_sa).grid(column = 0, row = 3)
+            self.button_reset = tk.Button(
+                self.frame[-1], text = 'Reset', fg = 'white', 
+                bg = self.parent['bg'], height = 3, width = 20, 
+                command = self.reset).grid(column = 2, row = 3)
+            button_main = tk.Button(
+                self.frame[-1], text="Done", fg = 'white', 
+                bg = self.parent['bg'], height = 3, width = 20,
+                command = self.check_quit).grid(column = 3, row = 3)
+            style = ttk.Style()
+            style.configure(
+                "Treeview", background = 'white', foreground = 'white', 
+                rowheight = 25, fieldbackground = 'white', 
+                font = self.controller.pages_font)
+            style.configure("Treeview.Heading", 
+                            font = self.controller.pages_font)
+            style.map('Treeview', background = [('selected', 'grey')])
+            
+
+            tree_frame = tk.Frame(self.frame[-1])
+            tree_frame.grid(row = 0, column = 4, columnspan = 3, rowspan = 10)
+            
+            tree_scrollx = tk.Scrollbar(tree_frame, orient = 'horizontal')
+            tree_scrollx.pack(side = tk.BOTTOM, fill = tk.X)
+            tree_scrolly = tk.Scrollbar(tree_frame)
+            tree_scrolly.pack(side = tk.RIGHT, fill = tk.Y)
+            
+            self.tree.append(ttk.Treeview(
+                tree_frame, 
+                yscrollcommand = tree_scrolly.set, 
+                xscrollcommand = tree_scrollx.set))
+            self.tree[-1].pack()
+            
+            tree_scrollx.config(command = self.tree[-1].xview)
+            tree_scrolly.config(command = self.tree[-1].yview)
+            
+            if self.type[-1] == 'Rotating':
+                self.x_ini, self.y_ini = int(self.width/2), int(self.height/2)
+                self.r = int(np.min([self.x_ini, self.y_ini])/1.5)
+                self.create_circle(self.x_ini, self.y_ini, 4, 
+                                    fill="#DDD", outline="", width=4)
+                self.create_circle(self.x_ini, self.y_ini, self.r, 
+                                    fill="", outline="#DDD", width=4)
+                
+            self.tree[-1]['columns'] = self._class_list[ii]
+            
+            # Format columns
+            self.tree[-1].column("#0", width = 50)
+            for n, cl in enumerate(self._class_list[ii]):
+                self.tree[-1].column(
+                    cl, width = int(
+                        self.controller.pages_font.measure(str(cl)))+20, 
+                    minwidth = 50, anchor = tk.CENTER)
+                    
+            # Headings
+            self.tree[-1].heading("#0", text = "Sample", anchor = tk.CENTER)
+            for cl in self._class_list[ii]:
+                self.tree[-1].heading(cl, text = cl, anchor = tk.CENTER)
+            
+            # for nn in np.arange(N):
+            #     self.tree[-1].insert(
+            #                 parent = '', index = 'end', iid = (nn+1)*1000, text = 'Trial '+str(nn), 
+            #                 values = tuple())
+            
+            self.tree[-1].tag_configure('odd', foreground = 'black', 
+                                        background='#E8E8E8')
+            self.tree[-1].tag_configure('even', foreground = 'black', 
+                                        background='#DFDFDF')
+        
+            # Define double-click on row action
+            self.tree[-1].bind("<Double-1>", self.OnDoubleClick)
+
+    def draw_dot(self, event):
+        
+        ii = self.notebook.index(self.notebook.select())
+        if self.draw[ii].get() == 'drag':
+            self.selected = self.canvas[ii].find_overlapping(
+                event.x-10, event.y-10, event.x+10, event.y+10)
+            if self.selected:
+                if len(self.selected) > 2:
+                    self.canvas[ii].selected = self.selected[-2]
+                else:
+                    self.canvas[ii].selected = self.selected[-1]
+                self.canvas[ii].startxy = (event.x, event.y)
+            else:
+                self.canvas[ii].selected = None
+
+        if self.draw[ii].get()  == 'draw':
+            # Draw an oval in the given coordinates
+            self.saved = False
+            # Angle of the selected point with respect to the circle center
+            if self.type[ii] == 'Rotating':
+                alpha, x_o, y_o = self.angle_calc(event.x, event.y, 
+                                                  circxy=True)
+                if self.state[ii].get()  == 'state': # Write state coordinates
+                    self.canvas[ii].create_oval(
+                        x_o-3, y_o-3, x_o+3, y_o+3, fill="black", width=0, 
+                        tags=("state"+str(ii)+'-'
+                              + str(len(self.out_data[ii]['State_a']))))
+                    self.out_data[ii]['State_a'].append((
+                        -np.rad2deg(alpha)+360)%360)
+                    self.state[ii].set('action')
+                    if self.tree[ii].selection(): # Update coordinates in corresponding row if exists.
+                        n = int(self.tree[ii].selection()[0]) + 1
+                        if n%2 == 0:
+                            tag = ('even',)
+                        else:
+                            tag = ('odd',)
+                        self.tree[ii].insert(
+                            parent = '', index = 'end', iid = n, text = n+1, 
+                            values = tuple(self.dict2mat(
+                                self.out_data[ii])[n,:].astype(int)), 
+                            tags = tag)
+                        self.tree[ii].selection_set(str(n))
+                    else:
+                        self.tree[ii].insert(
+                            parent = '', index = 'end', iid = 0, text = 1, 
+                            values = tuple(self.dict2mat(
+                                self.out_data[ii])[0,:].astype(int)), 
+                                tags = ('even',))
+                        self.tree[ii].selection_set(str(0))
+                    
+                elif self.state[ii].get()  == 'action':
+                    if self.clock[ii].get()  == 'clock':
+                        start = self.out_data[ii]['State_a'][-1]
+                        end = np.rad2deg(-alpha)
+                    else:
+                        start = self.out_data[ii]['State_a'][-1] - 360
+                        end = 360 - np.rad2deg(alpha)
+                    self.create_circle_arc(
+                        self.x_ini, self.y_ini, self.r, fill = "", 
+                        outline = "red", start = start, end = end, width=2, 
+                        style = tk.ARC, tags = ("action"+str(ii)+'-' + str(len(
+                            self.out_data[ii]['Action_a']))))              
+                    self.out_data[ii]['Action_a'].append((
+                        -np.rad2deg(alpha)+360)%360)
+                    self.state[ii].set('state')
+                    n = int(self.tree[ii].selection()[0])
+                    self.tree[ii].item(
+                        self.tree[ii].get_children()[n], text = n+1, 
+                        values = tuple(self.dict2mat(
+                            self.out_data[ii])[n,:].astype(int)))
+            else:
+                if self.state[ii].get()  == 'state': # Write state coordinates
+                    self.canvas[ii].create_oval(
+                        event.x-3, event.y-3, event.x+3, event.y+3, 
+                        fill="black", width=0, 
+                        tags=("state"+str(ii)+'-'+ str(len(
+                            self.out_data[ii]['State_x']))))
+                    self.out_data[ii]['State_x'].append(event.x)
+                    self.out_data[ii]['State_y'].append(event.y)
+                    self.state[ii].set('action')
+                    if self.tree[ii].selection(): # Update coordinates in corresponding row if exists.
+                        n = int(self.tree[ii].selection()[0]) + 1
+                        if n%2 == 0:
+                            tag = ('even',)
+                        else:
+                            tag = ('odd',)
+                        self.tree[ii].insert(
+                            parent = '', index = 'end', iid = n, text = n+1, 
+                            values = tuple(self.dict2mat(
+                                self.out_data[ii])[n,:].astype(int)), 
+                            tags = tag)
+                        self.tree[ii].selection_set(str(n))
+                    else:
+                        self.tree[ii].insert(
+                            parent = '', index = 'end', iid = 0, text = 1, 
+                            values = tuple(self.dict2mat(
+                                self.out_data[ii])[0,:].astype(int)), 
+                                tags = ('even',))
+                        self.tree[ii].selection_set(str(0))
+                    
+                elif self.state[ii].get()  == 'action':
+                    self.canvas[ii].create_line(
+                        self.out_data[ii]['State_x'][-1], 
+                        self.out_data[ii]['State_y'][-1], event.x, event.y, 
+                        fill="red", arrow=tk.LAST, 
+                        tags=("action"+str(ii)+'-' + str(len(
+                            self.out_data[ii]['Action_x']))))
+                    self.out_data[ii]['Action_x'].append(event.x)
+                    self.out_data[ii]['Action_y'].append(event.y)
+                    self.state[ii].set('state')
+                    n = int(self.tree[ii].selection()[0])
+                    self.tree[ii].item(
+                        self.tree[ii].get_children()[n], text = n+1, 
+                        values = tuple(self.dict2mat(
+                            self.out_data[ii])[n,:].astype(int)))
+        
+    def on_drag(self, event):
+        
+        ii = self.notebook.index(self.notebook.select())
+        if self.draw[ii].get()  == 'drag' and self.canvas[ii].selected:
+            # move the selected item
+            n = int(self.canvas[ii].gettags("current")[0].split('-')[1])
+            
+            if self.type[ii] == 'Rotating':
+                alpha, dx, dy = self.angle_calc(event.x, event.y, circxy=True)
+                _, dx_pr, dy_pr = self.angle_calc(
+                    self.canvas[ii].startxy[0], self.canvas[ii].startxy[1], 
+                    circxy=True)
+                xa = self.r * np.cos(
+                    np.deg2rad(self.out_data[ii]['Action_a'][n])) + self.x_ini
+                ya = self.r * np.sin(
+                    np.deg2rad(self.out_data[ii]['Action_a'][n])) + self.y_ini
+                if self.canvas[ii].gettags(
+                        "current")[0].split('-')[0] == 'state'+str(ii): # If state, updates both the state placement and the arrow
+                    self.canvas[ii].move(
+                        self.canvas[ii].selected, dx-dx_pr, 
+                        dy-dy_pr)
+                    self.out_data[ii]['State_a'][n] = (360-np.rad2deg(alpha))%360
+
+                    if self.clock[ii].get()  == 'clock':
+                        start = self.out_data[ii]['Action_a'][n]
+                        end = np.rad2deg(-alpha)
+                    else:
+                        start = self.out_data[ii]['Action_a'][n] - 360
+                        end = 360 - np.rad2deg(alpha)
+
+                    self.canvas[ii].itemconfigure(
+                        "action"+str(ii)+'-'+str(n), start = start, 
+                        extent = end - start)
+                    
+                elif self.canvas[ii].gettags(
+                        "current")[0].split('-')[0] == 'action'+str(ii): # If action, updates the arrow end of the line position
+                    
+                    self.out_data[ii]['Action_a'][n] = (360-np.rad2deg(alpha))%360
+
+                    if self.clock[ii].get()  == 'clock':
+                        start = self.out_data[ii]['State_a'][n]
+                        end = np.rad2deg(-alpha)
+                    else:
+                        start = self.out_data[ii]['State_a'][n] - 360
+                        end = 360 - np.rad2deg(alpha)
+                    
+                    self.canvas[ii].itemconfigure(
+                        "action"+str(ii)+'-'+str(n), start = start, 
+                        extent = end - start)
+
+            else:
+                # calculate distance moved from last position
+                dx = event.x - self.canvas[ii].startxy[0]
+                dy = event.y - self.canvas[ii].startxy[1]
+                if self.canvas[ii].gettags(
+                        "current")[0].split('-')[0] == 'state'+str(ii): # If state, updates both the state placement and the arrow
+                    self.canvas[ii].move(self.canvas[ii].selected, dx, dy)
+                    self.canvas[ii].coords(
+                        "action"+str(ii)+'-'+str(n), 
+                        (event.x, event.y, self.out_data[ii]['Action_x'][n], 
+                        self.out_data[ii]['Action_y'][n]))
+                    self.out_data[ii]['State_x'][n] = event.x
+                    self.out_data[ii]['State_y'][n] = event.y  
+                elif self.canvas[ii].gettags(
+                        "current")[0].split('-')[0] == 'action'+str(ii): # If action, updates the arrow end of the line position
+                    self.canvas[ii].coords(
+                        self.canvas[ii].gettags("current")[0], 
+                        (self.out_data[ii]['State_x'][n], 
+                         self.out_data[ii]['State_y'][n], event.x, event.y))
+                    self.out_data[ii]['Action_x'][n] = event.x
+                    self.out_data[ii]['Action_y'][n] = event.y
+            # update last position
+            self.canvas[ii].startxy = (event.x, event.y)                
+            self.tree[ii].item(self.tree[ii].get_children()[n], text = n+1, 
+                           values = tuple(self.dict2mat(
+                               self.out_data[ii])[n,:].astype(int)))
+            self.tree[ii].selection_set(str(n))
+
+    def checkered(self, line_distance):
+        
+        ii = self.notebook.index(self.notebook.select()) 
+        # vertical lines at an interval of "line_distance" pixel
+        for x in range(line_distance, self.width, line_distance):
+            self.canvas[ii].create_line(x, 0, x, self.height, fill="#476042")
+        # horizontal lines at an interval of "line_distance" pixel
+        for y in range(line_distance, self.height, line_distance):
+            self.canvas[ii].create_line(0, y, self.width, y, fill="#476042")   
+            
+    def check_quit(self):
+        if not self.saved:
+            response = messagebox.askokcancel(
+                "Exit?", 
+                "Do you want to leave the program without saving?")
+            if response:
+                self.controller.destroy()
+        else:
+            response = messagebox.askokcancel(
+                "Exit?",
+                "Are you sure you are finished?")
+            self.controller.destroy()
+            
+    def save_file_as(self):
+        self.save_path = asksaveasfile(mode='w')
+        self.save_file()
+        
+    def save_file(self):
+        
+        if self.save_path == '':
+            self.save_path = asksaveasfile(defaultextension = '.txt', filetypes = 
+                                      [('Text file', '.txt'), 
+                                       ('CSV file', '.csv'), 
+                                       ('All Files', '*.*')])
+        if self.save_path is not None: # asksaveasfile return `None` if dialog closed with "cancel".
+            # Transform input for output file    
+            data = {}
+            samples = np.max([len(self.out_data[element][
+                list(self.out_data[element].keys())[0]]) 
+                for element in self.out_data])
+            for ii in self.out_data.keys():
+                for c in self.out_data[ii].keys():
+                    a, b = c.split('_')
+                    aux = self.out_data[ii][c]
+                    aux.extend([-1]*(samples - len(self.out_data[ii][c])))
+                    data[a+str(ii)+'_'+b] = aux
+                    
+            filedata = pd.DataFrame(data, columns = data.keys()).to_string()
+            self.save_path.seek(0) # Move to the first row to overwrite it
+            self.save_path.write(filedata)
+            self.save_path.flush() # Save without closing
+            # typically the above line would do. however this is used to ensure that the file is written
+            os.fsync(self.save_path.fileno())
+            self.saved = True
+         
+    def on_return(self, event):
+        ii = self.notebook.index(self.notebook.select())
+        val = self.tree[ii].item(self.treerow)['values']
+        val = [float(i) for i in val]
+        val[int(self.treecol[1:])-1] = float(self.entry.get())
+        self.tree[ii].item(self.treerow, values = val)
+        self.entry.destroy()        
+        self.saved = False
+        
+        if self.type[ii] == 'Rotating':
+            dx, dy = self.coord_calc(np.deg2rad(360 - val[0]))
+            dx_pr, dy_pr = self.coord_calc(np.deg2rad(360 - self.out_data[ii]['State_a'][self.treerow]))            
+            self.out_data[ii]['State_a'][self.treerow] = float(val[0])
+            self.canvas[ii].move("state"+str(ii)+'-'+str(self.treerow), dx-dx_pr, 
+                dy-dy_pr)
+            
+            self.out_data[ii]['Action_a'][self.treerow] = float(val[1])
+            if self.clock[ii].get()  == 'clock':
+                start = self.out_data[ii]['State_a'][self.treerow]
+                end = self.out_data[ii]['Action_a'][self.treerow]
+            else:
+                start = self.out_data[ii]['State_a'][self.treerow] - 360
+                end = self.out_data[ii]['Action_a'][self.treerow]
+            
+            self.canvas[ii].itemconfigure(
+                "action"+str(ii)+'-'+str(self.treerow), start = start, 
+                extent = end - start)
+        else:
+            # calculate distance moved from last position
+            dx_s = val[0] - self.out_data[ii]['State_x'][self.treerow]
+            dy_s = val[1] - self.out_data[ii]['State_y'][self.treerow]
+
+            self.canvas[ii].move("state"+str(ii)+'-'+str(self.treerow), dx_s, dy_s)
+
+            self.out_data[ii]['State_x'][self.treerow] = val[0]
+            self.out_data[ii]['State_y'][self.treerow] = val[1]
+
+            self.canvas[ii].coords(
+                "action"+str(ii)+'-'+str(self.treerow), 
+                (self.out_data[ii]['State_x'][self.treerow], 
+                  self.out_data[ii]['State_y'][self.treerow], val[2], val[3]))
+            self.out_data[ii]['Action_x'][self.treerow] = val[2]
+            self.out_data[ii]['Action_y'][self.treerow] = val[3]
+            
+    def OnDoubleClick(self, event):
+        
+        """ Executed when a row is double clicked.
+        Opens an entry box to edit a cell and updates the canvas and the 
+        stored data. """
+        
+        ii = self.notebook.index(self.notebook.select())
+        self.treerow = int(self.tree[ii].identify_row(event.y))
+        self.treecol = self.tree[ii].identify_column(event.x)
+        
+        # get column position info
+        x, y, width, height = self.tree[ii].bbox(self.treerow, self.treecol)
+    
+        # y-axis offset
+        pady = height // 2
+        # pady = 0
+        
+        if hasattr(self, 'entry'):
+            self.entry.destroy()
+            
+        self.entry = tk.Entry(self.tree[ii], justify='center') 
+        
+        if int(self.treecol[1:]) > 0:
+            self.entry.insert(
+                0, self.tree[ii].item(self.treerow)['values'][int(str(self.treecol[1:]))-1])
+            # self.entry['selectbackground'] = '#123456'
+            self.entry['exportselection'] = False
+    
+            self.entry.focus_force()
+            self.entry.bind("<Return>", self.on_return)
+            self.entry.bind("<Escape>", lambda *ignore: self.entry.destroy())
+            
+            self.entry.place(x = x, 
+                             y = y + pady, 
+                             anchor = tk.W, width = width)
+
+    def upload_sa(self):
+        
+        ii = self.notebook.index(self.notebook.select())
+        filename = askopenfilename(initialdir = os.getcwd(), 
+                                   title = 'Select a file', 
+                                   defaultextension = '.txt', 
+                                   filetypes = [('Text file', '.txt'), 
+                                                ('CSS file', '.css'), 
+                                                ('All Files', '*.*')])
+        if filename is not None:
+            data = open(filename,'r')
+            read = False
+            self.draw[ii].set('drag')
+            for n, point in enumerate(data):
+                if read: # Not elegant at all, just to omit the header.
+                    i, sx, sy, ax, ay = point.split()
+                    # Draw an oval in the given coordinates
+                    self.canvas[ii].create_oval(
+                        float(sx)-3, float(sy)-3, float(sx)+3, float(sy)+3, 
+                        fill="black", width=0, 
+                        tags=("state-" + str(len(
+                            self.out_data[ii]['State_x']))))
+                    self.canvas[ii].create_line(
+                        float(sx), float(sy), float(ax), float(ay), 
+                        fill="red", arrow=tk.LAST, 
+                        tags=("action"+str(ii)+"-"+ str(len(
+                            self.out_data[ii]['Action_x']))))
+                    self.out_data[ii]['State_x'].append(sx)
+                    self.out_data[ii]['State_y'].append(sy)
+                    self.out_data[ii]['Action_x'].append(ax)
+                    self.out_data[ii]['Action_y'].append(ay)
+                    self.tree.insert(
+                        parent = '', index = 'end', 
+                        iid = len(self.out_data[ii]['Action_x'])-1, 
+                        text = len(self.out_data[ii]['Action_x']), 
+                        values = tuple(self.dict2mat(
+                            self.out_data[ii])
+                            [len(self.out_data[ii]
+                                 ['Action_x'])-1,:].astype(int)))
+                    self.tree.selection_set(str(len(
+                        self.out_data[ii]['Action_x'])-1))
+                else:
+                    read = True
+    
+    def reset(self):
+        
+        ii = self.notebook.index(self.notebook.select())
+        msg = messagebox.askyesnocancel(
+            'Info', 'Are you sure you want to reset the canvas?')
+        if msg:
+            self.canvas[ii].delete(tk.ALL) # Reset canvas
+            self.state[ii].set('state')
+            self.out_data[ii] = {}
+            for key in self._class_list[ii]:
+                self.out_data[ii][key] = []
+                    
+            for record in self.tree[ii].get_children(): #Reset treeview
+                self.tree[ii].delete(record)
+            if self.type[ii] == 'Rotating': 
+                self.create_circle(self.x_ini, self.y_ini, 4, 
+                                   fill="#DDD", outline="", width=4)
+                self.create_circle(self.x_ini, self.y_ini, self.r, 
+                                   fill="", outline="#DDD", width=4)
+    
+    def dict2mat(self, X):
+        
+        max_size = 0
+        for key in X:
+            if len(X[key]) > max_size:
+                max_size = len(X[key])
+
+        result = -np.ones((max_size, len(X)))
+        for k, key in enumerate(X):
+            result[:len(X[key]), k]  = X[key]
+            
+        return result
+    
+    def angle_calc(self, x, y, circxy = False):
+        if (x == self.x_ini) & (y < self.y_ini):
+            alpha = math.pi/2
+        elif (x == self.x_ini) & (y > self.y_ini):
+            alpha = 3*math.pi/2
+        else:
+            alpha = np.arctan((y-self.y_ini)/(x-self.x_ini))
+            alpha += ((x-self.x_ini) < 0) * math.pi
+            alpha += (alpha < 0) * 2*math.pi
+        if circxy:
+            cx, cy = self.coord_calc(alpha)
+            return alpha, cx, cy
+        else:
+            return alpha
+        
+    def coord_calc(self, alpha):
+        cx = self.r * np.cos(alpha) + self.x_ini
+        cy = self.r * np.sin(alpha) + self.y_ini
+        return cx, cy
+
+        
+    def create_circle(self, x, y, r, **kwargs):
+        
+        return self.canvas[self.notebook.index(
+            self.notebook.select())].create_oval(x-r, y-r, x+r, y+r, **kwargs)
+    
+    def create_circle_arc(self, x, y, r, **kwargs):
+        
+        if "start" in kwargs and "end" in kwargs:
+            kwargs["extent"] = kwargs["end"] - kwargs["start"]
+            # if self.clock == 'countclock':
+            #     kwargs["extent"] += (kwargs["extent"] < 0) * 360
+            del kwargs["end"]
+        return self.canvas[self.notebook.index(
+            self.notebook.select())].create_arc(x-r, y-r, x+r, y+r, **kwargs)
\ No newline at end of file
diff --git a/aidesign/UserFeedback/plugins/ManualInput.py b/aidesign/UserFeedback/plugins/ManualInput.py
new file mode 100644
index 00000000..5f1c50be
--- /dev/null
+++ b/aidesign/UserFeedback/plugins/ManualInput.py
@@ -0,0 +1,285 @@
+from . import UI
+import tkinter as tk
+import os
+from PIL import Image, ImageTk
+from tkinter import messagebox, ttk
+from tkinter.filedialog import asksaveasfile, askopenfile, askopenfilename
+import numpy as np
+import pandas as pd
+
+
+class ManualInput(tk.Frame,UI):
+    def __init__(self, parent, controller, config:dict):
+        self.parent = parent    
+        super().__init__(parent, bg = self.parent['bg'])
+        self.controller = controller
+        self.controller.title('Manual Input')
+        
+        
+        self.dirpath = os.path.dirname(os.path.realpath(__file__))
+        self.assets_path = os.path.join(self.dirpath,'resources','Assets')
+
+        self._class_list = None        
+        pixels = 500
+        path = os.path.join(self.dirpath,'resources','example_radiography_images')
+        self.N = len(os.listdir(path))
+        
+        self.image_list = []
+        for f in os.listdir(path):
+            self.image_list.append(
+                ImageTk.PhotoImage(self.expand2square(
+                    Image.open(os.path.join(path, f)), 
+                    (0, 0, 0)).resize((pixels, pixels)))) # (0, 0, 0) is the padding colour
+        
+        # Status bar in the lower part of the window
+        status = tk.Label(self, text='Image 1 of '+str(self.N), bd = 1, 
+                          relief = tk.SUNKEN, anchor = tk.E, fg = 'white', 
+                          bg = self.parent['bg'])
+        status.grid(row=20, column=0, columnspan=4, pady = 10, 
+                    sticky = tk.W+tk.E)
+        
+        # Inital window
+        self.my_label = tk.Label(self, image = self.image_list[0], 
+                                 bg = self.parent['bg'])
+        self.my_label.grid(column = 0, row = 0, rowspan = 10, columnspan = 3)
+    
+        # Buttons initialisation
+        self.back_img = ImageTk.PhotoImage(Image.open(
+            os.path.join(self.assets_path,'back_arrow.png')).resize((150, 50)))
+        self.forw_img = ImageTk.PhotoImage(Image.open(
+            os.path.join(self.assets_path,'forw_arrow.png')).resize((150, 50)))
+        self.button_back = tk.Button(
+            self, image = self.back_img, bg = self.parent['bg'], 
+            state = tk.DISABLED).grid(column = 0,row = 19)
+        self.button_save = tk.Button(
+            self, text = 'Save', fg = 'white', bg = self.parent['bg'], height = 3, 
+            width = 20, command = self.save_file).grid(column = 1,row = 19)
+        self.button_forw = tk.Button(
+            self, image = self.forw_img, bg = self.parent['bg'], 
+            command = lambda: self.forward_back(2)).grid(column = 2,row = 19)
+        
+        button_main = tk.Button(
+            self, text="Done", 
+            fg = 'white', bg = self.parent['bg'], height = 3, width = 20, 
+            command = self.check_quit).grid(column = 4,row = 19)
+        self._parse_config(config)
+        self.save_path = ''
+        self.saved = True
+        
+    def _parse_config(self, config):
+        self.config = config
+        self.class_list(self.config["plugin"]["options"]["class_list"])
+
+    def class_list(self):
+        """Getter for required _class_list variable
+        
+        :return: list of class labels
+        :rtype: list of strings
+        """
+        return self._class_list
+
+    def class_list(self,value):
+        """Setter for required _class_list variable
+        
+        :param value: class labels for binary classification
+        :type value: list of strings
+        """
+        self._class_list = value
+        self.out_data = np.zeros((self.N, len(self._class_list)))
+        self.button_cl = {}
+        
+        self.var = {}
+        for i,cl in enumerate(self._class_list):
+            self.var[0,i] = tk.IntVar(value=self.out_data[0,i])
+            self.button_cl[cl] = tk.Checkbutton(
+                self, text = cl, fg = 'white', bg = self.parent['bg'], 
+                selectcolor = 'black', height = 3, width = 20, 
+                variable = self.var[0,i], 
+                command=(lambda i=i: self.onPress(0,i)))
+            self.button_cl[cl].grid(column = 4,row = i)
+        
+        #Tree defintion. Output display
+        style = ttk.Style()
+        style.configure(
+            "Treeview", background = 'white', foreground = 'white', 
+            rowheight = 25, fieldbackground = 'white', 
+            font = self.controller.pages_font)
+        style.configure("Treeview.Heading", font = self.controller.pages_font)
+        style.map('Treeview', background = [('selected', 'grey')])
+        
+        tree_frame = tk.Frame(self)
+        tree_frame.grid(row = 0, column = 5, columnspan = 4, rowspan = 10)
+        
+        tree_scrollx = tk.Scrollbar(tree_frame, orient = 'horizontal')
+        tree_scrollx.pack(side = tk.BOTTOM, fill = tk.X)
+        tree_scrolly = tk.Scrollbar(tree_frame)
+        tree_scrolly.pack(side = tk.RIGHT, fill = tk.Y)
+        
+        self.tree = ttk.Treeview(tree_frame, 
+                                 yscrollcommand = tree_scrolly.set, 
+                                 xscrollcommand = tree_scrollx.set)
+        self.tree.pack()
+        
+        tree_scrollx.config(command = self.tree.xview)
+        tree_scrolly.config(command = self.tree.yview)
+        
+        self.tree['columns'] = self._class_list
+        
+        # Format columns
+        self.tree.column("#0", width = 50)
+        for n, cl in enumerate(self._class_list):
+            self.tree.column(
+                cl, width = int(self.controller.pages_font.measure(str(cl)))+20, 
+                minwidth = 50, anchor = tk.CENTER)
+        # Headings
+        self.tree.heading("#0", text = "Image", anchor = tk.CENTER)
+        for cl in self._class_list:
+            self.tree.heading(cl, text = cl, anchor = tk.CENTER)
+        self.tree.tag_configure('odd', foreground = 'black', 
+                                background='#E8E8E8')
+        self.tree.tag_configure('even', foreground = 'black', 
+                                background='#DFDFDF')
+        # Add data
+        for n, sample in enumerate(self.out_data):
+            if n%2 == 0:
+                self.tree.insert(parent = '', index = 'end', iid = n, text = n+1, 
+                                 values = tuple(sample.astype(int)), tags = ('even',))
+            else:
+                self.tree.insert(parent = '', index = 'end', iid = n, text = n+1, 
+                                 values = tuple(sample.astype(int)), tags = ('odd',))
+        
+        # Select the current row
+        self.tree.selection_set(str(int(0)))
+        
+        # Define double-click on row action
+        self.tree.bind("<Double-1>", self.OnDoubleClick)
+
+    def expand2square(self, pil_img, background_color):
+        
+        " Adds padding to make the image a square."
+        width, height = pil_img.size
+        if width == height:
+            return pil_img
+        elif width > height:
+            result = Image.new(pil_img.mode, (width, width), background_color)
+            result.paste(pil_img, (0, (width - height) // 2))
+            return result
+        else:
+            result = Image.new(pil_img.mode, (height, height), 
+                               background_color)
+            result.paste(pil_img, ((height - width) // 2, 0))
+            return result
+
+    def check_quit(self):
+        
+        if not self.saved:
+            response = messagebox.askokcancel(
+                "Exit?", 
+                "Do you want to leave the program without saving?")
+            if response:
+                self.controller.destroy()
+        else:
+            response = messagebox.askokcancel(
+                "Exit?",
+                "Are you sure you are finished?")
+            self.controller.destroy()
+    
+    def open_file(self):
+        
+        self.save_path = askopenfile(mode='r+')
+        if self.save_path is not None:
+            t = self.save_path.read()
+            # textentry.delete('0.0', 'end')
+            # textentry.insert('0.0', t)
+            # textentry.focus()
+            
+    def save_file_as(self):
+        
+        self.save_path = asksaveasfile(mode='w')
+        self.save_file()
+        
+    def save_file(self):
+        
+        if self.save_path == '':
+            self.save_path = asksaveasfile(defaultextension = '.txt', 
+                                      filetypes = [('Text file', '.txt'), 
+                                                   ('CSV file', '.csv'), 
+                                                   ('All Files', '*.*')])
+        if self.save_path is not None: # asksaveasfile return `None` if dialog closed with "cancel".
+            filedata = pd.DataFrame(
+                self.out_data, columns = self._class_list).to_string()
+            self.save_path.seek(0) # Move to the first row to overwrite it
+            self.save_path.write(filedata)
+            self.save_path.flush() # Save without closing
+            # typically the above line would do. however this is used to ensure that the file is written
+            os.fsync(self.save_path.fileno())
+            self.saved = True
+            
+    def forward_back(self, image_number):
+        
+        " Forward button to continue to the next image in the folder."
+        
+        self.tree.selection_set(str(int(image_number-1)))
+        # Print the corresponding image
+        self.my_label.grid_forget()
+        self.my_label = tk.Label(self, image=self.image_list[image_number-1])
+        
+        # Update button commands
+        self.button_forw = tk.Button(
+            self, image = self.forw_img, bg = self.parent['bg'], 
+            command = lambda: self.forward_back(
+                image_number+1)).grid(column = 2,row = 19)
+        self.button_back = tk.Button(
+            self, image = self.back_img, bg = self.parent['bg'], 
+            command = lambda: self.forward_back(
+                image_number-1)).grid(column = 0,row = 19)
+        if image_number == self.N:
+            self.button_forw = tk.Button(
+                self, image = self.forw_img, bg = self.parent['bg'], 
+                state = tk.DISABLED).grid(column = 2,row = 19)
+        if image_number == 1:
+            self.button_back = tk.Button(
+                self, image = self.back_img, bg = self.parent['bg'], 
+                state = tk.DISABLED).grid(column = 0,row = 19)
+            
+        self.my_label.grid(column=0, row=0, rowspan = 10, columnspan=3)
+        
+        # Classes buttons
+        # var = {}
+        for i,cl in enumerate(self._class_list):
+            # print(out_data[image_number-1,i])
+            self.var[image_number-1,i] = tk.IntVar(
+                value = int(self.out_data[image_number-1,i]))
+            # var[i] = tk.IntVar(value = int(self.out_data[image_number-1,i]))
+            # I can not make this be selected when going backwards or forward 
+            # if it was previously selected.
+            self.button_cl[cl] = tk.Checkbutton(
+                self, text = cl, fg = 'white', bg = self.parent['bg'], 
+                selectcolor = 'black', height = 3, width = 20, 
+                variable = self.var[image_number-1,i], 
+                command=(lambda i=i: self.onPress(image_number-1,i)))
+            self.button_cl[cl].grid(column = 4,row = i)
+
+        # Status bar    
+        status = tk.Label(
+            self, text='Image ' + str(image_number) + ' of '+str(self.N), 
+            bd = 1, relief = tk.SUNKEN, anchor = tk.E, fg = 'white', 
+            bg = self.parent['bg'])
+        status.grid(row=20, column=0, columnspan=4, pady = 10, 
+                    sticky = tk.W+tk.E)
+            
+    def onPress(self, n,i):
+        
+        "Updates the stored values on clicking the checkbutton."
+        
+        self.out_data[n,i] = not self.out_data[n,i]
+        self.tree.item(self.tree.get_children()[n], text = n+1, 
+                       values = tuple(self.out_data[n,:].astype(int)))
+        self.saved = False
+        
+    def OnDoubleClick(self, event):
+        
+        "Moves to the image corresponding to the row clicked on the tree."
+        
+        item = self.tree.selection()[0]
+        self.forward_back(self.tree.item(item,"text"))
diff --git a/aidesign/UserFeedback/plugins/__init__.py b/aidesign/UserFeedback/plugins/__init__.py
new file mode 100644
index 00000000..75182d4e
--- /dev/null
+++ b/aidesign/UserFeedback/plugins/__init__.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+
+"""Import all submodules inside this folder and expose them with their respective names
+
+Let's look for a more automatic way of doing this when we have more modules
+"""
+from ..User_Feedback_template import UI
+# from .canvas_input import PageCanvas
+# from .manual_input import PageManual
+# from .startpage import StartPage
diff --git a/aidesign/UserFeedback/plugins/resources/Assets/UFAIcon.ico b/aidesign/UserFeedback/plugins/resources/Assets/UFAIcon.ico
new file mode 100644
index 00000000..60f8034e
Binary files /dev/null and b/aidesign/UserFeedback/plugins/resources/Assets/UFAIcon.ico differ
diff --git a/aidesign/UserFeedback/plugins/resources/Assets/UFAIcon_name.png b/aidesign/UserFeedback/plugins/resources/Assets/UFAIcon_name.png
new file mode 100644
index 00000000..fb03392b
Binary files /dev/null and b/aidesign/UserFeedback/plugins/resources/Assets/UFAIcon_name.png differ
diff --git a/aidesign/UserFeedback/plugins/resources/Assets/back_arrow.png b/aidesign/UserFeedback/plugins/resources/Assets/back_arrow.png
new file mode 100644
index 00000000..3c2e4408
Binary files /dev/null and b/aidesign/UserFeedback/plugins/resources/Assets/back_arrow.png differ
diff --git a/aidesign/UserFeedback/plugins/resources/Assets/forw_arrow.png b/aidesign/UserFeedback/plugins/resources/Assets/forw_arrow.png
new file mode 100644
index 00000000..10d3333b
Binary files /dev/null and b/aidesign/UserFeedback/plugins/resources/Assets/forw_arrow.png differ
diff --git a/aidesign/UserFeedback/plugins/resources/example_radiography_images/00028173_005.png b/aidesign/UserFeedback/plugins/resources/example_radiography_images/00028173_005.png
new file mode 100644
index 00000000..01ee87a5
Binary files /dev/null and b/aidesign/UserFeedback/plugins/resources/example_radiography_images/00028173_005.png differ
diff --git a/aidesign/UserFeedback/plugins/resources/example_radiography_images/00028179_000.png b/aidesign/UserFeedback/plugins/resources/example_radiography_images/00028179_000.png
new file mode 100644
index 00000000..11a884c5
Binary files /dev/null and b/aidesign/UserFeedback/plugins/resources/example_radiography_images/00028179_000.png differ
diff --git a/aidesign/UserFeedback/plugins/resources/example_radiography_images/00028198_002.png b/aidesign/UserFeedback/plugins/resources/example_radiography_images/00028198_002.png
new file mode 100644
index 00000000..eeb7795d
Binary files /dev/null and b/aidesign/UserFeedback/plugins/resources/example_radiography_images/00028198_002.png differ
diff --git a/aidesign/UserFeedback/plugins/resources/example_radiography_images/00028208_035.png b/aidesign/UserFeedback/plugins/resources/example_radiography_images/00028208_035.png
new file mode 100644
index 00000000..16f02301
Binary files /dev/null and b/aidesign/UserFeedback/plugins/resources/example_radiography_images/00028208_035.png differ
diff --git a/aidesign/UserFeedback/plugins/resources/example_radiography_images/00028211_021.png b/aidesign/UserFeedback/plugins/resources/example_radiography_images/00028211_021.png
new file mode 100644
index 00000000..50e34df8
Binary files /dev/null and b/aidesign/UserFeedback/plugins/resources/example_radiography_images/00028211_021.png differ
diff --git a/aidesign/UserFeedback/plugins/resources/example_radiography_images/00028253_000.png b/aidesign/UserFeedback/plugins/resources/example_radiography_images/00028253_000.png
new file mode 100644
index 00000000..ba612af8
Binary files /dev/null and b/aidesign/UserFeedback/plugins/resources/example_radiography_images/00028253_000.png differ
diff --git a/aidesign/UserFeedback/plugins/resources/example_radiography_images/00028279_000.png b/aidesign/UserFeedback/plugins/resources/example_radiography_images/00028279_000.png
new file mode 100644
index 00000000..dce305a9
Binary files /dev/null and b/aidesign/UserFeedback/plugins/resources/example_radiography_images/00028279_000.png differ
diff --git a/aidesign/__init__.py b/aidesign/__init__.py
index 1d4aab20..e2b5da56 100644
--- a/aidesign/__init__.py
+++ b/aidesign/__init__.py
@@ -1,3 +1,7 @@
-from .GUI import GUI
+from .Settings import Settings
 from .utils import AID
-from .Settings import Settings
\ No newline at end of file
+from. import utils
+from .UserFeedback import UserFeedback
+from .Modelling import Modelling
+from .Core import Core
+from .GUI import GUI
\ No newline at end of file
diff --git a/aidesign/utils/__init__.py b/aidesign/utils/__init__.py
index 7302c32e..17f3d4e4 100644
--- a/aidesign/utils/__init__.py
+++ b/aidesign/utils/__init__.py
@@ -6,3 +6,4 @@
 """
 from .AID_core import AID
 from .UserInterfaceClass import UI
+from . import import_helper
diff --git a/aidesign/utils/import_helper.py b/aidesign/utils/import_helper.py
new file mode 100644
index 00000000..acc416fe
--- /dev/null
+++ b/aidesign/utils/import_helper.py
@@ -0,0 +1,16 @@
+def import_plugin(script_config, plugin_name):
+    plugin_list = __import__(script_config["__package__"]+'.plugins.' + plugin_name,
+                             script_config,
+                             {},
+                             [plugin_name])
+    plugin_class = getattr(plugin_list, plugin_name)
+    return plugin_class
+
+
+def import_module(script_config, module_name):
+    module_list = __import__('aidesign.' + module_name,
+                             script_config,
+                             {},
+                             [module_name])
+    module_class = getattr(module_list, module_name)
+    return module_class
diff --git a/aidesign/utils/plugins/aidCanvas.py b/aidesign/utils/plugins/aidCanvas.py
index 57a8d233..4c01ee24 100644
--- a/aidesign/utils/plugins/aidCanvas.py
+++ b/aidesign/utils/plugins/aidCanvas.py
@@ -1,4 +1,3 @@
-# Import the required libraries
 import tkinter as tk
 from PIL import Image, ImageTk
 import os
@@ -14,7 +13,7 @@ class aidCanvas(tk.Frame):
     """ Creates a frame with a canvas and allows to include different modules
     for display which translate into new modules defined for the framework."""
     
-    def __init__(self, parent, controller):
+    def __init__(self, parent, controller, core = None):
         
         " Here we define the main frame displayed upon opening the program."
         " This leads to the different methods to provide feedback."
@@ -24,13 +23,23 @@ def __init__(self, parent, controller):
         self.controller = controller
         self.controller.title('Assisted Design Display')
         
+        script_dir = os.path.dirname(__file__)
+        print(os.path.join(
+                script_dir, 
+                'resources',
+                'Assets',
+                'AIDIcon.ico'))
         self.tk.call('wm','iconphoto', self.controller._w, ImageTk.PhotoImage(
-            file = os.path.join(os.path.join(
-                os.getcwd(), 'aidesign', 'utils', 'plugins', 
-                'resources', 'Assets', 'AIDIcon.ico'))))
+            file = os.path.join(
+                script_dir, 
+                'resources',
+                'Assets',
+                'AIDIcon.ico')))
         self.my_img1 = ImageTk.PhotoImage(Image.open(os.path.join(
-                os.getcwd(), 'aidesign', 'utils', 'plugins', 
-                'resources', 'Assets', 'AIDIcon_name.png')).resize((250, 200)))
+                script_dir, 
+                'resources',
+                'Assets',
+                'AIDIcon_name.ico')).resize((250, 200)))
         
         self.my_label = tk.Label(self, image = self.my_img1, bg = parent['bg'])
         self.my_label.grid(column = 5, row = 0)
@@ -43,54 +52,19 @@ def __init__(self, parent, controller):
                          padx = 10, pady = 10)
         
         self.canvas.startxy = []
-        self.out_data = pd.DataFrame(np.zeros((2,2)), 
-                                     columns = ['Initialiser', 'Output'], 
-                                     index = ['Initialiser', 'Output'])
+        self.out_data = pd.DataFrame()
         self.connections = {}
-        self.connections[0] = {}
-        self.connections[1] = {}
-        self.module_list = ['Initialiser', 'Output']
-        self.module_names = ['Initialiser', 'Output']
+        self.modules = 0
+        self.module_list = []
+        self.module_names = []
+        
         # Create module
         self.w, self.h = 100, 50
-        x0 = self.width/2 - (
-            self.controller.pages_font.measure('Initialiser')+10)/2
-
         self.cr = 4
+        
         #Initialiser module
-        self.canvas.create_rectangle(x0, self.h, x0 + self.w, 2*self.h, 
-                                     tags = 'p0', fill = self.bg, width = 3,
-                                     activefill = '#dbaa21')
-        self.canvas.create_text(x0 + self.w/2, 3*self.h/2, 
-                                text = 'Initialiser', tags = 't0', 
-                                fill = '#d0d4d9', 
-                                font = self.controller.pages_font)
-        self.canvas.create_oval(
-                        x0 + self.w/2-self.cr, 2*self.h-self.cr, 
-                        x0 + self.w/2+self.cr, 2*self.h+self.cr, 
-                        width=2, fill = 'black', tags='d0')
-        self.canvas.tag_bind('d0', "<Button-1>", self.join_modules)
-        self.canvas.startxy.append((x0 + self.w/2, 
-                                    3*self.h/2))
-        # Output module
-        h_out = self.height - 2*self.h
-        self.canvas.create_rectangle(x0, h_out, x0 + self.w, h_out + self.h, 
-                                     tags = ('p1'), 
-                                     fill = self.bg, width = 3,
-                                     activefill = '#dbaa21')
-        self.canvas.create_text(x0 + self.w/2, h_out + self.h/2, 
-                                text = 'Output', tags = ('t1'), 
-                                fill = '#d0d4d9', 
-                                font = self.controller.pages_font)
-        self.canvas.create_oval(
-                        x0 + self.w/2 - self.cr, 
-                        h_out - self.cr, 
-                        x0 + self.w/2 + self.cr, 
-                        h_out  + self.cr,
-                        width=2, fill = 'black', tags = ('u1'))
-        self.canvas.tag_bind('u1', "<Button-1>", self.join_modules)
-        self.canvas.startxy.append((x0 + self.w/2, 
-                                    h_out + self.h/2))
+        self.add_module('Initialiser', self.width/2, self.h, ini = True)
+        self.add_module('Output', self.width/2, self.height - self.h, out = True)
         
         self.draw = False
         self.canvas.bind("<B1-Motion>", self.on_drag)
@@ -102,34 +76,42 @@ def __init__(self, parent, controller):
         # self.canvas.bind("<ButtonPress-1>", self.on_button_press)
         self.canvas.bind("<ButtonRelease-1>", self.on_button_release)
         
-        # modules = ['Data preprocessing', 'Modelling', 'Decision making', 'User Feedback Adaptation']
         
-        self.modules = 2
         # for m, module in enumerate(modules):
         tk.Button(
             self, text = 'Data processing', fg = 'white', bg = parent['bg'],
             height = 3, width = 25, font = self.controller.pages_font,
-            command = lambda: self.add_module('DataProcessing')
+            command = lambda: self.add_module('Data Processing', 
+                                              self.width/2, 
+                                              self.height/2)
             ).grid(column = 5, row = 1)
         tk.Button(
             self, text = 'Modelling', fg = 'white', bg = parent['bg'],
             height = 3, width = 25, font = self.controller.pages_font,
-            command = lambda: self.add_module('Modelling')
+            command = lambda: self.add_module('Modelling', 
+                                              self.width/2, 
+                                              self.height/2)
             ).grid(column = 5, row = 2)
         tk.Button(
             self, text = 'Decision making', fg = 'white', bg = parent['bg'],
             height = 3, width = 25, font = self.controller.pages_font,
-            command = lambda: self.add_module('DecisionMaking')
+            command = lambda: self.add_module('Decision Making', 
+                                              self.width/2, 
+                                              self.height/2)
             ).grid(column = 5, row = 3)
         tk.Button(
-            self, text = 'User Feedback Adaptation', fg = 'white', bg = parent['bg'],
+            self, text = 'User Feedback', fg = 'white', bg = parent['bg'],
             height = 3, width = 25, font = self.controller.pages_font,
-            command = lambda: self.add_module('UserFeedbackAdaptation')
+            command = lambda: self.add_module('User Feedback', 
+                                              self.width/2, 
+                                              self.height/2)
             ).grid(column = 5, row = 4)
         tk.Button(
             self, text = 'Input data', fg = 'white', bg = parent['bg'],
             height = 3, width = 25, font = self.controller.pages_font,
-            command = lambda: self.add_module('InputData')
+            command = lambda: self.add_module('Input Data', 
+                                              self.width/2, 
+                                              self.height/2)
             ).grid(column = 5, row = 5)
         tk.Button(
             self, text = 'Delete selection', fg = 'white', bg = parent['bg'],
@@ -152,31 +134,33 @@ def __init__(self, parent, controller):
         self.saved = True
 
     def on_return_display(self, event):
-            if self.loopDisp:
-                condition = self.entry1.get()
-                self.entry1.destroy()
-                if hasattr(self, 'entry2'):
-                    self.entry2.focus()
-                key = 'type'
-                text_w = self.controller.pages_font.measure(condition) + 20
-                x = self.start_x + text_w/2
-            else:
-                condition = self.entry2.get()
-                self.entry2.destroy()
-                key = 'condition'
-                text_w = self.controller.pages_font.measure(condition) + 20
-                x = self.curX - text_w/2
-            self.loops[-1][key] = condition
-            self.canvas.create_text(
-                x, 
-                self.start_y + 20, 
-                font = self.controller.pages_font, 
-                text = condition, 
-                tags = ('loop-'+str(self.l-1), key+'-'+str(self.l-1)), 
-                justify = tk.CENTER)
-            self.canvas.tag_bind(key+'-'+str(self.l-1), 
-                             "<Double-1>", self.OnDoubleClick)
-            self.loopDisp = not self.loopDisp
+        """ Defines the type of loop and condition for the indicated loop
+        """
+        if self.loopDisp:
+            condition = self.entry1.get()
+            self.entry1.destroy()
+            if hasattr(self, 'entry2'):
+                self.entry2.focus()
+            key = 'type'
+            text_w = self.controller.pages_font.measure(condition) + 20
+            x = self.start_x + text_w/2
+        else:
+            condition = self.entry2.get()
+            self.entry2.destroy()
+            key = 'condition'
+            text_w = self.controller.pages_font.measure(condition) + 20
+            x = self.curX - text_w/2
+        self.loops[-1][key] = condition
+        self.canvas.create_text(
+            x, 
+            self.start_y + 20, 
+            font = self.controller.pages_font, 
+            text = condition, 
+            tags = ('loop-'+str(self.l-1), key+'-'+str(self.l-1)), 
+            justify = tk.CENTER)
+        self.canvas.tag_bind(key+'-'+str(self.l-1), 
+                         "<Double-1>", self.OnDoubleClick)
+        self.loopDisp = not self.loopDisp
     
     def on_button_release(self, event):
         """ Finishes drawing the rectangle for loop definition. 
@@ -256,7 +240,7 @@ def updateLoops(self):
                 if len(aux) == 2 and aux[0] == 't':
                     self.loops[l]['mod'].append(self.canvas.itemcget(
                         mod, 'text'))
-        print('Updated loop info:', self.loops)
+        # print('Updated loop info:', self.loops)
         
     def select(self, event):
         """ Selects the module at the mouse location. """
@@ -341,10 +325,10 @@ def on_drag(self, event):
                     self.out_data.values[self.m, :]==1]
                 for o in out:
                     xycoord_i = self.canvas.coords(
-                        self.connections[self.m][o].split('-')[0])
+                        self.connections[o][self.m].split('-')[0])
                     xycoord_o = self.canvas.coords(
-                        self.connections[self.m][o].split('-')[1])
-                    self.canvas.coords(self.connections[self.m][o], 
+                        self.connections[o][self.m].split('-')[1])
+                    self.canvas.coords(self.connections[o][self.m], 
                             (xycoord_i[0] + self.cr, 
                              xycoord_i[1] + self.cr, 
                              xycoord_o[0] + self.cr, 
@@ -353,10 +337,10 @@ def on_drag(self, event):
                     self.out_data.values[:, self.m]==1]
                 for i in inp:
                     xycoord_i = self.canvas.coords(
-                        self.connections[i][self.m].split('-')[0])
+                        self.connections[self.m][i].split('-')[0])
                     xycoord_o = self.canvas.coords(
-                        self.connections[i][self.m].split('-')[1])
-                    self.canvas.coords(self.connections[i][self.m], 
+                        self.connections[self.m][i].split('-')[1])
+                    self.canvas.coords(self.connections[self.m][i], 
                             (xycoord_i[0] + self.cr, 
                              xycoord_i[1] + self.cr, 
                              xycoord_o[0] + self.cr, 
@@ -371,9 +355,11 @@ def module_out(self, name):
         :type name: str
         """
         name_list = list(self.out_data.columns)
-        m_num = [n.split('-')[1] for n in name_list if (
-            len(n.split('-')) > 1) and (n.split('-')[0] == name)]
-        name_list.append(name + '-' + str(len(m_num)))
+        m_num = [n for n in name_list if (n.split('-')[0] == name)]
+        if len(m_num) > 0:
+            name_list.append(name + '-' + str(len(m_num)))
+        else:
+            name_list.append(name)
         self.canvas.itemconfig('t'+str(self.modules), text = name_list[-1])
         values = self.out_data.values
         values = np.vstack((
@@ -384,73 +370,83 @@ def module_out(self, name):
                                      index = name_list)
         self.module_names.append(name_list[-1])
 
-    def add_module(self, boxName):
+    def add_module(self, boxName, x, y, ini = False, out = False):
         """ Creates a rectangular module with the corresponding text inside.
         
         :param boxName: name of the model
         :type boxName: str
         """
+        if not ini and not out:
+            tag = ('o'+str(self.modules),)
+        else: #Make initialisation and output unmoveable
+            tag = ('n0',)
         text_w = self.controller.pages_font.measure(boxName+'-00') + 10
         self.canvas.create_rectangle(
-            self.width/2 - text_w/2 , 
-            self.height/2 - self.h/2, 
-            self.width/2 + text_w/2, 
-            self.height/2 + self.h/2, 
-            tags = ('o'+str(self.modules), 'p'+str(self.modules)), 
+            x - text_w/2 , 
+            y - self.h/2, 
+            x + text_w/2, 
+            y + self.h/2, 
+            tags = tag + ('p'+str(self.modules),), 
             fill = self.bg, width = 3,
             activefill = '#dbaa21')
         self.canvas.create_text(
-            self.width/2, 
-            self.height/2, 
+            x, 
+            y, 
             font = self.controller.pages_font, 
             text = boxName, 
-            tags = ('o'+str(self.modules), 't'+str(self.modules)), 
+            tags = tag + ('t'+str(self.modules),), 
             fill = '#d0d4d9', 
             justify = tk.CENTER)
         self.canvas.tag_bind('t'+str(self.modules), 
                              "<Double-1>", self.OnDoubleClick)
-        self.canvas.create_oval(
-            self.width/2 - self.cr, 
-            self.height/2 + self.h/2 - self.cr, 
-            self.width/2 + self.cr, 
-            self.height/2 + self.h/2 + self.cr, 
-            width = 2, 
-            fill = 'black', 
-            tags = ('o'+str(self.modules), 'd'+str(self.modules)))
-        self.canvas.tag_bind('d'+str(self.modules), 
-                             "<Button-1>", self.join_modules)
-        self.canvas.create_oval(
-            self.width/2 - text_w/2 - self.cr, 
-            self.height/2 - self.cr, 
-            self.width/2 - text_w/2 + self.cr, 
-            self.height/2 + self.cr, 
-            width = 2, 
-            fill = 'black', 
-            tags = ('o'+str(self.modules), 'l'+str(self.modules)))
-        self.canvas.tag_bind('l'+str(self.modules), 
-                             "<Button-1>", self.join_modules)
-        self.canvas.create_oval(
-            self.width/2 - self.cr, 
-            self.height/2 - self.h/2 - self.cr, 
-            self.width/2 + self.cr, 
-            self.height/2 - self.h/2 + self.cr, 
-            width = 2, 
-            fill = 'black', 
-            tags = ('o'+str(self.modules), 'u'+str(self.modules)))
-        self.canvas.tag_bind('u'+str(self.modules), 
-                             "<Button-1>", self.join_modules)
-        self.canvas.create_oval(
-            self.width/2 + text_w/2 - self.cr, 
-            self.height/2 - self.cr, 
-            self.width/2 + text_w/2 + self.cr, 
-            self.height/2 + self.cr, 
-            width = 2, 
-            fill = 'black', 
-            tags = ('o'+str(self.modules), 'r'+str(self.modules)))
-        self.canvas.tag_bind('r'+str(self.modules), 
-                             "<Button-1>", self.join_modules)
-        self.canvas.startxy.append((self.width/2, 
-                                    self.height/2))
+        if not out:
+            self.canvas.create_oval(
+                x - self.cr, 
+                y + self.h/2 - self.cr, 
+                x + self.cr, 
+                y + self.h/2 + self.cr, 
+                width = 2, 
+                fill = 'black', 
+                tags = tag + ('d'+str(self.modules),))
+            self.canvas.tag_bind('d'+str(self.modules), 
+                                 "<Button-1>", self.join_modules)
+            
+        if not ini:
+            self.canvas.create_oval(
+                x - self.cr, 
+                y - self.h/2 - self.cr, 
+                x + self.cr, 
+                y - self.h/2 + self.cr, 
+                width = 2, 
+                fill = 'black', 
+                tags = tag + ('u'+str(self.modules),))
+            self.canvas.tag_bind('u'+str(self.modules), 
+                                 "<Button-1>", self.join_modules)
+        
+        if not out and not ini:
+            self.canvas.create_oval(
+                x - text_w/2 - self.cr, 
+                y - self.cr, 
+                x - text_w/2 + self.cr, 
+                y + self.cr, 
+                width = 2, 
+                fill = 'black', 
+                tags = tag + ('l'+str(self.modules),))
+            self.canvas.tag_bind('l'+str(self.modules), 
+                                 "<Button-1>", self.join_modules)
+        
+            self.canvas.create_oval(
+                x + text_w/2 - self.cr, 
+                y - self.cr, 
+                x + text_w/2 + self.cr, 
+                y + self.cr, 
+                width = 2, 
+                fill = 'black', 
+                tags = tag + ('r'+str(self.modules),))
+            self.canvas.tag_bind('r'+str(self.modules), 
+                                 "<Button-1>", self.join_modules)
+        self.canvas.startxy.append((x, 
+                                    y))
         self.connections[self.modules] = {}
         self.module_out(boxName)
         self.module_list.append(boxName)
@@ -490,6 +486,7 @@ def on_return_rename(self, event):
                                 y1)
             self.entry.destroy()
             self.module_names[self.m] = moduleName
+            self.saved = False
             
     def on_return_editLoop(self, event):
         """ This function renames loop conditions and updates the loop
@@ -501,6 +498,7 @@ def on_return_editLoop(self, event):
         self.canvas.itemconfig(self.selected[0], text = newText)
         self.entry.destroy()
         self.loops[int(conditions[1])][conditions[0]] = newText
+        self.saved = False
         
     def OnDoubleClick(self, event):
         
@@ -574,6 +572,7 @@ def delete_sel(self):
                 self.out_data[col] = 0
                 self.out_data.loc[col] = 0
                 self.saved = False
+                self.module_names[self.m] = None
                 
     def join_modules(self, event):
         """ Draws a connecting line between two connecting circles. """
@@ -594,8 +593,8 @@ def join_modules(self, event):
                             tags = ('o'+str(int(self.tag[1:])), 
                                   'o'+str(int(tag2[1:])), self.tag + '-' + tag2))
                 self.out_data.iloc[int(self.tag[1:])][int(tag2[1:])] = 1
-                self.connections[
-                    int(self.tag[1:])][int(tag2[1:])] = self.tag + '-' + tag2
+                self.connections[int(tag2[1:])][
+                    int(self.tag[1:])] = self.tag + '-' + tag2
             self.draw = False
             self.saved = False
         else:
@@ -632,17 +631,20 @@ def save_file(self):
             for c in np.arange(len(idx))[idx]:
                 data = data.drop(columns=col[c], index=col[c])
                 
-            print(col)
-            print(self.module_names)
             loop_modules = np.unique([v for a in self.loops for v in a['mod']])
+
             out_loops = np.zeros_like(self.loops)
             
             values = data.values.astype(bool)
             mn = np.array(self.module_names)
-            
+            mn_id = [m is not None for m in mn]
+            mn = mn[mn_id]
+
+            # to avoid numpy bug during elementwise comparison of lists 
+            loop_modules.dtype = mn.dtype if len(loop_modules) == 0 else loop_modules.dtype
+
             s = Settings()
             s.new_config_file(self.save_path.name)
-            s.parse_XML()
             s.filename = self.save_path.name
             s.append_pipeline_module(self.module_list[0], # Initialiser
                                   mn[0],
@@ -651,35 +653,28 @@ def save_file(self):
                                   list(mn[values[:,0]]),
                                   list(mn[values[0,:]]),
                                   None,
-                                  self.canvas.startxy[0])
-            for i in np.arange(len(mn)-2)+2:
-                xml_parent = None
-                parent_loops = []
-                if mn[i] in loop_modules: # Model in any loop
-                    for x, loop in enumerate(self.loops):
-                        if mn[i] in loop['mod']: # Model in this loop
-                            if not out_loops[x]: # Loop already defined
-                                s.append_pipeline_loop(self.loops[x]['type'],
-                                            self.loops[x]['condition'],
-                                            "loop"+str(x),
-                                            parent_loops,
-                                            list(loop['mod']),
-                                            xml_parent,
-                                            self.loops[x]['coord']
-                                            )
-                                out_loops[x] = 1
-                            xml_parent = "loop"+str(x) # parent is the last loop
-                            parent_loops.append("loop"+str(x))
-                
-                s.append_pipeline_module(self.module_list[i],
-                  mn[i],
-                  "",
-                  {},
-                  list(mn[values[:,i]]),
-                  list(mn[values[i,:]]),
-                  xml_parent,
-                  self.canvas.startxy[i])
-                
+                                  [self.canvas.startxy[0], 0, self.connections[0]])      
+            
+            for i, mnn in enumerate(mn_id):
+                if (i > 1) and mnn:
+                    xml_parent = None
+                    parent_loops = []
+                    if mn[i] in loop_modules: # Model in any loop
+                        for x, loop in enumerate(self.loops):
+                            if mn[i] in loop['mod']: # Model in this loop
+                                if not out_loops[x]: # Loop already defined
+                                    s.append_pipeline_loop(self.loops[x]['type'],
+                                                self.loops[x]['condition'],
+                                                "loop"+str(x),
+                                                parent_loops,
+                                                list(loop['mod']),
+                                                xml_parent,
+                                                self.loops[x]['coord']
+                                                )
+                                    out_loops[x] = 1
+                                xml_parent = "loop"+str(x) # parent is the last loop
+                                parent_loops.append("loop"+str(x))
+                                
             s.append_pipeline_module(self.module_list[1], # Out
                       mn[1],
                       "",
@@ -687,27 +682,124 @@ def save_file(self):
                       list(mn[values[:,1]]),
                       list(mn[values[1,:]]),
                       None,
-                      self.canvas.startxy[1])
+                      [self.canvas.startxy[1], 1, self.connections[1]])
             s.write_to_XML()
             self.saved = True
          
     def upload(self):
         
-        self.reset()
         filename = askopenfilename(initialdir = os.getcwd(), 
                                    title = 'Select a file', 
                                    defaultextension = '.xml', 
                                    filetypes = [('XML file', '.xml'), 
                                                 ('All Files', '*.*')])
         if filename is not None:
-            data = open(filename,'r')
+            self.reset()
 
-            # for n, point in enumerate(data):
+            s = Settings()
+            s.load_XML(filename)
+            s._print_pretty(s.loaded_modules)
+            modules = s.loaded_modules
+            modout = modules['output']
+            del modules['Initialiser'], modules['output'] # They are generated when resetting
+            disp_mod = ['Initialiser', 'output']
+            id_mod = [0, 1]
+            
+            # Place the modules
+            disp_mod, id_mod = self.place_modules(modules, id_mod, disp_mod)
+    
+            connect = list(modout['coordinates'][2].keys())
+            for p, parent in enumerate(modout['parents']):
+                    parent_id = id_mod[np.where(np.array(disp_mod) == parent)[0][0]]
+                    out, ins = modout['coordinates'][2][connect[p]].split('-')
+                    xout, yout, _ , _ = self.canvas.coords(out[0]+str(parent_id))
+                    xins, yins, _, _ =self.canvas.coords(ins[0]+str(1))
+                    self.canvas.create_line(
+                                xout + self.cr, 
+                                yout + self.cr, 
+                                xins + self.cr, 
+                                yins + self.cr,
+                                fill = "red", 
+                                arrow = tk.LAST, 
+                                tags = ('o'+str(parent_id), 
+                                      'o'+str(1), modout['coordinates'][2][connect[p]]))
+                    self.out_data.iloc[int(parent_id)][1] = 1
+                    self.connections[1][
+                        int(parent_id)] = out[0]+str(parent_id) + '-' + ins[0]+str(1)
+                    
+    def place_modules(self, modules, id_mod, disp_mod):
+        # Place the modules
+        for key in [key for key, val in modules.items() if type(val) == dict]:
+            if modules[key]['class'] == 'loop':
+                # Extracts numbers from string
+                l = int(''.join(map(str, list(filter(str.isdigit, modules[key]['name'])))))
+                x0, y0, x1, y1 = modules[key]['coordinates']
+                self.canvas.create_rectangle(x0, y0, 
+                                             x1, y1, 
+                                             outline = '#4ff07a',
+                                             tag = 'loop-'+str(l))
+                text_w = self.controller.pages_font.measure(modules[key]['type']) + 20
+                self.canvas.create_text(
+                    x0 + text_w/2, 
+                    y0 + 20, 
+                    font = self.controller.pages_font, 
+                    text = modules[key]['type'], 
+                    tags = ('loop-'+str(l), 'type'+'-'+str(l)), 
+                    justify = tk.CENTER)
+                text_w = self.controller.pages_font.measure(modules[key]['condition']) + 20
+                self.canvas.create_text(
+                    x1 - text_w/2, 
+                    y0 + 20, 
+                    font = self.controller.pages_font, 
+                    text = modules[key]['condition'], 
+                    tags = ('loop-'+str(l), 'condition'+'-'+str(l)), 
+                    justify = tk.CENTER)
+                self.loops.append({'type': modules[key]['type'],
+                                   'condition': modules[key]['condition'],
+                                   'mod': [], 
+                                   'coord': (x0, y0, 
+                                             x1, y1)})
+                disp_mod, id_mod = self.place_modules(modules[key], id_mod, disp_mod)
+            else:
+                # Display module
+                self.add_module(key,
+                                modules[key]['coordinates'][0][0],
+                                modules[key]['coordinates'][0][1])
+                id_mod.append(modules[key]['coordinates'][1])
+                connect = list(modules[key]['coordinates'][2].keys())
+                
+                # Connect modules
+                for p, parent in enumerate(modules[key]['parents']):
+                    if not (parent[:4] == 'loop'):
+                        parent_id = id_mod[np.where(np.array(disp_mod) == parent)[0][0]]
+                        out, ins = modules[key]['coordinates'][2][connect[p]].split('-')
+                        xout, yout, _ , _ = self.canvas.coords(out[0]+str(parent_id))
+                        xins, yins, _, _ =self.canvas.coords(ins[0]+str(id_mod[-1]))
+                        self.canvas.create_line(
+                                    xout + self.cr, 
+                                    yout + self.cr, 
+                                    xins + self.cr, 
+                                    yins + self.cr,
+                                    fill = "red", 
+                                    arrow = tk.LAST, 
+                                    tags = ('o'+str(parent_id), 
+                                          'o'+str(id_mod[-1]), modules[key]['coordinates'][2][connect[p]]))
+                        self.out_data.iloc[int(parent_id)][int(id_mod[-1])] = 1
+                        self.connections[int(id_mod[-1])][
+                            int(parent_id)] = out[0]+str(parent_id) + '-' + ins[0]+str(id_mod[-1])
+                        print(self.connections)
+                    else:
+                        self.loops[-1]['mod'].append(key)
+                disp_mod.append(key)
+        return disp_mod, id_mod
     
     def reset(self):
         
-        msg = messagebox.askyesnocancel(
-            'Info', 'Are you sure you want to reset the canvas?')
+        if not self.saved:
+            msg = messagebox.askyesnocancel(
+                'Info', 'Are you sure you want to reset the canvas?')
+        else:
+            msg = True
         if msg:
             self.canvas.delete(tk.ALL) # Reset canvas
             
@@ -717,59 +809,21 @@ def reset(self):
                 self.entry1.destroy()
             if hasattr(self, 'entry2'):
                 self.entry2.destroy()
-            
-            self.out_data = pd.DataFrame(np.zeros((2,2)), 
-                                         columns = ['Initialiser', 'Output'], 
-                                         index = ['Initialiser', 'Output'])
-            x0 = self.width/2 - (
-            self.controller.pages_font.measure('Initialiser')+10)/2
-            y0 = self.h
-            self.canvas.create_rectangle(x0, y0, x0 + self.w, y0 + self.h, 
-                                     tags = 'p0', fill = self.bg, width = 3,
-                                     activefill = '#dbaa21')
-            self.canvas.create_text(x0 + self.w/2, y0 + self.h/2, 
-                                    text = 'Initialiser', tags = 't0', 
-                                    fill = '#d0d4d9', 
-                                    font = self.controller.pages_font)
-            self.canvas.create_oval(
-                            x0 + self.w/2-self.cr, y0 + self.h-self.cr, 
-                            x0 + self.w/2+self.cr, y0 + self.h+self.cr, 
-                            width=2, fill = 'black', tags='d0')
-            self.canvas.tag_bind('d0', "<Button-1>", self.join_modules)
-            # Output module
-            h_out = self.height - 2*self.h
-            self.canvas.create_rectangle(x0, h_out, x0 + self.w, h_out + self.h, 
-                                         tags = ('p1'), 
-                                         fill = self.bg, width = 3,
-                                         activefill = '#dbaa21')
-            self.canvas.create_text(x0 + self.w/2, h_out + self.h/2, 
-                                    text = 'Output', tags = ('t1'), 
-                                    fill = '#d0d4d9', 
-                                    font = self.controller.pages_font)
-            self.canvas.create_oval(
-                            x0 + self.w/2 - self.cr, 
-                            h_out - self.cr, 
-                            x0 + self.w/2 + self.cr, 
-                            h_out  + self.cr,
-                            width=2, fill = 'black', tags = ('u1'))
-            self.canvas.tag_bind('u1', "<Button-1>", self.join_modules)
-            
-            self.draw = False
+                
             self.canvas.startxy = []
-            self.canvas.startxy.append((x0 + self.w/2, 
-                                        y0 + self.h/2))
-            self.canvas.startxy.append((x0 + self.w/2, 
-                                        h_out + self.h/2))
+            self.out_data = pd.DataFrame()
             self.connections = {}
-            self.connections[0] = {}
-            self.connections[1] = {}
-            self.module_list = ['Initialiser', 'Output']
-            self.module_names = ['Initialiser', 'Output']
+            self.modules = 0
+            self.module_list = []
+            self.module_names = []
+            
+            self.add_module('Initialiser', self.width/2, self.h, ini = True)
+            self.add_module('Output', self.width/2, self.height - self.h, out = True)
+        
+            self.draw = False
             self.loops = []
             self.drawLoop = False
-            self.modules = 2
             self.l = 0
-            self.saved = False
     
 if __name__ == "__main__":
     app = aidCanvas()
diff --git a/run_UI.py b/run_UI.py
index 96d421a7..7ad0792c 100644
--- a/run_UI.py
+++ b/run_UI.py
@@ -1,37 +1,6 @@
 import aidesign as ai
 
-test_cases = {
-    "manual": {
-                "class_list": ['Atelectasis',
-                                'Cardiomelagy',
-                                'Effusion',
-                                'Infiltration',
-                                'Mass',
-                                'Nodule',
-                                'Pneumonia',
-                                'Pneumothorax'
-                                ]
-                },
-    "canvas": {
-                "class_list":[['State_a', 'Action_a',], 
-                            ['State_x', 'State_y', 'Action_x', 'Action_y'], 
-                           ['State_x', 'State_y', 'Action_x', 'Action_y']]
-
-                },
-    "startpage": {
-                "class_list":[] # This is currently set by default in GUI_core.py (need to find a better method)
-                }
-}
-
-test_name = "aidcanvas"
-
-# ui_app = ai.GUI()
-# ui_app.plugin_name(test_name)
-# ui_app.set_class_list(test_cases[test_name]["class_list"])
-
-ui_app = ai.AID()
-ui_app.plugin_name(test_name)
-# ui_app.set_class_list(test_cases[test_name]["class_list"])
-
-ui_app.launch()
+core = ai.Core()
 
+# core.load_config_file("./resources/basic_operation.xml")
+core.launch()


Test patch:
diff --git a/aidesign/Settings/resources/test.xml b/aidesign/Settings/resources/test.xml
new file mode 100644
index 00000000..8f2fa7d5
--- /dev/null
+++ b/aidesign/Settings/resources/test.xml
@@ -0,0 +1,42 @@
+<Settings>
+    <pipeline>
+        <Initialiser name="Initialiser">
+            <plugin type="" />
+            <relationships>
+                <child name="User Feedback" />
+            </relationships>
+            <coordinates>
+                (300.0, 50)
+            </coordinates>
+        </Initialiser>
+        <UserFeedback name="UserFeedback">
+            <plugin type="manuald">
+                <class_list>
+                    test_1
+                    test_2
+                </class_list>
+                <class_list_2>
+                    test_1
+                    test_2
+                </class_list_2>
+            </plugin>
+            <relationships>
+                <parent name="Initialiser" />
+                <child name="Output" />
+            </relationships>
+            <coordinates>
+                (300.0, 300.0)
+            </coordinates>
+        </UserFeedback>
+        <Output name="Output">
+            <plugin type="" />
+            <relationships>
+                <parent name="User Feedback" />
+            </relationships>
+            <coordinates>
+                (300.0, 550)
+            </coordinates>
+        </Output>
+    </pipeline>
+    <datastructure />
+</Settings>
diff --git a/aidesign/utils/plugins/test.xml b/aidesign/utils/plugins/test.xml
index d856ee39..d84941e3 100644
--- a/aidesign/utils/plugins/test.xml
+++ b/aidesign/utils/plugins/test.xml
@@ -3,13 +3,13 @@
         <Initialiser name="Initialiser">
             <plugin type="" />
             <relationships>
-                <child name="Data proc" />
+                <child name="DataProcessing" />
             </relationships>
             <coordinates>
-                (314.5, 75.0)
+                (300.0, 50)
             </coordinates>
         </Initialiser>
-        <Data processing name="Data proc">
+        <DataProcessing name="DataProcessing">
             <plugin type="" />
             <relationships>
                 <parent name="Initialiser" />
@@ -18,14 +18,14 @@
             <coordinates>
                 (300.0, 300.0)
             </coordinates>
-        </Data processing>
+        </DataProcessing>
         <Output name="Output">
             <plugin type="" />
             <relationships>
-                <parent name="Data proc" />
+                <parent name="DataProcessing" />
             </relationships>
             <coordinates>
-                (314.5, 525.0)
+                (300.0, 550)
             </coordinates>
         </Output>
     </pipeline>
diff --git a/aidesign/utils/test.xml b/aidesign/utils/test.xml
new file mode 100644
index 00000000..66ee708f
--- /dev/null
+++ b/aidesign/utils/test.xml
@@ -0,0 +1,33 @@
+<Settings>
+    <pipeline>
+        <Initialiser name="Initialiser">
+            <plugin type="" />
+            <relationships>
+                <child name="UserFeedbackAdaptation-0" />
+            </relationships>
+            <coordinates>
+                (314.5, 75.0)
+            </coordinates>
+        </Initialiser>
+        <UserFeedbackAdaptation name="UserFeedbackAdaptation-0">
+            <plugin type="" />
+            <relationships>
+                <parent name="Initialiser" />
+                <child name="Output" />
+            </relationships>
+            <coordinates>
+                (300.0, 300.0)
+            </coordinates>
+        </UserFeedbackAdaptation>
+        <Output name="Output">
+            <plugin type="" />
+            <relationships>
+                <parent name="UserFeedbackAdaptation-0" />
+            </relationships>
+            <coordinates>
+                (314.5, 525.0)
+            </coordinates>
+        </Output>
+    </pipeline>
+    <datastructure />
+</Settings>
diff --git a/test 4.xml b/test 4.xml
new file mode 100644
index 00000000..32990034
--- /dev/null
+++ b/test 4.xml
@@ -0,0 +1,33 @@
+<Settings>
+    <pipeline>
+        <Initialiser name="Initialiser">
+            <plugin type="" />
+            <relationships>
+                <child name="DataProcessing" />
+            </relationships>
+            <coordinates>
+                [(300.0, 50), 0, {}]
+            </coordinates>
+        </Initialiser>
+        <DataProcessing name="DataProcessing">
+            <plugin type="" />
+            <relationships>
+                <parent name="Initialiser" />
+                <child name="Output" />
+            </relationships>
+            <coordinates>
+                [(300.0, 300.0), 2, {0: 'd0-u2'}]
+            </coordinates>
+        </DataProcessing>
+        <Output name="Output">
+            <plugin type="" />
+            <relationships>
+                <parent name="DataProcessing" />
+            </relationships>
+            <coordinates>
+                [(300.0, 550), 1, {2: 'd2-u1'}]
+            </coordinates>
+        </Output>
+    </pipeline>
+    <datastructure />
+</Settings>
diff --git a/test.xml b/test.xml
new file mode 100644
index 00000000..20060fb0
--- /dev/null
+++ b/test.xml
@@ -0,0 +1,43 @@
+<Settings>
+    <pipeline>
+        <Initialiser name="Initialiser">
+            <plugin type="" />
+            <relationships>
+                <child name="UserFeedback" />
+            </relationships>
+            <coordinates>
+                [(300.0, 50), 0, {}]
+            </coordinates>
+        </Initialiser>
+        <loop type="for" condition="100" name="loop0">
+            <relationships>
+                <child name="UserFeedback" />
+            </relationships>
+            <coordinates>
+                (158.0, 143.0, 443.0, 303.0)
+            </coordinates>
+            <relationships />
+            <UserFeedback name="UserFeedback">
+                <plugin type="" />
+                <relationships>
+                    <parent name="Initialiser" />
+                    <parent name="loop0" />
+                    <child name="Output" />
+                </relationships>
+                <coordinates>
+                    [(274, 218), 2, {0: 'd0-u2'}]
+                </coordinates>
+            </UserFeedback>
+        </loop>
+        <Output name="Output">
+            <plugin type="" />
+            <relationships>
+                <parent name="UserFeedback" />
+            </relationships>
+            <coordinates>
+                [(300.0, 550), 1, {2: 'd2-u1'}]
+            </coordinates>
+        </Output>
+    </pipeline>
+    <datastructure />
+</Settings>
diff --git a/test2.xml b/test2.xml
new file mode 100644
index 00000000..56f43644
--- /dev/null
+++ b/test2.xml
@@ -0,0 +1,43 @@
+<Settings>
+    <pipeline>
+        <Initialiser name="Initialiser">
+            <plugin type="" />
+            <relationships>
+                <child name="DataProcessing" />
+            </relationships>
+            <coordinates>
+                (300.0, 50)
+            </coordinates>
+        </Initialiser>
+        <loop type="For/While" condition="Condition" name="loop0">
+            <relationships>
+                <child name="DataProcessing" />
+            </relationships>
+            <coordinates>
+                (150.0, 197.0, 469.0, 418.0)
+            </coordinates>
+            <relationships />
+            <DataProcessing name="DataProcessing">
+                <plugin type="" />
+                <relationships>
+                    <parent name="Initialiser" />
+                    <parent name="loop0" />
+                    <child name="Output" />
+                </relationships>
+                <coordinates>
+                    (300.0, 300.0)
+                </coordinates>
+            </DataProcessing>
+        </loop>
+        <Output name="Output">
+            <plugin type="" />
+            <relationships>
+                <parent name="DataProcessing" />
+            </relationships>
+            <coordinates>
+                (300.0, 550)
+            </coordinates>
+        </Output>
+    </pipeline>
+    <datastructure />
+</Settings>
diff --git a/test3.xml b/test3.xml
new file mode 100644
index 00000000..a494d7a6
--- /dev/null
+++ b/test3.xml
@@ -0,0 +1,43 @@
+<Settings>
+    <pipeline>
+        <Initialiser name="Initialiser">
+            <plugin type="" />
+            <relationships>
+                <child name="DataProcessing" />
+            </relationships>
+            <coordinates>
+                [(300.0, 50), 0, {}]
+            </coordinates>
+        </Initialiser>
+        <DataProcessing name="DataProcessing">
+            <plugin type="" />
+            <relationships>
+                <parent name="Initialiser" />
+                <child name="Modelling" />
+            </relationships>
+            <coordinates>
+                [(221, 223), 2, {0: 'd0-u2'}]
+            </coordinates>
+        </DataProcessing>
+        <Modelling name="Modelling">
+            <plugin type="" />
+            <relationships>
+                <parent name="DataProcessing" />
+                <child name="Output" />
+            </relationships>
+            <coordinates>
+                [(310, 391), 3, {2: 'd2-u3'}]
+            </coordinates>
+        </Modelling>
+        <Output name="Output">
+            <plugin type="" />
+            <relationships>
+                <parent name="Modelling" />
+            </relationships>
+            <coordinates>
+                [(300.0, 550), 1, {3: 'd3-u1'}]
+            </coordinates>
+        </Output>
+    </pipeline>
+    <datastructure />
+</Settings>