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

Issue:
options now indexed by class names, not script names
# Purpose
In `plugin_helpers.py`, `optional_settings` now returns dict indexed by:

`optional_settings[<module-name>][<plugin-class-name>]`

Instead of previously:

`optional_settings[<module-name>][<plugin-script-name>]`

This imporoves accesibility from other functions and internal compatibility within `plugin_helpers.py`

## `pluginCanvas.py`

`pluginCanvas.py` no longer has to append `.py` to the class name, can get with only class name


Discussion / hints:

Fix patch:
diff --git a/aidesign/Core/AI_design_core.py b/aidesign/Core/AI_design_core.py
index 7079dac2..51552b2a 100644
--- a/aidesign/Core/AI_design_core.py
+++ b/aidesign/Core/AI_design_core.py
@@ -11,6 +11,7 @@ def __init__(self) -> None:
         self._avail_plugins = PluginSpecs()
         self.data = Data()
         self.loop_level = 0
+        self.setup_complete = False
 
     def launch(self):
         gui_app = GUI()
@@ -22,11 +23,11 @@ def launch(self):
                 self.load_config_file(gui_output["xml_filename"])
             except:
                 raise Exception("No XML File Selected. Cannot Run Pipeline")
-            self.run()
             self._load_data()
 
     def load_config_file(self, filename: str):
         self._xml_handler.load_XML(filename)
+        self.setup_complete = True
 
     def _load_data(self):
         init_data_fn = self._xml_handler.data_to_load
@@ -48,6 +49,7 @@ def _execute_module(self, specs):
                 + "processing..."
               )
         mod.launch()
+        self.data = mod.get_result()
 
     def _execute_loop(self, specs):
         try:
@@ -100,6 +102,10 @@ def _execute(self, specs):
                 specs[key]["class"]))(specs[key])
 
     def run(self):
+        if not self.setup_complete:
+            print("No pipeline specified. Running GUI.")
+            print("To load existing config, run core.load_config_file(<path_to_file>)")
+            self.launch()
         print("Running pipeline...")
         self._load_data()
         self._execute(self._xml_handler.loaded_modules)
diff --git a/aidesign/Data/Data_core.py b/aidesign/Data/Data_core.py
index 11b8728f..c751ca6a 100644
--- a/aidesign/Data/Data_core.py
+++ b/aidesign/Data/Data_core.py
@@ -94,7 +94,7 @@ def import_data(self,
         :param filename: str, filename of file to be loaded
         :param data_name: str, name of class variable data will be loaded to
         """
-        filename = self._rel_to_abs(filename)
+        filename = self._rel_to_abs(filename).replace("\\","/").replace("/",path.sep)
         ext = self._get_ext(filename)
         getattr(self,"_import_{0}".format(ext))(filename,data_name)
 
diff --git a/aidesign/Data/resources/supervised_classification/X_tr.csv b/aidesign/Data/resources/supervised_classification/X_tr.csv
new file mode 100644
index 00000000..a2f4ca8e
--- /dev/null
+++ b/aidesign/Data/resources/supervised_classification/X_tr.csv
@@ -0,0 +1,61 @@
+0,1,2,3,4
+0.6536019370953916,0.9728611306253917,0.17524259972508305,0.18495499862508935,0.20917819714520425
+0.6822355342389689,0.2945577814811787,0.7920793575410713,0.12780822690155846,0.17777277066620234
+0.8716346000701825,0.22396791686945083,0.0032909677599025633,0.7228073174169739,0.8345411202386198
+0.4467440220733788,0.00918675352204934,0.14351400964918504,0.5594361872310152,0.2837398973306018
+0.6308677021723542,0.9444347398222249,0.422981233259484,0.2862148967645676,0.635642612158541
+0.052830818704745264,0.4516291570792007,0.9928156791841022,0.7772533474083275,0.7509851001616825
+0.35159402885742197,0.8504855294935274,0.13157592550398944,0.5214221064007344,0.49645777110743383
+0.9295057610967805,0.7886235545752507,0.6552694960838137,0.5855904152865905,0.3096496073698678
+0.917204667480473,0.9702230131143266,0.20197614218529414,0.44872708032153175,0.8292163342494835
+0.7032905794804512,0.38123319002093403,0.15553900942534105,0.037414229760949214,0.7406224340502487
+0.5227029186804006,0.8852847610081667,0.6048923966179319,0.8984854265338973,0.4146847870252637
+0.8007947687734577,0.4186129281819193,0.6641726155271486,0.4945688613954874,0.7816404884072576
+0.21177659840817586,0.7475925454881504,0.7187658829456796,0.8660875926161647,0.44564237983521326
+0.7467366270388991,0.9365402232072291,0.7156193012846394,0.23963722078760064,0.3255205043696934
+0.005340356823446024,0.9018533597645824,0.3231241692389266,0.3896847453386235,0.48784324449520045
+0.419825979303288,0.9779365574291123,0.9789774806488478,0.4257102733647684,0.23998947841735274
+0.5983254002708412,0.9880249144205878,0.9508393030474156,0.9312084755396137,0.5832199435602291
+0.26665268075456505,0.7009351252276785,0.009027705477179904,0.6976967159323533,0.2759501653853589
+0.6887549342472856,0.19445006997681014,0.6511800326983455,0.3796240957112793,0.7607690758712645
+0.41667242114874337,0.9166371343827378,0.7872102441791128,0.5040847842276944,0.3266766172433765
+0.9164493489749754,0.29017757917143394,0.2614658911096309,0.6832285337739815,0.6061500519433838
+0.6363967135754184,0.04557642885459812,0.5189159313915994,0.9672639123423037,0.7421489421289109
+0.9696701052854241,0.21835383238632533,0.4340747573852445,0.9976295691516174,0.12238557267629835
+0.17635355495598848,0.7595999193170172,0.8102588331557556,0.3658263032386435,0.73433845306914
+0.726220221253069,0.8936144267084628,0.14147182928366586,0.15569546465556028,0.8198061397008658
+0.11020646653780464,0.7780448245901943,0.9709726854681705,0.3449122405666696,0.11295630581426308
+0.3114942716403354,0.8093079906279457,0.11611266242601959,0.38704652203757905,0.867925176283113
+0.17142167957802568,0.5235691041321331,0.5769526265223044,0.10577735225502438,0.23230943311939856
+0.1239163120160427,0.10551061776874282,0.25957545491396816,0.04909653495316846,0.739172645590461
+0.5472798140631929,0.922541998908177,0.6469807481994894,0.07267340684077539,0.9224934531723241
+0.5884067254323148,0.8319472201725835,0.5633734328759349,0.909910123020257,0.24293427869527606
+0.8088287770999582,0.4116359256050549,0.02717727506869183,0.009350728048297507,0.42559939784902345
+0.3678052247012329,0.24981660726656285,0.24244905722430088,0.17496781241088444,0.08060056143802752
+0.4978691806599872,0.89604582216366,0.054452119062950444,0.6898481120375053,0.9498380233048921
+0.6471714154757048,0.6735785120733329,0.3159087553261354,0.7288516108328276,0.6829174763382054
+0.8867409249480241,0.6515337899850854,0.022618731982358864,0.951535679406901,0.7656720333176243
+0.37047277608458806,0.8394096554369264,0.602559238327118,0.13196263560787758,0.6499269668622849
+0.07967866115566147,0.10759814573319726,0.049968600911051375,0.708487446471882,0.969145933029716
+0.3964844977626385,0.020915056802688237,0.6484980211981639,0.848046094999906,0.7402454187831985
+0.8525573746691598,0.09513311342369313,0.4973297368175452,0.07652219846929609,0.22787736368966705
+0.652217826510086,0.4108222731097064,0.6401622153883482,0.9356292384980522,0.16560737375275125
+0.6626668463036949,0.6971912016368984,0.955811506356,0.9737552274233017,0.7566545543909897
+0.6361648671035246,0.2615314832273056,0.8929144275232277,0.6539205332486135,0.28237401456427214
+0.5505707548528586,0.45343661318571593,0.804056220460837,0.32571661787118555,0.19817906497887416
+0.6738177755770295,0.21836152076034765,0.7103489234345763,0.7236066251856692,0.5136627687167891
+0.41894704190263654,0.5454549453227764,0.23033929599915848,0.5744738654205602,0.6324114863018893
+0.44811734999910124,0.32082414932567216,0.2485993167868581,0.3517651184489672,0.7646805433742703
+0.5197374341722053,0.7445240398609495,0.2175108246502815,0.1940612729996053,0.9419764603209504
+0.6198740677487199,0.27753801703516956,0.4839217471710566,0.7108601648714059,0.2751326507755705
+0.516548628662574,0.04565017655853709,0.4947687434962401,0.9553466157126757,0.7059949688231191
+0.190055270692419,0.15141531334473335,0.024753488829917347,0.08554609836231841,0.025231377568907254
+0.8299182031842655,0.05138305291351741,0.6643009506401021,0.8242658462262936,0.3830740364045674
+0.652030441156883,0.9391338932101979,0.31908498843491095,0.8744213304791586,0.5572722662386898
+0.26225594837858046,0.587391166860739,0.7225295276172042,0.59781440950453,0.715055102672961
+0.055864603742443064,0.05837737344756877,0.32830234664477687,0.8886685496888791,0.025247428244047465
+0.7006548974047545,0.4831911284432048,0.96537215611488,0.434611287152468,0.7547279246459913
+0.8276440444081429,0.02453381873163707,0.2217357735777774,0.811201380147679,0.539212845742977
+0.5832183202866177,0.7256350158883857,0.30812562686786915,0.9981251861098729,0.18874544799825677
+0.06647912455498628,0.8455212620920155,0.8802797790651201,0.8701264864976667,0.4704917097948764
+0.15857791551493972,0.6287528366645633,0.7041615357812132,0.21930038544219854,0.861520520674795
diff --git a/aidesign/Data/resources/supervised_classification/X_tst.csv b/aidesign/Data/resources/supervised_classification/X_tst.csv
new file mode 100644
index 00000000..d512c3b4
--- /dev/null
+++ b/aidesign/Data/resources/supervised_classification/X_tst.csv
@@ -0,0 +1,41 @@
+0,1,2,3,4
+0.8694769564024892,0.0009726408928109054,0.23509342889812956,0.8911338790714547,0.244858036324617
+0.16739013645016543,0.17785135243257588,0.1933356715170922,0.5369879917432954,0.9281233787396428
+0.6659420235807892,0.9873668338965017,0.5388932224170667,0.8446797889510411,0.35159317477608343
+0.6435376451684249,0.21974164424689424,0.7307230735174768,0.03692372517598852,0.9714521363400641
+0.9426542133095585,0.37829399685922915,0.16138353669908956,0.10390531510609191,0.3352840156035575
+0.14113821378663172,0.13598333231372062,0.6394075374727592,0.04344642796236842,0.6046895622870296
+0.5680318730217726,0.39883381835295595,0.9149404358826491,0.8404370174529772,0.7720468365231054
+0.43103385195600685,0.09852015578189677,0.02219522922236783,0.4641092253575312,0.6936290518259507
+0.25571501160349663,0.25750916237628696,0.1396978144968839,0.3221146427585948,0.11455555310879728
+0.4925866983385505,0.8944253082254187,0.5782364014837609,0.3113445168916932,0.11586393010056828
+0.1005272345200614,0.43718929555108266,0.6312025863994316,0.6911037525046704,0.9760336984022525
+0.10089005072430468,0.5569055000141364,0.7037582559674518,0.5132776008701839,0.08500248893354911
+0.9555837439872688,0.6259484583758211,0.6132678961001181,0.17503551998818678,0.808095910491738
+0.6343269928348277,0.4985801398064872,0.43364524900834445,0.8607475570684465,0.939187482565455
+0.7459998789387081,0.4872526849764268,0.7527601782552182,0.5432590729874157,0.2385526687575874
+0.3780362403598079,0.3964149230042776,0.27988742048418735,0.3271421582489429,0.24711674864636624
+0.11817282784698291,0.3462617365471081,0.7718492981669433,0.36290205556766786,0.49762816380503117
+0.9477103177622708,0.22308903702075178,0.5250748290564365,0.7637286645795974,0.6330082680515066
+0.84966866374881,0.46821352089959245,0.4616323605963153,0.21781506638088566,0.9404017765192493
+0.9693136999599169,0.22661877602439373,0.26241780847911755,0.30761187079989905,0.49186655319522865
+0.2441792495377244,0.8711546436169147,0.2312656600877404,0.6754105999298518,0.8626575510255353
+0.9243295865008301,0.8521090920676715,0.7075340820717908,0.48267759732505766,0.8276557996018271
+0.5487977302170436,0.050476883733371,0.503118060637043,0.18360604970018413,0.2909818577044385
+0.9224737015391291,0.763591835465188,0.1821832529330113,0.7451839041089814,0.318891072046775
+0.05985785919644582,0.9742811634231489,0.3180822491728612,0.897954373600423,0.9040194362128092
+0.62991088070221,0.5477595167167326,0.5437099503159423,0.742986753289712,0.24637103416511041
+0.722224757078667,0.8530492201330577,0.9435743178897503,0.36093517720647816,0.013819540672826314
+0.824585371395999,0.8533500125816227,0.09516953951499563,0.17648893159669143,0.29718452355533054
+0.07029933942841338,0.8513138106431989,0.7933928461351509,0.7130272718872805,0.6610303132958693
+0.5531919368458856,0.28557699522481705,0.33173611554415505,0.687558920183451,0.8600860269945048
+0.5033490813796557,0.12586511833788006,0.16828944072903207,0.27373378317223285,0.46845675831775213
+0.006274734697756079,0.20467100077673284,0.6258672307667318,0.027279299140743674,0.27330700047989154
+0.31247932503219866,0.08039717205465013,0.2988088256616943,0.1294513041461759,0.7191801594772681
+0.8978180657548572,0.6580657019032198,0.48313292242923833,0.7065754727769232,0.816325826475062
+0.26133894426558324,0.09832772773974308,0.3818265694555618,0.36400723213867203,0.9303629000527129
+0.41711520495255694,0.8470315855366058,0.8974131072664592,0.1935979909977371,0.6347471824524404
+0.46802414350990706,0.22925540600363625,0.8664641710703116,0.8654738419680906,0.038327128667975385
+0.4052683409613861,0.07477323558071447,0.4594854505845135,0.044327502158329035,0.8677797478543303
+0.6543143230629697,0.3993167835294765,0.54816140697107,0.9630429948315153,0.758048785446698
+0.04437984640945425,0.16740777743591573,0.7537233383763147,0.32376212278429173,0.9027137266112322
diff --git a/aidesign/Data/resources/supervised_classification/Y_tr.csv b/aidesign/Data/resources/supervised_classification/Y_tr.csv
new file mode 100644
index 00000000..ad200938
--- /dev/null
+++ b/aidesign/Data/resources/supervised_classification/Y_tr.csv
@@ -0,0 +1,61 @@
+0
+0
+1
+1
+1
+1
+0
+1
+1
+1
+0
+1
+0
+1
+0
+1
+1
+1
+0
+0
+1
+1
+1
+1
+0
+1
+1
+1
+1
+1
+1
+1
+0
+1
+1
+0
+0
+0
+0
+0
+1
+1
+1
+1
+1
+1
+0
+1
+0
+0
+0
+1
+1
+1
+0
+1
+1
+1
+1
+0
+1
diff --git a/aidesign/Data/resources/supervised_classification/Y_tst.csv b/aidesign/Data/resources/supervised_classification/Y_tst.csv
new file mode 100644
index 00000000..83a29109
--- /dev/null
+++ b/aidesign/Data/resources/supervised_classification/Y_tst.csv
@@ -0,0 +1,41 @@
+0
+1
+1
+0
+1
+1
+0
+1
+0
+0
+0
+0
+0
+1
+1
+1
+0
+0
+1
+0
+0
+1
+0
+0
+0
+0
+1
+1
+1
+1
+1
+0
+1
+1
+1
+1
+1
+1
+1
+1
+1
diff --git a/aidesign/Data/resources/supervised_classification/randomInputData.mat b/aidesign/Data/resources/supervised_classification/randomInputData.mat
new file mode 100644
index 00000000..27255533
Binary files /dev/null and b/aidesign/Data/resources/supervised_classification/randomInputData.mat differ
diff --git a/aidesign/Data/resources/supervised_classification/randomInputData.py b/aidesign/Data/resources/supervised_classification/randomInputData.py
new file mode 100644
index 00000000..90c6a88f
--- /dev/null
+++ b/aidesign/Data/resources/supervised_classification/randomInputData.py
@@ -0,0 +1,25 @@
+import numpy as np
+import pandas as pd
+from pathlib import Path  
+
+N = 100
+D = 5
+
+X = np.random.rand(N, D)
+Y = ((X @ np.random.rand(D, 1) + np.random.normal(0, 2, (N, 1))) > 0).astype(int)
+
+X_tr, Y_tr = pd.DataFrame(X[:int(N*0.6),:]), pd.DataFrame(Y[:int(N*0.6)])
+X_tst, Y_tst = pd.DataFrame(X[int(N*0.6):,:]), pd.DataFrame(Y[int(N*0.6):])
+
+filepath = Path('X_tr.csv')
+filepath.parent.mkdir(parents=True, exist_ok=True)
+X_tr.to_csv(filepath, index = False)
+filepath = Path('Y_tr.csv')
+filepath.parent.mkdir(parents=True, exist_ok=True)
+Y_tr.to_csv(filepath, index = False)
+filepath = Path('X_tst.csv')
+filepath.parent.mkdir(parents=True, exist_ok=True)
+X_tst.to_csv(filepath, index = False)
+filepath = Path('Y_tst.csv')
+filepath.parent.mkdir(parents=True, exist_ok=True)
+Y_tst.to_csv(filepath, index = False)
\ No newline at end of file
diff --git a/aidesign/Data/xml_handler.py b/aidesign/Data/xml_handler.py
index edc14dfa..14117cb3 100644
--- a/aidesign/Data/xml_handler.py
+++ b/aidesign/Data/xml_handler.py
@@ -433,7 +433,20 @@ def _add_plugin_options(self,
     def append_input_data(self,
                             data_name: str,
                             data_dir: str,
-                            xml_parent: ET.Element or str = "Initialiser"):
+                            xml_parent: ET.Element or str = "Initialiser",
+                            save_dir_as_relative: bool = True):
+        """Appened path to input datafile. Replaces windows backslash
+
+        :param plugin_type: string type of plugin to be loaded into module
+        :param plugin_options: dict where keys & values are options & values
+        :param xml_parent: dict OR str. 
+                            If string given, parent elem is found via search,
+                            Otherwise, plugin appeneded directly
+        :param save_dir_as_relative: bool. If True [default], attempts to 
+                            replace the library base path in the absolute
+                            filename with "./" to make it relative to library
+                            path. Recommended.
+        """
         if isinstance(xml_parent, str):
             xml_parent = self._get_element_from_name(xml_parent)
 
@@ -443,6 +456,9 @@ def append_input_data(self,
             input_data_elem = ET.SubElement(xml_parent, "inputdata")
             
         plugin_elem = ET.SubElement(input_data_elem, data_name)
+        if save_dir_as_relative:
+            data_dir = data_dir.replace(self.lib_base_path,"./")
+        data_dir = data_dir.replace("\\","/")
         plugin_elem.set('file', data_dir)
 
     def append_plugin_to_module(self,
diff --git a/aidesign/DataProcessing/DataProcessing_core.py b/aidesign/DataProcessing/DataProcessing_core.py
new file mode 100644
index 00000000..c3e2f6a6
--- /dev/null
+++ b/aidesign/DataProcessing/DataProcessing_core.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+from aidesign.utils.import_helper import import_plugin_absolute
+from copy import copy
+class DataProcessing(object):
+    def __init__(self):
+        self.output_data = None
+
+    def set_avail_plugins(self,avail_plugins):
+        self._avail_plugins = avail_plugins
+
+    def set_data_in(self,data_in):
+        self._data_in = data_in
+
+    def _load_plugin(self, plugin_name:str):
+        avail_plugins = self._avail_plugins.find_from_readable_name(plugin_name)
+        self._plugin_name = plugin_name
+        self._plugin = import_plugin_absolute(globals(),\
+                                avail_plugins["_PLUGIN_PACKAGE"],\
+                                avail_plugins["_PLUGIN_CLASS_NAME"])\
+                                .__call__()
+
+    def set_options(self, module_config: dict):
+        """Send configuration arguments to plugin
+
+        :param module_config: dict of settings to configure the plugin
+        """
+        self._module_config = module_config
+        self._load_plugin(self._module_config["plugin"]["plugin_name"])
+
+    def launch(self):
+        self._plugin.set_data_in(self._data_in)
+        self._plugin.configure(self._module_config["plugin"])
+        self._plugin.fit()
+        self.output_data = copy(self._data_in)
+        self.output_data = self._plugin.transform(self.output_data)
+
+    def get_result(self):
+        return self.output_data
diff --git a/aidesign/DataProcessing/Modelling_core.py b/aidesign/DataProcessing/Modelling_core.py
deleted file mode 100644
index 97a81d24..00000000
--- a/aidesign/DataProcessing/Modelling_core.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# -*- coding: utf-8 -*-
-class Modelling(object):
-    def __init__(self):
-        self.node_name = None
-        self.plugin_name = None
-        self.output_data = None
-
-    def set_input_data(self, data):
-        self.input_data = data
-
-    def set_target_data(self, data):
-        self.target_data = data
-
-    def _load_plugin(self):
-        pass
-
-    def set_plugin_name(self, plugin_name):
-        self.plugin_name = plugin_name
-        self._load_plugin()
-
-    def set_options(self, options):
-        self.options = options
-
-    def solve(self):
-        # send the input data to the modelling plugin and assign it to an output property
-        pass
-
-    def get_result(self):
-        return self.output_data
diff --git a/aidesign/DataProcessing/__init__.py b/aidesign/DataProcessing/__init__.py
index 99e78a66..147c2577 100644
--- a/aidesign/DataProcessing/__init__.py
+++ b/aidesign/DataProcessing/__init__.py
@@ -4,4 +4,4 @@
 
 Let's look for a more automatic way of doing this when we have more modules
 """
-from .Modelling_core import Modelling
+# from .DataProcessing_core import DataProcessing
diff --git a/aidesign/DataProcessing/plugins/binarizer.py b/aidesign/DataProcessing/plugins/binarizer.py
new file mode 100644
index 00000000..929466dd
--- /dev/null
+++ b/aidesign/DataProcessing/plugins/binarizer.py
@@ -0,0 +1,77 @@
+from sklearn.preprocessing import Binarizer as model
+import numpy as np
+import pandas as pd
+
+_PLUGIN_READABLE_NAMES = {"Binarizer":"default","binarizer":"alias"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "encoder"}
+_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}
+_PLUGIN_OPTIONAL_SETTINGS = {"threshold": "float"}
+_PLUGIN_REQUIRED_DATA = {}
+_PLUGIN_OPTIONAL_DATA = {"X","Y","X_tst", 'Y_tst'}
+
+class Binarizer(object):
+    """
+    Binarize data (set feature values to 0 or 1) according to a threshold
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.proc = model()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(Binarizer) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        if self._config["options"]["Data"] == 'X':
+            self.X  = self._data_in["X"]
+            self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        elif self._config["options"]["Data"] == 'Y':
+            self.X  = self._data_in["Y"]
+            self.X_tst = self._is_name_passed(self._data_in, "Y_test")
+        else:
+            print('Invalid Data name. Indicate whether to use `X` or `Y`')
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+
+    def _check_numeric(self, dict_opt):
+        new_dict = {}
+        for key, val in dict_opt.items():
+            """ 
+            TODO: Maybe, if list -> cv
+            """
+            if key != 'Data':
+                if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                    val = float(val)
+                    if val.is_integer():
+                        val = int(val)
+                new_dict[key] = val
+        return new_dict
+
+    def fit(self):
+        config = self._check_numeric(self._config["options"])
+        self.proc.set_params(**config)
+        self.proc.fit(self.X)
+
+    def transform(self,data):
+        data.append_data_column("X", pd.DataFrame(self.proc.transform(self.X)))
+        if self.X_tst is not None:
+            data.append_data_column("X_test", pd.DataFrame(self.proc.transform(self.X_tst)))
+        return data
\ No newline at end of file
diff --git a/aidesign/DataProcessing/plugins/kbinsdiscretizer.py b/aidesign/DataProcessing/plugins/kbinsdiscretizer.py
new file mode 100644
index 00000000..456d1405
--- /dev/null
+++ b/aidesign/DataProcessing/plugins/kbinsdiscretizer.py
@@ -0,0 +1,77 @@
+from sklearn.preprocessing import KBinsDiscretizer as model
+import numpy as np
+import pandas as pd
+
+_PLUGIN_READABLE_NAMES = {"KBinsDiscretizer":"default"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "encoder"}
+_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}
+_PLUGIN_OPTIONAL_SETTINGS = {"n_bins": "int"}
+_PLUGIN_REQUIRED_DATA = {}
+_PLUGIN_OPTIONAL_DATA = {"X","Y","X_tst", 'Y_tst'}
+
+class KBinsDiscretizer(object):
+    """
+    Bin continuous data into interval
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.proc = model()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(KBinsDiscretizer) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        if self._config["options"]["Data"] == 'X':
+            self.X  = self._data_in["X"]
+            self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        elif self._config["options"]["Data"] == 'Y':
+            self.X  = self._data_in["Y"]
+            self.X_tst = self._is_name_passed(self._data_in, "Y_test")
+        else:
+            print('Invalid Data name. Indicate whether to use `X` or `Y`')
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+
+    def _check_numeric(self, dict_opt):
+        new_dict = {}
+        for key, val in dict_opt.items():
+            """ 
+            TODO: Maybe, if list -> cv
+            """
+            if key != 'Data':
+                if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                    val = float(val)
+                    if val.is_integer():
+                        val = int(val)
+                new_dict[key] = val
+        return new_dict
+
+    def fit(self):
+        config = self._check_numeric(self._config["options"])
+        self.proc.set_params(**config)
+        self.proc.fit(self.X)
+
+    def transform(self,data):
+        data.append_data_column("X", pd.DataFrame(self.proc.transform(self.X)))
+        if self.X_tst is not None:
+            data.append_data_column("X_test", pd.DataFrame(self.proc.transform(self.X_tst)))
+        return data
\ No newline at end of file
diff --git a/aidesign/DataProcessing/plugins/labelbinarizer.py b/aidesign/DataProcessing/plugins/labelbinarizer.py
new file mode 100644
index 00000000..ae337aa8
--- /dev/null
+++ b/aidesign/DataProcessing/plugins/labelbinarizer.py
@@ -0,0 +1,77 @@
+from sklearn.preprocessing import LabelBinarizer as model
+import numpy as np
+import pandas as pd
+
+_PLUGIN_READABLE_NAMES = {"LabelBinarizer":"default"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "encoder"}
+_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}
+_PLUGIN_OPTIONAL_SETTINGS = {"neg_label": "int", "pos_label": "int"}
+_PLUGIN_REQUIRED_DATA = {}
+_PLUGIN_OPTIONAL_DATA = {"X","Y","X_tst", 'Y_tst'}
+
+class LabelBinarizer(object):
+    """
+    Binarize labels in a one-vs-all fashion
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.proc = model()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(LabelBinarizer) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        if self._config["options"]["Data"] == 'X':
+            self.X  = self._data_in["X"]
+            self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        elif self._config["options"]["Data"] == 'Y':
+            self.X  = self._data_in["Y"]
+            self.X_tst = self._is_name_passed(self._data_in, "Y_test")
+        else:
+            print('Invalid Data name. Indicate whether to use `X` or `Y`')
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+
+    def _check_numeric(self, dict_opt):
+        new_dict = {}
+        for key, val in dict_opt.items():
+            """ 
+            TODO: Maybe, if list -> cv
+            """
+            if key != 'Data':
+                if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                    val = float(val)
+                    if val.is_integer():
+                        val = int(val)
+                new_dict[key] = val
+        return new_dict
+
+    def fit(self):
+        config = self._check_numeric(self._config["options"])
+        self.proc.set_params(**config)
+        self.proc.fit(self.X)
+
+    def transform(self,data):
+        data.append_data_column("X", pd.DataFrame(self.proc.transform(self.X)))
+        if self.X_tst is not None:
+            data.append_data_column("X_test", pd.DataFrame(self.proc.transform(self.X_tst)))
+        return data
\ No newline at end of file
diff --git a/aidesign/DataProcessing/plugins/labelencoder.py b/aidesign/DataProcessing/plugins/labelencoder.py
new file mode 100644
index 00000000..c800a97c
--- /dev/null
+++ b/aidesign/DataProcessing/plugins/labelencoder.py
@@ -0,0 +1,77 @@
+from sklearn.preprocessing import LabelEncoder as model
+import numpy as np
+import pandas as pd
+
+_PLUGIN_READABLE_NAMES = {"LabelEncoder":"default","LE":"alias"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "encoder"}
+_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}
+_PLUGIN_OPTIONAL_SETTINGS = {}
+_PLUGIN_REQUIRED_DATA = {}
+_PLUGIN_OPTIONAL_DATA = {"X","Y","X_tst", 'Y_tst'}
+
+class LabelEncoder(object):
+    """
+    Encode target labels with value between 0 and n_classes-1
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.proc = model()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(LabelEncoder) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        if self._config["options"]["Data"] == 'X':
+            self.X  = self._data_in["X"]
+            self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        elif self._config["options"]["Data"] == 'Y':
+            self.X  = self._data_in["Y"]
+            self.X_tst = self._is_name_passed(self._data_in, "Y_test")
+        else:
+            print('Invalid Data name. Indicate whether to use `X` or `Y`')
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+
+    def _check_numeric(self, dict_opt):
+        new_dict = {}
+        for key, val in dict_opt.items():
+            """ 
+            TODO: Maybe, if list -> cv
+            """
+            if key != 'Data':
+                if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                    val = float(val)
+                    if val.is_integer():
+                        val = int(val)
+                new_dict[key] = val
+        return new_dict
+
+    def fit(self):
+        config = self._check_numeric(self._config["options"])
+        self.proc.set_params(**config)
+        self.proc.fit(self.X)
+
+    def transform(self,data):
+        data.append_data_column("X", pd.DataFrame(self.proc.transform(self.X)))
+        if self.X_tst is not None:
+            data.append_data_column("X_test", pd.DataFrame(self.proc.transform(self.X_tst)))
+        return data
\ No newline at end of file
diff --git a/aidesign/DataProcessing/plugins/maxabsscaler.py b/aidesign/DataProcessing/plugins/maxabsscaler.py
new file mode 100644
index 00000000..cb1d091a
--- /dev/null
+++ b/aidesign/DataProcessing/plugins/maxabsscaler.py
@@ -0,0 +1,77 @@
+from sklearn.preprocessing import MaxAbsScaler as model
+import numpy as np
+import pandas as pd
+
+_PLUGIN_READABLE_NAMES = {"MaxAbsScaler":"default"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "scaler"}
+_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}
+_PLUGIN_OPTIONAL_SETTINGS = {}
+_PLUGIN_REQUIRED_DATA = {}
+_PLUGIN_OPTIONAL_DATA = {"X","Y","X_tst", 'Y_tst'}
+
+class MaxAbsScaler(object):
+    """
+    Scale each feature by its maximum absolute value
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.proc = model()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(MaxAbsScaler) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        if self._config["options"]["Data"] == 'X':
+            self.X  = self._data_in["X"]
+            self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        elif self._config["options"]["Data"] == 'Y':
+            self.X  = self._data_in["Y"]
+            self.X_tst = self._is_name_passed(self._data_in, "Y_test")
+        else:
+            print('Invalid Data name. Indicate whether to use `X` or `Y`')
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+
+    def _check_numeric(self, dict_opt):
+        new_dict = {}
+        for key, val in dict_opt.items():
+            """ 
+            TODO: Maybe, if list -> cv
+            """
+            if key != 'Data':
+                if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                    val = float(val)
+                    if val.is_integer():
+                        val = int(val)
+                new_dict[key] = val
+        return new_dict
+
+    def fit(self):
+        config = self._check_numeric(self._config["options"])
+        self.proc.set_params(**config)
+        self.proc.fit(self.X)
+
+    def transform(self,data):
+        data.append_data_column("X", pd.DataFrame(self.proc.transform(self.X)))
+        if self.X_tst is not None:
+            data.append_data_column("X_test", pd.DataFrame(self.proc.transform(self.X_tst)))
+        return data
\ No newline at end of file
diff --git a/aidesign/DataProcessing/plugins/minmaxscaler.py b/aidesign/DataProcessing/plugins/minmaxscaler.py
new file mode 100644
index 00000000..62bc9a8d
--- /dev/null
+++ b/aidesign/DataProcessing/plugins/minmaxscaler.py
@@ -0,0 +1,78 @@
+from sklearn.preprocessing import MinMaxScaler as model
+import numpy as np
+import pandas as pd
+
+_PLUGIN_READABLE_NAMES = {"MinMaxScaler":"default"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "scaler"}
+_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}
+_PLUGIN_OPTIONAL_SETTINGS = {"feature_range": "tuple"}
+_PLUGIN_REQUIRED_DATA = {}
+_PLUGIN_OPTIONAL_DATA = {"X","Y","X_tst", 'Y_tst'}
+
+class MinMaxScaler(object):
+    """
+    This estimator scales and translates each feature individually such that it\n
+    is in the given range on the training set, e.g. between zero and one
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.proc = model()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(MinMaxScaler) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        if self._config["options"]["Data"] == 'X':
+            self.X  = self._data_in["X"]
+            self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        elif self._config["options"]["Data"] == 'Y':
+            self.X  = self._data_in["Y"]
+            self.X_tst = self._is_name_passed(self._data_in, "Y_test")
+        else:
+            print('Invalid Data name. Indicate whether to use `X` or `Y`')
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+
+    def _check_numeric(self, dict_opt):
+        new_dict = {}
+        for key, val in dict_opt.items():
+            """ 
+            TODO: Maybe, if list -> cv
+            """
+            if key != 'Data':
+                if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                    val = float(val)
+                    if val.is_integer():
+                        val = int(val)
+                new_dict[key] = val
+        return new_dict
+
+    def fit(self):
+        config = self._check_numeric(self._config["options"])
+        self.proc.set_params(**config)
+        self.proc.fit(self.X)
+
+    def transform(self,data):
+        data.append_data_column("X", pd.DataFrame(self.proc.transform(self.X)))
+        if self.X_tst is not None:
+            data.append_data_column("X_test", pd.DataFrame(self.proc.transform(self.X_tst)))
+        return data
\ No newline at end of file
diff --git a/aidesign/DataProcessing/plugins/multilabelbinarizer.py b/aidesign/DataProcessing/plugins/multilabelbinarizer.py
new file mode 100644
index 00000000..f9a3c2d0
--- /dev/null
+++ b/aidesign/DataProcessing/plugins/multilabelbinarizer.py
@@ -0,0 +1,77 @@
+from sklearn.preprocessing import MultiLabelBinarizer as model
+import numpy as np
+import pandas as pd
+
+_PLUGIN_READABLE_NAMES = {"MultiLabelBinarizer":"default"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "encoder"}
+_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}
+_PLUGIN_OPTIONAL_SETTINGS = {"classes": "array-like"}
+_PLUGIN_REQUIRED_DATA = {}
+_PLUGIN_OPTIONAL_DATA = {"X","Y","X_tst", 'Y_tst'}
+
+class MultiLabelBinarizer(object):
+    """
+    Transform between iterable of iterables and a multilabel format
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.proc = model()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(MultiLabelBinarizer) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        if self._config["options"]["Data"] == 'X':
+            self.X  = self._data_in["X"]
+            self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        elif self._config["options"]["Data"] == 'Y':
+            self.X  = self._data_in["Y"]
+            self.X_tst = self._is_name_passed(self._data_in, "Y_test")
+        else:
+            print('Invalid Data name. Indicate whether to use `X` or `Y`')
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+
+    def _check_numeric(self, dict_opt):
+        new_dict = {}
+        for key, val in dict_opt.items():
+            """ 
+            TODO: Maybe, if list -> cv
+            """
+            if key != 'Data':
+                if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                    val = float(val)
+                    if val.is_integer():
+                        val = int(val)
+                new_dict[key] = val
+        return new_dict
+
+    def fit(self):
+        config = self._check_numeric(self._config["options"])
+        self.proc.set_params(**config)
+        self.proc.fit(self.X)
+
+    def transform(self,data):
+        data.append_data_column("X", pd.DataFrame(self.proc.transform(self.X)))
+        if self.X_tst is not None:
+            data.append_data_column("X_test", pd.DataFrame(self.proc.transform(self.X_tst)))
+        return data
\ No newline at end of file
diff --git a/aidesign/DataProcessing/plugins/normalizer.py b/aidesign/DataProcessing/plugins/normalizer.py
new file mode 100644
index 00000000..26212c41
--- /dev/null
+++ b/aidesign/DataProcessing/plugins/normalizer.py
@@ -0,0 +1,77 @@
+from sklearn.preprocessing import Normalizer as model
+import numpy as np
+import pandas as pd
+
+_PLUGIN_READABLE_NAMES = {"Normalizer":"default","Norm":"alias","normalizer":"alias"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "scaler"}
+_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}
+_PLUGIN_OPTIONAL_SETTINGS = {"norm": "str"}
+_PLUGIN_REQUIRED_DATA = {}
+_PLUGIN_OPTIONAL_DATA = {"X","Y","X_tst", 'Y_tst'}
+
+class Normalizer(object):
+    """
+    Normalize samples individually to unit norm
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.proc = model()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(Normalizer) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        if self._config["options"]["Data"] == 'X':
+            self.X  = self._data_in["X"]
+            self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        elif self._config["options"]["Data"] == 'Y':
+            self.X  = self._data_in["Y"]
+            self.X_tst = self._is_name_passed(self._data_in, "Y_test")
+        else:
+            print('Invalid Data name. Indicate whether to use `X` or `Y`')
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+
+    def _check_numeric(self, dict_opt):
+        new_dict = {}
+        for key, val in dict_opt.items():
+            """ 
+            TODO: Maybe, if list -> cv
+            """
+            if key != 'Data':
+                if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                    val = float(val)
+                    if val.is_integer():
+                        val = int(val)
+                new_dict[key] = val
+        return new_dict
+
+    def fit(self):
+        config = self._check_numeric(self._config["options"])
+        self.proc.set_params(**config)
+        self.proc.fit(self.X)
+
+    def transform(self,data):
+        data.append_data_column("X", pd.DataFrame(self.proc.transform(self.X)))
+        if self.X_tst is not None:
+            data.append_data_column("X_test", pd.DataFrame(self.proc.transform(self.X_tst)))
+        return data
\ No newline at end of file
diff --git a/aidesign/DataProcessing/plugins/onehotencoder.py b/aidesign/DataProcessing/plugins/onehotencoder.py
new file mode 100644
index 00000000..1eb0eb8c
--- /dev/null
+++ b/aidesign/DataProcessing/plugins/onehotencoder.py
@@ -0,0 +1,77 @@
+from sklearn.preprocessing import OneHotEncoder as model
+import numpy as np
+import pandas as pd
+
+_PLUGIN_READABLE_NAMES = {"OneHotEncoder":"default"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "encoder"}
+_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}
+_PLUGIN_OPTIONAL_SETTINGS = {"categories": "array-like"}
+_PLUGIN_REQUIRED_DATA = {}
+_PLUGIN_OPTIONAL_DATA = {"X","Y","X_tst", 'Y_tst'}
+
+class OneHotEncoder(object):
+    """
+    Encode categorical features as a one-hot numeric array
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.proc = model()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(OneHotEncoder) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        if self._config["options"]["Data"] == 'X':
+            self.X  = self._data_in["X"]
+            self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        elif self._config["options"]["Data"] == 'Y':
+            self.X  = self._data_in["Y"]
+            self.X_tst = self._is_name_passed(self._data_in, "Y_test")
+        else:
+            print('Invalid Data name. Indicate whether to use `X` or `Y`')
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+
+    def _check_numeric(self, dict_opt):
+        new_dict = {}
+        for key, val in dict_opt.items():
+            """ 
+            TODO: Maybe, if list -> cv
+            """
+            if key != 'Data':
+                if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                    val = float(val)
+                    if val.is_integer():
+                        val = int(val)
+                new_dict[key] = val
+        return new_dict
+
+    def fit(self):
+        config = self._check_numeric(self._config["options"])
+        self.proc.set_params(**config)
+        self.proc.fit(self.X)
+
+    def transform(self,data):
+        data.append_data_column("X", pd.DataFrame(self.proc.transform(self.X)))
+        if self.X_tst is not None:
+            data.append_data_column("X_test", pd.DataFrame(self.proc.transform(self.X_tst)))
+        return data
\ No newline at end of file
diff --git a/aidesign/DataProcessing/plugins/ordinalencoder.py b/aidesign/DataProcessing/plugins/ordinalencoder.py
new file mode 100644
index 00000000..d522d99b
--- /dev/null
+++ b/aidesign/DataProcessing/plugins/ordinalencoder.py
@@ -0,0 +1,77 @@
+from sklearn.preprocessing import OrdinalEncoder as model
+import numpy as np
+import pandas as pd
+
+_PLUGIN_READABLE_NAMES = {"OrdinalEncoder":"default"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "encoder"}
+_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}
+_PLUGIN_OPTIONAL_SETTINGS = {"categories": "array-like"}
+_PLUGIN_REQUIRED_DATA = {}
+_PLUGIN_OPTIONAL_DATA = {"X","Y","X_tst", 'Y_tst'}
+
+class OrdinalEncoder(object):
+    """
+    Encode categorical features as an integer array
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.proc = model()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(OrdinalEncoder) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        if self._config["options"]["Data"] == 'X':
+            self.X  = self._data_in["X"]
+            self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        elif self._config["options"]["Data"] == 'Y':
+            self.X  = self._data_in["Y"]
+            self.X_tst = self._is_name_passed(self._data_in, "Y_test")
+        else:
+            print('Invalid Data name. Indicate whether to use `X` or `Y`')
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+
+    def _check_numeric(self, dict_opt):
+        new_dict = {}
+        for key, val in dict_opt.items():
+            """ 
+            TODO: Maybe, if list -> cv
+            """
+            if key != 'Data':
+                if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                    val = float(val)
+                    if val.is_integer():
+                        val = int(val)
+                new_dict[key] = val
+        return new_dict
+
+    def fit(self):
+        config = self._check_numeric(self._config["options"])
+        self.proc.set_params(**config)
+        self.proc.fit(self.X)
+
+    def transform(self,data):
+        data.append_data_column("X", pd.DataFrame(self.proc.transform(self.X)))
+        if self.X_tst is not None:
+            data.append_data_column("X_test", pd.DataFrame(self.proc.transform(self.X_tst)))
+        return data
\ No newline at end of file
diff --git a/aidesign/DataProcessing/plugins/polynomialfeatures.py b/aidesign/DataProcessing/plugins/polynomialfeatures.py
new file mode 100644
index 00000000..4511a8ef
--- /dev/null
+++ b/aidesign/DataProcessing/plugins/polynomialfeatures.py
@@ -0,0 +1,77 @@
+from sklearn.preprocessing import PolynomialFeatures as PF
+import numpy as np
+import pandas as pd
+
+_PLUGIN_READABLE_NAMES = {"PolynomialFeatures":"default","polyfeat":"alias","polynomialfeatures":"alias"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "other"}
+_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}
+_PLUGIN_OPTIONAL_SETTINGS = {"degree": "int", "interaction_only": "bool", "include_bias": "bool"}
+_PLUGIN_REQUIRED_DATA = {}
+_PLUGIN_OPTIONAL_DATA = {"X","Y","X_tst", 'Y_tst'}
+
+class PolynomialFeatures(object):
+    """ 
+    Generate polynomial and interaction features
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.proc = PF()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(PolynomialFeatures) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        if self._config["options"]["Data"] == 'X':
+            self.X  = self._data_in["X"]
+            self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        elif self._config["options"]["Data"] == 'Y':
+            self.X  = self._data_in["Y"]
+            self.X_tst = self._is_name_passed(self._data_in, "Y_test")
+        else:
+            print('Invalid Data name. Indicate whether to use `X` or `Y`')
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+
+    def _check_numeric(self, dict_opt):
+        new_dict = {}
+        for key, val in dict_opt.items():
+            """ 
+            TODO: Maybe, if list -> cv
+            """
+            if key != 'Data':
+                if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                    val = float(val)
+                    if val.is_integer():
+                        val = int(val)
+                new_dict[key] = val
+        return new_dict
+
+    def fit(self):
+        config = self._check_numeric(self._config["options"])
+        self.proc.set_params(**config)
+        self.proc.fit(self.X)
+
+    def transform(self,data):
+        data.append_data_column("X", pd.DataFrame(self.proc.transform(self.X)))
+        if self.X_tst is not None:
+            data.append_data_column("X_test", pd.DataFrame(self.proc.transform(self.X_tst)))
+        return data
\ No newline at end of file
diff --git a/aidesign/DataProcessing/plugins/quantiletransformer.py b/aidesign/DataProcessing/plugins/quantiletransformer.py
new file mode 100644
index 00000000..c56ecc77
--- /dev/null
+++ b/aidesign/DataProcessing/plugins/quantiletransformer.py
@@ -0,0 +1,77 @@
+from sklearn.preprocessing import QuantileTransformer as model
+import numpy as np
+import pandas as pd
+
+_PLUGIN_READABLE_NAMES = {"QuantileTransformer":"default","Quantile":"alias"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "encoder"}
+_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}
+_PLUGIN_OPTIONAL_SETTINGS = {"n_quantiles": "int"}
+_PLUGIN_REQUIRED_DATA = {}
+_PLUGIN_OPTIONAL_DATA = {"X","Y","X_tst", 'Y_tst'}
+
+class QuantileTransformer(object):
+    """
+    Transform features using quantiles information
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.proc = model()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(QuantileTransformer) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        if self._config["options"]["Data"] == 'X':
+            self.X  = self._data_in["X"]
+            self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        elif self._config["options"]["Data"] == 'Y':
+            self.X  = self._data_in["Y"]
+            self.X_tst = self._is_name_passed(self._data_in, "Y_test")
+        else:
+            print('Invalid Data name. Indicate whether to use `X` or `Y`')
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+
+    def _check_numeric(self, dict_opt):
+        new_dict = {}
+        for key, val in dict_opt.items():
+            """ 
+            TODO: Maybe, if list -> cv
+            """
+            if key != 'Data':
+                if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                    val = float(val)
+                    if val.is_integer():
+                        val = int(val)
+                new_dict[key] = val
+        return new_dict
+
+    def fit(self):
+        config = self._check_numeric(self._config["options"])
+        self.proc.set_params(**config)
+        self.proc.fit(self.X)
+
+    def transform(self,data):
+        data.append_data_column("X", pd.DataFrame(self.proc.transform(self.X)))
+        if self.X_tst is not None:
+            data.append_data_column("X_test", pd.DataFrame(self.proc.transform(self.X_tst)))
+        return data
\ No newline at end of file
diff --git a/aidesign/DataProcessing/plugins/standardscaler.py b/aidesign/DataProcessing/plugins/standardscaler.py
new file mode 100644
index 00000000..7f6c8c0a
--- /dev/null
+++ b/aidesign/DataProcessing/plugins/standardscaler.py
@@ -0,0 +1,77 @@
+from sklearn.preprocessing import StandardScaler as model
+import numpy as np
+import pandas as pd
+
+_PLUGIN_READABLE_NAMES = {"StandardScaler":"default","standardscaler":"alias"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "scaler"}
+_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}
+_PLUGIN_OPTIONAL_SETTINGS = {"with_mean": "bool"}
+_PLUGIN_REQUIRED_DATA = {}
+_PLUGIN_OPTIONAL_DATA = {"X","Y","X_tst", 'Y_tst'}
+
+class StandardScaler(object):
+    """
+    Standardize features by removing the mean and scaling to unit variance
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.proc = model()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(StandardScaler) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        if self._config["options"]["Data"] == 'X':
+            self.X  = self._data_in["X"]
+            self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        elif self._config["options"]["Data"] == 'Y':
+            self.X  = self._data_in["Y"]
+            self.X_tst = self._is_name_passed(self._data_in, "Y_test")
+        else:
+            print('Invalid Data name. Indicate whether to use `X` or `Y`')
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+
+    def _check_numeric(self, dict_opt):
+        new_dict = {}
+        for key, val in dict_opt.items():
+            """ 
+            TODO: Maybe, if list -> cv
+            """
+            if key != 'Data':
+                if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                    val = float(val)
+                    if val.is_integer():
+                        val = int(val)
+                new_dict[key] = val
+        return new_dict
+
+    def fit(self):
+        config = self._check_numeric(self._config["options"])
+        self.proc.set_params(**config)
+        self.proc.fit(self.X)
+
+    def transform(self,data):
+        data.append_data_column("X", pd.DataFrame(self.proc.transform(self.X)))
+        if self.X_tst is not None:
+            data.append_data_column("X_test", pd.DataFrame(self.proc.transform(self.X_tst)))
+        return data
\ No newline at end of file
diff --git a/aidesign/GUI/GUI_core.py b/aidesign/GUI/GUI_core.py
index 3034de48..9a9b5cf9 100644
--- a/aidesign/GUI/GUI_core.py
+++ b/aidesign/GUI/GUI_core.py
@@ -39,6 +39,10 @@ def set_gui_as_startpage(self):
         self._load_plugin("main")
         self.s = XML_handler()
         self.s.new_config_file()
+        
+    def set_gui(self):
+        self.startpage = True
+        self._load_plugin("pipelineCanvas")
 
     def _compare_layer_priority(self, ui_specs):
         """Check if a new module should have higher layer priority than the existing one
diff --git a/aidesign/Modelling/Modelling_core.py b/aidesign/Modelling/Modelling_core.py
index bcabd0a5..65fa4d5c 100644
--- a/aidesign/Modelling/Modelling_core.py
+++ b/aidesign/Modelling/Modelling_core.py
@@ -1,5 +1,6 @@
 # -*- coding: utf-8 -*-
 from aidesign.utils.import_helper import import_plugin_absolute
+from copy import copy
 class Modelling(object):
     def __init__(self):
         self.output_data = None
@@ -21,7 +22,7 @@ def _load_plugin(self, plugin_name:str):
     def set_options(self, module_config: dict):
         """Send configuration arguments to plugin
 
-        :param module_config: dict of settings to congfigure the plugin
+        :param module_config: dict of settings to configure the plugin
         """
         self._module_config = module_config
         self._load_plugin(self._module_config["plugin"]["plugin_name"])
@@ -30,7 +31,8 @@ def launch(self):
         self._plugin.set_data_in(self._data_in)
         self._plugin.configure(self._module_config["plugin"])
         self._plugin.solve()
-        self._plugin._test()
+        self.output_data = copy(self._data_in)
+        self.output_data = self._plugin._test(self.output_data)
 
     def get_result(self):
         return self.output_data
diff --git a/aidesign/Modelling/plugins/affinitypropagation.py b/aidesign/Modelling/plugins/affinitypropagation.py
new file mode 100644
index 00000000..3d6292cf
--- /dev/null
+++ b/aidesign/Modelling/plugins/affinitypropagation.py
@@ -0,0 +1,92 @@
+from sklearn.cluster import AffinityPropagation as model
+import numpy as np
+
+_PLUGIN_READABLE_NAMES = {"Birch":"default"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "clustering"}
+_PLUGIN_REQUIRED_SETTINGS = {}
+_PLUGIN_OPTIONAL_SETTINGS = {"damping": "float"}
+_PLUGIN_REQUIRED_DATA = {"X"}
+_PLUGIN_OPTIONAL_DATA = {"Y", "X_tst", 'Y_tst'}
+
+class AffinityPropagation(object):
+    """
+    Perform Affinity Propagation Clustering of data
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.clf = model()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(AffinityPropagation) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        self.X = self._data_in["X"]
+        self.Y = np.array(self._is_name_passed(self._data_in, "Y")).ravel()
+        self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        self.Y_tst = np.array(self._is_name_passed(self._data_in, "Y_test")).ravel()
+        # print(self._config["options"])
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+    
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+    
+    def _check_numeric(self, dict_opt):
+        for key, val in dict_opt.items():
+            """ 
+            TODO: maybe, if list -> cv
+            """
+            if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                val = float(val)
+                if val.is_integer():
+                    val = int(val)
+            dict_opt[key] = val
+        return dict_opt
+
+    def solve(self):
+        self._check_numeric(self._config["options"])
+        self.clf.set_params(**self._config["options"])
+        self.clf.fit(self.X, self.Y)
+
+    def predict(self,data):
+        return self.clf.predict(data)
+
+    def score(self,X_tst, Y_tst):
+        return self.clf.score(X_tst, Y_tst)
+
+    def _test(self, data):
+        if _PLUGIN_MODULE_OPTIONS['Type'] == 'classification':
+            print('Training accuracy: %.2f%%' %(self.score(self.X, self.Y)*100))
+            if self.Y_tst is not None:
+                print('Test accuracy: %.2f%%' %(self.score(self.X_tst, self.Y_tst)*100))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'regression':
+            print('Training R2 score: %.3f' %(self.score(self.X, self.Y)))
+            if self.Y_tst is not None:
+                print('Training R2 score: %.3f' %(self.score(self.X_tst, self.Y_tst)))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'clustering':
+            print('Clustering completed')
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/bayesianridge.py b/aidesign/Modelling/plugins/bayesianridge.py
new file mode 100644
index 00000000..3e58bd39
--- /dev/null
+++ b/aidesign/Modelling/plugins/bayesianridge.py
@@ -0,0 +1,92 @@
+from sklearn.linear_model import BayesianRidge as model
+import numpy as np
+
+_PLUGIN_READABLE_NAMES = {"BayesianRidge":"default"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "regression"}
+_PLUGIN_REQUIRED_SETTINGS = {}
+_PLUGIN_OPTIONAL_SETTINGS = {"n_iter": "int"}
+_PLUGIN_REQUIRED_DATA = {"X","Y"}
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}
+
+class BayesianRidge(object):
+    """
+    Bayesian ridge regression
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.clf = model()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(BayesianRidge) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        self.X = self._data_in["X"]
+        self.Y = np.array(self._data_in["Y"]).ravel()
+        self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        self.Y_tst = np.array(self._is_name_passed(self._data_in, "Y_test")).ravel()
+        # print(self._config["options"])
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+    
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+    
+    def _check_numeric(self, dict_opt):
+        for key, val in dict_opt.items():
+            """ 
+            TODO: maybe, if list -> cv
+            """
+            if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                val = float(val)
+                if val.is_integer():
+                    val = int(val)
+            dict_opt[key] = val
+        return dict_opt
+
+    def solve(self):
+        self._check_numeric(self._config["options"])
+        self.clf.set_params(**self._config["options"])
+        self.clf.fit(self.X, self.Y)
+
+    def predict(self,data):
+        return self.clf.predict(data)
+
+    def score(self,X_tst, Y_tst):
+        return self.clf.score(X_tst, Y_tst)
+
+    def _test(self, data):
+        if _PLUGIN_MODULE_OPTIONS['Type'] == 'classification':
+            print('Training accuracy: %.2f%%' %(self.score(self.X, self.Y)*100))
+            if self.Y_tst is not None:
+                print('Test accuracy: %.2f%%' %(self.score(self.X_tst, self.Y_tst)*100))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'regression':
+            print('Training R2 score: %.3f' %(self.score(self.X, self.Y)))
+            if self.Y_tst is not None:
+                print('Training R2 score: %.3f' %(self.score(self.X_tst, self.Y_tst)))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'clustering':
+            print('Clustering completed')
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/birch.py b/aidesign/Modelling/plugins/birch.py
new file mode 100644
index 00000000..b84b3b64
--- /dev/null
+++ b/aidesign/Modelling/plugins/birch.py
@@ -0,0 +1,92 @@
+from sklearn.cluster import Birch as model
+import numpy as np
+
+_PLUGIN_READABLE_NAMES = {"Birch":"default"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "clustering"}
+_PLUGIN_REQUIRED_SETTINGS = {}
+_PLUGIN_OPTIONAL_SETTINGS = {"n_clusters": "int", "threshold": "float"}
+_PLUGIN_REQUIRED_DATA = {"X"}
+_PLUGIN_OPTIONAL_DATA = {"Y", "X_tst", 'Y_tst'}
+
+class Birch(object):
+    """
+    Implements the BIRCH clustering algorithm
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.clf = model()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(Birch) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        self.X = self._data_in["X"]
+        self.Y = np.array(self._is_name_passed(self._data_in, "Y")).ravel()
+        self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        self.Y_tst = np.array(self._is_name_passed(self._data_in, "Y_test")).ravel()
+        # print(self._config["options"])
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+    
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+    
+    def _check_numeric(self, dict_opt):
+        for key, val in dict_opt.items():
+            """ 
+            TODO: maybe, if list -> cv
+            """
+            if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                val = float(val)
+                if val.is_integer():
+                    val = int(val)
+            dict_opt[key] = val
+        return dict_opt
+
+    def solve(self):
+        self._check_numeric(self._config["options"])
+        self.clf.set_params(**self._config["options"])
+        self.clf.fit(self.X, self.Y)
+
+    def predict(self,data):
+        return self.clf.predict(data)
+
+    def score(self,X_tst, Y_tst):
+        return self.clf.score(X_tst, Y_tst)
+
+    def _test(self, data):
+        if _PLUGIN_MODULE_OPTIONS['Type'] == 'classification':
+            print('Training accuracy: %.2f%%' %(self.score(self.X, self.Y)*100))
+            if self.Y_tst is not None:
+                print('Test accuracy: %.2f%%' %(self.score(self.X_tst, self.Y_tst)*100))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'regression':
+            print('Training R2 score: %.3f' %(self.score(self.X, self.Y)))
+            if self.Y_tst is not None:
+                print('Training R2 score: %.3f' %(self.score(self.X_tst, self.Y_tst)))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'clustering':
+            print('Clustering completed')
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/decisiontreeclassifier.py b/aidesign/Modelling/plugins/decisiontreeclassifier.py
new file mode 100644
index 00000000..a13034b2
--- /dev/null
+++ b/aidesign/Modelling/plugins/decisiontreeclassifier.py
@@ -0,0 +1,92 @@
+from sklearn.tree import DecisionTreeClassifier as model
+import numpy as np
+
+_PLUGIN_READABLE_NAMES = {"DecissionTreeClassifier":"default","DTClassifier":"alias"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "classification"}
+_PLUGIN_REQUIRED_SETTINGS = {}
+_PLUGIN_OPTIONAL_SETTINGS = {"max_depth": "int"} # model().get_params()
+_PLUGIN_REQUIRED_DATA = {"X","Y"}
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}
+
+class DecisionTreeClassifier(object):
+    """
+    A decision tree classifier
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.clf = model()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(DecisionTreeClassifier) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        self.X = self._data_in["X"]
+        self.Y = np.array(self._data_in["Y"]).ravel()
+        self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        self.Y_tst = np.array(self._is_name_passed(self._data_in, "Y_test")).ravel()
+        # print(self._config["options"])
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+    
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+    
+    def _check_numeric(self, dict_opt):
+        for key, val in dict_opt.items():
+            """ 
+            TODO: maybe, if list -> cv
+            """
+            if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                val = float(val)
+                if val.is_integer():
+                    val = int(val)
+            dict_opt[key] = val
+        return dict_opt
+
+    def solve(self):
+        self._check_numeric(self._config["options"])
+        self.clf.set_params(**self._config["options"])
+        self.clf.fit(self.X, self.Y)
+
+    def predict(self,data):
+        return self.clf.predict(data)
+
+    def score(self,X_tst, Y_tst):
+        return self.clf.score(X_tst, Y_tst)
+
+    def _test(self, data):
+        if _PLUGIN_MODULE_OPTIONS['Type'] == 'classification':
+            print('Training accuracy: %.2f%%' %(self.score(self.X, self.Y)*100))
+            if self.Y_tst is not None:
+                print('Test accuracy: %.2f%%' %(self.score(self.X_tst, self.Y_tst)*100))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'regression':
+            print('Training R2 score: %.3f' %(self.score(self.X, self.Y)))
+            if self.Y_tst is not None:
+                print('Training R2 score: %.3f' %(self.score(self.X_tst, self.Y_tst)))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'clustering':
+            print('Clustering completed')
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/decisiontreeregressor.py b/aidesign/Modelling/plugins/decisiontreeregressor.py
new file mode 100644
index 00000000..23899e5d
--- /dev/null
+++ b/aidesign/Modelling/plugins/decisiontreeregressor.py
@@ -0,0 +1,92 @@
+from sklearn.tree import DecisionTreeRegressor as model
+import numpy as np
+
+_PLUGIN_READABLE_NAMES = {"DecisionTreeRegressor":"default","DTregressor":"alias"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "regression"}
+_PLUGIN_REQUIRED_SETTINGS = {}
+_PLUGIN_OPTIONAL_SETTINGS = {"max_depth": "int"} # model().get_params()
+_PLUGIN_REQUIRED_DATA = {"X","Y"}
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}
+
+class DecisionTreeRegressor(object):
+    """
+    A decision tree regressor
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.clf = model()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(DecisionTreeRegressor) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        self.X = self._data_in["X"]
+        self.Y = np.array(self._data_in["Y"]).ravel()
+        self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        self.Y_tst = np.array(self._is_name_passed(self._data_in, "Y_test")).ravel()
+        # print(self._config["options"])
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+    
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+    
+    def _check_numeric(self, dict_opt):
+        for key, val in dict_opt.items():
+            """ 
+            TODO: maybe, if list -> cv
+            """
+            if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                val = float(val)
+                if val.is_integer():
+                    val = int(val)
+            dict_opt[key] = val
+        return dict_opt
+
+    def solve(self):
+        self._check_numeric(self._config["options"])
+        self.clf.set_params(**self._config["options"])
+        self.clf.fit(self.X, self.Y)
+
+    def predict(self,data):
+        return self.clf.predict(data)
+
+    def score(self,X_tst, Y_tst):
+        return self.clf.score(X_tst, Y_tst)
+
+    def _test(self, data):
+        if _PLUGIN_MODULE_OPTIONS['Type'] == 'classification':
+            print('Training accuracy: %.2f%%' %(self.score(self.X, self.Y)*100))
+            if self.Y_tst is not None:
+                print('Test accuracy: %.2f%%' %(self.score(self.X_tst, self.Y_tst)*100))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'regression':
+            print('Training R2 score: %.3f' %(self.score(self.X, self.Y)))
+            if self.Y_tst is not None:
+                print('Training R2 score: %.3f' %(self.score(self.X_tst, self.Y_tst)))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'clustering':
+            print('Clustering completed')
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/elasticnet.py b/aidesign/Modelling/plugins/elasticnet.py
new file mode 100644
index 00000000..c4714e76
--- /dev/null
+++ b/aidesign/Modelling/plugins/elasticnet.py
@@ -0,0 +1,92 @@
+from sklearn.linear_model import ElasticNet as model
+import numpy as np
+
+_PLUGIN_READABLE_NAMES = {"ElasticNet":"default"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "regression"}
+_PLUGIN_REQUIRED_SETTINGS = {}
+_PLUGIN_OPTIONAL_SETTINGS = {"alpha": "float", "l1_ratio": "float"}
+_PLUGIN_REQUIRED_DATA = {"X","Y"}
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}
+
+class ElasticNet(object):
+    """
+    Linear regression with combined L1 and L2 priors as regularizer
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.clf = model()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(ElasticNet) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        self.X = self._data_in["X"]
+        self.Y = np.array(self._data_in["Y"]).ravel()
+        self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        self.Y_tst = np.array(self._is_name_passed(self._data_in, "Y_test")).ravel()
+        # print(self._config["options"])
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+    
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+    
+    def _check_numeric(self, dict_opt):
+        for key, val in dict_opt.items():
+            """ 
+            TODO: maybe, if list -> cv
+            """
+            if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                val = float(val)
+                if val.is_integer():
+                    val = int(val)
+            dict_opt[key] = val
+        return dict_opt
+
+    def solve(self):
+        self._check_numeric(self._config["options"])
+        self.clf.set_params(**self._config["options"])
+        self.clf.fit(self.X, self.Y)
+
+    def predict(self,data):
+        return self.clf.predict(data)
+
+    def score(self,X_tst, Y_tst):
+        return self.clf.score(X_tst, Y_tst)
+
+    def _test(self, data):
+        if _PLUGIN_MODULE_OPTIONS['Type'] == 'classification':
+            print('Training accuracy: %.2f%%' %(self.score(self.X, self.Y)*100))
+            if self.Y_tst is not None:
+                print('Test accuracy: %.2f%%' %(self.score(self.X_tst, self.Y_tst)*100))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'regression':
+            print('Training R2 score: %.3f' %(self.score(self.X, self.Y)))
+            if self.Y_tst is not None:
+                print('Training R2 score: %.3f' %(self.score(self.X_tst, self.Y_tst)))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'clustering':
+            print('Clustering completed')
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/gpclassifier.py b/aidesign/Modelling/plugins/gpclassifier.py
new file mode 100644
index 00000000..3e3d3467
--- /dev/null
+++ b/aidesign/Modelling/plugins/gpclassifier.py
@@ -0,0 +1,92 @@
+from sklearn.gaussian_process import GaussianProcessClassifier as model
+import numpy as np
+
+_PLUGIN_READABLE_NAMES = {"GPClassifier":"default","GPC":"alias","GaussianProcessClassifier":"alias"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "classification"}
+_PLUGIN_REQUIRED_SETTINGS = {}
+_PLUGIN_OPTIONAL_SETTINGS = {"n_restarts_optimizer": "int", "random_state": "int"}
+_PLUGIN_REQUIRED_DATA = {"X","Y"}
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}
+
+class GPClassifier(object):
+    """
+    Gaussian process Classifier (GPC) based on Laplace approximation
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.clf = model()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(GPClassifier) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        self.X = self._data_in["X"]
+        self.Y = np.array(self._data_in["Y"]).ravel()
+        self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        self.Y_tst = np.array(self._is_name_passed(self._data_in, "Y_test")).ravel()
+        # print(self._config["options"])
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+
+    def _check_numeric(self, dict_opt):
+        for key, val in dict_opt.items():
+            """ 
+            TODO: Maybe, if list -> cv
+            """
+            if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                val = float(val)
+                if val.is_integer():
+                    val = int(val)
+            dict_opt[key] = val
+        return dict_opt
+
+    def solve(self):
+        self._check_numeric(self._config["options"])
+        self.clf.set_params(**self._config["options"])
+        self.clf.fit(self.X, self.Y)
+
+    def predict(self,data):
+        return self.clf.predict(data)
+
+    def score(self,X_tst, Y_tst):
+        return self.clf.score(X_tst, Y_tst)
+
+    def _test(self, data):
+        if _PLUGIN_MODULE_OPTIONS['Type'] == 'classification':
+            print('Training accuracy: %.2f%%' %(self.score(self.X, self.Y)*100))
+            if self.Y_tst is not None:
+                print('Test accuracy: %.2f%%' %(self.score(self.X_tst, self.Y_tst)*100))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'regression':
+            print('Training R2 score: %.3f' %(self.score(self.X, self.Y)))
+            if self.Y_tst is not None:
+                print('Training R2 score: %.3f' %(self.score(self.X_tst, self.Y_tst)))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'clustering':
+            print('Clustering completed')
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/gpregressor.py b/aidesign/Modelling/plugins/gpregressor.py
new file mode 100644
index 00000000..972ea19d
--- /dev/null
+++ b/aidesign/Modelling/plugins/gpregressor.py
@@ -0,0 +1,92 @@
+from sklearn.gaussian_process import GaussianProcessRegressor as model
+import numpy as np
+
+_PLUGIN_READABLE_NAMES = {"GPRegressor":"default","GPR":"alias","GaussianProcessRegressor":"alias"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "regression"}
+_PLUGIN_REQUIRED_SETTINGS = {}
+_PLUGIN_OPTIONAL_SETTINGS = {"n_restarts_optimizer": "int", "random_state": "int"}
+_PLUGIN_REQUIRED_DATA = {"X","Y"}
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}
+
+class GPRegressor(object):
+    """
+    Gaussian process regressor
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.clf = model()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(GPRegressor) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        self.X = self._data_in["X"]
+        self.Y = np.array(self._data_in["Y"]).ravel()
+        self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        self.Y_tst = np.array(self._is_name_passed(self._data_in, "Y_test")).ravel()
+        # print(self._config["options"])
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+
+    def _check_numeric(self, dict_opt):
+        for key, val in dict_opt.items():
+            """ 
+            TODO: Maybe, if list -> cv
+            """
+            if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                val = float(val)
+                if val.is_integer():
+                    val = int(val)
+            dict_opt[key] = val
+        return dict_opt
+
+    def solve(self):
+        self._check_numeric(self._config["options"])
+        self.clf.set_params(**self._config["options"])
+        self.clf.fit(self.X, self.Y)
+
+    def predict(self,data):
+        return self.clf.predict(data)
+
+    def score(self,X_tst, Y_tst):
+        return self.clf.score(X_tst, Y_tst)
+
+    def _test(self, data):
+        if _PLUGIN_MODULE_OPTIONS['Type'] == 'classification':
+            print('Training accuracy: %.2f%%' %(self.score(self.X, self.Y)*100))
+            if self.Y_tst is not None:
+                print('Test accuracy: %.2f%%' %(self.score(self.X_tst, self.Y_tst)*100))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'regression':
+            print('Training R2 score: %.3f' %(self.score(self.X, self.Y)))
+            if self.Y_tst is not None:
+                print('Training R2 score: %.3f' %(self.score(self.X_tst, self.Y_tst)))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'clustering':
+            print('Clustering completed')
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/kernelridge.py b/aidesign/Modelling/plugins/kernelridge.py
new file mode 100644
index 00000000..196a181e
--- /dev/null
+++ b/aidesign/Modelling/plugins/kernelridge.py
@@ -0,0 +1,92 @@
+from sklearn.kernel_ridge import KernelRidge as model
+import numpy as np
+
+_PLUGIN_READABLE_NAMES = {"KernelRidge":"default","KR":"alias"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "regression"}
+_PLUGIN_REQUIRED_SETTINGS = {}
+_PLUGIN_OPTIONAL_SETTINGS = {"alpha": "float", "kernel": "str", "gamma": "float", "degree": "int"}
+_PLUGIN_REQUIRED_DATA = {"X","Y"}
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}
+
+class KernelRidge(object):
+    """
+    Kernel ridge regression.
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.clf = model()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(KernelRidge) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        self.X = self._data_in["X"]
+        self.Y = np.array(self._data_in["Y"]).ravel()
+        self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        self.Y_tst = np.array(self._is_name_passed(self._data_in, "Y_test")).ravel()
+        # print(self._config["options"])
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+    
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+    
+    def _check_numeric(self, dict_opt):
+        for key, val in dict_opt.items():
+            """ 
+            TODO: maybe, if list -> cv
+            """
+            if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                val = float(val)
+                if val.is_integer():
+                    val = int(val)
+            dict_opt[key] = val
+        return dict_opt
+
+    def solve(self):
+        self._check_numeric(self._config["options"])
+        self.clf.set_params(**self._config["options"])
+        self.clf.fit(self.X, self.Y)
+
+    def predict(self,data):
+        return self.clf.predict(data)
+
+    def score(self,X_tst, Y_tst):
+        return self.clf.score(X_tst, Y_tst)
+
+    def _test(self, data):
+        if _PLUGIN_MODULE_OPTIONS['Type'] == 'classification':
+            print('Training accuracy: %.2f%%' %(self.score(self.X, self.Y)*100))
+            if self.Y_tst is not None:
+                print('Test accuracy: %.2f%%' %(self.score(self.X_tst, self.Y_tst)*100))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'regression':
+            print('Training R2 score: %.3f' %(self.score(self.X, self.Y)))
+            if self.Y_tst is not None:
+                print('Training R2 score: %.3f' %(self.score(self.X_tst, self.Y_tst)))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'clustering':
+            print('Clustering completed')
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/kmeans.py b/aidesign/Modelling/plugins/kmeans.py
new file mode 100644
index 00000000..1f5587cc
--- /dev/null
+++ b/aidesign/Modelling/plugins/kmeans.py
@@ -0,0 +1,92 @@
+from sklearn.cluster import KMeans as model
+import numpy as np
+
+_PLUGIN_READABLE_NAMES = {"KMeans":"default"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "clustering"}
+_PLUGIN_REQUIRED_SETTINGS = {}
+_PLUGIN_OPTIONAL_SETTINGS = {"n_clusters": "int", "n_init": "int"}
+_PLUGIN_REQUIRED_DATA = {"X"}
+_PLUGIN_OPTIONAL_DATA = {"Y", "X_tst", 'Y_tst'}
+
+class KMeans(object):
+    """
+    K-Means clustering
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.clf = model()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(KMeans) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        self.X = self._data_in["X"]
+        self.Y = np.array(self._is_name_passed(self._data_in, "Y")).ravel()
+        self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        self.Y_tst = np.array(self._is_name_passed(self._data_in, "Y_test")).ravel()
+        # print(self._config["options"])
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+    
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+    
+    def _check_numeric(self, dict_opt):
+        for key, val in dict_opt.items():
+            """ 
+            TODO: maybe, if list -> cv
+            """
+            if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                val = float(val)
+                if val.is_integer():
+                    val = int(val)
+            dict_opt[key] = val
+        return dict_opt
+
+    def solve(self):
+        self._check_numeric(self._config["options"])
+        self.clf.set_params(**self._config["options"])
+        self.clf.fit(self.X, self.Y)
+
+    def predict(self,data):
+        return self.clf.predict(data)
+
+    def score(self,X_tst, Y_tst):
+        return self.clf.score(X_tst, Y_tst)
+
+    def _test(self, data):
+        if _PLUGIN_MODULE_OPTIONS['Type'] == 'classification':
+            print('Training accuracy: %.2f%%' %(self.score(self.X, self.Y)*100))
+            if self.Y_tst is not None:
+                print('Test accuracy: %.2f%%' %(self.score(self.X_tst, self.Y_tst)*100))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'regression':
+            print('Training R2 score: %.3f' %(self.score(self.X, self.Y)))
+            if self.Y_tst is not None:
+                print('Training R2 score: %.3f' %(self.score(self.X_tst, self.Y_tst)))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'clustering':
+            print('Clustering completed')
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/knnclassifier.py b/aidesign/Modelling/plugins/knnclassifier.py
index 829696e7..b726d02d 100644
--- a/aidesign/Modelling/plugins/knnclassifier.py
+++ b/aidesign/Modelling/plugins/knnclassifier.py
@@ -1,28 +1,30 @@
-from sklearn.neighbors import KNeighborsClassifier
+from sklearn.neighbors import KNeighborsClassifier as model
 import numpy as np
-from matplotlib import pyplot as plt
 
-_PLUGIN_READABLE_NAMES = {"KNN Classifier":"default","KNN-C":"alias"}
-_PLUGIN_MODULE_OPTIONS = {}
+_PLUGIN_READABLE_NAMES = {"KNNClassifier":"default","KNN-C":"alias"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "classification"}
 _PLUGIN_REQUIRED_SETTINGS = {}
 _PLUGIN_OPTIONAL_SETTINGS = {"n_neighbors":"int", "weights": "str"}
 _PLUGIN_REQUIRED_DATA = {"X","Y"}
 _PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}
 
-class KNNClassifier(object):
-    """KNN for supervised classification"""
+class KNNclassifier(object):
+    """
+    Classifier implementing the k-nearest neighbors vote
+    """
 
     def __init__(self):
         self.X = None
         self.Y = None
         self.X_tst = None
         self.Y_tst = None
+        self.clf = model()
 
     def set_data_in(self,data_in):
         req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
         if len(req_check) > 0:
             raise Exception("Minimal Data Requirements not met"   \
-                            +"\n\t{0} ".format(KNNClassifier) \
+                            +"\n\t{0} ".format(KNNclassifier) \
                             +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
                             + "\n\tThe following data is missing:"\
                             + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
@@ -34,26 +36,32 @@ def configure(self, config:dict):
 
     def _parse_config(self):
         self.X = self._data_in["X"]
-        self.Y = self._data_in["Y"]
-        self.X_tst = self._is_name_passed(self._data_in, "X_tst")
-        self.Y_tst = self._is_name_passed(self._data_in, "Y_tst")
-        self.fit_degree = int(self._config["options"]["power"]["val"])
+        self.Y = np.array(self._data_in["Y"]).ravel()
+        self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        self.Y_tst = np.array(self._is_name_passed(self._data_in, "Y_test")).ravel()
+        # print(self._config["options"])
 
     def _is_name_passed(self, dic: dict, key: str, default = None):
         return dic[key] if key in dic.keys() and dic[key] is not None else default
-    
+
     def _reshape(self,data,shape):
         return data.reshape(shape[0],shape[1])
 
+    def _check_numeric(self, dict_opt):
+        for key, val in dict_opt.items():
+            """ 
+            TODO: Maybe, if list -> cv
+            """
+            if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                val = float(val)
+                if val.is_integer():
+                    val = int(val)
+            dict_opt[key] = val
+        return dict_opt
+
     def solve(self):
-        self.clf = KNeighborsClassifier(n_neighbors=5,
-                                        weights='uniform', 
-                                        algorithm='auto', 
-                                        leaf_size=30, 
-                                        p=2, 
-                                        metric='minkowski', 
-                                        metric_params=None, 
-                                        n_jobs=None)
+        self._check_numeric(self._config["options"])
+        self.clf.set_params(**self._config["options"])
         self.clf.fit(self.X, self.Y)
 
     def predict(self,data):
@@ -62,16 +70,23 @@ def predict(self,data):
     def score(self,X_tst, Y_tst):
         return self.clf.score(X_tst, Y_tst)
 
-    def _test(self):
-        in_test = self.input_data.values*4
-        pred = self.predict(in_test)
-        plt.figure()
-        plt.plot(in_test,
-                    pred,
-                    c='r',
-                    label="Test Data. Degree: {0}".format(self.fit_degree))
-        plt.scatter(self.input_data,
-                    self.target_data.values,
-                    label="Training Data")
-        plt.legend()
-        plt.show()
\ No newline at end of file
+    def _test(self, data):
+        if _PLUGIN_MODULE_OPTIONS['Type'] == 'classification':
+            print('Training accuracy: %.2f%%' %(self.score(self.X, self.Y)*100))
+            if self.Y_tst is not None:
+                print('Test accuracy: %.2f%%' %(self.score(self.X_tst, self.Y_tst)*100))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'regression':
+            print('Training R2 score: %.3f' %(self.score(self.X, self.Y)))
+            if self.Y_tst is not None:
+                print('Training R2 score: %.3f' %(self.score(self.X_tst, self.Y_tst)))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'clustering':
+            print('Clustering completed')
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/knnregressor.py b/aidesign/Modelling/plugins/knnregressor.py
new file mode 100644
index 00000000..efe0d888
--- /dev/null
+++ b/aidesign/Modelling/plugins/knnregressor.py
@@ -0,0 +1,92 @@
+from sklearn.neighbors import KNeighborsRegressor as model
+import numpy as np
+
+_PLUGIN_READABLE_NAMES = {"KNNRegressor":"default","KNN-R":"alias"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "regression"}
+_PLUGIN_REQUIRED_SETTINGS = {}
+_PLUGIN_OPTIONAL_SETTINGS = {"n_neighbors":"int", "weights": "str"}
+_PLUGIN_REQUIRED_DATA = {"X","Y"}
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}
+
+class KNNRegressor(object):
+    """
+    Regression based on k-nearest neighbors
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.clf = model()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(KNNRegressor) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        self.X = self._data_in["X"]
+        self.Y = np.array(self._data_in["Y"]).ravel()
+        self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        self.Y_tst = np.array(self._is_name_passed(self._data_in, "Y_test")).ravel()
+        # print(self._config["options"])
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+
+    def _check_numeric(self, dict_opt):
+        for key, val in dict_opt.items():
+            """ 
+            TODO: Maybe, if list -> cv
+            """
+            if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                val = float(val)
+                if val.is_integer():
+                    val = int(val)
+            dict_opt[key] = val
+        return dict_opt
+
+    def solve(self):
+        self._check_numeric(self._config["options"])
+        self.clf.set_params(**self._config["options"])
+        self.clf.fit(self.X, self.Y)
+
+    def predict(self,data):
+        return self.clf.predict(data)
+
+    def score(self,X_tst, Y_tst):
+        return self.clf.score(X_tst, Y_tst)
+
+    def _test(self, data):
+        if _PLUGIN_MODULE_OPTIONS['Type'] == 'classification':
+            print('Training accuracy: %.2f%%' %(self.score(self.X, self.Y)*100))
+            if self.Y_tst is not None:
+                print('Test accuracy: %.2f%%' %(self.score(self.X_tst, self.Y_tst)*100))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'regression':
+            print('Training R2 score: %.3f' %(self.score(self.X, self.Y)))
+            if self.Y_tst is not None:
+                print('Training R2 score: %.3f' %(self.score(self.X_tst, self.Y_tst)))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'clustering':
+            print('Clustering completed')
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/lasso.py b/aidesign/Modelling/plugins/lasso.py
new file mode 100644
index 00000000..a26d72b8
--- /dev/null
+++ b/aidesign/Modelling/plugins/lasso.py
@@ -0,0 +1,92 @@
+from sklearn.linear_model import Lasso as model
+import numpy as np
+
+_PLUGIN_READABLE_NAMES = {"Lasso":"default"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "regression"}
+_PLUGIN_REQUIRED_SETTINGS = {}
+_PLUGIN_OPTIONAL_SETTINGS = {"alpha": "float"}
+_PLUGIN_REQUIRED_DATA = {"X","Y"}
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}
+
+class Lasso(object):
+    """
+    Linear Model trained with L1 prior as regularizer
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.clf = model()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(Lasso) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        self.X = self._data_in["X"]
+        self.Y = np.array(self._data_in["Y"]).ravel()
+        self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        self.Y_tst = np.array(self._is_name_passed(self._data_in, "Y_test")).ravel()
+        # print(self._config["options"])
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+    
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+    
+    def _check_numeric(self, dict_opt):
+        for key, val in dict_opt.items():
+            """ 
+            TODO: maybe, if list -> cv
+            """
+            if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                val = float(val)
+                if val.is_integer():
+                    val = int(val)
+            dict_opt[key] = val
+        return dict_opt
+
+    def solve(self):
+        self._check_numeric(self._config["options"])
+        self.clf.set_params(**self._config["options"])
+        self.clf.fit(self.X, self.Y)
+
+    def predict(self,data):
+        return self.clf.predict(data)
+
+    def score(self,X_tst, Y_tst):
+        return self.clf.score(X_tst, Y_tst)
+
+    def _test(self, data):
+        if _PLUGIN_MODULE_OPTIONS['Type'] == 'classification':
+            print('Training accuracy: %.2f%%' %(self.score(self.X, self.Y)*100))
+            if self.Y_tst is not None:
+                print('Test accuracy: %.2f%%' %(self.score(self.X_tst, self.Y_tst)*100))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'regression':
+            print('Training R2 score: %.3f' %(self.score(self.X, self.Y)))
+            if self.Y_tst is not None:
+                print('Training R2 score: %.3f' %(self.score(self.X_tst, self.Y_tst)))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'clustering':
+            print('Clustering completed')
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/linearregression.py b/aidesign/Modelling/plugins/linearregression.py
new file mode 100644
index 00000000..44e51aae
--- /dev/null
+++ b/aidesign/Modelling/plugins/linearregression.py
@@ -0,0 +1,92 @@
+from sklearn.linear_model import LinearRegression as model
+import numpy as np
+
+_PLUGIN_READABLE_NAMES = {"LinearRegression":"default","LR":"alias"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "regression"}
+_PLUGIN_REQUIRED_SETTINGS = {}
+_PLUGIN_OPTIONAL_SETTINGS = {}
+_PLUGIN_REQUIRED_DATA = {"X","Y"}
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}
+
+class LinearRegression(object):
+    """
+    Ordinary least squares Linear Regression
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.clf = model()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(LinearRegression) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        self.X = self._data_in["X"]
+        self.Y = np.array(self._data_in["Y"]).ravel()
+        self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        self.Y_tst = np.array(self._is_name_passed(self._data_in, "Y_test")).ravel()
+        # print(self._config["options"])
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+
+    def _check_numeric(self, dict_opt):
+        for key, val in dict_opt.items():
+            """ 
+            TODO: Maybe, if list -> cv
+            """
+            if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                val = float(val)
+                if val.is_integer():
+                    val = int(val)
+            dict_opt[key] = val
+        return dict_opt
+
+    def solve(self):
+        self._check_numeric(self._config["options"])
+        self.clf.set_params(**self._config["options"])
+        self.clf.fit(self.X, self.Y)
+
+    def predict(self,data):
+        return self.clf.predict(data)
+
+    def score(self,X_tst, Y_tst):
+        return self.clf.score(X_tst, Y_tst)
+
+    def _test(self, data):
+        if _PLUGIN_MODULE_OPTIONS['Type'] == 'classification':
+            print('Training accuracy: %.2f%%' %(self.score(self.X, self.Y)*100))
+            if self.Y_tst is not None:
+                print('Test accuracy: %.2f%%' %(self.score(self.X_tst, self.Y_tst)*100))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'regression':
+            print('Training R2 score: %.3f' %(self.score(self.X, self.Y)))
+            if self.Y_tst is not None:
+                print('Training R2 score: %.3f' %(self.score(self.X_tst, self.Y_tst)))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'clustering':
+            print('Clustering completed')
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/logisticregression.py b/aidesign/Modelling/plugins/logisticregression.py
new file mode 100644
index 00000000..9d4843f6
--- /dev/null
+++ b/aidesign/Modelling/plugins/logisticregression.py
@@ -0,0 +1,92 @@
+from sklearn.linear_model import LogisticRegression as model
+import numpy as np
+
+_PLUGIN_READABLE_NAMES = {"LogisticRegression":"default","logit":"alias","MaxEnt":"alias"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "classification"}
+_PLUGIN_REQUIRED_SETTINGS = {}
+_PLUGIN_OPTIONAL_SETTINGS = {"penalty":"str", "C": "float"}
+_PLUGIN_REQUIRED_DATA = {"X","Y"}
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}
+
+class LogisticRegression(object):
+    """
+    Logistic regression classification.
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.clf = model()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(LogisticRegression) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        self.X = self._data_in["X"]
+        self.Y = np.array(self._data_in["Y"]).ravel()
+        self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        self.Y_tst = np.array(self._is_name_passed(self._data_in, "Y_test")).ravel()
+        # print(self._config["options"])
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+    
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+    
+    def _check_numeric(self, dict_opt):
+        for key, val in dict_opt.items():
+            """ 
+            TODO: maybe, if list -> cv
+            """
+            if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                val = float(val)
+                if val.is_integer():
+                    val = int(val)
+            dict_opt[key] = val
+        return dict_opt
+
+    def solve(self):
+        self._check_numeric(self._config["options"])
+        self.clf.set_params(**self._config["options"])
+        self.clf.fit(self.X, self.Y)
+
+    def predict(self,data):
+        return self.clf.predict(data)
+
+    def score(self,X_tst, Y_tst):
+        return self.clf.score(X_tst, Y_tst)
+
+    def _test(self, data):
+        if _PLUGIN_MODULE_OPTIONS['Type'] == 'classification':
+            print('Training accuracy: %.2f%%' %(self.score(self.X, self.Y)*100))
+            if self.Y_tst is not None:
+                print('Test accuracy: %.2f%%' %(self.score(self.X_tst, self.Y_tst)*100))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'regression':
+            print('Training R2 score: %.3f' %(self.score(self.X, self.Y)))
+            if self.Y_tst is not None:
+                print('Training R2 score: %.3f' %(self.score(self.X_tst, self.Y_tst)))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'clustering':
+            print('Clustering completed')
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/meanshift.py b/aidesign/Modelling/plugins/meanshift.py
new file mode 100644
index 00000000..e06a1ba9
--- /dev/null
+++ b/aidesign/Modelling/plugins/meanshift.py
@@ -0,0 +1,92 @@
+from sklearn.cluster import MeanShift as model
+import numpy as np
+
+_PLUGIN_READABLE_NAMES = {"MeanShift":"default"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "clustering"}
+_PLUGIN_REQUIRED_SETTINGS = {}
+_PLUGIN_OPTIONAL_SETTINGS = {"bandwidth": "float"}
+_PLUGIN_REQUIRED_DATA = {"X"}
+_PLUGIN_OPTIONAL_DATA = {"Y", "X_tst", 'Y_tst'}
+
+class MeanShift(object):
+    """
+    Mean shift clustering using a flat kernel.
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.clf = model()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(MeanShift) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        self.X = self._data_in["X"]
+        self.Y = np.array(self._is_name_passed(self._data_in, "Y")).ravel()
+        self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        self.Y_tst = np.array(self._is_name_passed(self._data_in, "Y_test")).ravel()
+        # print(self._config["options"])
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+    
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+    
+    def _check_numeric(self, dict_opt):
+        for key, val in dict_opt.items():
+            """ 
+            TODO: maybe, if list -> cv
+            """
+            if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                val = float(val)
+                if val.is_integer():
+                    val = int(val)
+            dict_opt[key] = val
+        return dict_opt
+
+    def solve(self):
+        self._check_numeric(self._config["options"])
+        self.clf.set_params(**self._config["options"])
+        self.clf.fit(self.X, self.Y)
+
+    def predict(self,data):
+        return self.clf.predict(data)
+
+    def score(self,X_tst, Y_tst):
+        return self.clf.score(X_tst, Y_tst)
+
+    def _test(self, data):
+        if _PLUGIN_MODULE_OPTIONS['Type'] == 'classification':
+            print('Training accuracy: %.2f%%' %(self.score(self.X, self.Y)*100))
+            if self.Y_tst is not None:
+                print('Test accuracy: %.2f%%' %(self.score(self.X_tst, self.Y_tst)*100))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'regression':
+            print('Training R2 score: %.3f' %(self.score(self.X, self.Y)))
+            if self.Y_tst is not None:
+                print('Training R2 score: %.3f' %(self.score(self.X_tst, self.Y_tst)))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'clustering':
+            print('Clustering completed')
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/passiveaggressiveclassifier.py b/aidesign/Modelling/plugins/passiveaggressiveclassifier.py
new file mode 100644
index 00000000..57e38ca9
--- /dev/null
+++ b/aidesign/Modelling/plugins/passiveaggressiveclassifier.py
@@ -0,0 +1,92 @@
+from sklearn.linear_model import PassiveAggressiveClassifier as model
+import numpy as np
+
+_PLUGIN_READABLE_NAMES = {"PassiveAggressiveClassifier":"default","PassiveAgressive":"alias"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "classification"}
+_PLUGIN_REQUIRED_SETTINGS = {}
+_PLUGIN_OPTIONAL_SETTINGS = {"C": "float"}
+_PLUGIN_REQUIRED_DATA = {"X","Y"}
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}
+
+class PassiveAggressiveClassifier(object):
+    """
+    Passive aggressive classifier
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.clf = model()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(PassiveAggressiveClassifier) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        self.X = self._data_in["X"]
+        self.Y = np.array(self._data_in["Y"]).ravel()
+        self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        self.Y_tst = np.array(self._is_name_passed(self._data_in, "Y_test")).ravel()
+        # print(self._config["options"])
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+
+    def _check_numeric(self, dict_opt):
+        for key, val in dict_opt.items():
+            """ 
+            TODO: Maybe, if list -> cv
+            """
+            if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                val = float(val)
+                if val.is_integer():
+                    val = int(val)
+            dict_opt[key] = val
+        return dict_opt
+
+    def solve(self):
+        self._check_numeric(self._config["options"])
+        self.clf.set_params(**self._config["options"])
+        self.clf.fit(self.X, self.Y)
+
+    def predict(self,data):
+        return self.clf.predict(data)
+
+    def score(self,X_tst, Y_tst):
+        return self.clf.score(X_tst, Y_tst)
+
+    def _test(self, data):
+        if _PLUGIN_MODULE_OPTIONS['Type'] == 'classification':
+            print('Training accuracy: %.2f%%' %(self.score(self.X, self.Y)*100))
+            if self.Y_tst is not None:
+                print('Test accuracy: %.2f%%' %(self.score(self.X_tst, self.Y_tst)*100))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'regression':
+            print('Training R2 score: %.3f' %(self.score(self.X, self.Y)))
+            if self.Y_tst is not None:
+                print('Training R2 score: %.3f' %(self.score(self.X_tst, self.Y_tst)))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'clustering':
+            print('Clustering completed')
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/perceptron.py b/aidesign/Modelling/plugins/perceptron.py
new file mode 100644
index 00000000..4f727ae7
--- /dev/null
+++ b/aidesign/Modelling/plugins/perceptron.py
@@ -0,0 +1,92 @@
+from sklearn.linear_model import Perceptron as model
+import numpy as np
+
+_PLUGIN_READABLE_NAMES = {"Perceptron":"default","LinearPerceptron":"alias"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "classification"}
+_PLUGIN_REQUIRED_SETTINGS = {}
+_PLUGIN_OPTIONAL_SETTINGS = {"alpha": "float", "penalty": "str"}
+_PLUGIN_REQUIRED_DATA = {"X","Y"}
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}
+
+class Perceptron(object):
+    """
+    Linear perceptron classification
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.clf = model()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(Perceptron) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        self.X = self._data_in["X"]
+        self.Y = np.array(self._data_in["Y"]).ravel()
+        self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        self.Y_tst = np.array(self._is_name_passed(self._data_in, "Y_test")).ravel()
+        # print(self._config["options"])
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+    
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+    
+    def _check_numeric(self, dict_opt):
+        for key, val in dict_opt.items():
+            """ 
+            TODO: maybe, if list -> cv
+            """
+            if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                val = float(val)
+                if val.is_integer():
+                    val = int(val)
+            dict_opt[key] = val
+        return dict_opt
+
+    def solve(self):
+        self._check_numeric(self._config["options"])
+        self.clf.set_params(**self._config["options"])
+        self.clf.fit(self.X, self.Y)
+
+    def predict(self,data):
+        return self.clf.predict(data)
+
+    def score(self,X_tst, Y_tst):
+        return self.clf.score(X_tst, Y_tst)
+
+    def _test(self, data):
+        if _PLUGIN_MODULE_OPTIONS['Type'] == 'classification':
+            print('Training accuracy: %.2f%%' %(self.score(self.X, self.Y)*100))
+            if self.Y_tst is not None:
+                print('Test accuracy: %.2f%%' %(self.score(self.X_tst, self.Y_tst)*100))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'regression':
+            print('Training R2 score: %.3f' %(self.score(self.X, self.Y)))
+            if self.Y_tst is not None:
+                print('Training R2 score: %.3f' %(self.score(self.X_tst, self.Y_tst)))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'clustering':
+            print('Clustering completed')
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/randomforestclassifier.py b/aidesign/Modelling/plugins/randomforestclassifier.py
new file mode 100644
index 00000000..e34a69b8
--- /dev/null
+++ b/aidesign/Modelling/plugins/randomforestclassifier.py
@@ -0,0 +1,92 @@
+from sklearn.ensemble import RandomForestClassifier as model
+import numpy as np
+
+_PLUGIN_READABLE_NAMES = {"RandomForestClassifier":"default","RFclassifier":"alias","RFC":"alias"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "classification"}
+_PLUGIN_REQUIRED_SETTINGS = {}
+_PLUGIN_OPTIONAL_SETTINGS = {"max_depth": "int", "n_estimators": "int"} # model().get_params()
+_PLUGIN_REQUIRED_DATA = {"X","Y"}
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}
+
+class RandomForestClassifier(object):
+    """
+    A random forest classifier
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.clf = model()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(RandomForestClassifier) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        self.X = self._data_in["X"]
+        self.Y = np.array(self._data_in["Y"]).ravel()
+        self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        self.Y_tst = np.array(self._is_name_passed(self._data_in, "Y_test")).ravel()
+        # print(self._config["options"])
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+    
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+    
+    def _check_numeric(self, dict_opt):
+        for key, val in dict_opt.items():
+            """ 
+            TODO: maybe, if list -> cv
+            """
+            if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                val = float(val)
+                if val.is_integer():
+                    val = int(val)
+            dict_opt[key] = val
+        return dict_opt
+
+    def solve(self):
+        self._check_numeric(self._config["options"])
+        self.clf.set_params(**self._config["options"])
+        self.clf.fit(self.X, self.Y)
+
+    def predict(self,data):
+        return self.clf.predict(data)
+
+    def score(self,X_tst, Y_tst):
+        return self.clf.score(X_tst, Y_tst)
+
+    def _test(self, data):
+        if _PLUGIN_MODULE_OPTIONS['Type'] == 'classification':
+            print('Training accuracy: %.2f%%' %(self.score(self.X, self.Y)*100))
+            if self.Y_tst is not None:
+                print('Test accuracy: %.2f%%' %(self.score(self.X_tst, self.Y_tst)*100))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'regression':
+            print('Training R2 score: %.3f' %(self.score(self.X, self.Y)))
+            if self.Y_tst is not None:
+                print('Training R2 score: %.3f' %(self.score(self.X_tst, self.Y_tst)))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'clustering':
+            print('Clustering completed')
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/randomforestregressor.py b/aidesign/Modelling/plugins/randomforestregressor.py
new file mode 100644
index 00000000..1f2470c7
--- /dev/null
+++ b/aidesign/Modelling/plugins/randomforestregressor.py
@@ -0,0 +1,92 @@
+from sklearn.ensemble import RandomForestRegressor as model
+import numpy as np
+
+_PLUGIN_READABLE_NAMES = {"RandomForestRegressor":"default","RFRegressor":"alias","RFR":"alias"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "regression"}
+_PLUGIN_REQUIRED_SETTINGS = {}
+_PLUGIN_OPTIONAL_SETTINGS = {"max_depth": "int", "n_estimators": "int"} # model().get_params()
+_PLUGIN_REQUIRED_DATA = {"X","Y"}
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}
+
+class RandomForestRegressor(object):
+    """
+    A random forest regression
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.clf = model()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(RandomForestRegressor) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        self.X = self._data_in["X"]
+        self.Y = np.array(self._data_in["Y"]).ravel()
+        self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        self.Y_tst = np.array(self._is_name_passed(self._data_in, "Y_test")).ravel()
+        # print(self._config["options"])
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+    
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+    
+    def _check_numeric(self, dict_opt):
+        for key, val in dict_opt.items():
+            """ 
+            TODO: maybe, if list -> cv
+            """
+            if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                val = float(val)
+                if val.is_integer():
+                    val = int(val)
+            dict_opt[key] = val
+        return dict_opt
+
+    def solve(self):
+        self._check_numeric(self._config["options"])
+        self.clf.set_params(**self._config["options"])
+        self.clf.fit(self.X, self.Y)
+
+    def predict(self,data):
+        return self.clf.predict(data)
+
+    def score(self,X_tst, Y_tst):
+        return self.clf.score(X_tst, Y_tst)
+
+    def _test(self, data):
+        if _PLUGIN_MODULE_OPTIONS['Type'] == 'classification':
+            print('Training accuracy: %.2f%%' %(self.score(self.X, self.Y)*100))
+            if self.Y_tst is not None:
+                print('Test accuracy: %.2f%%' %(self.score(self.X_tst, self.Y_tst)*100))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'regression':
+            print('Training R2 score: %.3f' %(self.score(self.X, self.Y)))
+            if self.Y_tst is not None:
+                print('Training R2 score: %.3f' %(self.score(self.X_tst, self.Y_tst)))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'clustering':
+            print('Clustering completed')
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/regression.py b/aidesign/Modelling/plugins/regression.py
deleted file mode 100644
index 777bfeb8..00000000
--- a/aidesign/Modelling/plugins/regression.py
+++ /dev/null
@@ -1,73 +0,0 @@
-from sklearn import linear_model
-from sklearn.preprocessing import PolynomialFeatures
-import numpy as np
-from matplotlib import pyplot as plt
-
-_PLUGIN_READABLE_NAMES = {"Regression":"default","Regression":"alias","reg":"alias"}
-_PLUGIN_MODULE_OPTIONS = {}
-_PLUGIN_REQUIRED_SETTINGS = {"power":"int","train_name":"str","target_name":"str"}
-_PLUGIN_OPTIONAL_SETTINGS = {}
-_PLUGIN_REQUIRED_DATA = {"X","Y"}
-_PLUGIN_OPTIONAL_DATA = {}
-
-class Regression(object):
-    """Regression for supervised training"""
-
-    def __init__(self):
-        self.input_data = None
-        self.target_data = None
-        self.fit_degree = 1
-        self.regression_function = linear_model.LinearRegression()
-
-    def set_data_in(self,data_in):
-        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
-        if len(req_check) > 0:
-            raise Exception("Minimal Data Requirements not met"   \
-                            +"\n\t{0} ".format(Regression) \
-                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
-                            + "\n\tThe following data is missing:"\
-                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
-        self._data_in = data_in
-
-    def configure(self, config:dict):
-        self._config = config
-        self._parse_config()
-
-    def _parse_config(self):
-        self.input_data = self._data_in["X"]
-        self.target_data = self._data_in["Y"]
-        self.fit_degree = int(self._config["options"]["power"])
-
-    def _reshape(self,data,shape):
-        return data.reshape(shape[0],shape[1])
-
-    def _is_shape_allowed(self,arr):
-        return len(arr.shape) > 1 and len(arr.shape) <= 2
-
-    def _convert_to_poly(self,data):
-        self.poly = PolynomialFeatures(degree=self.fit_degree)
-        if not self._is_shape_allowed(data):
-            data = self._reshape(data,[len(data),1])
-        return self.poly.fit_transform(data)
-
-    def solve(self):
-        in_data_poly = self._convert_to_poly(self.input_data.values)
-        self.regression_function.fit(in_data_poly,self.target_data)
-
-    def predict(self,data):
-        data = self._convert_to_poly(data)
-        return self.regression_function.predict(data)
-        
-    def _test(self):
-        in_test = self.input_data.values*4
-        pred = self.predict(in_test)
-        plt.figure()
-        plt.plot(in_test,
-                    pred,
-                    c='r',
-                    label="Test Data. Degree: {0}".format(self.fit_degree))
-        plt.scatter(self.input_data,
-                    self.target_data.values,
-                    label="Training Data")
-        plt.legend()
-        plt.show()
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/ridgeregression.py b/aidesign/Modelling/plugins/ridgeregression.py
new file mode 100644
index 00000000..117e4e0d
--- /dev/null
+++ b/aidesign/Modelling/plugins/ridgeregression.py
@@ -0,0 +1,92 @@
+from sklearn.linear_model import Ridge as model
+import numpy as np
+
+_PLUGIN_READABLE_NAMES = {"Ridge":"default", "RidgeRegression": "alias"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "regression"}
+_PLUGIN_REQUIRED_SETTINGS = {}
+_PLUGIN_OPTIONAL_SETTINGS = {"alpha": "float"}
+_PLUGIN_REQUIRED_DATA = {"X","Y"}
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}
+
+class RidgeRegression(object):
+    """
+    Linear least squares with l2 regularization
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.clf = model()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(RidgeRegression) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        self.X = self._data_in["X"]
+        self.Y = np.array(self._data_in["Y"]).ravel()
+        self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        self.Y_tst = np.array(self._is_name_passed(self._data_in, "Y_test")).ravel()
+        # print(self._config["options"])
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+    
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+    
+    def _check_numeric(self, dict_opt):
+        for key, val in dict_opt.items():
+            """ 
+            TODO: maybe, if list -> cv
+            """
+            if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                val = float(val)
+                if val.is_integer():
+                    val = int(val)
+            dict_opt[key] = val
+        return dict_opt
+
+    def solve(self):
+        self._check_numeric(self._config["options"])
+        self.clf.set_params(**self._config["options"])
+        self.clf.fit(self.X, self.Y)
+
+    def predict(self,data):
+        return self.clf.predict(data)
+
+    def score(self,X_tst, Y_tst):
+        return self.clf.score(X_tst, Y_tst)
+
+    def _test(self, data):
+        if _PLUGIN_MODULE_OPTIONS['Type'] == 'classification':
+            print('Training accuracy: %.2f%%' %(self.score(self.X, self.Y)*100))
+            if self.Y_tst is not None:
+                print('Test accuracy: %.2f%%' %(self.score(self.X_tst, self.Y_tst)*100))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'regression':
+            print('Training R2 score: %.3f' %(self.score(self.X, self.Y)))
+            if self.Y_tst is not None:
+                print('Training R2 score: %.3f' %(self.score(self.X_tst, self.Y_tst)))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'clustering':
+            print('Clustering completed')
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/svc.py b/aidesign/Modelling/plugins/svc.py
new file mode 100644
index 00000000..17ec4fd6
--- /dev/null
+++ b/aidesign/Modelling/plugins/svc.py
@@ -0,0 +1,92 @@
+from sklearn.svm import SVC as model
+import numpy as np
+
+_PLUGIN_READABLE_NAMES = {"SVC":"default","SupportVectorClassification":"alias"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "classification"}
+_PLUGIN_REQUIRED_SETTINGS = {}
+_PLUGIN_OPTIONAL_SETTINGS = {"C": "float", "kernel": "str", "gamma": "float", "degree": "int"}
+_PLUGIN_REQUIRED_DATA = {"X","Y"}
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}
+
+class SVC(object):
+    """
+    C-Support Vector Classification
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.clf = model()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(SVC) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        self.X = self._data_in["X"]
+        self.Y = np.array(self._data_in["Y"]).ravel()
+        self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        self.Y_tst = np.array(self._is_name_passed(self._data_in, "Y_test")).ravel()
+        # print(self._config["options"])
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+    
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+    
+    def _check_numeric(self, dict_opt):
+        for key, val in dict_opt.items():
+            """ 
+            TODO: maybe, if list -> cv
+            """
+            if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                val = float(val)
+                if val.is_integer():
+                    val = int(val)
+            dict_opt[key] = val
+        return dict_opt
+
+    def solve(self):
+        self._check_numeric(self._config["options"])
+        self.clf.set_params(**self._config["options"])
+        self.clf.fit(self.X, self.Y)
+
+    def predict(self,data):
+        return self.clf.predict(data)
+
+    def score(self,X_tst, Y_tst):
+        return self.clf.score(X_tst, Y_tst)
+
+    def _test(self, data):
+        if _PLUGIN_MODULE_OPTIONS['Type'] == 'classification':
+            print('Training accuracy: %.2f%%' %(self.score(self.X, self.Y)*100))
+            if self.Y_tst is not None:
+                print('Test accuracy: %.2f%%' %(self.score(self.X_tst, self.Y_tst)*100))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'regression':
+            print('Training R2 score: %.3f' %(self.score(self.X, self.Y)))
+            if self.Y_tst is not None:
+                print('Training R2 score: %.3f' %(self.score(self.X_tst, self.Y_tst)))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'clustering':
+            print('Clustering completed')
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/svr.py b/aidesign/Modelling/plugins/svr.py
new file mode 100644
index 00000000..2bb2e1c4
--- /dev/null
+++ b/aidesign/Modelling/plugins/svr.py
@@ -0,0 +1,92 @@
+from sklearn.svm import SVR as model
+import numpy as np
+
+_PLUGIN_READABLE_NAMES = {"SVR":"default","SupportVectorRegression":"alias"}
+_PLUGIN_MODULE_OPTIONS = {"Type": "regression"}
+_PLUGIN_REQUIRED_SETTINGS = {}
+_PLUGIN_OPTIONAL_SETTINGS = {"C": "float", "kernel": "str", "gamma": "float", "degree": "int"}
+_PLUGIN_REQUIRED_DATA = {"X","Y"}
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}
+
+class SVR(object):
+    """
+    Epsilon-Support Vector Regression
+    """
+
+    def __init__(self):
+        self.X = None
+        self.Y = None
+        self.X_tst = None
+        self.Y_tst = None
+        self.clf = model()
+
+    def set_data_in(self,data_in):
+        req_check = [r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+        if len(req_check) > 0:
+            raise Exception("Minimal Data Requirements not met"   \
+                            +"\n\t{0} ".format(SVR) \
+                            +"requires data: {0}".format(_PLUGIN_REQUIRED_DATA)\
+                            + "\n\tThe following data is missing:"\
+                            + "\n\t\u2022 {}".format(",\n\t\u2022 ".join([*req_check])))
+        self._data_in = data_in
+
+    def configure(self, config:dict):
+        self._config = config
+        self._parse_config()
+
+    def _parse_config(self):
+        self.X = self._data_in["X"]
+        self.Y = np.array(self._data_in["Y"]).ravel()
+        self.X_tst = self._is_name_passed(self._data_in, "X_test")
+        self.Y_tst = np.array(self._is_name_passed(self._data_in, "Y_test")).ravel()
+        # print(self._config["options"])
+
+    def _is_name_passed(self, dic: dict, key: str, default = None):
+        return dic[key] if key in dic.keys() and dic[key] is not None else default
+    
+    def _reshape(self,data,shape):
+        return data.reshape(shape[0],shape[1])
+    
+    def _check_numeric(self, dict_opt):
+        for key, val in dict_opt.items():
+            """ 
+            TODO: maybe, if list -> cv
+            """
+            if type(val) == str and val.replace('.','').replace(',','').isnumeric():
+                val = float(val)
+                if val.is_integer():
+                    val = int(val)
+            dict_opt[key] = val
+        return dict_opt
+
+    def solve(self):
+        self._check_numeric(self._config["options"])
+        self.clf.set_params(**self._config["options"])
+        self.clf.fit(self.X, self.Y)
+
+    def predict(self,data):
+        return self.clf.predict(data)
+
+    def score(self,X_tst, Y_tst):
+        return self.clf.score(X_tst, Y_tst)
+
+    def _test(self, data):
+        if _PLUGIN_MODULE_OPTIONS['Type'] == 'classification':
+            print('Training accuracy: %.2f%%' %(self.score(self.X, self.Y)*100))
+            if self.Y_tst is not None:
+                print('Test accuracy: %.2f%%' %(self.score(self.X_tst, self.Y_tst)*100))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'regression':
+            print('Training R2 score: %.3f' %(self.score(self.X, self.Y)))
+            if self.Y_tst is not None:
+                print('Training R2 score: %.3f' %(self.score(self.X_tst, self.Y_tst)))
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
+        elif _PLUGIN_MODULE_OPTIONS['Type'] == 'clustering':
+            print('Clustering completed')
+            if self.X_tst is not None:
+                data.append_data_column("Y_pred", self.predict(self.X_tst))
+            return data
\ No newline at end of file
diff --git a/aidesign/utils/plugin_helpers.py b/aidesign/utils/plugin_helpers.py
index b325c850..4b5f0df6 100644
--- a/aidesign/utils/plugin_helpers.py
+++ b/aidesign/utils/plugin_helpers.py
@@ -92,12 +92,22 @@ def visit_Assign(self, node):
                 self.available_plugins[self.curr_module][self.curr_plugin][key] = val
 
     def get_option_specs(self, option:str) -> dict:
+        """Gets either required OR optional settings for each plugin.
+        
+        :param option: str containing either:
+                        "_PLUGIN_OPTIONAL_SETTINGS"
+                        OR
+                        "_PLUGIN_REQUIRED_SETTINGS"
+
+        :returns output: nested dict of options by [Module][Plugin Class Name]
+        """
         output = {}
         for module in self.available_plugins.keys():
             output[module] = {}
             for plugin in self.available_plugins[module].keys():
                 if option in self.available_plugins[module][plugin]:
-                    output[module][plugin] = self.available_plugins\
+                    cn = self.available_plugins[module][plugin]["_PLUGIN_CLASS_NAME"]
+                    output[module][cn] = self.available_plugins\
                                                         [module]\
                                                         [plugin]\
                                                         [option]
@@ -121,6 +131,10 @@ def names(self):
     def class_names(self):
         return self.get_option_specs("_PLUGIN_CLASS_NAME")
 
+    @property
+    def module_options(self):
+        return self.get_option_specs("_PLUGIN_MODULE_OPTIONS")
+
     @property
     def required_settings(self):
         return self.get_option_specs("_PLUGIN_REQUIRED_SETTINGS")
@@ -132,7 +146,7 @@ def class_descriptions(self):
     @property
     def optional_settings(self):
         return self.get_option_specs("_PLUGIN_OPTIONAL_SETTINGS")
-
+    
     @property
     def available_plugin_names(self):
         names = self.names
diff --git a/aidesign/utils/plugins/MainPage.py b/aidesign/utils/plugins/MainPage.py
index b6bc9f6d..e00f4b4b 100644
--- a/aidesign/utils/plugins/MainPage.py
+++ b/aidesign/utils/plugins/MainPage.py
@@ -21,7 +21,7 @@ def __init__(self, parent, controller, config:dict):
         self.controller = controller
         self.controller.title('AI Assisted Framework Design')
         
-        
+        self.controller.output_type = 'regression'
         self.bg = parent['bg']
         
         script_dir = os.path.dirname(__file__)
@@ -296,19 +296,24 @@ def start_dataloader(self):
         # self.s.load_XML(self.controller.output["xml_filename"])
         
         data = {}
+        isVar = [0] * len(self.var)
         if len(self.label_list[0].cget("text")) > 0:
             for i,filename in enumerate(self.filenames):
                 variable = self.var[i]
                 if filename is not None and len(filename) > 0:
                     if filename.lower().endswith(('.csv')):
                         data[variable] = pd.read_csv(filename) #Infers by default, should it be None?
+                        isVar[i] = 1
                         self.controller.s.append_input_data(variable,filename)
                         if i == 0:
                             self.controller.Data.set(True)
-
+                        if any(isVar[1::2]) and (
+                                len(pd.unique(data[variable].to_numpy().flatten())) < len(data[variable].to_numpy().flatten())*0.2):
+                            self.controller.output_type = 'classification'*all([float(i).is_integer() for i in data[variable].to_numpy().flatten()])
+            if not any(isVar[1::2]):
+                self.controller.output_type = 'unsupervised'
             self.newWindow.destroy()
             dataLoader(self.controller, data)
-
         else:
             tk.messagebox.showwarning(title = 'Error - X not specified',
                                       message = 'You need to specify X before proceeding.')
diff --git a/aidesign/utils/plugins/pipelineCanvas.py b/aidesign/utils/plugins/pipelineCanvas.py
new file mode 100644
index 00000000..7c3f89dd
--- /dev/null
+++ b/aidesign/utils/plugins/pipelineCanvas.py
@@ -0,0 +1,659 @@
+# 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.Data.xml_handler import XML_handler
+from aidesign.utils.plugin_helpers import PluginSpecs
+
+_PLUGIN_READABLE_NAMES = {"pipeline_canvas":"default","pipelineCanvas":"alias","pipeline canvas":"alias"}
+_PLUGIN_MODULE_OPTIONS = {"layer_priority": 2,
+                            "required_children": None}
+_PLUGIN_REQUIRED_SETTINGS = {}
+_PLUGIN_OPTIONAL_SETTINGS = {}
+
+class pipelineCanvas(tk.Frame):
+    """Canvas for the visualisation of the pipeline state."""
+    
+    def __init__(self, parent, controller, config:dict):
+        
+        """ Here we define the frame displayed for the plugin specification."""
+        
+        super().__init__(parent, bg = parent['bg'])
+        self.bg = parent['bg']
+        self.controller = controller
+        self.s = XML_handler()
+        
+        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.grid_rowconfigure(tuple(range(2)), weight=1)
+        self.grid_columnconfigure(0, weight=1)
+        
+        frame1 = tk.Frame(self, bg = self.bg)
+        self.frame2 = tk.Frame(self, bg = self.bg)
+        frame3 = tk.Frame(self, bg = self.bg)
+        self.frame4 = tk.Frame(self, bg = self.bg)
+        
+        # Create canvas
+        self.width, self.height = 600, 600
+        self.canvas = tk.Canvas(frame1, width=self.width, 
+            height=self.height, background="white")
+        self.canvas.pack(fill = tk.BOTH, expand = True, padx=(10,0), pady=10)
+        
+        self.w, self.h = 100, 50
+        self.cr = 4
+        """
+        TODO: Implement clicking on canvas actions
+        """
+        # self.canvas.bind('<Button-1>', self.on_click)
+        self.plugin = {}
+        self.allWeHearIs = []
+        
+        
+        self.upload()
+        
+        self.save_path = ''
+        self.saved = True
+        frame1.grid(column=0, row=0, sticky="nsew")
+        self.frame2.grid(column=1, row=0, sticky="ne")
+        frame3.grid(column=0, row=1, sticky="swe")
+        self.frame4.grid(column=1, row=1, sticky="sew")
+        
+        frame3.grid_columnconfigure(tuple(range(2)), weight=1)
+        self.frame4.grid_columnconfigure(tuple(range(2)), weight=1)
+        
+    def class_list(self,value):
+        """ Temporary fix """
+        return value
+
+    def on_click(self, event):
+        """ Passes the mouse click coordinates to the select function."""
+        self.select(event.x, event.y)
+        
+    def select(self, x: float, y:float):
+        """ 
+        Selects the module at the mouse location and updates the associated 
+        plugins as well as the colours. 
+        Blue means no plugin has been specified,
+        Orange means the module is selected.
+        Green means the plugin for this module is already set. 
+        :param x: float type of module x coordinate
+        :param y: float type of module y coordinate
+        """
+        self.selected = self.canvas.find_overlapping(
+            x-5, y-5, x+5, y+5)
+        if self.selected:
+            if len(self.selected) > 2:
+                self.canvas.selected = self.selected[-2]
+            else:
+                self.canvas.selected = self.selected[-1]
+        
+        if (self.m in self.plugin.keys()) and\
+                (self.plugin[self.m].get() != 'None') and \
+                (self.m not in self.id_done): # add
+                self.id_done.append(self.m)
+                print(np.array(self.module_names)[self.m == np.array(self.id_mod)][0])
+                self.s.append_plugin_to_module(self.plugin[self.m].get(),
+                                                 {**self.req_settings, **self.opt_settings},
+                                                 np.array(self.module_names)[self.m == np.array(self.id_mod)][0],
+                                                 True)
+        if self.m in self.id_done and self.m > 1:
+            self.canvas.itemconfig('p'+str(self.m), fill = '#46da63')
+        else:
+            self.canvas.itemconfig('p'+str(self.m), fill = self.bg)
+        
+        if len(self.canvas.gettags(self.canvas.selected)) > 0:
+            if not (len(self.canvas.gettags(self.canvas.selected)[0].split('-')) > 1) and\
+                not (self.canvas.gettags(self.canvas.selected)[0].split('-')[0] == 'loop'):
+                self.m = int(self.canvas.gettags(self.canvas.selected)[0][1:])
+            if self.m > 1:
+                if self.m not in self.id_done and self.m > 1:
+                    self.canvas.itemconfig('p'+str(self.m), fill = '#dbaa21')
+                for widget in self.allWeHearIs:
+                    widget.grid_remove()
+                
+            #     self.display_buttons()
+            #     module_number = self.id_mod.index(self.m)
+            #     if hasattr(self, 'button_forw'):
+            #         if module_number == len(self.id_mod)-1:
+            #             self.button_forw.config(text = 'Finish', bg = self.bg, 
+            #                 font = self.controller.pages_font,
+            #                 fg = 'white', height = 3, width = 15,
+            #                 command = self.finnish, state = tk.NORMAL, image = '')
+            #         else:
+            #             pCoord = self.canvas.coords('p'+str(self.id_mod[module_number+1]))
+            #             self.button_forw.config(image = self.forw_img, bg = self.bg, 
+            #                 command = lambda: self.select(
+            #                     pCoord[0], pCoord[1]), text = '', state = tk.NORMAL)
+            #         if module_number < 3:
+            #             self.button_back.config(image = self.back_img, bg = self.bg, 
+            #                 state = tk.DISABLED, text = '')
+            #         else:
+            #             mCoord = self.canvas.coords('p'+str(self.id_mod[module_number-1]))
+            #             self.button_back.config(image = self.back_img, bg = self.bg, 
+            #                 command = lambda: self.select(
+            #                     mCoord[0], mCoord[1]), text = '', state = tk.NORMAL)
+            #     else:
+            #         if module_number == len(self.id_mod)-1:
+            #             self.button_forw = tk.Button(
+            #                 self.frame4, text = 'Finish', bg = self.bg, 
+            #                 font = self.controller.pages_font,
+            #                 fg = 'white', height = 3, width = 15,
+            #                 command = self.finnish, state = tk.NORMAL, image = '')
+            #         else:
+            #             pCoord = self.canvas.coords('p'+str(self.id_mod[module_number+1]))
+            #             self.button_forw = tk.Button(
+            #                 self.frame4, image = self.forw_img, bg = self.bg, 
+            #                 command = lambda: self.select(
+            #                     pCoord[0], pCoord[1]), state = tk.NORMAL)
+            #         self.button_forw.grid(column = 1,row = 0, sticky="news", pady=(0,10), padx=(0,10))
+            #         if module_number < 3:
+            #             self.button_back = tk.Button(
+            #                 self.frame4, image = self.back_img, bg = self.bg, 
+            #                 state = tk.DISABLED)
+            #         else:
+            #             mCoord = self.canvas.coords('p'+str(self.id_mod[module_number-1]))
+            #             self.button_back = tk.Button(
+            #                 self.frame4, image = self.back_img, bg = self.bg, 
+            #                 command = lambda: self.select(
+            #                     mCoord[0], mCoord[1]), state = tk.NORMAL)
+            #         self.button_back.grid(column = 0,row = 0, sticky="news", pady=(0,10))
+            # else: # If user clicks on Initialiser or Output
+            #     # self.my_label.config(text = '')
+            #     for widget in self.allWeHearIs:
+            #         widget.grid_remove()
+            #     if hasattr(self, 'button_forw'):
+            #         self.button_back.config(image = self.back_img, bg = self.bg, 
+            #                 state = tk.DISABLED, text = '')
+            #         pCoord = self.canvas.coords('p'+str(self.id_mod[2]))
+            #         self.button_forw.config(image = self.forw_img, bg = self.bg, 
+            #                 command = lambda: self.select(
+            #                     pCoord[0], pCoord[1]), text = '', state = tk.NORMAL)
+
+    def finnish(self):
+        """ Calls function check_quit.
+        Before that, it checks if the current module plugins have been changed 
+        and, if so, updates their information in the XML_handler class.
+        """
+        if (self.m in self.plugin.keys()) and\
+                (self.plugin[self.m].get() != 'None') and \
+                (self.m not in self.id_done): # add
+                self.id_done.append(self.m)
+                self.s.append_plugin_to_module(self.plugin[self.m].get(),
+                                               {**self.req_settings, **self.opt_settings},
+                                                 np.array(self.module_names)[self.m == np.array(self.id_mod)][0],
+                                                 True)
+        self.check_quit()
+        
+    def display_buttons(self):
+        """ Updates the displayed radiobuttons and the description windows.
+        It loads the information corresponding to the selected module (self.m)
+        and shows the available plugins and their corresponding descriptions.
+        """
+        module = np.array(self.module_list)[self.m == np.array(self.id_mod)][0]
+        name = self.canvas.itemcget('t'+str(self.m), 'text')
+        # self.my_label.config(text = 'Choose a plugin for the '+name+' module')
+        ps = PluginSpecs()
+        plugin_list = list(ps.class_names[module].values())
+        plugin_list.append('Custom')
+        descriptions = list(ps.class_descriptions[module].values())
+        descriptions.append('User specified plugin')
+        if self.m not in self.plugin:
+            self.plugin[self.m] = tk.StringVar()
+            self.plugin[self.m].set(None)
+        self.allWeHearIs = []
+        for p, plug in enumerate(plugin_list):
+            rb = tk.Radiobutton(self.frame2, text = plug, fg = 'white', bg = self.bg,
+                height = 3, width = 20, var = self.plugin[self.m], 
+                selectcolor = 'black', value = plug,
+                font = self.controller.pages_font, command = self.optionsWindow)
+            rb.grid(column = 5+ (p%2 != 0), row = int(p/2)+1)
+            self.CreateToolTip(rb, text = descriptions[p])
+            self.allWeHearIs.append(rb)
+
+    def optionsWindow(self):
+        """ Function to create a new window displaying the available options 
+        of the selected plugin."""
+        
+        module = np.array(self.module_list)[self.m == np.array(self.id_mod)][0]
+        ps = PluginSpecs()
+        self.opt_settings = ps.optional_settings[module][self.plugin[self.m].get().lower()+'.py']
+        self.req_settings = ps.required_settings[module][self.plugin[self.m].get().lower()+'.py'] 
+        # req_settings = {'arg1': 'int', 'arg2': ['C', 'F']}
+        # opt_settings = {'arg3': 'int', 'arg4': ['C', 'F']}
+        if (len(self.opt_settings) != 0) or (len(self.req_settings) != 0):
+            if hasattr(self, 'newWindow') and (self.newWindow!= None):
+                self.newWindow.destroy()
+            self.newWindow = tk.Toplevel(self.controller)
+            # Window options
+            self.newWindow.title(self.plugin[self.m].get()+' plugin options')
+            script_dir = os.path.dirname(__file__)
+            self.tk.call('wm','iconphoto', self.newWindow, ImageTk.PhotoImage(
+                file = os.path.join(os.path.join(
+                    script_dir, 
+                    'resources', 
+                    'Assets', 
+                    'AIDIcon.ico'))))
+            self.newWindow.geometry("350x400")
+
+            frame1 = tk.Frame(self.newWindow)
+            frame4 = tk.Frame(self.newWindow)
+            
+            # Print settings
+            tk.Label(frame1,
+                  text ="Please indicate your desired options for the "+self.plugin[self.m].get()+" plugin.", anchor = tk.N, justify=tk.LEFT).pack(expand = True)
+            self.entry = []
+            # Required
+            r = 1
+            if len(self.req_settings) > 0:
+                frame2 = tk.Frame(self.newWindow)
+                tk.Label(frame2,
+                      text ="Required settings:", anchor = tk.W, justify=tk.LEFT).grid(row=0,column=0 , columnspan=2)
+                self.displaySettings(frame2, self.req_settings)
+                frame2.grid(column=0, row=r, sticky="nswe")
+                r += 1
+            # Optional
+            if len(self.opt_settings) > 0:
+                frame3 = tk.Frame(self.newWindow)
+                tk.Label(frame3,
+                      text ="Optional settings:", anchor = tk.W, justify=tk.LEFT).grid(row=0,column=0, columnspan=2)
+                self.displaySettings(frame3, self.opt_settings)
+                frame3.grid(column=0, row=r, sticky="nswe")
+                r += 1
+
+            self.entry[0].focus()
+            self.finishButton = tk.Button(
+                frame4, text = 'Finish', command = self.removewindow)
+            self.finishButton.grid(column = 1, row = r+1, sticky="es", pady=(0,10), padx=(0,10))
+            self.finishButton.bind("<Return>", lambda event: self.removewindow())
+            self.newWindow.protocol('WM_DELETE_WINDOW', self.removewindow)
+
+            frame1.grid(column=0, row=0, sticky="new")
+            frame4.grid(column=0, row=r, sticky="se")
+            self.newWindow.grid_rowconfigure(0, weight=1)
+            self.newWindow.grid_rowconfigure(tuple(range(r+1)), weight=2)
+            self.newWindow.grid_columnconfigure(0, weight=1)
+
+    def displaySettings(self, frame, settings):
+        """ Adds an entry for each input setting. Displays it in the specified row.
+        :param frame: tkinter frame type of frame
+        :param settings: dict type of plugin setting options
+        """
+        r = 1
+        for arg, val in settings.items():
+                tk.Label(frame,
+                  text = arg).grid(row=r,column=0)
+                self.entry.append(EntryWithPlaceholder(frame, val))
+                self.entry[-1].grid(row=r, column=1)
+                self.entry[-1].bind("<Return>", lambda event, a = len(self.entry): self.on_return_entry(a))
+                r += 1
+        frame.grid_rowconfigure(tuple(range(r)), weight=1)
+        frame.grid_columnconfigure(tuple(range(2)), weight=1)
+
+    def removewindow(self):
+        """ Stores settings options and closes window """
+        for e,ent in enumerate(self.entry):
+            if e < len(self.req_settings):
+                self.req_settings[list(self.req_settings.keys())[e]] = ent.get()
+            else:
+                print(self.opt_settings)
+                print(list(self.opt_settings.keys()))
+                print(e-len(self.req_settings))
+                self.opt_settings[list(self.opt_settings.keys())[e-len(self.req_settings)]] = ent.get()
+        self.newWindow.destroy()
+        self.newWindow = None
+        self.focus()
+
+    def on_return_entry(self, r):
+        """ Changes focus to the next available entry. When no more, focuses 
+        on the finish button.
+        : param r: int type of entry id.
+        """
+        if r < len(self.entry):
+            self.entry[r].focus()
+        else:
+            self.finishButton.focus()
+
+    def CreateToolTip(self, widget, text):
+        """ Calls ToolTip to create a window with a widget description. """
+        toolTip = ToolTip(widget)
+        def enter(event):
+            toolTip.showtip(text)
+        def leave(event):
+            toolTip.hidetip()
+        widget.bind('<Enter>', enter)
+        widget.bind('<Leave>', leave)
+        
+    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: str,x: float,y:float,ini = False,out = False):
+        """ Creates a rectangular module with the corresponding text inside.
+        
+        :param boxName: name of the model
+        :type boxName: str
+        :param x: float type of module x coordinate
+        :param y: float type of module y coordinate
+        :param ini: bool type of whether the module corresponds to initialiser.
+        :param out: bool type of whether the module corresponds to output.
+        """
+        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)
+        
+        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),))
+            
+        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),))
+        
+        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.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.startxy.append((x, y))
+        self.connections[self.modules] = {}
+        self.module_out(boxName)
+        self.module_list.append(boxName)
+        self.modules += 1
+
+    def upload(self):
+        """ Opens the XML file that was previously uploaded and places the 
+        modules, loops and connections in the canvas."""
+        
+        filename = self.controller.output["xml_filename"]
+        
+        self.reset()
+
+        self.s = XML_handler()
+        self.s.load_XML(filename)
+        self.s._print_pretty(self.s.loaded_modules)
+        modules = self.s.loaded_modules
+        modout = modules['output']
+        del modules['Initialiser'], modules['output'] # They are generated when resetting
+        self.disp_mod = ['Initialiser', 'output']
+        self.id_mod = [0, 1]
+        
+        # Place the modules
+        self.place_modules(modules)
+        connect = list(modout['coordinates'][2].keys())
+        for p, parent in enumerate(modout['parents']):
+                parent_id = self.id_mod[np.where(np.array(self.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.m = self.id_mod[2]
+        x0, y0, x1, y1 = self.canvas.coords('p'+str(self.m))
+        self.select(x0, y0)
+
+    def place_modules(self,modules: dict):
+        """Places the modules in the dictionary in the canvas.
+        :param modules: dict type of modules in the pipeline.
+        """
+        
+        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)})
+                self.place_modules(modules[key])
+            else:
+                # Display module
+                self.add_module(key,
+                                modules[key]['coordinates'][0][0],
+                                modules[key]['coordinates'][0][1])
+                self.module_list[-1] = modules[key]['module_type']
+                self.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 = self.id_mod[np.where(np.array(self.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(self.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(self.id_mod[-1]), modules[key]['coordinates'][2][connect[p]]))
+                        self.out_data.iloc[int(parent_id)][int(self.id_mod[-1])] = 1
+                        self.connections[int(self.id_mod[-1])][
+                            int(parent_id)] = out[0]+str(parent_id) + '-' + ins[0]+str(self.id_mod[-1])
+                    else:
+                        self.loops[-1]['mod'].append(key)
+                self.disp_mod.append(key)
+
+    def reset(self):
+        """ Resets the canvas and the stored information."""
+        self.canvas.delete(tk.ALL) # Reset canvas
+        
+        if hasattr(self, 'newWindow') and (self.newWindow!= None):
+            self.newWindow.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
+        self.id_done = [0,1]
+        self.plugin = {}
+        for widget in self.allWeHearIs:
+                widget.grid_remove()
+        self.allWeHearIs = []
+        # self.my_label.config(text = '')
+
+            
+    def check_quit(self):
+        """Checks if all plugins are specified to reset and go back to the startpage"""
+        if not len(self.id_done) == len(self.id_mod):
+            response = messagebox.askokcancel(
+                "Exit", 
+                "There are some unspecified plugins. Are you sure you want to leave?")
+            if response:
+                self.reset()
+                self.canvas.delete(tk.ALL)
+                self.saved = True
+                self.s.write_to_XML()
+                self.controller.Plugin.set(True)
+                self.controller._show_frame("MainPage")
+        # TODO: Check if loaded
+        elif len(self.s.loaded_modules) == 0:
+            self.controller._show_frame("MainPage")
+            self.controller.Plugin.set(False)
+        else:
+            self.reset()
+            self.canvas.delete(tk.ALL)
+            self.s.write_to_XML()
+            self.controller.Plugin.set(True)
+            self.controller._show_frame("MainPage")
+
+class ToolTip(object):
+    """ Defines a text window associated to a widget with a description. """
+    
+    def __init__(self, widget):
+        self.widget = widget
+        self.tipwindow = None
+        self.id = None
+        self.x = self.y = 0
+
+    def showtip(self, text):
+        "Display text in tooltip window"
+        self.text = text
+        if self.tipwindow or not self.text:
+            return
+        x, y, cx, cy = self.widget.bbox("insert")
+        x = x + self.widget.winfo_rootx() + 60
+        y = y + cy + self.widget.winfo_rooty() + 50
+        self.tipwindow = tw = tk.Toplevel(self.widget)
+        tw.wm_overrideredirect(1)
+        tw.wm_geometry("+%d+%d" % (x, y))
+        label = tk.Label(tw, text=self.text, justify=tk.LEFT,
+                      background="#d0d4d9", relief=tk.SOLID, borderwidth=1,
+                      font=("Helvetica", "8", "normal"))
+        label.pack(ipadx=1)
+
+    def hidetip(self):
+        tw = self.tipwindow
+        self.tipwindow = None
+        if tw:
+            tw.destroy()
+
+class EntryWithPlaceholder(tk.Entry):
+    """ Defines an entry with a placeholder text displayed if not focused on."""
+    def __init__(self, master=None, placeholder="PLACEHOLDER", color='grey'):
+        super().__init__(master)
+
+        self.placeholder = placeholder
+        self.placeholder_color = color
+        self.default_fg_color = self['fg']
+
+        self.bind("<FocusIn>", self.foc_in)
+        self.bind("<FocusOut>", self.foc_out)
+
+        self.put_placeholder()
+
+    def put_placeholder(self):
+        self.insert(0, self.placeholder)
+        self['fg'] = self.placeholder_color
+
+    def foc_in(self, *args):
+        if self['fg'] == self.placeholder_color:
+            self.delete('0', 'end')
+            self['fg'] = self.default_fg_color
+
+    def foc_out(self, *args):
+        if not self.get():
+            self.put_placeholder()
+
+if __name__ == "__main__":
+    app = pluginCanvas()
+    app.mainloop()
diff --git a/aidesign/utils/plugins/pluginCanvas.py b/aidesign/utils/plugins/pluginCanvas.py
index abce06df..019c748a 100644
--- a/aidesign/utils/plugins/pluginCanvas.py
+++ b/aidesign/utils/plugins/pluginCanvas.py
@@ -4,7 +4,6 @@
 import os
 import numpy as np
 import pandas as pd
-from tkinter.filedialog import asksaveasfile, askopenfile, askopenfilename
 from tkinter import messagebox
 
 from aidesign.Data.xml_handler import XML_handler
@@ -25,6 +24,7 @@ def __init__(self, parent, controller, config:dict):
         
         super().__init__(parent, bg = parent['bg'])
         self.bg = parent['bg']
+        self.parent = parent
         self.controller = controller
         self.s = XML_handler()
         
@@ -35,11 +35,12 @@ def __init__(self, parent, controller, config:dict):
                 'resources', 
                 'Assets', 
                 'AIDIcon.ico'))))
-        self.grid_rowconfigure(tuple(range(2)), weight=1)
+        self.grid_rowconfigure(tuple(range(5)), weight=1)
         self.grid_columnconfigure(0, weight=1)
         
         frame1 = tk.Frame(self, bg = self.bg)
         self.frame2 = tk.Frame(self, bg = self.bg)
+        self.framex = tk.Frame(self, bg = self.bg)
         frame3 = tk.Frame(self, bg = self.bg)
         self.frame4 = tk.Frame(self, bg = self.bg)
         
@@ -55,6 +56,7 @@ def __init__(self, parent, controller, config:dict):
         self.id_done = [0,1]
         self.id_mod = [0,1]
         self.plugin = {}
+        self.dataType = {}
         self.allWeHearIs = []
         
         self.my_label = tk.Label(self.frame2, 
@@ -63,7 +65,8 @@ def __init__(self, parent, controller, config:dict):
                     pady= 10,
                     font = self.controller.title_font,
                     bg = self.bg,
-                    fg = 'white')
+                    fg = 'white',
+                    anchor = tk.CENTER)
         self.my_label.grid(column = 5,
                             row = 0, columnspan = 2, padx=10)
         
@@ -88,16 +91,23 @@ def __init__(self, parent, controller, config:dict):
             height = 3, width = 15, font = self.controller.pages_font, 
             command = self.check_quit).grid(column = 1, row = 26, sticky="news", pady=(0,10))
         
+        self.frame_canvas = tk.Canvas(self.framex, bg = self.bg, bd = 0, highlightthickness=0)
+        self.tree_scrolly = tk.Scrollbar(self.framex, command=self.frame_canvas.yview)
+        self.tree_scrolly.pack(side=tk.RIGHT, fill = tk.Y)
+        self.tree_scrollx = tk.Scrollbar(self.framex, command=self.frame_canvas.xview, orient = 'horizontal')
+        self.tree_scrollx.pack(side=tk.BOTTOM, fill = tk.X)
+        
         self.save_path = ''
         self.saved = True
-        frame1.grid(column=0, row=0, sticky="nsew")
-        self.frame2.grid(column=1, row=0, sticky="ne")
-        frame3.grid(column=0, row=1, sticky="swe")
-        self.frame4.grid(column=1, row=1, sticky="sew")
-        
-        frame3.grid_columnconfigure(tuple(range(2)), weight=1)
-        self.frame4.grid_columnconfigure(tuple(range(2)), weight=1)
+        frame1.grid(column=0, row=0, rowspan=5, sticky="nsew")
+        self.frame2.grid(column=1, row=0, sticky="new")
+        frame3.grid(column=0, row=5, sticky="swe")
+        self.frame4.grid(column=1, row=5, sticky="sew")
         
+        # self.frame2.grid_columnconfigure(tuple(range(2)), weight=1)
+        frame3.grid_columnconfigure(tuple(range(2)), weight=2)
+        self.frame4.grid_columnconfigure(tuple(range(2)), weight=2)
+
     def class_list(self,value):
         """ Temporary fix """
         return value
@@ -128,7 +138,7 @@ def select(self, x: float, y:float):
                 (self.plugin[self.m].get() != 'None') and \
                 (self.m not in self.id_done): # add
                 self.id_done.append(self.m)
-                print(np.array(self.module_names)[self.m == np.array(self.id_mod)][0])
+                # print(np.array(self.module_names)[self.m == np.array(self.id_mod)][0])
                 self.s.append_plugin_to_module(self.plugin[self.m].get(),
                                                  {**self.req_settings, **self.opt_settings},
                                                  np.array(self.module_names)[self.m == np.array(self.id_mod)][0],
@@ -228,22 +238,55 @@ def display_buttons(self):
         """
         module = np.array(self.module_list)[self.m == np.array(self.id_mod)][0]
         name = self.canvas.itemcget('t'+str(self.m), 'text')
-        self.my_label.config(text = 'Choose a plugin for the '+name+' module')
         ps = PluginSpecs()
         plugin_list = list(ps.class_names[module].values())
         plugin_list.append('Custom')
         descriptions = list(ps.class_descriptions[module].values())
         descriptions.append('User specified plugin')
+        type_list = np.array([x.get('Type', None) for x in list(ps.module_options[module].values())])
+        type_list = np.append(type_list, None)
+        text = ''
+        if module.lower() == 'modelling':
+            text = '\nPluggins in white correspond to '+self.controller.output_type
+        self.my_label.config(text = 'Choose a plugin for the '+name+' module'+text)
         if self.m not in self.plugin:
             self.plugin[self.m] = tk.StringVar()
             self.plugin[self.m].set(None)
         self.allWeHearIs = []
+        
+        framexx = []
+        framexx_name = np.array([])
+        framexx_i = []
+        values = np.append(np.unique(type_list[type_list != None]), 'other') if not any(type_list == 'other') else np.unique(type_list[type_list != None])
+        for i,t in enumerate(values):
+            framexx.append(tk.Frame(self.framep, bg = self.bg))
+            framexx_name = np.append(framexx_name, t if t is not None else 'other')
+            framexx_i.append(0)
+            label = tk.Label(framexx[-1], 
+                        text = framexx_name[-1].capitalize(),
+                        pady= 10,
+                        font = self.controller.title_font,
+                        bg = self.bg,
+                        fg = 'white')
+            label.grid(column = 0,
+                                row = 0, padx = (10,0), sticky = 'nw')
+            framexx[-1].grid(column = 0, row = i, sticky="nsew")
+        unsupervised = ['clustering', 'RL']
         for p, plug in enumerate(plugin_list):
-            rb = tk.Radiobutton(self.frame2, text = plug, fg = 'white', bg = self.bg,
-                height = 3, width = 20, var = self.plugin[self.m], 
-                selectcolor = 'black', value = plug,
+            if module.lower() == 'modelling'and p < len(plugin_list)-1:
+                colour = 'white' if type_list[p] == self.controller.output_type or (self.controller.output_type == 'unsupervised' and type_list[p] in unsupervised) else 'grey'
+            else:
+                colour = 'white'
+            frame_idx = np.where(framexx_name == type_list[p])[0][0] if type_list[p] in framexx_name else np.where(framexx_name == 'other')[0][0]
+            rb = tk.Radiobutton(framexx[frame_idx], text = plug, fg = colour, bg = self.bg,
+                height = 3, var = self.plugin[self.m], 
+                selectcolor = 'black', value = plug, justify = tk.LEFT,
                 font = self.controller.pages_font, command = self.optionsWindow)
-            rb.grid(column = 5+ (p%2 != 0), row = int(p/2)+1)
+            rb.grid(column = int(framexx_i[frame_idx]%2 != 0), 
+                    row = int(framexx_i[frame_idx]/2)+1, 
+                    sticky = 'w',
+                    padx=(10, 0))
+            framexx_i[frame_idx] += 1
             self.CreateToolTip(rb, text = descriptions[p])
             self.allWeHearIs.append(rb)
 
@@ -253,10 +296,8 @@ def optionsWindow(self):
         
         module = np.array(self.module_list)[self.m == np.array(self.id_mod)][0]
         ps = PluginSpecs()
-        self.opt_settings = ps.optional_settings[module][self.plugin[self.m].get().lower()+'.py']
-        self.req_settings = ps.required_settings[module][self.plugin[self.m].get().lower()+'.py'] 
-        # req_settings = {'arg1': 'int', 'arg2': ['C', 'F']}
-        # opt_settings = {'arg3': 'int', 'arg4': ['C', 'F']}
+        self.opt_settings = ps.optional_settings[module][self.plugin[self.m].get()]
+        self.req_settings = ps.required_settings[module][self.plugin[self.m].get()] 
         if (len(self.opt_settings) != 0) or (len(self.req_settings) != 0):
             if hasattr(self, 'newWindow') and (self.newWindow!= None):
                 self.newWindow.destroy()
@@ -296,8 +337,8 @@ def optionsWindow(self):
                 self.displaySettings(frame3, self.opt_settings)
                 frame3.grid(column=0, row=r, sticky="nswe")
                 r += 1
-
-            self.entry[0].focus()
+            if len(self.entry) > 0:
+                self.entry[0].focus()
             self.finishButton = tk.Button(
                 frame4, text = 'Finish', command = self.removewindow)
             self.finishButton.grid(column = 1, row = r+1, sticky="es", pady=(0,10), padx=(0,10))
@@ -317,25 +358,49 @@ def displaySettings(self, frame, settings):
         """
         r = 1
         for arg, val in settings.items():
-                tk.Label(frame,
-                  text = arg).grid(row=r,column=0)
+            tk.Label(frame,
+              text = arg).grid(row=r,column=0)
+            if arg == 'Data':
+                if self.m not in self.dataType:
+                    self.dataType[self.m] = tk.StringVar()
+                    self.dataType[self.m].set('X')
+                tk.Radiobutton(frame, text = 'Input (X)', fg = 'black', 
+                               var = self.dataType[self.m], value = 'X'
+                               ).grid(row=r, column=1)
+                if len(self.s._get_all_elements_with_tag("Y")) > 0 or \
+                    len(self.s._get_all_elements_with_tag("Y_test")) > 0:
+                    tk.Radiobutton(frame, text = 'Output (Y)', fg = 'black', 
+                                   var = self.dataType[self.m], value = 'Y'
+                                   ).grid(row=r, column=2)
+            else:
                 self.entry.append(EntryWithPlaceholder(frame, val))
                 self.entry[-1].grid(row=r, column=1)
                 self.entry[-1].bind("<Return>", lambda event, a = len(self.entry): self.on_return_entry(a))
-                r += 1
+            r += 1
         frame.grid_rowconfigure(tuple(range(r)), weight=1)
         frame.grid_columnconfigure(tuple(range(2)), weight=1)
 
     def removewindow(self):
         """ Stores settings options and closes window """
+        self.req_settings.pop("Data", None)
+        req_keys = list(self.req_settings.keys())
+        opt_keys = list(self.opt_settings.keys())
         for e,ent in enumerate(self.entry):
             if e < len(self.req_settings):
-                self.req_settings[list(self.req_settings.keys())[e]] = ent.get()
+                if ent.get() == self.entry[e].placeholder or len(ent.get()) == 0:
+                    self.req_settings.pop(req_keys[e], None)
+                else:
+                    self.req_settings[req_keys[e]] = ent.get()
             else:
-                print(self.opt_settings)
-                print(list(self.opt_settings.keys()))
-                print(e-len(self.req_settings))
-                self.opt_settings[list(self.opt_settings.keys())[e-len(self.req_settings)]] = ent.get()
+                # print(self.opt_settings)
+                # print(list(self.opt_settings.keys()))
+                # print(e-len(self.req_settings))
+                if ent.get() == self.entry[e].placeholder or len(ent.get()) == 0:
+                    self.opt_settings.pop(opt_keys[e-len(req_keys)], None)
+                else:
+                    self.opt_settings[opt_keys[e-len(req_keys)]] = ent.get()
+        if self.m in self.dataType:
+            self.req_settings['Data'] = self.dataType[self.m].get()
         self.newWindow.destroy()
         self.newWindow = None
         self.focus()
@@ -471,7 +536,7 @@ def upload(self):
 
         self.s = XML_handler()
         self.s.load_XML(filename)
-        self.s._print_pretty(self.s.loaded_modules)
+        # self.s._print_pretty(self.s.loaded_modules)
         modules = self.s.loaded_modules
         modout = modules['output']
         del modules['Initialiser'], modules['output'] # They are generated when resetting
@@ -500,8 +565,27 @@ def upload(self):
                     int(parent_id)] = out[0]+str(parent_id) + '-' + ins[0]+str(1)
         self.m = self.id_mod[2]
         x0, y0, x1, y1 = self.canvas.coords('p'+str(self.m))
+        
+        # Configure frame for scrollbar
+        self.frame_canvas.configure(yscrollcommand=self.tree_scrolly.set, xscrollcommand=self.tree_scrollx.set)
+        self.frame_canvas.bind('<Configure>', lambda e: self.frame_canvas.configure(scrollregion = self.frame_canvas.bbox("all")))
+        self.framep = tk.Frame(self.frame_canvas, bg = self.bg)
+        self.frame_canvas.create_window((0,0), window = self.framep)
+        
+        self.set_mousewheel(self.frame_canvas, lambda e: self.frame_canvas.yview_scroll(-1*(e.delta//120), "units"))
+        
         self.select(x0, y0)
+        
+        self.framex.grid(column=1, row=1, sticky="nswe", rowspan=4, pady=(0,10))
+        self.framex.grid_columnconfigure(tuple(range(2)), weight=1)
+        self.frame_canvas.pack(side=tk.LEFT, fill = tk.BOTH, expand = True)
 
+    def set_mousewheel(self, widget, command):
+        """Activate / deactivate mousewheel scrolling when 
+        cursor is over / not over the widget respectively."""
+        widget.bind("<Enter>", lambda _: widget.bind_all('<MouseWheel>', command))
+        widget.bind("<Leave>", lambda _: widget.unbind_all('<MouseWheel>'))
+            
     def place_modules(self,modules: dict):
         """Places the modules in the dictionary in the canvas.
         :param modules: dict type of modules in the pipeline.
@@ -608,6 +692,7 @@ def check_quit(self):
             if response:
                 self.reset()
                 self.canvas.delete(tk.ALL)
+                self.frame_canvas.delete(tk.ALL)
                 self.saved = True
                 self.s.write_to_XML()
                 self.controller.Plugin.set(True)
@@ -619,6 +704,7 @@ def check_quit(self):
         else:
             self.reset()
             self.canvas.delete(tk.ALL)
+            self.frame_canvas.delete(tk.ALL)
             self.s.write_to_XML()
             self.controller.Plugin.set(True)
             self.controller._show_frame("MainPage")
diff --git a/run_UI.py b/run_UI.py
index b8eacc8a..34ef80e1 100644
--- a/run_UI.py
+++ b/run_UI.py
@@ -4,11 +4,11 @@
 # from setup import SetupPrequisties
 
 core = ai.Core()
-core.load_config_file(os.path.join(
-    ".", 
-    "Data",
-    'resources', 
-    'xml_files', 
-    'regerssion_test.xml'))
-# core.launch()
+# core.load_config_file(os.path.join(
+#     ".", 
+#     "Data",
+#     'resources', 
+#     'xml_files', 
+#     'proc_reg_test.xml'))
+core.launch()
 core.run()
\ No newline at end of file


Test patch:
diff --git a/aidesign/Data/resources/xml_files/SVR_test.xml b/aidesign/Data/resources/xml_files/SVR_test.xml
new file mode 100644
index 00000000..b5358e04
--- /dev/null
+++ b/aidesign/Data/resources/xml_files/SVR_test.xml
@@ -0,0 +1,45 @@
+<pipeline>
+    <Initialiser name="Initialiser">
+        <relationships>
+            <child name="Modelling" />
+        </relationships>
+        <coordinates>
+            [(300.0,50),0,{}]
+        </coordinates>
+        <inputdata>
+            <X file="./Data/resources/supervised_regression/X_tr.csv" />
+            <Y file="./Data/resources/supervised_regression/Y_tr.csv" />
+            <X_test file="./Data/resources/supervised_regression/X_tst.csv" />
+            <Y_test file="./Data/resources/supervised_regression/Y_tst.csv" />
+        </inputdata>
+    </Initialiser>
+    <Modelling name="Modelling">
+        <relationships>
+            <parent name="Initialiser" />
+            <child name="Output" />
+        </relationships>
+        <coordinates>
+            [(300.0,300.0),2,{0:'d0-u2'}]
+        </coordinates>
+        <plugin type="SVR">
+            <C>
+                 0.01
+            </C>
+            <kernel>
+                 rbf
+            </kernel>
+            <gamma>
+                 10
+            </gamma>
+        </plugin>
+    </Modelling>
+    <Output name="Output">
+        <plugin type="" />
+        <relationships>
+            <parent name="Modelling" />
+        </relationships>
+        <coordinates>
+            [(300.0,550),1,{2:'d2-u1'}]
+        </coordinates>
+    </Output>
+</pipeline>
diff --git a/aidesign/Data/resources/xml_files/classification_test.xml b/aidesign/Data/resources/xml_files/classification_test.xml
new file mode 100644
index 00000000..f39cbbe3
--- /dev/null
+++ b/aidesign/Data/resources/xml_files/classification_test.xml
@@ -0,0 +1,42 @@
+<pipeline>
+    <Initialiser name="Initialiser">
+        <relationships>
+            <child name="Modelling" />
+        </relationships>
+        <coordinates>
+            [(300.0,50),0,{}]
+        </coordinates>
+        <inputdata>
+            <X file="./Data/resources/supervised_classification/X_tr.csv" />
+            <Y file="./Data/resources/supervised_classification/Y_tr.csv" />
+            <X_test file="./Data/resources/supervised_classification/X_tst.csv" />
+            <Y_test file="./Data/resources/supervised_classification/Y_tst.csv" />
+        </inputdata>
+    </Initialiser>
+    <Modelling name="Modelling">
+        <relationships>
+            <parent name="Initialiser" />
+            <child name="Output" />
+        </relationships>
+        <coordinates>
+            [(300.0,300.0),2,{0:'d0-u2'}]
+        </coordinates>
+        <plugin type="KNNClassifier">
+            <n_neighbors>
+                 50
+            </n_neighbors>
+            <weights>
+                 distance
+            </weights>
+        </plugin>
+    </Modelling>
+    <Output name="Output">
+        <plugin type="" />
+        <relationships>
+            <parent name="Modelling" />
+        </relationships>
+        <coordinates>
+            [(300.0,550),1,{2:'d2-u1'}]
+        </coordinates>
+    </Output>
+</pipeline>
diff --git a/aidesign/Data/resources/xml_files/logisticregression_test.xml b/aidesign/Data/resources/xml_files/logisticregression_test.xml
new file mode 100644
index 00000000..d10e8e00
--- /dev/null
+++ b/aidesign/Data/resources/xml_files/logisticregression_test.xml
@@ -0,0 +1,42 @@
+<pipeline>
+    <Initialiser name="Initialiser">
+        <relationships>
+            <child name="Modelling" />
+        </relationships>
+        <coordinates>
+            [(300.0,50),0,{}]
+        </coordinates>
+        <inputdata>
+            <X file="./Data/resources/supervised_classification/X_tr.csv" />
+            <Y file="./Data/resources/supervised_classification/Y_tr.csv" />
+            <X_test file="./Data/resources/supervised_classification/X_tst.csv" />
+            <Y_test file="./Data/resources/supervised_classification/Y_tst.csv" />
+        </inputdata>
+    </Initialiser>
+    <Modelling name="Modelling">
+        <relationships>
+            <parent name="Initialiser" />
+            <child name="Output" />
+        </relationships>
+        <coordinates>
+            [(300.0,300.0),2,{0:'d0-u2'}]
+        </coordinates>
+        <plugin type="LogisticRegression">
+            <penalty>
+                 l2
+            </penalty>
+            <C>
+                 0.01
+            </C>
+        </plugin>
+    </Modelling>
+    <Output name="Output">
+        <plugin type="" />
+        <relationships>
+            <parent name="Modelling" />
+        </relationships>
+        <coordinates>
+            [(300.0,550),1,{2:'d2-u1'}]
+        </coordinates>
+    </Output>
+</pipeline>
diff --git a/aidesign/Data/resources/xml_files/proc_class_test.xml b/aidesign/Data/resources/xml_files/proc_class_test.xml
new file mode 100644
index 00000000..47a46169
--- /dev/null
+++ b/aidesign/Data/resources/xml_files/proc_class_test.xml
@@ -0,0 +1,49 @@
+<pipeline>
+    <Initialiser name="Initialiser">
+        <relationships>
+            <child name="Data Processing" />
+        </relationships>
+        <coordinates>
+            [(303,530),0,{}]
+        </coordinates>
+        <inputdata>
+            <X file="./Data/resources/supervised_classification/X_tr.csv" />
+            <Y file="./Data/resources/supervised_classification/Y_tr.csv" />
+            <X_test file="./Data/resources/supervised_classification/X_tst.csv" />
+            <Y_test file="./Data/resources/supervised_classification/Y_tst.csv" />
+        </inputdata>
+    </Initialiser>
+    <DataProcessing name="Data Processing">
+        <relationships>
+            <parent name="Initialiser" />
+            <child name="Modelling" />
+        </relationships>
+        <coordinates>
+            [(296,248),2,{0:'d0-u2'}]
+        </coordinates>
+        <plugin type="StandardScaler">
+            <Data>
+                X
+            </Data>
+        </plugin>
+    </DataProcessing>
+    <Modelling name="Modelling">
+        <relationships>
+            <parent name="Data Processing" />
+            <child name="Output" />
+        </relationships>
+        <coordinates>
+            [(302,391),3,{2:'d2-u3'}]
+        </coordinates>
+        <plugin type="RandomForestClassifier" />
+    </Modelling>
+    <Output name="Output">
+        <plugin type="" />
+        <relationships>
+            <parent name="Modelling" />
+        </relationships>
+        <coordinates>
+            [(300.0,550),1,{3:'d3-u1'}]
+        </coordinates>
+    </Output>
+</pipeline>
diff --git a/aidesign/Data/resources/xml_files/proc_clust_test.xml b/aidesign/Data/resources/xml_files/proc_clust_test.xml
new file mode 100644
index 00000000..191d200d
--- /dev/null
+++ b/aidesign/Data/resources/xml_files/proc_clust_test.xml
@@ -0,0 +1,47 @@
+<pipeline>
+    <Initialiser name="Initialiser">
+        <relationships>
+            <child name="Data Processing" />
+        </relationships>
+        <coordinates>
+            [(300.0,50),0,{}]
+        </coordinates>
+        <inputdata>
+            <X file="./Data/resources/supervised_regression/X_tr.csv" />
+            <X_test file="./Data/resources/supervised_regression/X_tst.csv" />
+        </inputdata>
+    </Initialiser>
+    <DataProcessing name="Data Processing">
+        <relationships>
+            <parent name="Initialiser" />
+            <child name="Modelling" />
+        </relationships>
+        <coordinates>
+            [(301,201),2,{0:'d0-u2'}]
+        </coordinates>
+        <plugin type="StandardScaler">
+            <Data>
+                X
+            </Data>
+        </plugin>
+    </DataProcessing>
+    <Modelling name="Modelling">
+        <relationships>
+            <parent name="Data Processing" />
+            <child name="Output" />
+        </relationships>
+        <coordinates>
+            [(300.0,300.0),3,{2:'d2-u3'}]
+        </coordinates>
+        <plugin type="KMeans" />
+    </Modelling>
+    <Output name="Output">
+        <plugin type="" />
+        <relationships>
+            <parent name="Modelling" />
+        </relationships>
+        <coordinates>
+            [(300.0,550),1,{3:'d3-u1'}]
+        </coordinates>
+    </Output>
+</pipeline>
diff --git a/aidesign/Data/resources/xml_files/proc_reg_test.xml b/aidesign/Data/resources/xml_files/proc_reg_test.xml
new file mode 100644
index 00000000..35b3f1bb
--- /dev/null
+++ b/aidesign/Data/resources/xml_files/proc_reg_test.xml
@@ -0,0 +1,49 @@
+<pipeline>
+    <Initialiser name="Initialiser">
+        <relationships>
+            <child name="Data Processing" />
+        </relationships>
+        <coordinates>
+            [(300.0,50),0,{}]
+        </coordinates>
+        <inputdata>
+            <X file="./Data/resources/supervised_regression/X_tr.csv" />
+            <Y file="./Data/resources/supervised_regression/Y_tr.csv" />
+            <X_test file="./Data/resources/supervised_regression/X_tst.csv" />
+            <Y_test file="./Data/resources/supervised_regression/Y_tst.csv" />
+        </inputdata>
+    </Initialiser>
+    <DataProcessing name="Data Processing">
+        <relationships>
+            <parent name="Initialiser" />
+            <child name="Modelling" />
+        </relationships>
+        <coordinates>
+            [(304,203),2,{0:'d0-u2'}]
+        </coordinates>
+        <plugin type="MinMaxScaler">
+            <Data>
+                 X
+            </Data>
+        </plugin>
+    </DataProcessing>
+    <Modelling name="Modelling">
+        <relationships>
+            <parent name="Data Processing" />
+            <child name="Output" />
+        </relationships>
+        <coordinates>
+            [(300.0,300.0),3,{2:'d2-u3'}]
+        </coordinates>
+        <plugin type="Lasso" />
+    </Modelling>
+    <Output name="Output">
+        <plugin type="" />
+        <relationships>
+            <parent name="Modelling" />
+        </relationships>
+        <coordinates>
+            [(300.0,550),1,{3:'d3-u1'}]
+        </coordinates>
+    </Output>
+</pipeline>
diff --git a/aidesign/Data/resources/xml_files/processing_test.xml b/aidesign/Data/resources/xml_files/processing_test.xml
new file mode 100644
index 00000000..892b782d
--- /dev/null
+++ b/aidesign/Data/resources/xml_files/processing_test.xml
@@ -0,0 +1,35 @@
+<pipeline>
+    <Initialiser name="Initialiser">
+        <relationships>
+            <child name="Data Processing" />
+        </relationships>
+        <coordinates>
+            [(300.0,50),0,{}]
+        </coordinates>
+        <inputdata>
+            <X file="./Data/resources/supervised_regression/X_tr.csv" />
+            <Y file="./Data/resources/supervised_regression/Y_tr.csv" />
+            <X_test file="./Data/resources/supervised_regression/X_tst.csv" />
+            <Y_test file="./Data/resources/supervised_regression/Y_tst.csv" />
+        </inputdata>
+    </Initialiser>
+    <DataProcessing name="Data Processing">
+        <relationships>
+            <parent name="Initialiser" />
+            <child name="Output" />
+        </relationships>
+        <coordinates>
+            [(300.0,300.0),2,{0:'d0-u2'}]
+        </coordinates>
+        <plugin type="StandardScaler" />
+    </DataProcessing>
+    <Output name="Output">
+        <plugin type="" />
+        <relationships>
+            <parent name="Data Processing" />
+        </relationships>
+        <coordinates>
+            [(300.0,550),1,{2:'d2-u1'}]
+        </coordinates>
+    </Output>
+</pipeline>
diff --git a/aidesign/Data/resources/xml_files/reg_proc_reg_test.xml b/aidesign/Data/resources/xml_files/reg_proc_reg_test.xml
new file mode 100644
index 00000000..269dedc5
--- /dev/null
+++ b/aidesign/Data/resources/xml_files/reg_proc_reg_test.xml
@@ -0,0 +1,67 @@
+<pipeline>
+    <Initialiser name="Initialiser">
+        <relationships>
+            <child name="Modelling" />
+        </relationships>
+        <coordinates>
+            [(300.0,50),0,{}]
+        </coordinates>
+        <inputdata>
+            <X file="./Data/resources/supervised_regression/X_tr.csv" />
+            <Y file="./Data/resources/supervised_regression/Y_tr.csv" />
+            <X_test file="./Data/resources/supervised_regression/X_tst.csv" />
+            <Y_test file="./Data/resources/supervised_regression/Y_tst.csv" />
+        </inputdata>
+    </Initialiser>
+    <Modelling name="Modelling">
+        <relationships>
+            <parent name="Initialiser" />
+            <child name="Data Processing" />
+        </relationships>
+        <coordinates>
+            [(300,180),2,{0:'d0-u2'}]
+        </coordinates>
+        <plugin type="RidgeRegression">
+            <alpha>
+                 0.01
+            </alpha>
+        </plugin>
+    </Modelling>
+    <DataProcessing name="Data Processing">
+        <relationships>
+            <parent name="Modelling" />
+            <child name="Modelling-1" />
+        </relationships>
+        <coordinates>
+            [(300.0,300.0),3,{2:'d2-u3'}]
+        </coordinates>
+        <plugin type="StandardScaler">
+            <with_mean>
+                 
+            </with_mean>
+        </plugin>
+    </DataProcessing>
+    <Modelling name="Modelling-1">
+        <relationships>
+            <parent name="Data Processing" />
+            <child name="Output" />
+        </relationships>
+        <coordinates>
+            [(300,408),4,{3:'d3-u4'}]
+        </coordinates>
+        <plugin type="RidgeRegression">
+            <alpha>
+                 0.01
+            </alpha>
+        </plugin>
+    </Modelling>
+    <Output name="Output">
+        <plugin type="" />
+        <relationships>
+            <parent name="Modelling-1" />
+        </relationships>
+        <coordinates>
+            [(300.0,550),1,{4:'d4-u1'}]
+        </coordinates>
+    </Output>
+</pipeline>
diff --git a/aidesign/Data/resources/xml_files/regerssion_test.xml b/aidesign/Data/resources/xml_files/regerssion_test.xml
index d403f14b..9accbcc4 100644
--- a/aidesign/Data/resources/xml_files/regerssion_test.xml
+++ b/aidesign/Data/resources/xml_files/regerssion_test.xml
@@ -7,8 +7,8 @@
             [(300.0,50),0,{}]
         </coordinates>
         <inputdata>
-            <X file="/home/chris/github/ai-assisted-design-framework/aidesign/Data/resources/supervised_regression/1/x_train.csv" />
-            <Y file="/home/chris/github/ai-assisted-design-framework/aidesign/Data/resources/supervised_regression/1/y_train.csv" />
+            <X file="./Data/resources/supervised_regression/1/x_train.csv" />
+            <Y file="./Data/resources/supervised_regression/1/y_train.csv" />
         </inputdata>
     </Initialiser>
     <Modelling name="Modelling">
diff --git a/aidesign/Data/resources/xml_files/ridge_test.xml b/aidesign/Data/resources/xml_files/ridge_test.xml
new file mode 100644
index 00000000..2ba254ed
--- /dev/null
+++ b/aidesign/Data/resources/xml_files/ridge_test.xml
@@ -0,0 +1,39 @@
+<pipeline>
+    <Initialiser name="Initialiser">
+        <relationships>
+            <child name="Modelling" />
+        </relationships>
+        <coordinates>
+            [(300.0,50),0,{}]
+        </coordinates>
+        <inputdata>
+            <X file="./Data/resources/supervised_regression/X_tr.csv" />
+            <Y file="./Data/resources/supervised_regression/Y_tr.csv" />
+            <X_test file="./Data/resources/supervised_regression/X_tst.csv" />
+            <Y_test file="./Data/resources/supervised_regression/Y_tst.csv" />
+        </inputdata>
+    </Initialiser>
+    <Modelling name="Modelling">
+        <relationships>
+            <parent name="Initialiser" />
+            <child name="Output" />
+        </relationships>
+        <coordinates>
+            [(300.0,300.0),2,{0:'d0-u2'}]
+        </coordinates>
+        <plugin type="RidgeRegression">
+            <alpha>
+                 0.02
+            </alpha>
+        </plugin>
+    </Modelling>
+    <Output name="Output">
+        <plugin type="" />
+        <relationships>
+            <parent name="Modelling" />
+        </relationships>
+        <coordinates>
+            [(300.0,550),1,{2:'d2-u1'}]
+        </coordinates>
+    </Output>
+</pipeline>
diff --git a/aidesign/Data/resources/xml_files/test.xml b/aidesign/Data/resources/xml_files/test.xml
index 08efb9dd..7d6cfc02 100644
--- a/aidesign/Data/resources/xml_files/test.xml
+++ b/aidesign/Data/resources/xml_files/test.xml
@@ -18,7 +18,7 @@
         </coordinates>
         <plugin type="ManualInput">
             <image_dir>
-                 C:\Users\sevisal\Documents\GitHub\ai-assisted-design-framework\aidesign\Data\resources\image_classification\training_images
+                 .\Data\resources\image_classification\training_images
             </image_dir>
         </plugin>
     </UserInteraction>