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

Issue:
update conda env part in README.md
Add compilers to conda env creation and clean up a bit.


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/sphinx.yml b/.github/workflows/sphinx.yml
new file mode 100644
index 00000000..f47767ca
--- /dev/null
+++ b/.github/workflows/sphinx.yml
@@ -0,0 +1,30 @@
+name: sphinx
+
+on: [push, pull_request, workflow_dispatch]
+jobs:
+  build-docs:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - uses: actions/setup-python@v2
+        with:
+          python-version: '3.10'
+      - name: pip cache
+        uses: actions/setup-python@v4
+        with:
+          python-version: ${{ matrix.python-version }}
+          cache: 'pip'
+      - name: install dependencies
+        run: python3 -m pip install sphinx sphinx-rtd-theme myst-parser
+      - name: build
+        run: |
+          sphinx-apidoc --templatedir docs/templates/apidoc -o docs/source src/vai_lab
+          sphinx-build -M html docs/source docs/build
+      - name: publish gh-pages
+        uses: peaceiris/actions-gh-pages@v3
+        if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
+        with:
+          publish_branch: gh-pages
+          github_token: ${{ secrets.GITHUB_TOKEN }}
+          publish_dir: docs/build/html
+          force_orphan: true
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index b59e92f6..13f4543d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
 __pycache__/
 **/__pycache__
 **/.vscode
+*.code-workspace
 **/*.pyc
 *.py[cod]
 *$py.class
@@ -72,7 +73,9 @@ instance/
 .scrapy
 
 # Sphinx documentation
-docs/_build/
+docs/build/
+docs/source/vai_lab*.rst
+docs/source/modules.rst
 
 # PyBuilder
 .pybuilder/
@@ -119,6 +122,7 @@ celerybeat.pid
 .venv
 env/
 venv/
+venv*/
 ENV/
 env.bak/
 venv.bak/
diff --git a/README.md b/README.md
index 6c3ed770..d5abe5b1 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,14 @@
-# Virtual Artificially Intelligent Laboratories (VAI Labs)
 
-![AIDBANNER](./aidesign/utils/resources/Assets/readme_images/VAIL_banner_image.png)
+[![CI Tests](https://img.shields.io/github/actions/workflow/status/AaltoPML/VAI-Lab/pythonpackage.yml?branch=main&label=CI%20Test&logo=github)](https://github.com/AaltoPML/VAI-Lab/actions/workflows/documentation.yml)[![Docs Test](https://img.shields.io/github/actions/workflow/status/AaltoPML/VAI-Lab/documentation.yml?branch=main&label=Docs&logo=github)](https://github.com/AaltoPML/VAI-Lab/actions/workflows/documentation.yml)
+[![PyPI Version](https://img.shields.io/pypi/v/vai-lab?color=blue)](https://pypi.org/project/vai-lab/) [![Python Versions](https://img.shields.io/pypi/pyversions/vai-lab?color=blue)](https://pypi.org/project/vai-lab/) [![Wheel](https://img.shields.io/pypi/wheel/vai-lab)](https://pypi.org/project/vai-lab/) [![License](https://img.shields.io/pypi/l/vai-lab)](https://pypi.org/project/vai-lab/)
 
-VAIL is a modular, easy-to-use framework for Virtual Laboraties for science and design, where Artifical Intelligence assists the user in their goals.
+
+
+# Virtual Artificially Intelligent Laboratories (VAI-Lab)
+
+![VAILBANNER](https://raw.githubusercontent.com/AaltoPML/VAI-Lab/main/imgs/VAIL_banner_image.png)
+
+VAI-Lab is a modular, easy-to-use framework for Virtual Laboraties for science and design, where Artifical Intelligence assists the user in their goals.
 
 > **Warning**
 > This project is currently a work in progress and is intended for wider use when a full release is made.
@@ -15,243 +21,66 @@ VAIL is a modular, easy-to-use framework for Virtual Laboraties for science and
 
 # How to Contribute
 
-The aim of this framework is to be a community effort that will benefit the science, engineering and more.
+The aim of this framework is to be a community effort that will benefit science, engineering and more.
 
 We are actively seeking contribution in the form of users, testers, developers, and anyone else who would like to contribute.
 
- - If you have methods which can be added to the framework, [get in touch](#Get-in-Touch)!
- - If you think this framework will be useful to your research, [get in touch](#Get-in-Touch)!
- - If want to get invovled in development, [get in touch](#Get-in-Touch)!
- - Noticed a bug or other issue? [get in touch](#Get-in-Touch)!
+ - If you have methods which can be added to the framework, [get in touch](https://github.com/AaltoPML/VAI-Lab#get-in-touch)!
+ - If you think this framework will be useful to your research, [get in touch](https://github.com/AaltoPML/VAI-Lab#get-in-touch)!
+ - If want to get invovled in development, [get in touch](https://github.com/AaltoPML/VAI-Lab#get-in-touch)!
+ - Noticed a bug or other issue? [get in touch](https://github.com/AaltoPML/VAI-Lab#get-in-touch)!
 
 # How it Works
 
-The VAIL framework uses a modular, plugin-based architecture.
+The VAI-Lab framework uses a modular, plugin-based architecture.
 
-```mermaid
-stateDiagram-v2
-
-    VAIL --> Module1
-    VAIL --> Module2
-    VAIL --> Module_n
-    Module1 --> plugin_1.1
-    Module1 --> plugin_1.2
-    Module1 --> plugin_1.m
-    Module2 --> plugin_2.1
-    Module2 --> plugin_2.2
-    Module2 --> plugin_2.m
-    Module_n --> plugin_m.1
-    Module_n --> plugin_m.2
-    Module_n --> plugin_m.n
-```
+![PLUGINDIAGRAM](https://raw.githubusercontent.com/AaltoPML/VAI-Lab/main/imgs/VAIL_plugin_diagram.png)
 
 Each module represents a process (e.g. Modelling) and each plugin is a specific implementation of that process (e.g. linear regression).
 
 Modules can be chained, looped and modified in real-time to create a highly customisable framework for the user's requirements.
 
-## Installation
+# Installation
 
-Clone this repository via HTTPS:
-```bash
-git clone https://github.com/AaltoPML/VAI-labs.git
-```
-OR SSH:
-```bash
-git clone git@github.com:AaltoPML/VAI-labs.git
-```
-Change directory
-```bash
-cd VAI-labs
-```
-Create a virtual environment and activate it using venv
-```bash
-python3 -m venv venv && source venv/bin/activate
-```
-or using conda
-```bash
-conda create --name aidesign python=3.8 && conda activate aidesign
-```
-Upgrade pip and install flit
-```bash
-python3 -m pip install -U pip flit
-```
-Install the package
-```bash
-flit install
-```
-
-
-
-## Launching and Examples
-
-To launch the framework with the GUI:
+## Install from package
 
-```python
-import aidesign as ai
+To install the latest pip release:
 
-core = ai.Core()
-core.run()
 ```
-
-or to execute an existing config file:
-
-```python
-import aidesign as ai
-
-core = ai.Core()
-core.load_config_file("<path_to_config_file>")
-core.run()
+pip install vai-lab
 ```
 
-### Examples
-
-Pre-made [examples](https://github.com/AaltoPML/ai-assisted-framework-design/tree/main/aidesign/examples/xml_files) show the syntax and form of the config files and pipeline as a whole, and are the best way to get started.
-
-Some basic use-cases are provided among many others:
- - [user_feedback_demo.xml](https://github.com/AaltoPML/ai-assisted-framework-design/blob/main/aidesign/examples/xml_files/user_feedback_demo.xml) Demonstrates manual image classification of chest X-rays
- - [canvas_demo.xml](https://github.com/AaltoPML/ai-assisted-framework-design/blob/main/aidesign/examples/xml_files/canvas_demo.xml) Launches the canvas state-action pair visualiser and editor
- - [regression_demo.xml](https://github.com/AaltoPML/ai-assisted-framework-design/blob/main/aidesign/examples/xml_files/regression_demo.xml) Demonstrates simple linear regression on a small set of sample data
-
-#### Launching examples:
-
-To demonstrate the syntax for launching examples using `user_feedback_demo.xml`:
-
-```python
-import aidesign as ai
+## Install from source 
 
-core = ai.Core()
-core.load_config_file(("./examples","xml_files",'user_feedback_demo.xml'))
-core.run()
+Clone this repository via HTTPS
+```bash,
+git clone https://github.com/AaltoPML/VAI-lab.git
 ```
-
-> Config file paths can be passed as path stings, a list or tuple of directory paths.
-
-Absolute paths, as well paths relative to the library's base directory can be used.
-For library-relative paths, starting a path definition with `"./"` defaults to the directory of `<path-to-this-repo>/ai-assisted-framework-design/aidesign/`
-
-
-# Defining Pipelines in GUI
-
-The VAIL module allows to define a pipeline and the relations within by drawing a flowchart on a canvas. This canvas always starts with an `initialiser` module and an `output` module and allows to define any number of modules between these two. To do so, the user needs to define the modules and the relations between them.
-
-![PipelineLoop](./aidesign/utils/resources/Assets/readme_images/VAIL_GUI_screenshot.png)
-
-### Modules
-At this moment, there are 7 possible modules for VAIL. `initialiser` and `output` are compulsory for the pipeline, the rest of them can be freely placed in the canvas. These are:
- - `Data processing`.
- - `Modelling`.
- - `Decision making`.
- - `User Feedback Adaptation`.
- - `Input data`.
-
-If you click on a module and drag it you can modify its position on the canvas.
-Finally, they can be deleted by clicking on the desired module and then clicking on the `Delete selection` button.
-
-### Module joining
-Each module object has a number of circles that can be used to join two modules. The initially clicked circle will be identified as the parent and the second one as the child (the output of the father is fed to the input of the child). There can be only one connection from each circle. As of this version, if you need to edit an existing connection you need to delete one of the connected modules.
-
-### Loops
-If you click on the canvas and drag, you can draw a rectangle that defines which modules are inside the loop. Upon releasing the button you are requested to input what type of loop you want and what condition should be fulfilled to end the loop.
-
-## Pipeline uploading via XML file
-
-The pipeline can also be defined uploading an existing XML file. The structure of the XML file is described in the Back-end section.
-
-# Plugin Examples
-
-### `manual_input`
-Requires the user to indicate to which class the specified data corresponds to.
-In the current example, the model needs to classify images and the model requires expert knowledge for specific images.
-The user needs to indicate which classes correspond to the image and save the results to send them to the model.
-
-### `canvas_input`
-Requires the user to give feedback to state-action pairs.
-It opens a tab for each interactable object in the model and either requires adding new state-action samples or to modify the existing ones.
-In the current example, the model has two interactable objects that require feedback in two forms: (1) an _angle_ for the state and for the action or (2) a tuple of _Cartesian coordinates_ for the state and for the action. It has been adapted to be able to give feedback to any number of objects. These, at the same time, can be either `sliding` or `rotating` objects. Specifically, `sliding` refers to objects that need Cartesian feedback in a two-dimensional space, while `rotating` refers to objects that require an angle. In order to give feedback, you can choose to either move the corresponding state-action pairs on the canvas or directly edit the tree view display. This last option results in an automatic update on the canvas of the state-action location.
-
-## Defining a pipeline in XML
-
-The pipeline structure is defined between the `pipeline` tags:
-```XML
-<pipeline>
-    ...
-</pipeline>
+or, alternatively, via SSH
+```bash
+git clone git@github.com:AaltoPML/VAI-lab.git
 ```
-
-### Initialise
-The `Initialise` tag is the dedicated entry point to the pipeline. No other entry points can be declared.
-
-Current options:
- - `name`: attribute for user defined name
- - `initial_data`: element for declaring directory for initial data
- - `relationships`: User defined names of modules this one is connected to
-
-Example from [canvas_demo.xml](https://github.com/AaltoPML/ai-assisted-framework-design/blob/main/aidesign/examples/xml_files/canvas_demo.xml)
-```XML
-<Initialiser name="Init">
-        <inputdata>
-            <X file="./examples/state-action/X_data.csv" />
-        </inputdata>
-        <relationships>
-            <child name="My First UserFeedback Module" />
-        </relationships>
-    </Initialiser>
+Change directory
+```bash
+cd VAI-lab
 ```
-
-### Loops
-Loop tags are used to iterate over a given set of modules until a condition is met. Loops can be nested and named.  
-
-See [basic_operation.py](https://github.com/AaltoPML/ai-assisted-framework-design/blob/main/aidesign/examples/xml_files/basic_operation.xml) for full example.
-Current options:
- - `type`: what variety of loop will this be: `for`, `while`, `manual`(user defined stopping condition on-the-fly)
- - `condition`: Termination condition for the loop. I'm not sure how to deal with the criteria for `while` loops
- - `name`: User defined name for loop
-```XML
-<loop type="for" condition="10" name="For Loop 1">
-    ...
-</loop>
+Install into a [virtual environment](https://docs.python.org/3/library/venv.html)
+```bash
+python3 -m venv .venv && source .venv/bin/activate && python3 -m pip install -U pip && python3 -m pip install .
 ```
-
-### Modules
-Modules are declared by tags matching their names, e.g. the GUI module is loaded with the `GUI` tag:
-
-Required:
- - `name`: Unique user defined name for module, so can be referenced later
- - `plugin`: The type of plugin to be loaded into the module, along with associated options.
- - `relationships`: User-defined names of the `parent` modules which this module receives data from and `child` modules that this module passes data to.
-
-Example from [ridge_regression_demo.xml](https://github.com/AaltoPML/ai-assisted-framework-design/blob/main/aidesign/examples/xml_files/ridge_regression_demo.xml):
-```XML
-     <Modelling name="Modelling">
-        <relationships>
-            <parent name="Initialiser" />
-            <child name="Output" />
-        </relationships>
-        <plugin type="RidgeRegression">
-            <alpha>
-                 0.02
-            </alpha>
-        </plugin>
-    </Modelling>
+or, alternatively, install into a [conda environment](https://conda.io/projects/conda/en/latest/user-guide/getting-started.html)
+```bash
+conda create --file environment.yml && conda activate vai-lab
 ```
+You can verify the installation was successful by [running unit tests](#run-unit-tests).
 
-## Data Definition
-
-Data is loaded from existing files in either the `Initialiser` or `Input Data` modules and is specified using the `inputdata` tags.
+# Run Unit Tests
 
-Example from [ridge_regression_demo.xml](https://github.com/AaltoPML/ai-assisted-framework-design/blob/main/aidesign/examples/xml_files/ridge_regression_demo.xml):
-
-```XML
-<inputdata>
-    <X file="./examples/supervised_regression/X_tr.csv" />
-    <Y file="./examples/supervised_regression/Y_tr.csv" />
-    <X_test file="./examples/supervised_regression/X_tst.csv" />
-    <Y_test file="./examples/supervised_regression/Y_tst.csv" />
-</inputdata>
+Run unit tests with pytest
+```bash
+pytest .
 ```
 
-### Writing Data
-Two methods are given to add data to the XML file. One for modules (`append_pipeline_module_to_file`) and one for data structures (`append_data_structure_field_to_file`).
 
 # Feature and Release Schedule :calendar:
 
@@ -264,4 +93,4 @@ Two methods are given to add data to the XML file. One for modules (`append_pipe
 If you would like contribute, test, give feedback, or ask questions about this framework, we'd like to hear from you!
 Email us at:
 - Chris McGreavy, chris.mcgreavy [at] aalto.fi
-- Carlos Sevilla-Salcedo, carlos.sevillasalcedo [at] aalto.fi
+- Carlos Sevilla-Salcedo, carlos.sevillasalcedo [at] aalto.fi
\ No newline at end of file
diff --git a/aidesign/DataProcessing/plugins/binarizer.py b/aidesign/DataProcessing/plugins/binarizer.py
deleted file mode 100644
index e4761698..00000000
--- a/aidesign/DataProcessing/plugins/binarizer.py
+++ /dev/null
@@ -1,47 +0,0 @@
-from aidesign._plugin_templates import DataProcessingT
-
-import pandas as pd
-from sklearn.preprocessing import Binarizer as model
-
-_PLUGIN_READABLE_NAMES = {"Binarizer":"default","binarizer":"alias"}    # type:ignore  
-_PLUGIN_MODULE_OPTIONS = {"Type": "encoder"}                            # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}                             # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {"threshold": "float"}                      # type:ignore
-_PLUGIN_REQUIRED_DATA = {}                                              # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"X","Y","X_tst", 'Y_tst'}                      # type:ignore
-
-class Binarizer(DataProcessingT):
-    """
-    Binarize data (set feature values to 0 or 1) according to a threshold
-    """
-
-    def __init__(self):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.proc = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def fit(self):
-        cleaned_options = self._clean_solver_options()
-        self.proc.set_params(**cleaned_options)
-        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
deleted file mode 100644
index 3e2e26ab..00000000
--- a/aidesign/DataProcessing/plugins/kbinsdiscretizer.py
+++ /dev/null
@@ -1,47 +0,0 @@
-from aidesign._plugin_templates import DataProcessingT
-
-import pandas as pd
-from sklearn.preprocessing import KBinsDiscretizer as model
-
-_PLUGIN_READABLE_NAMES = {"KBinsDiscretizer":"default"} # type:ignore
-_PLUGIN_MODULE_OPTIONS = {"Type": "encoder"}            # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}             # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {"n_bins": "int"}           # type:ignore
-_PLUGIN_REQUIRED_DATA = {}                              # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"X","Y","X_tst", 'Y_tst'}      # type:ignore
-
-class KBinsDiscretizer(DataProcessingT):
-    """
-    Bin continuous data into interval
-    """
-
-    def __init__(self):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.proc = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def fit(self):
-        cleaned_options = self._clean_solver_options()
-        self.proc.set_params(**cleaned_options)
-        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
deleted file mode 100644
index a9908045..00000000
--- a/aidesign/DataProcessing/plugins/labelbinarizer.py
+++ /dev/null
@@ -1,45 +0,0 @@
-from sklearn.preprocessing import LabelBinarizer as model
-from aidesign._plugin_templates import DataProcessingT
-import pandas as pd
-
-_PLUGIN_READABLE_NAMES = {"LabelBinarizer":"default"}                   # type:ignore
-_PLUGIN_MODULE_OPTIONS = {"Type": "encoder"}                            # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}                             # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {"neg_label": "int", "pos_label": "int"}    # type:ignore
-_PLUGIN_REQUIRED_DATA = {}                                              # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"X","Y","X_tst", 'Y_tst'}                      # type:ignore
-
-class LabelBinarizer(DataProcessingT):
-    """
-    Binarize labels in a one-vs-all fashion
-    """
-    def __init__(self):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.proc = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def fit(self):
-        cleaned_options = self._clean_solver_options()
-        self.proc.set_params(**cleaned_options)
-        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
deleted file mode 100644
index 594c4c20..00000000
--- a/aidesign/DataProcessing/plugins/labelencoder.py
+++ /dev/null
@@ -1,46 +0,0 @@
-from sklearn.preprocessing import LabelEncoder as model
-from aidesign._plugin_templates import DataProcessingT
-import pandas as pd
-
-_PLUGIN_READABLE_NAMES = {"LabelEncoder":"default","LE":"alias"}    # type:ignore
-_PLUGIN_MODULE_OPTIONS = {"Type": "encoder"}                        # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}                         # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {}                                      # type:ignore
-_PLUGIN_REQUIRED_DATA = {}                                          # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"X","Y","X_tst", 'Y_tst'}                  # type:ignore
-
-class LabelEncoder(DataProcessingT):
-    """
-    Encode target labels with value between 0 and n_classes-1
-    """
-
-    def __init__(self):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.proc = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def fit(self):
-        cleaned_options = self._clean_solver_options()
-        self.proc.set_params(**cleaned_options)
-        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
deleted file mode 100644
index c513aee0..00000000
--- a/aidesign/DataProcessing/plugins/maxabsscaler.py
+++ /dev/null
@@ -1,46 +0,0 @@
-from sklearn.preprocessing import MaxAbsScaler as model
-from aidesign._plugin_templates import DataProcessingT
-import pandas as pd
-
-_PLUGIN_READABLE_NAMES = {"MaxAbsScaler":"default"}     # type:ignore
-_PLUGIN_MODULE_OPTIONS = {"Type": "scaler"}             # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}             # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {}                          # type:ignore
-_PLUGIN_REQUIRED_DATA = {}                              # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"X","Y","X_tst", 'Y_tst'}      # type:ignore
-
-class MaxAbsScaler(DataProcessingT):
-    """
-    Scale each feature by its maximum absolute value
-    """
-
-    def __init__(self):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.proc = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def fit(self):
-        cleaned_options = self._clean_solver_options()
-        self.proc.set_params(**cleaned_options)
-        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
deleted file mode 100644
index a156adee..00000000
--- a/aidesign/DataProcessing/plugins/minmaxscaler.py
+++ /dev/null
@@ -1,47 +0,0 @@
-from sklearn.preprocessing import MinMaxScaler as model
-from aidesign._plugin_templates import DataProcessingT
-import pandas as pd
-
-_PLUGIN_READABLE_NAMES = {"MinMaxScaler":"default"}     # type:ignore
-_PLUGIN_MODULE_OPTIONS = {"Type": "scaler"}             # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}             # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {"feature_range": "tuple"}  # type:ignore
-_PLUGIN_REQUIRED_DATA = {}                              # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"X","Y","X_tst", 'Y_tst'}      # type:ignore
-
-class MinMaxScaler(DataProcessingT):
-    """
-    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):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.proc = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def fit(self):
-        cleaned_options = self._clean_solver_options()
-        self.proc.set_params(**cleaned_options)
-        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
deleted file mode 100644
index 0c66f8eb..00000000
--- a/aidesign/DataProcessing/plugins/multilabelbinarizer.py
+++ /dev/null
@@ -1,46 +0,0 @@
-from sklearn.preprocessing import MultiLabelBinarizer as model
-from aidesign._plugin_templates import DataProcessingT
-import pandas as pd
-
-_PLUGIN_READABLE_NAMES = {"MultiLabelBinarizer":"default"}  # type:ignore
-_PLUGIN_MODULE_OPTIONS = {"Type": "encoder"}                # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}                 # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {"classes": "array-like"}       # type:ignore
-_PLUGIN_REQUIRED_DATA = {}                                  # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"X","Y","X_tst", 'Y_tst'}          # type:ignore
-
-class MultiLabelBinarizer(DataProcessingT):
-    """
-    Transform between iterable of iterables and a multilabel format
-    """
-
-    def __init__(self):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.proc = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def fit(self):
-        cleaned_options = self._clean_solver_options()
-        self.proc.set_params(**cleaned_options)
-        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
deleted file mode 100644
index b8273e5d..00000000
--- a/aidesign/DataProcessing/plugins/normalizer.py
+++ /dev/null
@@ -1,51 +0,0 @@
-from aidesign._plugin_templates import DataProcessingT
-from aidesign._types import DataInterface
-
-from sklearn.preprocessing import Normalizer as model
-import pandas as pd
-
-_PLUGIN_READABLE_NAMES = {"Normalizer": "default",
-                          "Norm": "alias", "normalizer": "alias"}   # type:ignore
-_PLUGIN_MODULE_OPTIONS = {"Type": "scaler"}                         # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}                         # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {"norm": "str"}                         # type:ignore
-_PLUGIN_REQUIRED_DATA = {}                                          # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"X", "Y", "X_tst", 'Y_tst'}                # type:ignore
-
-
-class Normalizer(DataProcessingT):
-    """
-    Normalize samples individually to unit norm
-    """
-
-    def __init__(self):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.proc = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def fit(self):
-        cleaned_options = self._clean_solver_options()
-        self.proc.set_params(**cleaned_options)
-        self.proc.fit(self.X)
-
-    def transform(self, data: DataInterface) -> DataInterface:
-        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
diff --git a/aidesign/DataProcessing/plugins/onehotencoder.py b/aidesign/DataProcessing/plugins/onehotencoder.py
deleted file mode 100644
index 127735f2..00000000
--- a/aidesign/DataProcessing/plugins/onehotencoder.py
+++ /dev/null
@@ -1,46 +0,0 @@
-from sklearn.preprocessing import OneHotEncoder as model
-from aidesign._plugin_templates import DataProcessingT
-import pandas as pd
-
-_PLUGIN_READABLE_NAMES = {"OneHotEncoder":"default"}        # type:ignore
-_PLUGIN_MODULE_OPTIONS = {"Type": "encoder"}                # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}                 # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {"categories": "array-like"}    # type:ignore
-_PLUGIN_REQUIRED_DATA = {}                                  # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"X","Y","X_tst", 'Y_tst'}          # type:ignore
-
-class OneHotEncoder(DataProcessingT):
-    """
-    Encode categorical features as a one-hot numeric array
-    """
-
-    def __init__(self):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.proc = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def fit(self):
-        cleaned_options = self._clean_solver_options()
-        self.proc.set_params(**cleaned_options)
-        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
deleted file mode 100644
index f1d2c347..00000000
--- a/aidesign/DataProcessing/plugins/ordinalencoder.py
+++ /dev/null
@@ -1,48 +0,0 @@
-from sklearn.preprocessing import OrdinalEncoder as model
-from aidesign._plugin_templates import DataProcessingT
-import pandas as pd
-
-_PLUGIN_READABLE_NAMES = {"OrdinalEncoder": "default"}      # type:ignore
-_PLUGIN_MODULE_OPTIONS = {"Type": "encoder"}                # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}                 # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {"categories": "array-like"}    # type:ignore
-_PLUGIN_REQUIRED_DATA = {}                                  # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"X", "Y", "X_tst", 'Y_tst'}        # type:ignore
-
-
-class OrdinalEncoder(DataProcessingT):
-    """
-    Encode categorical features as an integer array
-    """
-
-    def __init__(self):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.proc = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def fit(self):
-        cleaned_options = self._clean_solver_options()
-        self.proc.set_params(**cleaned_options)
-        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
diff --git a/aidesign/DataProcessing/plugins/quantiletransformer.py b/aidesign/DataProcessing/plugins/quantiletransformer.py
deleted file mode 100644
index 472f7358..00000000
--- a/aidesign/DataProcessing/plugins/quantiletransformer.py
+++ /dev/null
@@ -1,49 +0,0 @@
-from sklearn.preprocessing import QuantileTransformer as model
-from aidesign._plugin_templates import DataProcessingT
-import pandas as pd
-
-_PLUGIN_READABLE_NAMES = {
-    "QuantileTransformer": "default", "Quantile": "alias"}  # type:ignore
-_PLUGIN_MODULE_OPTIONS = {"Type": "encoder"}                # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}                 # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {"n_quantiles": "int"}          # type:ignore
-_PLUGIN_REQUIRED_DATA = {}                                  # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"X", "Y", "X_tst", 'Y_tst'}        # type:ignore
-
-
-class QuantileTransformer(DataProcessingT):
-    """
-    Transform features using quantiles information
-    """
-
-    def __init__(self):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.proc = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def fit(self):
-        cleaned_options = self._clean_solver_options()
-        self.proc.set_params(**cleaned_options)
-        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
diff --git a/aidesign/DataProcessing/plugins/standardscaler.py b/aidesign/DataProcessing/plugins/standardscaler.py
deleted file mode 100644
index 34904d0e..00000000
--- a/aidesign/DataProcessing/plugins/standardscaler.py
+++ /dev/null
@@ -1,49 +0,0 @@
-from sklearn.preprocessing import StandardScaler as model
-from aidesign._plugin_templates import DataProcessingT
-import pandas as pd
-
-_PLUGIN_READABLE_NAMES = {
-    "StandardScaler": "default", "standardscaler": "alias"}     # type:ignore
-_PLUGIN_MODULE_OPTIONS = {"Type": "scaler"}                     # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}                     # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {"with_mean": "bool"}               # type:ignore
-_PLUGIN_REQUIRED_DATA = {}                                      # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"X", "Y", "X_tst", 'Y_tst'}            # type:ignore
-
-
-class StandardScaler(DataProcessingT):
-    """
-    Standardize features by removing the mean and scaling to unit variance
-    """
-
-    def __init__(self):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.proc = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def fit(self):
-        cleaned_options = self._clean_solver_options()
-        self.proc.set_params(**cleaned_options)
-        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
diff --git a/aidesign/Modelling/plugins/affinitypropagation.py b/aidesign/Modelling/plugins/affinitypropagation.py
deleted file mode 100644
index 2f0b529e..00000000
--- a/aidesign/Modelling/plugins/affinitypropagation.py
+++ /dev/null
@@ -1,61 +0,0 @@
-from aidesign._plugin_templates import ModellingPluginT
-from sklearn.cluster import AffinityPropagation as model
-
-_PLUGIN_READABLE_NAMES = {"Birch": "default"}       # type:ignore
-_PLUGIN_MODULE_OPTIONS = {"Type": "clustering"}     # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {}                      # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {"damping": "float"}    # type:ignore
-_PLUGIN_REQUIRED_DATA = {"X"}                       # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"Y", "X_tst", 'Y_tst'}     # type:ignore
-
-
-class AffinityPropagation(ModellingPluginT):
-    """
-    Perform Affinity Propagation Clustering of data
-    """
-
-    def __init__(self):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.clf = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def solve(self):
-        """Sends params to solver, then runs solver"""
-        self.clf.set_params(**self._config["options"])
-        self.clf.fit(self.X, self.Y)
-
-    def predict(self, data):
-        """Uses fitted model to predict output of a given Y
-        :param data: array-like or sparse matrix, shape (n_samples, n_features)
-                    Samples
-                    expected type: aidesign.Data.Data_core.Data
-        :returns: array, shape (n_samples,)
-                    Returns predicted values.
-        """
-        return self.clf.predict(data)
-
-    def score(self, X, Y, sample_weight=None):
-        """Return the coefficient of determination
-        :param  X : array-like of shape (n_samples, n_features)
-        :param  Y :  array-like of shape (n_samples,) or (n_samples, n_outputs)
-        :param sample_weight : array-like of shape (n_samples,), default=None
-                    Sample weights.
-
-        :returns: score : float R^2` of ``self.predict(X)`` wrt. `y`.
-        """
-        return self.clf.score(X, Y, sample_weight=None)
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/bayesianridge.py b/aidesign/Modelling/plugins/bayesianridge.py
deleted file mode 100644
index a4e29dbb..00000000
--- a/aidesign/Modelling/plugins/bayesianridge.py
+++ /dev/null
@@ -1,61 +0,0 @@
-from aidesign._plugin_templates import ModellingPluginT
-from sklearn.linear_model import BayesianRidge as model
-
-_PLUGIN_READABLE_NAMES = {"BayesianRidge": "default"}   # type:ignore
-_PLUGIN_MODULE_OPTIONS = {"Type": "regression"}         # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {}                          # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {"n_iter": "int"}           # type:ignore
-_PLUGIN_REQUIRED_DATA = {"X", "Y"}                      # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}              # type:ignore
-
-
-class BayesianRidge(ModellingPluginT):
-    """
-    Bayesian ridge regression
-    """
-
-    def __init__(self):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.clf = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def solve(self):
-        """Sends params to solver, then runs solver"""
-        self.clf.set_params(**self._config["options"])
-        self.clf.fit(self.X, self.Y)
-
-    def predict(self, data):
-        """Uses fitted model to predict output of a given Y
-        :param data: array-like or sparse matrix, shape (n_samples, n_features)
-                    Samples
-                    expected type: aidesign.Data.Data_core.Data
-        :returns: array, shape (n_samples,)
-                    Returns predicted values.
-        """
-        return self.clf.predict(data)
-
-    def score(self, X, Y, sample_weight=None):
-        """Return the coefficient of determination
-        :param  X : array-like of shape (n_samples, n_features)
-        :param  Y :  array-like of shape (n_samples,) or (n_samples, n_outputs)
-        :param sample_weight : array-like of shape (n_samples,), default=None
-                    Sample weights.
-
-        :returns: score : float R^2` of ``self.predict(X)`` wrt. `y`.
-        """
-        return self.clf.score(X, Y, sample_weight=None)
diff --git a/aidesign/Modelling/plugins/birch.py b/aidesign/Modelling/plugins/birch.py
deleted file mode 100644
index 4b426d3c..00000000
--- a/aidesign/Modelling/plugins/birch.py
+++ /dev/null
@@ -1,62 +0,0 @@
-from aidesign._plugin_templates import ModellingPluginT
-from sklearn.cluster import Birch as model
-
-_PLUGIN_READABLE_NAMES = {"Birch": "default"}       # type:ignore
-_PLUGIN_MODULE_OPTIONS = {"Type": "clustering"}     # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {}                      # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {"n_clusters": "int",   # type:ignore
-                             "threshold": "float"}  # type:ignore
-_PLUGIN_REQUIRED_DATA = {"X"}                       # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"Y", "X_tst", 'Y_tst'}     # type:ignore
-
-
-class Birch(ModellingPluginT):
-    """
-    Implements the BIRCH clustering algorithm
-    """
-
-    def __init__(self):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.clf = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def solve(self):
-        """Sends params to solver, then runs solver"""
-        self.clf.set_params(**self._config["options"])
-        self.clf.fit(self.X, self.Y)
-
-    def predict(self, data):
-        """Uses fitted model to predict output of a given Y
-        :param data: array-like or sparse matrix, shape (n_samples, n_features)
-                    Samples
-                    expected type: aidesign.Data.Data_core.Data
-        :returns: array, shape (n_samples,)
-                    Returns predicted values.
-        """
-        return self.clf.predict(data)
-
-    def score(self, X, Y, sample_weight=None):
-        """Return the coefficient of determination
-        :param  X : array-like of shape (n_samples, n_features)
-        :param  Y :  array-like of shape (n_samples,) or (n_samples, n_outputs)
-        :param sample_weight : array-like of shape (n_samples,), default=None
-                    Sample weights.
-
-        :returns: score : float R^2` of ``self.predict(X)`` wrt. `y`.
-        """
-        return self.clf.score(X, Y, sample_weight=None)
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/decisiontreeclassifier.py b/aidesign/Modelling/plugins/decisiontreeclassifier.py
deleted file mode 100644
index 33d4f140..00000000
--- a/aidesign/Modelling/plugins/decisiontreeclassifier.py
+++ /dev/null
@@ -1,62 +0,0 @@
-from aidesign._plugin_templates import ModellingPluginT
-from sklearn.tree import DecisionTreeClassifier as model
-
-_PLUGIN_READABLE_NAMES = {"DecissionTreeClassifier": "default",
-                          "DTClassifier": "alias"}              # type:ignore
-_PLUGIN_MODULE_OPTIONS = {"Type": "classification"}             # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {}                                  # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {"max_depth": "int"}                # type:ignore
-_PLUGIN_REQUIRED_DATA = {"X", "Y"}                              # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}                      # type:ignore
-
-
-class DecisionTreeClassifier(ModellingPluginT):
-    """
-    A decision tree classifier
-    """
-
-    def __init__(self):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.clf = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def solve(self):
-        """Sends params to solver, then runs solver"""
-        self.clf.set_params(**self._config["options"])
-        self.clf.fit(self.X, self.Y)
-
-    def predict(self, data):
-        """Uses fitted model to predict output of a given Y
-        :param data: array-like or sparse matrix, shape (n_samples, n_features)
-                    Samples
-                    expected type: aidesign.Data.Data_core.Data
-        :returns: array, shape (n_samples,)
-                    Returns predicted values.
-        """
-        return self.clf.predict(data)
-
-    def score(self, X, Y, sample_weight=None):
-        """Return the coefficient of determination
-        :param  X : array-like of shape (n_samples, n_features)
-        :param  Y :  array-like of shape (n_samples,) or (n_samples, n_outputs)
-        :param sample_weight : array-like of shape (n_samples,), default=None
-                    Sample weights.
-
-        :returns: score : float R^2` of ``self.predict(X)`` wrt. `y`.
-        """
-        return self.clf.score(X, Y, sample_weight=None)
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/decisiontreeregressor.py b/aidesign/Modelling/plugins/decisiontreeregressor.py
deleted file mode 100644
index 64cb7d80..00000000
--- a/aidesign/Modelling/plugins/decisiontreeregressor.py
+++ /dev/null
@@ -1,62 +0,0 @@
-from aidesign._plugin_templates import ModellingPluginT
-from sklearn.tree import DecisionTreeRegressor as model
-
-_PLUGIN_READABLE_NAMES = {"DecisionTreeRegressor": "default",
-                          "DTregressor": "alias"}               # type:ignore
-_PLUGIN_MODULE_OPTIONS = {"Type": "regression"}                 # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {}                                  # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {"max_depth": "int"}                # type:ignore
-_PLUGIN_REQUIRED_DATA = {"X", "Y"}                              # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}                      # type:ignore
-
-
-class DecisionTreeRegressor(ModellingPluginT):
-    """
-    A decision tree regressor
-    """
-
-    def __init__(self):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.clf = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def solve(self):
-        """Sends params to solver, then runs solver"""
-        self.clf.set_params(**self._config["options"])
-        self.clf.fit(self.X, self.Y)
-
-    def predict(self, data):
-        """Uses fitted model to predict output of a given Y
-        :param data: array-like or sparse matrix, shape (n_samples, n_features)
-                    Samples
-                    expected type: aidesign.Data.Data_core.Data
-        :returns: array, shape (n_samples,)
-                    Returns predicted values.
-        """
-        return self.clf.predict(data)
-
-    def score(self, X, Y, sample_weight=None):
-        """Return the coefficient of determination
-        :param  X : array-like of shape (n_samples, n_features)
-        :param  Y :  array-like of shape (n_samples,) or (n_samples, n_outputs)
-        :param sample_weight : array-like of shape (n_samples,), default=None
-                    Sample weights.
-
-        :returns: score : float R^2` of ``self.predict(X)`` wrt. `y`.
-        """
-        return self.clf.score(X, Y, sample_weight=None)
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/elasticnet.py b/aidesign/Modelling/plugins/elasticnet.py
deleted file mode 100644
index 1621561e..00000000
--- a/aidesign/Modelling/plugins/elasticnet.py
+++ /dev/null
@@ -1,62 +0,0 @@
-from aidesign._plugin_templates import ModellingPluginT
-from sklearn.linear_model import ElasticNet as model
-
-_PLUGIN_READABLE_NAMES = {"ElasticNet": "default"}      # type:ignore
-_PLUGIN_MODULE_OPTIONS = {"Type": "regression"}         # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {}                          # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {"alpha": "float",
-                             "l1_ratio": "float"}       # type:ignore
-_PLUGIN_REQUIRED_DATA = {"X", "Y"}                      # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}              # type:ignore
-
-
-class ElasticNet(ModellingPluginT):
-    """
-    Linear regression with combined L1 and L2 priors as regularizer
-    """
-
-    def __init__(self):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.clf = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def solve(self):
-        """Sends params to solver, then runs solver"""
-        self.clf.set_params(**self._config["options"])
-        self.clf.fit(self.X, self.Y)
-
-    def predict(self, data):
-        """Uses fitted model to predict output of a given Y
-        :param data: array-like or sparse matrix, shape (n_samples, n_features)
-                    Samples
-                    expected type: aidesign.Data.Data_core.Data
-        :returns: array, shape (n_samples,)
-                    Returns predicted values.
-        """
-        return self.clf.predict(data)
-
-    def score(self, X_tst, Y_tst, sample_weight=None):
-        """Return the coefficient of determination
-        :param  X_tst : array-like of shape (n_samples, n_features)
-        :param  Y_tst :  array-like of shape (n_samples,) or (n_samples, n_outputs)
-        :param sample_weight : array-like of shape (n_samples,), default=None
-                    Sample weights.
-
-        :returns: score : float R^2` of ``self.predict(X)`` wrt. `y`.
-        """
-        return self.clf.score(X_tst, Y_tst, sample_weight)
diff --git a/aidesign/Modelling/plugins/gpclassifier.py b/aidesign/Modelling/plugins/gpclassifier.py
deleted file mode 100644
index 307bf85d..00000000
--- a/aidesign/Modelling/plugins/gpclassifier.py
+++ /dev/null
@@ -1,64 +0,0 @@
-from aidesign._plugin_templates import ModellingPluginT
-from sklearn.gaussian_process import GaussianProcessClassifier as model
-
-_PLUGIN_READABLE_NAMES = {"GPClassifier": "default",
-                          "GPC": "alias",
-                          "GaussianProcessClassifier": "alias"}     # type:ignore
-_PLUGIN_MODULE_OPTIONS = {"Type": "classification"}                 # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {}                                      # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {"n_restarts_optimizer": "int",
-                             "random_state": "int"}                 # type:ignore
-_PLUGIN_REQUIRED_DATA = {"X", "Y"}                                  # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}                          # type:ignore
-
-
-class GPClassifier(ModellingPluginT):
-    """
-    Gaussian process Classifier (GPC) based on Laplace approximation
-    """
-
-    def __init__(self):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.clf = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def solve(self):
-        """Sends params to solver, then runs solver"""
-        self.clf.set_params(**self._config["options"])
-        self.clf.fit(self.X, self.Y)
-
-    def predict(self, data):
-        """Uses fitted model to predict output of a given Y
-        :param data: array-like or sparse matrix, shape (n_samples, n_features)
-                    Samples
-                    expected type: aidesign.Data.Data_core.Data
-        :returns: array, shape (n_samples,)
-                    Returns predicted values.
-        """
-        return self.clf.predict(data)
-
-    def score(self, X, Y, sample_weight=None):
-        """Return the coefficient of determination
-        :param  X : array-like of shape (n_samples, n_features)
-        :param  Y :  array-like of shape (n_samples,) or (n_samples, n_outputs)
-        :param sample_weight : array-like of shape (n_samples,), default=None
-                    Sample weights.
-
-        :returns: score : float R^2` of ``self.predict(X)`` wrt. `y`.
-        """
-        return self.clf.score(X, Y, sample_weight=None)
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/gpregressor.py b/aidesign/Modelling/plugins/gpregressor.py
deleted file mode 100644
index c07b3f06..00000000
--- a/aidesign/Modelling/plugins/gpregressor.py
+++ /dev/null
@@ -1,64 +0,0 @@
-from aidesign._plugin_templates import ModellingPluginT
-from sklearn.gaussian_process import GaussianProcessRegressor as model
-
-_PLUGIN_READABLE_NAMES = {"GPRegressor": "default",
-                          "GPR": "alias",
-                          "GaussianProcessRegressor": "alias"}      # type:ignore
-_PLUGIN_MODULE_OPTIONS = {"Type": "regression"}                     # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {}                                      # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {"n_restarts_optimizer": "int",
-                             "random_state": "int"}                 # type:ignore
-_PLUGIN_REQUIRED_DATA = {"X", "Y"}                                  # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}                          # type:ignore
-
-
-class GPRegressor(ModellingPluginT):
-    """
-    Gaussian process regressor
-    """
-
-    def __init__(self):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.clf = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def solve(self):
-        """Sends params to solver, then runs solver"""
-        self.clf.set_params(**self._config["options"])
-        self.clf.fit(self.X, self.Y)
-
-    def predict(self, data):
-        """Uses fitted model to predict output of a given Y
-        :param data: array-like or sparse matrix, shape (n_samples, n_features)
-                    Samples
-                    expected type: aidesign.Data.Data_core.Data
-        :returns: array, shape (n_samples,)
-                    Returns predicted values.
-        """
-        return self.clf.predict(data)
-
-    def score(self, X, Y, sample_weight=None):
-        """Return the coefficient of determination
-        :param  X : array-like of shape (n_samples, n_features)
-        :param  Y :  array-like of shape (n_samples,) or (n_samples, n_outputs)
-        :param sample_weight : array-like of shape (n_samples,), default=None
-                    Sample weights.
-
-        :returns: score : float R^2` of ``self.predict(X)`` wrt. `y`.
-        """
-        return self.clf.score(X, Y, sample_weight=None)
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/kernelridge.py b/aidesign/Modelling/plugins/kernelridge.py
deleted file mode 100644
index eb73e53f..00000000
--- a/aidesign/Modelling/plugins/kernelridge.py
+++ /dev/null
@@ -1,65 +0,0 @@
-from aidesign._plugin_templates import ModellingPluginT
-from sklearn.kernel_ridge import KernelRidge as model
-
-_PLUGIN_READABLE_NAMES = {"KernelRidge": "default",
-                          "KR": "alias"}                # type:ignore
-_PLUGIN_MODULE_OPTIONS = {"Type": "regression"}         # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {}                          # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {"alpha": "float",
-                             "kernel": "str",
-                             "gamma": "float",
-                             "degree": "int"}           # type:ignore
-_PLUGIN_REQUIRED_DATA = {"X", "Y"}                      # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}              # type:ignore
-
-
-class KernelRidge(ModellingPluginT):
-    """
-    Kernel ridge regression.
-    """
-
-    def __init__(self):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.clf = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def solve(self):
-        """Sends params to solver, then runs solver"""
-        self.clf.set_params(**self._config["options"])
-        self.clf.fit(self.X, self.Y)
-
-    def predict(self, data):
-        """Uses fitted model to predict output of a given Y
-        :param data: array-like or sparse matrix, shape (n_samples, n_features)
-                    Samples
-                    expected type: aidesign.Data.Data_core.Data
-        :returns: array, shape (n_samples,)
-                    Returns predicted values.
-        """
-        return self.clf.predict(data)
-
-    def score(self, X, Y, sample_weight=None):
-        """Return the coefficient of determination
-        :param  X : array-like of shape (n_samples, n_features)
-        :param  Y :  array-like of shape (n_samples,) or (n_samples, n_outputs)
-        :param sample_weight : array-like of shape (n_samples,), default=None
-                    Sample weights.
-
-        :returns: score : float R^2` of ``self.predict(X)`` wrt. `y`.
-        """
-        return self.clf.score(X, Y, sample_weight=None)
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/kmeans.py b/aidesign/Modelling/plugins/kmeans.py
deleted file mode 100644
index 0879145b..00000000
--- a/aidesign/Modelling/plugins/kmeans.py
+++ /dev/null
@@ -1,62 +0,0 @@
-from aidesign._plugin_templates import ModellingPluginT
-from sklearn.cluster import KMeans as model
-
-_PLUGIN_READABLE_NAMES = {"KMeans": "default"}      # type:ignore
-_PLUGIN_MODULE_OPTIONS = {"Type": "clustering"}     # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {}                      # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {"n_clusters": "int",
-                             "n_init": "int"}       # type:ignore
-_PLUGIN_REQUIRED_DATA = {"X"}                       # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"Y", "X_tst", 'Y_tst'}     # type:ignore
-
-
-class KMeans(ModellingPluginT):
-    """
-    K-Means clustering
-    """
-
-    def __init__(self):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.clf = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def solve(self):
-        """Sends params to solver, then runs solver"""
-        self.clf.set_params(**self._config["options"])
-        self.clf.fit(self.X, self.Y)
-
-    def predict(self, data):
-        """Uses fitted model to predict output of a given Y
-        :param data: array-like or sparse matrix, shape (n_samples, n_features)
-                    Samples
-                    expected type: aidesign.Data.Data_core.Data
-        :returns: array, shape (n_samples,)
-                    Returns predicted values.
-        """
-        return self.clf.predict(data)
-
-    def score(self, X, Y, sample_weight=None):
-        """Return the coefficient of determination
-        :param  X : array-like of shape (n_samples, n_features)
-        :param  Y :  array-like of shape (n_samples,) or (n_samples, n_outputs)
-        :param sample_weight : array-like of shape (n_samples,), default=None
-                    Sample weights.
-
-        :returns: score : float R^2` of ``self.predict(X)`` wrt. `y`.
-        """
-        return self.clf.score(X, Y, sample_weight=None)
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/knnclassifier.py b/aidesign/Modelling/plugins/knnclassifier.py
deleted file mode 100644
index 40dc3610..00000000
--- a/aidesign/Modelling/plugins/knnclassifier.py
+++ /dev/null
@@ -1,63 +0,0 @@
-from aidesign._plugin_templates import ModellingPluginT
-from sklearn.neighbors import KNeighborsClassifier as model
-
-_PLUGIN_READABLE_NAMES = {"KNNClassifier": "default",
-                          "KNN-C": "alias"}             # type:ignore
-_PLUGIN_MODULE_OPTIONS = {"Type": "classification"}     # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {}                          # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {"n_neighbors": "int",
-                             "weights": "str"}          # type:ignore
-_PLUGIN_REQUIRED_DATA = {"X", "Y"}                      # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}              # type:ignore
-
-
-class KNNclassifier(ModellingPluginT):
-    """
-    Classifier implementing the k-nearest neighbors vote
-    """
-
-    def __init__(self):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.clf = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def solve(self):
-        """Sends params to solver, then runs solver"""
-        self.clf.set_params(**self._config["options"])
-        self.clf.fit(self.X, self.Y)
-
-    def predict(self, data):
-        """Uses fitted model to predict output of a given Y
-        :param data: array-like or sparse matrix, shape (n_samples, n_features)
-                    Samples
-                    expected type: aidesign.Data.Data_core.Data
-        :returns: array, shape (n_samples,)
-                    Returns predicted values.
-        """
-        return self.clf.predict(data)
-
-    def score(self, X, Y, sample_weight=None):
-        """Return the coefficient of determination
-        :param  X : array-like of shape (n_samples, n_features)
-        :param  Y :  array-like of shape (n_samples,) or (n_samples, n_outputs)
-        :param sample_weight : array-like of shape (n_samples,), default=None
-                    Sample weights.
-
-        :returns: score : float R^2` of ``self.predict(X)`` wrt. `y`.
-        """
-        return self.clf.score(X, Y, sample_weight=None)
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/knnregressor.py b/aidesign/Modelling/plugins/knnregressor.py
deleted file mode 100644
index 78e4c78a..00000000
--- a/aidesign/Modelling/plugins/knnregressor.py
+++ /dev/null
@@ -1,63 +0,0 @@
-from aidesign._plugin_templates import ModellingPluginT
-from sklearn.neighbors import KNeighborsRegressor as model
-
-_PLUGIN_READABLE_NAMES = {"KNNRegressor": "default",
-                          "KNN-R": "alias"}             # type:ignore
-_PLUGIN_MODULE_OPTIONS = {"Type": "regression"}         # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {}                          # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {"n_neighbors": "int",
-                             "weights": "str"}          # type:ignore  
-_PLUGIN_REQUIRED_DATA = {"X", "Y"}                      # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}              # type:ignore
-
-
-class KNNRegressor(ModellingPluginT):
-    """
-    Regression based on k-nearest neighbors
-    """
-
-    def __init__(self):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.clf = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def solve(self):
-        """Sends params to solver, then runs solver"""
-        self.clf.set_params(**self._config["options"])
-        self.clf.fit(self.X, self.Y)
-
-    def predict(self, data):
-        """Uses fitted model to predict output of a given Y
-        :param data: array-like or sparse matrix, shape (n_samples, n_features)
-                    Samples
-                    expected type: aidesign.Data.Data_core.Data
-        :returns: array, shape (n_samples,)
-                    Returns predicted values.
-        """
-        return self.clf.predict(data)
-
-    def score(self, X, Y, sample_weight=None):
-        """Return the coefficient of determination
-        :param  X : array-like of shape (n_samples, n_features)
-        :param  Y :  array-like of shape (n_samples,) or (n_samples, n_outputs)
-        :param sample_weight : array-like of shape (n_samples,), default=None
-                    Sample weights.
-
-        :returns: score : float R^2` of ``self.predict(X)`` wrt. `y`.
-        """
-        return self.clf.score(X, Y, sample_weight=None)
diff --git a/aidesign/Modelling/plugins/lasso.py b/aidesign/Modelling/plugins/lasso.py
deleted file mode 100644
index abc8d026..00000000
--- a/aidesign/Modelling/plugins/lasso.py
+++ /dev/null
@@ -1,60 +0,0 @@
-from aidesign._plugin_templates import ModellingPluginT
-from sklearn.linear_model import Lasso as model
-
-_PLUGIN_READABLE_NAMES = {"Lasso":"default"}        # type:ignore
-_PLUGIN_MODULE_OPTIONS = {"Type": "regression"}     # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {}                      # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {"alpha": "float"}      # type:ignore
-_PLUGIN_REQUIRED_DATA = {"X","Y"}                   # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}          # type:ignore
-
-class Lasso(ModellingPluginT):
-    """
-    Linear Model trained with L1 prior as regularizer
-    """
-
-    def __init__(self):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.clf = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def solve(self):
-        """Sends params to solver, then runs solver"""
-        self.clf.set_params(**self._config["options"])
-        self.clf.fit(self.X, self.Y)
-
-    def predict(self, data):
-        """Uses fitted model to predict output of a given Y
-        :param data: array-like or sparse matrix, shape (n_samples, n_features)
-                    Samples
-                    expected type: aidesign.Data.Data_core.Data
-        :returns: array, shape (n_samples,)
-                    Returns predicted values.
-        """
-        return self.clf.predict(data)
-
-    def score(self, X, Y, sample_weight=None):
-        """Return the coefficient of determination
-        :param  X : array-like of shape (n_samples, n_features)
-        :param  Y :  array-like of shape (n_samples,) or (n_samples, n_outputs)
-        :param sample_weight : array-like of shape (n_samples,), default=None
-                    Sample weights.
-
-        :returns: score : float R^2` of ``self.predict(X)`` wrt. `y`.
-        """
-        return self.clf.score(X, Y, sample_weight=None)
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/linearregression.py b/aidesign/Modelling/plugins/linearregression.py
deleted file mode 100644
index fbb6232d..00000000
--- a/aidesign/Modelling/plugins/linearregression.py
+++ /dev/null
@@ -1,62 +0,0 @@
-from aidesign._plugin_templates import ModellingPluginT
-from sklearn.linear_model import LinearRegression as model
-
-_PLUGIN_READABLE_NAMES = {"LinearRegression": "default",
-                          "LR": "alias"}                    # type:ignore
-_PLUGIN_MODULE_OPTIONS = {"Type": "regression"}             # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {}                              # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {}                              # type:ignore
-_PLUGIN_REQUIRED_DATA = {"X", "Y"}                          # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}                  # type:ignore
-
-
-class LinearRegression(ModellingPluginT):
-    """
-    Ordinary least squares Linear Regression
-    """
-
-    def __init__(self):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.clf = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def solve(self):
-        """Sends params to solver, then runs solver"""
-        self.clf.set_params(**self._config["options"])
-        self.clf.fit(self.X, self.Y)
-
-    def predict(self, data):
-        """Uses fitted model to predict output of a given Y
-        :param data: array-like or sparse matrix, shape (n_samples, n_features)
-                    Samples
-                    expected type: aidesign.Data.Data_core.Data
-        :returns: array, shape (n_samples,)
-                    Returns predicted values.
-        """
-        return self.clf.predict(data)
-
-    def score(self, X, Y, sample_weight=None):
-        """Return the coefficient of determination
-        :param  X : array-like of shape (n_samples, n_features)
-        :param  Y :  array-like of shape (n_samples,) or (n_samples, n_outputs)
-        :param sample_weight : array-like of shape (n_samples,), default=None
-                    Sample weights.
-
-        :returns: score : float R^2` of ``self.predict(X)`` wrt. `y`.
-        """
-        return self.clf.score(X, Y, sample_weight=None)
diff --git a/aidesign/Modelling/plugins/logisticregression.py b/aidesign/Modelling/plugins/logisticregression.py
deleted file mode 100644
index 7ad50423..00000000
--- a/aidesign/Modelling/plugins/logisticregression.py
+++ /dev/null
@@ -1,63 +0,0 @@
-from aidesign._plugin_templates import ModellingPluginT
-from sklearn.linear_model import LogisticRegression as model
-
-_PLUGIN_READABLE_NAMES = {"LogisticRegression": "default",
-                          "logit": "alias",
-                          "MaxEnt": "alias"}                    # type:ignore
-_PLUGIN_MODULE_OPTIONS = {"Type": "classification"}             # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {}                                  # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {"penalty": "str", "C": "float"}    # type:ignore
-_PLUGIN_REQUIRED_DATA = {"X", "Y"}                              # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}                      # type:ignore
-
-
-class LogisticRegression(ModellingPluginT):
-    """
-    Logistic regression classification.
-    """
-
-    def __init__(self):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.clf = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def solve(self):
-        """Sends params to solver, then runs solver"""
-        self.clf.set_params(**self._config["options"])
-        self.clf.fit(self.X, self.Y)
-
-    def predict(self, data):
-        """Uses fitted model to predict output of a given Y
-        :param data: array-like or sparse matrix, shape (n_samples, n_features)
-                    Samples
-                    expected type: aidesign.Data.Data_core.Data
-        :returns: array, shape (n_samples,)
-                    Returns predicted values.
-        """
-        return self.clf.predict(data)
-
-    def score(self, X, Y, sample_weight=None):
-        """Return the coefficient of determination
-        :param  X : array-like of shape (n_samples, n_features)
-        :param  Y :  array-like of shape (n_samples,) or (n_samples, n_outputs)
-        :param sample_weight : array-like of shape (n_samples,), default=None
-                    Sample weights.
-
-        :returns: score : float R^2` of ``self.predict(X)`` wrt. `y`.
-        """
-        return self.clf.score(X, Y, sample_weight=None)
diff --git a/aidesign/Modelling/plugins/meanshift.py b/aidesign/Modelling/plugins/meanshift.py
deleted file mode 100644
index a2994637..00000000
--- a/aidesign/Modelling/plugins/meanshift.py
+++ /dev/null
@@ -1,60 +0,0 @@
-from aidesign._plugin_templates import ModellingPluginT
-from sklearn.cluster import MeanShift as model
-
-_PLUGIN_READABLE_NAMES = {"MeanShift":"default"}        # type:ignore
-_PLUGIN_MODULE_OPTIONS = {"Type": "clustering"}         # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {}                          # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {"bandwidth": "float"}      # type:ignore
-_PLUGIN_REQUIRED_DATA = {"X"}                           # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"Y", "X_tst", 'Y_tst'}         # type:ignore
-
-class MeanShift(ModellingPluginT):
-    """
-    Mean shift clustering using a flat kernel.
-    """
-
-    def __init__(self):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.clf = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def solve(self):
-        """Sends params to solver, then runs solver"""
-        self.clf.set_params(**self._config["options"])
-        self.clf.fit(self.X, self.Y)
-
-    def predict(self, data):
-        """Uses fitted model to predict output of a given Y
-        :param data: array-like or sparse matrix, shape (n_samples, n_features)
-                    Samples
-                    expected type: aidesign.Data.Data_core.Data
-        :returns: array, shape (n_samples,)
-                    Returns predicted values.
-        """
-        return self.clf.predict(data)
-
-    def score(self, X, Y, sample_weight=None):
-        """Return the coefficient of determination
-        :param  X : array-like of shape (n_samples, n_features)
-        :param  Y :  array-like of shape (n_samples,) or (n_samples, n_outputs)
-        :param sample_weight : array-like of shape (n_samples,), default=None
-                    Sample weights.
-
-        :returns: score : float R^2` of ``self.predict(X)`` wrt. `y`.
-        """
-        return self.clf.score(X, Y, sample_weight=None)
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/passiveaggressiveclassifier.py b/aidesign/Modelling/plugins/passiveaggressiveclassifier.py
deleted file mode 100644
index 289efa3b..00000000
--- a/aidesign/Modelling/plugins/passiveaggressiveclassifier.py
+++ /dev/null
@@ -1,62 +0,0 @@
-from aidesign._plugin_templates import ModellingPluginT
-from sklearn.linear_model import PassiveAggressiveClassifier as model
-
-_PLUGIN_READABLE_NAMES = {"PassiveAggressiveClassifier": "default",
-                          "PassiveAgressive": "alias"}                  # type:ignore
-_PLUGIN_MODULE_OPTIONS = {"Type": "classification"}                     # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {}                                          # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {"C": "float"}                              # type:ignore
-_PLUGIN_REQUIRED_DATA = {"X", "Y"}                                      # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}                              # type:ignore
-
-
-class PassiveAggressiveClassifier(ModellingPluginT):
-    """
-    Passive aggressive classifier
-    """
-
-    def __init__(self):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.clf = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def solve(self):
-        """Sends params to solver, then runs solver"""
-        self.clf.set_params(**self._config["options"])
-        self.clf.fit(self.X, self.Y)
-
-    def predict(self, data):
-        """Uses fitted model to predict output of a given Y
-        :param data: array-like or sparse matrix, shape (n_samples, n_features)
-                    Samples
-                    expected type: aidesign.Data.Data_core.Data
-        :returns: array, shape (n_samples,)
-                    Returns predicted values.
-        """
-        return self.clf.predict(data)
-
-    def score(self, X, Y, sample_weight=None):
-        """Return the coefficient of determination
-        :param  X : array-like of shape (n_samples, n_features)
-        :param  Y :  array-like of shape (n_samples,) or (n_samples, n_outputs)
-        :param sample_weight : array-like of shape (n_samples,), default=None
-                    Sample weights.
-
-        :returns: score : float R^2` of ``self.predict(X)`` wrt. `y`.
-        """
-        return self.clf.score(X, Y, sample_weight=None)
diff --git a/aidesign/Modelling/plugins/perceptron.py b/aidesign/Modelling/plugins/perceptron.py
deleted file mode 100644
index 4683321c..00000000
--- a/aidesign/Modelling/plugins/perceptron.py
+++ /dev/null
@@ -1,63 +0,0 @@
-from aidesign._plugin_templates import ModellingPluginT
-from sklearn.linear_model import Perceptron as model
-
-_PLUGIN_READABLE_NAMES = {"Perceptron": "default",
-                          "LinearPerceptron": "alias"}      # type:ignore
-_PLUGIN_MODULE_OPTIONS = {"Type": "classification"}         # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {}                              # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {"alpha": "float",
-                             "penalty": "str"}              # type:ignore
-_PLUGIN_REQUIRED_DATA = {"X", "Y"}                          # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}                  # type:ignore
-
-
-class Perceptron(ModellingPluginT):
-    """
-    Linear perceptron classification
-    """
-
-    def __init__(self):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.clf = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def solve(self):
-        """Sends params to solver, then runs solver"""
-        self.clf.set_params(**self._config["options"])
-        self.clf.fit(self.X, self.Y)
-
-    def predict(self, data):
-        """Uses fitted model to predict output of a given Y
-        :param data: array-like or sparse matrix, shape (n_samples, n_features)
-                    Samples
-                    expected type: aidesign.Data.Data_core.Data
-        :returns: array, shape (n_samples,)
-                    Returns predicted values.
-        """
-        return self.clf.predict(data)
-
-    def score(self, X, Y, sample_weight=None):
-        """Return the coefficient of determination
-        :param  X : array-like of shape (n_samples, n_features)
-        :param  Y :  array-like of shape (n_samples,) or (n_samples, n_outputs)
-        :param sample_weight : array-like of shape (n_samples,), default=None
-                    Sample weights.
-
-        :returns: score : float R^2` of ``self.predict(X)`` wrt. `y`.
-        """
-        return self.clf.score(X, Y, sample_weight=None)
diff --git a/aidesign/Modelling/plugins/randomforestclassifier.py b/aidesign/Modelling/plugins/randomforestclassifier.py
deleted file mode 100644
index dcfdc0c9..00000000
--- a/aidesign/Modelling/plugins/randomforestclassifier.py
+++ /dev/null
@@ -1,64 +0,0 @@
-from aidesign._plugin_templates import ModellingPluginT
-from sklearn.ensemble import RandomForestClassifier as model
-
-_PLUGIN_READABLE_NAMES = {"RandomForestClassifier": "default",
-                          "RFclassifier": "alias",
-                          "RFC": "alias"}                       # type:ignore
-_PLUGIN_MODULE_OPTIONS = {"Type": "classification"}             # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {}                                  # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {"max_depth": "int",
-                             "n_estimators": "int"}             # type:ignore
-_PLUGIN_REQUIRED_DATA = {"X", "Y"}                              # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}                      # type:ignore
-
-
-class RandomForestClassifier(ModellingPluginT):
-    """
-    A random forest classifier
-    """
-
-    def __init__(self):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.clf = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def solve(self):
-        """Sends params to solver, then runs solver"""
-        self.clf.set_params(**self._config["options"])
-        self.clf.fit(self.X, self.Y)
-
-    def predict(self, data):
-        """Uses fitted model to predict output of a given Y
-        :param data: array-like or sparse matrix, shape (n_samples, n_features)
-                    Samples
-                    expected type: aidesign.Data.Data_core.Data
-        :returns: array, shape (n_samples,)
-                    Returns predicted values.
-        """
-        return self.clf.predict(data)
-
-    def score(self, X, Y, sample_weight=None):
-        """Return the coefficient of determination
-        :param  X : array-like of shape (n_samples, n_features)
-        :param  Y :  array-like of shape (n_samples,) or (n_samples, n_outputs)
-        :param sample_weight : array-like of shape (n_samples,), default=None
-                    Sample weights.
-
-        :returns: score : float R^2` of ``self.predict(X)`` wrt. `y`.
-        """
-        return self.clf.score(X, Y, sample_weight=None)
diff --git a/aidesign/Modelling/plugins/randomforestregressor.py b/aidesign/Modelling/plugins/randomforestregressor.py
deleted file mode 100644
index 77bd729f..00000000
--- a/aidesign/Modelling/plugins/randomforestregressor.py
+++ /dev/null
@@ -1,64 +0,0 @@
-from aidesign._plugin_templates import ModellingPluginT
-from sklearn.ensemble import RandomForestRegressor as model
-
-_PLUGIN_READABLE_NAMES = {"RandomForestRegressor": "default",
-                          "RFRegressor": "alias",
-                          "RFR": "alias"}               # type:ignore
-_PLUGIN_MODULE_OPTIONS = {"Type": "regression"}         # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {}                          # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {"max_depth": "int",
-                             "n_estimators": "int"}     # type:ignore
-_PLUGIN_REQUIRED_DATA = {"X", "Y"}                      # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}              # type:ignore
-
-
-class RandomForestRegressor(ModellingPluginT):
-    """
-    A random forest regression
-    """
-
-    def __init__(self):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.clf = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def solve(self):
-        """Sends params to solver, then runs solver"""
-        self.clf.set_params(**self._config["options"])
-        self.clf.fit(self.X, self.Y)
-
-    def predict(self, data):
-        """Uses fitted model to predict output of a given Y
-        :param data: array-like or sparse matrix, shape (n_samples, n_features)
-                    Samples
-                    expected type: aidesign.Data.Data_core.Data
-        :returns: array, shape (n_samples,)
-                    Returns predicted values.
-        """
-        return self.clf.predict(data)
-
-    def score(self, X, Y, sample_weight=None):
-        """Return the coefficient of determination
-        :param  X : array-like of shape (n_samples, n_features)
-        :param  Y :  array-like of shape (n_samples,) or (n_samples, n_outputs)
-        :param sample_weight : array-like of shape (n_samples,), default=None
-                    Sample weights.
-
-        :returns: score : float R^2` of ``self.predict(X)`` wrt. `y`.
-        """
-        return self.clf.score(X, Y, sample_weight=None)
diff --git a/aidesign/Modelling/plugins/ridgeregression.py b/aidesign/Modelling/plugins/ridgeregression.py
deleted file mode 100644
index 273834eb..00000000
--- a/aidesign/Modelling/plugins/ridgeregression.py
+++ /dev/null
@@ -1,62 +0,0 @@
-from aidesign._plugin_templates import ModellingPluginT
-from sklearn.linear_model import Ridge as model
-
-_PLUGIN_READABLE_NAMES = {"Ridge": "default",
-                          "RidgeRegression": "alias"}       # type:ignore
-_PLUGIN_MODULE_OPTIONS = {"Type": "regression"}             # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {}                              # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {"alpha": "float"}              # type:ignore
-_PLUGIN_REQUIRED_DATA = {"X", "Y"}                          # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}                  # type:ignore
-
-
-class RidgeRegression(ModellingPluginT):
-    """
-    Linear least squares with l2 regularization
-    """
-
-    def __init__(self):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.clf = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def solve(self):
-        """Sends params to solver, then runs solver"""
-        self.clf.set_params(**self._config["options"])
-        self.clf.fit(self.X, self.Y)
-
-    def predict(self, data):
-        """Uses fitted model to predict output of a given Y
-        :param data: array-like or sparse matrix, shape (n_samples, n_features)
-                    Samples
-                    expected type: aidesign.Data.Data_core.Data
-        :returns: array, shape (n_samples,)
-                    Returns predicted values.
-        """
-        return self.clf.predict(data)
-
-    def score(self, X, Y, sample_weight=None):
-        """Return the coefficient of determination
-        :param  X : array-like of shape (n_samples, n_features)
-        :param  Y :  array-like of shape (n_samples,) or (n_samples, n_outputs)
-        :param sample_weight : array-like of shape (n_samples,), default=None
-                    Sample weights.
-
-        :returns: score : float R^2` of ``self.predict(X)`` wrt. `y`.
-        """
-        return self.clf.score(X, Y, sample_weight=None)
\ No newline at end of file
diff --git a/aidesign/Modelling/plugins/svc.py b/aidesign/Modelling/plugins/svc.py
deleted file mode 100644
index cce3c59c..00000000
--- a/aidesign/Modelling/plugins/svc.py
+++ /dev/null
@@ -1,65 +0,0 @@
-from aidesign._plugin_templates import ModellingPluginT
-from sklearn.svm import SVC as model
-
-_PLUGIN_READABLE_NAMES = {"SVC": "default",
-                          "SupportVectorClassification": "alias"}   # type:ignore
-_PLUGIN_MODULE_OPTIONS = {"Type": "classification"}                 # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {}                                      # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {"C": "float",
-                             "kernel": "str",
-                             "gamma": "float",
-                             "degree": "int"}                       # type:ignore
-_PLUGIN_REQUIRED_DATA = {"X", "Y"}                                  # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}                          # type:ignore
-
-
-class SVC(ModellingPluginT):
-    """
-    C-Support Vector Classification
-    """
-
-    def __init__(self):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.clf = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def solve(self):
-        """Sends params to solver, then runs solver"""
-        self.clf.set_params(**self._config["options"])
-        self.clf.fit(self.X, self.Y)
-
-    def predict(self, data):
-        """Uses fitted model to predict output of a given Y
-        :param data: array-like or sparse matrix, shape (n_samples, n_features)
-                    Samples
-                    expected type: aidesign.Data.Data_core.Data
-        :returns: array, shape (n_samples,)
-                    Returns predicted values.
-        """
-        return self.clf.predict(data)
-
-    def score(self, X, Y, sample_weight=None):
-        """Return the coefficient of determination
-        :param  X : array-like of shape (n_samples, n_features)
-        :param  Y :  array-like of shape (n_samples,) or (n_samples, n_outputs)
-        :param sample_weight : array-like of shape (n_samples,), default=None
-                    Sample weights.
-
-        :returns: score : float R^2` of ``self.predict(X)`` wrt. `y`.
-        """
-        return self.clf.score(X, Y, sample_weight=None)
diff --git a/aidesign/Modelling/plugins/svr.py b/aidesign/Modelling/plugins/svr.py
deleted file mode 100644
index fc1a9ff9..00000000
--- a/aidesign/Modelling/plugins/svr.py
+++ /dev/null
@@ -1,65 +0,0 @@
-from aidesign._plugin_templates import ModellingPluginT
-from sklearn.svm import SVR as model
-
-_PLUGIN_READABLE_NAMES = {"SVR": "default",
-                          "SupportVectorRegression": "alias"}   # type:ignore
-_PLUGIN_MODULE_OPTIONS = {"Type": "regression"}                 # type:ignore
-_PLUGIN_REQUIRED_SETTINGS = {}                                  # type:ignore
-_PLUGIN_OPTIONAL_SETTINGS = {"C": "float",
-                             "kernel": "str",
-                             "gamma": "float",
-                             "degree": "int"}                   # type:ignore
-_PLUGIN_REQUIRED_DATA = {"X", "Y"}                              # type:ignore
-_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}                      # type:ignore
-
-
-class SVR(ModellingPluginT):
-    """
-    Epsilon-Support Vector Regression
-    """
-
-    def __init__(self):
-        """Initialises parent class. 
-            Passes `globals` dict of all current variables
-        """
-        super().__init__(globals())
-        self.clf = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def solve(self):
-        """Sends params to solver, then runs solver"""
-        self.clf.set_params(**self._config["options"])
-        self.clf.fit(self.X, self.Y)
-
-    def predict(self, data):
-        """Uses fitted model to predict output of a given Y
-        :param data: array-like or sparse matrix, shape (n_samples, n_features)
-                    Samples
-                    expected type: aidesign.Data.Data_core.Data
-        :returns: array, shape (n_samples,)
-                    Returns predicted values.
-        """
-        return self.clf.predict(data)
-
-    def score(self, X, Y, sample_weight=None):
-        """Return the coefficient of determination
-        :param  X : array-like of shape (n_samples, n_features)
-        :param  Y :  array-like of shape (n_samples,) or (n_samples, n_outputs)
-        :param sample_weight : array-like of shape (n_samples,), default=None
-                    Sample weights.
-
-        :returns: score : float R^2` of ``self.predict(X)`` wrt. `y`.
-        """
-        return self.clf.score(X, Y, sample_weight=None)
diff --git a/aidesign/__init__.py b/aidesign/__init__.py
deleted file mode 100644
index 954d4c59..00000000
--- a/aidesign/__init__.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from .Core import Core
-# from .Settings import Settings
-# from .utils import AID
-# from. import utils
-# from .UserFeedback import UserFeedback
-# from .Modelling import Modelling
-# from .GUI import GUI
\ No newline at end of file
diff --git a/aidesign/examples/xml_files/Hospital_use_case_visual.xml b/aidesign/examples/xml_files/Hospital_use_case_visual.xml
deleted file mode 100644
index f8076273..00000000
--- a/aidesign/examples/xml_files/Hospital_use_case_visual.xml
+++ /dev/null
@@ -1,101 +0,0 @@
-<pipeline>
-    <Initialiser name="Initialiser">
-        <plugin type="" />
-        <relationships>
-            <child name="Data preprocessing" />
-        </relationships>
-        <coordinates>
-            (314.5, 75.0)
-        </coordinates>
-    </Initialiser>
-    <DataProcessing name="Data preprocessing">
-        <plugin type="" />
-        <relationships>
-            <parent name="Initialiser" />
-            <child name="Modelling-0" />
-        </relationships>
-        <coordinates>
-            (117, 180)
-        </coordinates>
-    </DataProcessing>
-    <Modelling name="Modelling-0">
-        <plugin type="" />
-        <relationships>
-            <parent name="Data preprocessing" />
-            <child name="Modelling-1" />
-        </relationships>
-        <coordinates>
-            (117, 273)
-        </coordinates>
-    </Modelling>
-    <loop type="While" condition="Convergence" name="loop0">
-        <relationships>
-            <child name="Modelling-1" />
-            <child name="Input data" />
-            <child name="User Feedback Adaptation" />
-            <child name="Decision making" />
-            <child name="Modelling-1" />
-            <child name="Input data" />
-            <child name="User Feedback Adaptation" />
-            <child name="Decision making" />
-        </relationships>
-        <coordinates>
-            (202.0, 154.0, 585.0, 461.0)
-        </coordinates>
-        <Modelling name="Modelling-1">
-            <plugin type="" />
-            <relationships>
-                <parent name="Modelling-0" />
-                <parent name="Input data" />
-                <parent name="User Feedback Adaptation" />
-                <parent name="loop0" />
-                <child name="Decision making" />
-            </relationships>
-            <coordinates>
-                (257, 326)
-            </coordinates>
-        </Modelling>
-        <InputData name="Input data">
-            <plugin type="" />
-            <relationships>
-                <parent name="loop0" />
-                <child name="Modelling-1" />
-            </relationships>
-            <coordinates>
-                (322, 207)
-            </coordinates>
-        </InputData>
-        <UserInteraction name="User Feedback Adaptation">
-            <plugin type="" />
-            <relationships>
-                <parent name="Decision making" />
-                <parent name="loop0" />
-                <child name="Modelling-1" />
-            </relationships>
-            <coordinates>
-                (454, 325)
-            </coordinates>
-        </UserInteraction>
-        <DecisionMaking name="Decision making">
-            <plugin type="" />
-            <relationships>
-                <parent name="Modelling-1" />
-                <parent name="loop0" />
-                <child name="Output" />
-                <child name="User Feedback Adaptation" />
-            </relationships>
-            <coordinates>
-                (354, 419)
-            </coordinates>
-        </DecisionMaking>
-    </loop>
-    <Output name="Output">
-        <plugin type="" />
-        <relationships>
-            <parent name="Decision making" />
-        </relationships>
-        <coordinates>
-            (314.5, 525.0)
-        </coordinates>
-    </Output>
-</pipeline>
\ No newline at end of file
diff --git a/aidesign/examples/xml_files/Kevin_experimentalDesign.xml b/aidesign/examples/xml_files/Kevin_experimentalDesign.xml
deleted file mode 100644
index bdb45db6..00000000
--- a/aidesign/examples/xml_files/Kevin_experimentalDesign.xml
+++ /dev/null
@@ -1,163 +0,0 @@
-<pipeline>
-    <Initialiser name="Initialiser">
-        <relationships>
-            <child name="Input Data" />
-        </relationships>
-        <coordinates>
-                        [(350.0,50),0,{}]
-                    
-        </coordinates>
-    </Initialiser>
-    <InputData name="Input Data">
-        <plugin type="" />
-        <relationships>
-            <parent name="Initialiser" />
-            <child name="Data Processing" />
-        </relationships>
-        <coordinates>
-                        [(173,184),2,{0:'d0-u2'}]
-                    
-        </coordinates>
-    </InputData>
-    <DataProcessing name="Data Processing">
-        <plugin type="" />
-        <relationships>
-            <parent name="Input Data" />
-            <child name="Modelling" />
-        </relationships>
-        <coordinates>
-                        [(173,294),3,{2:'d2-u3'}]
-                    
-        </coordinates>
-    </DataProcessing>
-    <Modelling name="Modelling">
-        <plugin type="" />
-        <relationships>
-            <parent name="Data Processing" />
-            <parent name="User Interaction" />
-            <child name="Decision Making" />
-        </relationships>
-        <coordinates>
-                        [(175,409),4,{3:'d3-u4',5:'l5-r4'}]
-                    
-        </coordinates>
-    </Modelling>
-    <UserInteraction name="User Interaction">
-        <plugin type="" />
-        <relationships>
-            <parent name="Decision Making" />
-            <child name="Modelling" />
-        </relationships>
-        <coordinates>
-                        [(543,414),5,{6:'r6-d5'}]
-                    
-        </coordinates>
-    </UserInteraction>
-    <DecisionMaking name="Decision Making">
-        <plugin type="" />
-        <relationships>
-            <parent name="Modelling" />
-            <child name="Output" />
-            <child name="User Interaction" />
-        </relationships>
-        <coordinates>
-                        [(351,522),6,{4:'d4-l6'}]
-                    
-        </coordinates>
-    </DecisionMaking>
-    <Output name="Output">
-        <plugin type="" />
-        <relationships>
-            <parent name="Decision Making" />
-        </relationships>
-        <coordinates>
-                        [(350.0,650),1,{6:'d6-u1'}]
-                    
-        </coordinates>
-    </Output>
-    <loop type="for" condition="number of experiments" name="loop0">
-        <relationships>
-            <child name="Input Data" />
-            <child name="Data Processing" />
-            <child name="Modelling" />
-            <child name="User Interaction" />
-            <child name="Decision Making" />
-        </relationships>
-        <coordinates>
-                        (48.0,110.0,651.0,572.0)
-                    
-        </coordinates>
-        <InputData name="Input Data">
-            <plugin type="" />
-            <relationships>
-                <parent name="Initialiser" />
-                <parent name="loop0" />
-                <child name="Data Processing" />
-            </relationships>
-            <coordinates>
-                                [(173,184),2,{0:'d0-u2'}]
-                            
-            </coordinates>
-        </InputData>
-        <DataProcessing name="Data Processing">
-            <plugin type="" />
-            <relationships>
-                <parent name="Input Data" />
-                <parent name="loop0" />
-                <child name="Modelling" />
-            </relationships>
-            <coordinates>
-                                [(173,294),3,{2:'d2-u3'}]
-                            
-            </coordinates>
-        </DataProcessing>
-        <Modelling name="Modelling">
-            <plugin type="" />
-            <relationships>
-                <parent name="Data Processing" />
-                <parent name="User Interaction" />
-                <parent name="loop0" />
-                <child name="Decision Making" />
-            </relationships>
-            <coordinates>
-                                [(173,404),4,{3:'d3-u4',5:'l5-r4'}]
-                            
-            </coordinates>
-        </Modelling>
-        <UserInteraction name="User Interaction">
-            <plugin type="" />
-            <relationships>
-                <parent name="Decision Making" />
-                <parent name="loop0" />
-                <child name="Modelling" />
-            </relationships>
-            <coordinates>
-                                [(536,410),5,{6:'r6-d5'}]
-                            
-            </coordinates>
-        </UserInteraction>
-        <DecisionMaking name="Decision Making">
-            <plugin type="" />
-            <relationships>
-                <parent name="Modelling" />
-                <parent name="loop0" />
-                <child name="Output" />
-                <child name="User Interaction" />
-            </relationships>
-            <coordinates>
-                                [(351,522),6,{4:'d4-l6'}]
-                            
-            </coordinates>
-        </DecisionMaking>
-    </loop>
-    <Output name="Output">
-        <plugin type="" />
-        <relationships>
-            <parent name="Decision Making" />
-        </relationships>
-        <coordinates>
-                        [(350.0,650),1,{6:'d6-u1'}]
-                    
-        </coordinates>
-    </Output>
-</pipeline>
diff --git a/aidesign/examples/xml_files/basic_operation.xml b/aidesign/examples/xml_files/basic_operation.xml
deleted file mode 100644
index 82219316..00000000
--- a/aidesign/examples/xml_files/basic_operation.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<pipeline>
-    <Initialiser name="Init">
-        <inputdata>
-            <X file="./examples/image_classification/training_images/" />
-            <Y file="./examples/image_classification/Y_data.csv" />
-        </inputdata>
-        <relationships>
-            <child name="My First UserInteraction Module" />
-        </relationships>
-    </Initialiser>
-    <loop type="for" condition="2" name="For Loop 1">
-        <UserInteraction name="My First UserInteraction Module">
-            <plugin type="manual">
-            </plugin>
-            <relationships>
-                <parent name="Init" />
-                <child name="Data Processing 1" />
-            </relationships>
-        </UserInteraction>
-        <loop type="for" condition="1" name="For Loop 2">
-            <UserInteraction name="My Second UserInteraction Module">
-                <plugin type="canvas">
-                    <class_list>
-                        ['State_a','Action_a']
-                        ['State_x','State_y','Action_x','Action_y']
-                        ['State_x','State_y','Action_x','Action_y']
-                    </class_list>
-                </plugin>
-                <relationships>
-                    <parent name="Data Processing 1" />
-                    <child name="Data Processing 1" />
-                    <child name="Output" />
-                </relationships>
-            </UserInteraction>
-        </loop>
-    </loop>
-    <Output>
-        <plugin type="">
-            <out_data>
-                all
-            </out_data>
-            <save_to file="../resources/output_test.csv" />
-        </plugin>
-        <relationships>
-            <parent name="My Second UserInteraction Module" />
-        </relationships>
-    </Output>
-</pipeline>
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 00000000..d0c3cbf1
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS    ?=
+SPHINXBUILD   ?= sphinx-build
+SOURCEDIR     = source
+BUILDDIR      = build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/make.bat b/docs/make.bat
new file mode 100644
index 00000000..747ffb7b
--- /dev/null
+++ b/docs/make.bat
@@ -0,0 +1,35 @@
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+	set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=source
+set BUILDDIR=build
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+	echo.
+	echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+	echo.installed, then set the SPHINXBUILD environment variable to point
+	echo.to the full path of the 'sphinx-build' executable. Alternatively you
+	echo.may add the Sphinx directory to PATH.
+	echo.
+	echo.If you don't have Sphinx installed, grab it from
+	echo.https://www.sphinx-doc.org/
+	exit /b 1
+)
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+
+:end
+popd
diff --git a/docs/source/conf.py b/docs/source/conf.py
new file mode 100644
index 00000000..26283a34
--- /dev/null
+++ b/docs/source/conf.py
@@ -0,0 +1,37 @@
+# Configuration file for the Sphinx documentation builder.
+#
+# For the full list of built-in configuration values, see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+# -- Project information -----------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
+
+import pathlib
+import sys
+
+project = 'VAI-Lab'
+copyright = '2023, Chris McGreavy, Carlos Sevilla-Salcedo'
+author = 'Chris McGreavy, Carlos Sevilla-Salcedo'
+
+
+# -- General configuration ---------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
+
+extensions = [
+    'sphinx.ext.autodoc',       # For API 
+    'sphinx.ext.autosummary',   # For API
+    'myst_parser',              # For importing README.md
+    ]
+
+templates_path = ['_templates']
+exclude_patterns = []
+
+
+
+# -- Options for HTML output -------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
+
+html_theme = 'sphinx_rtd_theme'
+html_static_path = []
+
+
diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst
new file mode 100644
index 00000000..316d9abe
--- /dev/null
+++ b/docs/source/getting_started.rst
@@ -0,0 +1,280 @@
+Getting Started
+===============
+
+
+Launching
+---------
+
+From Command Line
+^^^^^^^^^^^^^^^^^
+
+To launch the framework with the GUI:
+
+.. code-block:: bash
+    
+    vai_lab
+
+
+To launch the framework with an existing config file:
+
+.. code-block:: bash
+
+    vai_lab --file <path_to_config_file>
+
+
+From Code
+^^^^^^^^^
+
+To launch the framework with the GUI:
+
+.. code-block:: python
+    :linenos:
+    
+    import vai_lab as ai
+
+    core = ai.Core()
+    core.run()
+
+or to execute an existing config file:
+
+.. code-block:: python
+    :linenos:
+
+    import vai_lab as ai
+
+    core = ai.Core()
+    core.load_config_file("<path_to_config_file>")
+    core.run()
+
+
+Examples
+--------
+
+Pre-made `examples <https://github.com/AaltoPML/VAI-lab/tree/main/src/vai_lab/examples/xml_files>`_ show the syntax and form of the config files and pipeline as a whole, and are the best way to get started.
+
+Some basic use-cases are provided among many others:
+ - `user_feedback_demo.xml <https://github.com/AaltoPML/VAI-lab/tree/main/src/vai_lab/examples/xml_files/user_feedback_demo.xml>`_ Demonstrates manual image classification of chest X-rays
+ - `canvas_demo.xml <https://github.com/AaltoPML/VAI-lab/tree/main/src/vai_lab/examples/xml_files/canvas_demo.xml>`_ Launches the canvas state-action pair visualiser and editor
+ - `regression_demo.xml <https://github.com/AaltoPML/VAI-lab/tree/main/src/vai_lab/examples/xml_files/regression_demo.xml>`_ Demonstrates simple linear regression on a small set of sample data
+
+Launching examples:
+^^^^^^^^^^^^^^^^^^^
+
+To demonstrate the syntax for launching examples using ``user_feedback_demo.xml``.
+
+From Command Line
+"""""""""""""""""
+
+.. code-block:: bash
+
+    vai_lab --file ./src/vai_lab/examples/xml_files/user_feedback_demo.xml
+
+From code
+"""""""""
+
+.. code-block:: python
+    :linenos:
+
+    import vai_lab as ai
+
+    core = ai.Core()
+    core.load_config_file(("./examples/xml_files/user_feedback_demo.xml"))
+    core.run()
+
+Absolute paths, as well paths relative to the library's base directory can be used.
+For library-relative paths, starting a path definition with ``./`` defaults to the directory ``<path_to_site_packages>/vai_lab/``
+
+In addition to path strings, the config file paths can be passed as lists or tuples of directory paths. Therefore, the above command/code are equivalent to
+
+.. code-block:: bash
+
+    vai_lab --file ./src/vai_lab/examples xml_files user_feedback_demo.xml
+
+and 
+
+.. code-block:: python
+    :linenos:
+
+    import vai_lab as ai
+
+    core = ai.Core()
+    core.load_config_file(("./examples","xml_files","user_feedback_demo.xml"))
+    core.run()
+
+Defining Pipelines in GUI
+-------------------------
+
+The VAIL module allows to define a pipeline and the relations within by drawing a flowchart on a canvas. This canvas always starts with an ``initialiser`` module and an ``output`` module and allows to define any number of modules between these two. To do so, the user needs to define the modules and the relations between them.
+
+.. image::../../imgs/VAIL_GUI_screenshot.png
+    :alt: VAIL GUI screenshot
+
+
+Modules
+^^^^^^^
+
+At this moment, there are 7 possible modules for VAIL. ``initialiser`` and ``output`` are compulsory for the pipeline, the rest of them can be freely placed in the canvas. These are:
+ - ``Data processing``
+ - ``Modelling``
+ - ``Decision making``
+ - ``User Feedback Adaptation``
+ - ``Input data``
+
+If you click on a module and drag it you can modify its position on the canvas.
+
+Finally, they can be deleted by clicking on the desired module and then clicking on the ``Delete selection`` button.
+
+Connecting Modules
+^^^^^^^^^^^^^^^^^^
+
+Each module object has a number of circles that can be used to join two modules. The initially clicked circle will be identified as the parent and the second one as the child (the output of the father is fed to the input of the child). There can be only one connection from each circle. As of this version, if you need to edit an existing connection you need to delete one of the connected modules.
+
+Loops
+^^^^^
+
+If you click on the canvas and drag, you can draw a rectangle that defines which modules are inside the loop. Upon releasing the button you are requested to input what type of loop you want and what condition should be fulfilled to end the loop.
+
+Loading from XML
+----------------
+
+The pipeline can also be defined uploading an existing XML file. The structure of the XML file is described in the Back-end section.
+
+Plugin Examples
+---------------
+
+manual_input
+^^^^^^^^^^^^
+
+Requires the user to indicate to which class the specified data corresponds to.
+In the current example, the model needs to classify images and the model requires expert knowledge for specific images.
+The user needs to indicate which classes correspond to the image and save the results to send them to the model.
+
+canvas_input
+^^^^^^^^^^^^
+
+Requires the user to give feedback to state-action pairs.
+It opens a tab for each interactable object in the model and either requires adding new state-action samples or to modify the existing ones.
+In the current example, the model has two interactable objects that require feedback in two forms: (1) an *angle* for the state and for the action or (2) a tuple of Cartesian coordinates for the state and for the action. It has been adapted to be able to give feedback to any number of objects. These, at the same time, can be either ``sliding`` or ``rotating`` objects. Specifically, ``sliding`` refers to objects that need Cartesian feedback in a two-dimensional space, while ``rotating`` refers to objects that require an angle. In order to give feedback, you can choose to either move the corresponding state-action pairs on the canvas or directly edit the tree view display. This last option results in an automatic update on the canvas of the state-action location.
+
+Defining a pipeline in XML
+--------------------------
+
+The pipeline structure is defined between the ``pipeline`` tags:
+
+.. code-block:: xml
+    :linenos:
+
+    <pipeline>
+        ...
+    </pipeline>
+
+Initialising
+^^^^^^^^^^^^
+
+The ``Initialise`` tag is the dedicated entry point to the pipeline. No other entry points can be declared.
+
+Current options:
+ - ``name``: attribute for user defined name
+ - ``initial_data``: element for declaring directory for initial data
+ - ``relationships``: User defined names of modules this one is connected to
+
+Example from `canvas_demo.xml <https://github.com/AaltoPML/VAI-lab/tree/main/src/vai_lab/examples/xml_files/canvas_demo.xml>`_:
+
+.. code-block:: xml
+    :linenos:
+
+    <Initialiser name="Init">
+        <inputdata>
+            <X file="./examples/state-action/X_data.csv" />
+        </inputdata>
+        <relationships>
+            <child name="My First UserFeedback Module" />
+        </relationships>
+    </Initialiser>
+
+Loops
+^^^^^
+
+Loop tags are used to iterate over a given set of modules until a condition is met. Loops can be nested and named.  
+
+See `basic_operation.xml <https://github.com/AaltoPML/VAI-lab/tree/main/src/vai_lab/examples/xml_files/basic_operation.xml>`_ for full example.
+Current options:
+
+ - ``type``: what variety of loop will this be: ``for``, ``while``, ``manual`` (user defined stopping condition on-the-fly)
+ - ``condition``: Termination condition for the loop. I'm not sure how to deal with the criteria for ``while`` loops
+ - ``name``: User defined name for loop
+
+.. code-block:: xml
+    :linenos:
+
+    <loop type="for" condition="10" name="For Loop 1">
+        ...
+    </loop>
+
+
+Modules
+^^^^^^^
+
+Modules are declared by tags matching their names, e.g. the GUI module is loaded with the ``GUI`` tag:
+
+Required:
+ - ``name``: Unique user defined name for module, so can be referenced later
+ - ``plugin``: The type of plugin to be loaded into the module, along with associated options.
+ - ``relationships``: User-defined names of the ``parent`` modules which this module receives data from and ``child`` modules that this module passes data to.
+
+Example from `ridge_regression_demo.xml <https://github.com/AaltoPML/VAI-lab/tree/main/src/vai_lab/examples/xml_files/ridge_regression_demo.xml>`_:
+
+.. code-block:: xml
+    :linenos:
+
+     <Modelling name="Modelling">
+        <relationships>
+            <parent name="Initialiser" />
+            <child name="Output" />
+        </relationships>
+        <plugin type="RidgeRegression">
+            <alpha>
+                 0.02
+            </alpha>
+        </plugin>
+    </Modelling>
+
+Data Definition
+^^^^^^^^^^^^^^^
+
+Data is loaded from existing files in either the ``Initialiser`` or ``Input Data`` modules and is specified using the ``inputdata`` tags.
+
+Example from `ridge_regression_demo.xml <https://github.com/AaltoPML/VAI-lab/tree/main/src/vai_lab/examples/xml_files/ridge_regression_demo.xml>`_`:
+
+.. code-block:: xml
+    :linenos:
+
+    <inputdata>
+        <X file="./examples/supervised_regression/X_tr.csv" />
+        <Y file="./examples/supervised_regression/Y_tr.csv" />
+        <X_test file="./examples/supervised_regression/X_tst.csv" />
+        <Y_test file="./examples/supervised_regression/Y_tst.csv" />
+    </inputdata>
+
+Writing Data
+^^^^^^^^^^^^
+
+Two methods are given to add data to the XML file. One for modules (``append_pipeline_module_to_file``) and one for data structures (``append_data_structure_field_to_file``).
+
+Building Documentation
+----------------------
+
+To build documentation locally from source, install the required tools
+
+.. code-block:: bash
+    
+    python3 -m pip install sphinx sphinx-rtd-theme myst-parser
+
+and run
+
+.. code-block:: bash
+
+    sphinx-apidoc --templatedir docs/templates/apidoc -o docs/source src/vai_lab
+    sphinx-build -M html docs/source docs/build
+
+The generated HTML pages are in ``docs/build/html``.
\ No newline at end of file
diff --git a/docs/source/index.rst b/docs/source/index.rst
new file mode 100644
index 00000000..1987d344
--- /dev/null
+++ b/docs/source/index.rst
@@ -0,0 +1,28 @@
+.. VAI-Lab documentation master file, created by
+   sphinx-quickstart on Thu Dec  8 15:28:40 2022.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+Welcome to VAI-Lab's Documentation!
+===================================
+
+VAI-Lab is a modular, easy-to-use framework for Virtual Laboraties for science and design, where Artifical Intelligence assists the user in their goals.
+
+
+Contents
+--------
+
+.. toctree::
+   :maxdepth: 1
+
+   Intro and Installation <readme>
+   Getting Started <getting_started>
+   API <modules>
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`   
+
diff --git a/docs/source/readme.rst b/docs/source/readme.rst
new file mode 100644
index 00000000..de92cbb2
--- /dev/null
+++ b/docs/source/readme.rst
@@ -0,0 +1,5 @@
+Intro & Installation 
+====================
+
+.. include:: ../../README.md
+   :parser: myst_parser.sphinx_
diff --git a/docs/templates/apidoc/module.rst_t b/docs/templates/apidoc/module.rst_t
new file mode 100644
index 00000000..24902785
--- /dev/null
+++ b/docs/templates/apidoc/module.rst_t
@@ -0,0 +1,9 @@
+{%- if show_headings %}
+{{- [basename, "module"] | join(' ') | e | heading }}
+
+{% endif -%}
+.. automodule:: {{ qualname }}
+{%- for option in automodule_options %}
+   :{{ option }}:
+{%- endfor %}
+
diff --git a/docs/templates/apidoc/package.rst_t b/docs/templates/apidoc/package.rst_t
new file mode 100644
index 00000000..aa1fd459
--- /dev/null
+++ b/docs/templates/apidoc/package.rst_t
@@ -0,0 +1,53 @@
+{%- macro automodule(modname, options) -%}
+.. automodule:: {{ modname }}
+{%- for option in options %}
+   :{{ option }}:
+{%- endfor %}
+{%- endmacro %}
+
+{%- macro toctree(docnames) -%}
+.. toctree::
+   :maxdepth: {{ maxdepth }}
+{% for docname in docnames %}
+   {{ docname }}
+{%- endfor %}
+{%- endmacro %}
+
+{%- if is_namespace %}
+{{- [pkgname, "namespace"] | join(" ") | e | heading }}
+{% else %}
+{{- [pkgname, "package"] | join(" ") | e | heading }}
+{% endif %}
+
+{%- if is_namespace %}
+.. py:module:: {{ pkgname }}
+{% endif %}
+
+{%- if modulefirst and not is_namespace %}
+{{ automodule(pkgname, automodule_options) }}
+{% endif %}
+
+{%- if subpackages %}
+
+{{ toctree(subpackages) }}
+{% endif %}
+
+{%- if submodules %}
+
+{% if separatemodules %}
+{{ toctree(submodules) }}
+{% else %}
+{%- for submodule in submodules %}
+{% if show_headings %}
+{{- [submodule, "module"] | join(" ") | e | heading(2) }}
+{% endif %}
+{{ automodule(submodule, automodule_options) }}
+{% endfor %}
+{%- endif %}
+{%- endif %}
+
+{%- if not modulefirst and not is_namespace %}
+
+
+{{ automodule(pkgname, automodule_options) }}
+{% endif %}
diff --git a/docs/templates/apidoc/toc.rst_t b/docs/templates/apidoc/toc.rst_t
new file mode 100644
index 00000000..f0877eeb
--- /dev/null
+++ b/docs/templates/apidoc/toc.rst_t
@@ -0,0 +1,8 @@
+{{ header | heading }}
+
+.. toctree::
+   :maxdepth: {{ maxdepth }}
+{% for docname in docnames %}
+   {{ docname }}
+{%- endfor %}
+
diff --git a/environment.yml b/environment.yml
new file mode 100644
index 00000000..eb23e3ce
--- /dev/null
+++ b/environment.yml
@@ -0,0 +1,9 @@
+name: vai-lab
+channels:
+  - conda-forge
+dependencies:
+  - python=3.10
+  - gcc
+  - pip
+  - pip:
+    - .
diff --git a/aidesign/utils/resources/Assets/readme_images/VAIL_GUI_screenshot.png b/imgs/VAIL_GUI_screenshot.png
similarity index 100%
rename from aidesign/utils/resources/Assets/readme_images/VAIL_GUI_screenshot.png
rename to imgs/VAIL_GUI_screenshot.png
diff --git a/aidesign/utils/resources/Assets/readme_images/VAIL_banner_image.png b/imgs/VAIL_banner_image.png
similarity index 100%
rename from aidesign/utils/resources/Assets/readme_images/VAIL_banner_image.png
rename to imgs/VAIL_banner_image.png
diff --git a/aidesign/utils/resources/Assets/readme_images/VAIL_mainpage_screenshot.png b/imgs/VAIL_mainpage_screenshot.png
similarity index 100%
rename from aidesign/utils/resources/Assets/readme_images/VAIL_mainpage_screenshot.png
rename to imgs/VAIL_mainpage_screenshot.png
diff --git a/imgs/VAIL_plugin_diagram.png b/imgs/VAIL_plugin_diagram.png
new file mode 100644
index 00000000..2b43ec6d
Binary files /dev/null and b/imgs/VAIL_plugin_diagram.png differ
diff --git a/pybullet.pyi b/pybullet.pyi
new file mode 100644
index 00000000..029c8601
--- /dev/null
+++ b/pybullet.pyi
@@ -0,0 +1,971 @@
+# Adapted from temporary pybullet stubs at:
+# https://github.com/rohit-kumar-j/temporary_pybullet_stub/blob/main/pybullet.pyi
+
+
+ACTIVATION_STATE_DISABLE_SLEEPING = 2
+ACTIVATION_STATE_DISABLE_WAKEUP = 32
+
+ACTIVATION_STATE_ENABLE_SLEEPING = 1
+ACTIVATION_STATE_ENABLE_WAKEUP = 16
+
+ACTIVATION_STATE_SLEEP = 8
+
+ACTIVATION_STATE_WAKE_UP = 4
+
+AddFileIOAction = 1024
+
+B3G_ALT = 65308
+B3G_BACKSPACE = 65305
+B3G_CONTROL = 65307
+B3G_DELETE = 65304
+
+B3G_DOWN_ARROW = 65298
+
+B3G_END = 65301
+B3G_F1 = 65280
+B3G_F10 = 65289
+B3G_F11 = 65290
+B3G_F12 = 65291
+B3G_F13 = 65292
+B3G_F14 = 65293
+B3G_F15 = 65294
+B3G_F2 = 65281
+B3G_F3 = 65282
+B3G_F4 = 65283
+B3G_F5 = 65284
+B3G_F6 = 65285
+B3G_F7 = 65286
+B3G_F8 = 65287
+B3G_F9 = 65288
+B3G_HOME = 65302
+B3G_INSERT = 65303
+
+B3G_LEFT_ARROW = 65295
+
+B3G_PAGE_DOWN = 65300
+B3G_PAGE_UP = 65299
+
+B3G_RETURN = 65309
+
+B3G_RIGHT_ARROW = 65296
+
+B3G_SHIFT = 65306
+B3G_SPACE = 32
+
+B3G_UP_ARROW = 65297
+
+CNSFileIO = 3
+
+CONSTRAINT_SOLVER_LCP_DANTZIG = 3
+CONSTRAINT_SOLVER_LCP_PGS = 2
+CONSTRAINT_SOLVER_LCP_SI = 1
+
+CONTACT_RECOMPUTE_CLOSEST = 1
+
+CONTACT_REPORT_EXISTING = 0
+
+COV_ENABLE_DEPTH_BUFFER_PREVIEW = 14
+
+COV_ENABLE_GUI = 1
+
+COV_ENABLE_KEYBOARD_SHORTCUTS = 9
+
+COV_ENABLE_MOUSE_PICKING = 10
+
+COV_ENABLE_PLANAR_REFLECTION = 16
+
+COV_ENABLE_RENDERING = 7
+
+COV_ENABLE_RGB_BUFFER_PREVIEW = 13
+
+COV_ENABLE_SEGMENTATION_MARK_PREVIEW = 15
+
+COV_ENABLE_SHADOWS = 2
+
+COV_ENABLE_SINGLE_STEP_RENDERING = 17
+
+COV_ENABLE_TINY_RENDERER = 12
+
+COV_ENABLE_VR_PICKING = 5
+
+COV_ENABLE_VR_RENDER_CONTROLLERS = 6
+
+COV_ENABLE_VR_TELEPORTING = 4
+
+COV_ENABLE_WIREFRAME = 3
+
+COV_ENABLE_Y_AXIS_UP = 11
+
+DIRECT = 2
+
+ER_BULLET_HARDWARE_OPENGL = 131072
+
+ER_NO_SEGMENTATION_MASK = 4
+
+ER_SEGMENTATION_MASK_OBJECT_AND_LINKINDEX = 1
+
+ER_TINY_RENDERER = 65536
+
+ER_USE_PROJECTIVE_TEXTURE = 2
+
+GEOM_BOX = 3
+GEOM_CAPSULE = 7
+
+GEOM_CONCAVE_INTERNAL_EDGE = 2
+
+GEOM_CYLINDER = 4
+
+GEOM_FORCE_CONCAVE_TRIMESH = 1
+
+GEOM_HEIGHTFIELD = 9
+GEOM_MESH = 5
+GEOM_PLANE = 6
+GEOM_SPHERE = 2
+
+GRAPHICS_CLIENT = 14
+GRAPHICS_SERVER = 15
+
+GRAPHICS_SERVER_MAIN_THREAD = 17
+
+GRAPHICS_SERVER_TCP = 16
+
+GUI = 1
+
+GUI_MAIN_THREAD = 8
+
+GUI_SERVER = 7
+
+IK_DLS = 0
+
+IK_HAS_JOINT_DAMPING = 128
+
+IK_HAS_NULL_SPACE_VELOCITY = 64
+
+IK_HAS_TARGET_ORIENTATION = 32
+IK_HAS_TARGET_POSITION = 16
+
+IK_SDLS = 1
+
+JOINT_FEEDBACK_IN_JOINT_FRAME = 2
+
+JOINT_FEEDBACK_IN_WORLD_SPACE = 1
+
+JOINT_FIXED = 4
+JOINT_GEAR = 6
+JOINT_PLANAR = 3
+JOINT_POINT2POINT = 5
+JOINT_PRISMATIC = 1
+JOINT_REVOLUTE = 0
+JOINT_SPHERICAL = 2
+
+KEY_IS_DOWN = 1
+
+KEY_WAS_RELEASED = 4
+KEY_WAS_TRIGGERED = 2
+
+LINK_FRAME = 1
+
+MAX_RAY_INTERSECTION_BATCH_SIZE = 16384
+
+MESH_DATA_SIMULATION_MESH = 1
+
+MJCF_COLORS_FROM_FILE = 512
+
+PD_CONTROL = 3
+
+POSITION_CONTROL = 2
+
+PosixFileIO = 1
+
+RemoveFileIOAction = 1025
+
+RESET_USE_DEFORMABLE_WORLD = 1
+
+RESET_USE_DISCRETE_DYNAMICS_WORLD = 2
+
+RESET_USE_SIMPLE_BROADPHASE = 4
+
+SENSOR_FORCE_TORQUE = 1
+
+SHARED_MEMORY = 3
+
+SHARED_MEMORY_GUI = 14
+SHARED_MEMORY_KEY = 12347
+SHARED_MEMORY_KEY2 = 12348
+SHARED_MEMORY_SERVER = 9
+
+STABLE_PD_CONTROL = 4
+
+STATE_LOGGING_ALL_COMMANDS = 7
+
+STATE_LOGGING_CONTACT_POINTS = 5
+
+STATE_LOGGING_CUSTOM_TIMER = 9
+
+STATE_LOGGING_GENERIC_ROBOT = 1
+
+STATE_LOGGING_MINITAUR = 0
+
+STATE_LOGGING_PROFILE_TIMINGS = 6
+
+STATE_LOGGING_VIDEO_MP4 = 3
+
+STATE_LOGGING_VR_CONTROLLERS = 2
+
+STATE_LOG_JOINT_MOTOR_TORQUES = 1
+
+STATE_LOG_JOINT_TORQUES = 3
+
+STATE_LOG_JOINT_USER_TORQUES = 2
+
+STATE_REPLAY_ALL_COMMANDS = 8
+
+TCP = 5
+
+TORQUE_CONTROL = 1
+
+UDP = 4
+
+URDF_ENABLE_CACHED_GRAPHICS_SHAPES = 1024
+
+URDF_ENABLE_SLEEPING = 2048
+URDF_ENABLE_WAKEUP = 262144
+
+URDF_GLOBAL_VELOCITIES_MB = 256
+
+URDF_GOOGLEY_UNDEFINED_COLORS = 8388608
+
+URDF_IGNORE_COLLISION_SHAPES = 2097152
+
+URDF_IGNORE_VISUAL_SHAPES = 1048576
+
+URDF_INITIALIZE_SAT_FEATURES = 4096
+
+URDF_MAINTAIN_LINK_ORDER = 131072
+
+URDF_MERGE_FIXED_LINKS = 524288
+
+URDF_PRINT_URDF_INFO = 4194304
+
+URDF_USE_IMPLICIT_CYLINDER = 128
+
+URDF_USE_INERTIA_FROM_FILE = 2
+
+URDF_USE_MATERIAL_COLORS_FROM_MTL = 32768
+
+URDF_USE_MATERIAL_TRANSPARANCY_FROM_MTL = 65536
+
+URDF_USE_SELF_COLLISION = 8
+
+URDF_USE_SELF_COLLISION_EXCLUDE_ALL_PARENTS = 32
+
+URDF_USE_SELF_COLLISION_EXCLUDE_PARENT = 16
+
+URDF_USE_SELF_COLLISION_INCLUDE_PARENT = 8192
+
+VELOCITY_CONTROL = 0
+
+VISUAL_SHAPE_DATA_TEXTURE_UNIQUE_IDS = 1
+
+VISUAL_SHAPE_DOUBLE_SIDED = 4
+
+VR_BUTTON_IS_DOWN = 1
+
+VR_BUTTON_WAS_RELEASED = 4
+VR_BUTTON_WAS_TRIGGERED = 2
+
+VR_CAMERA_TRACK_OBJECT_ORIENTATION = 1
+
+VR_DEVICE_CONTROLLER = 1
+
+VR_DEVICE_GENERIC_TRACKER = 4
+
+VR_DEVICE_HMD = 2
+
+VR_MAX_BUTTONS = 64
+VR_MAX_CONTROLLERS = 8
+
+WORLD_FRAME = 2
+
+ZipFileIO = 2
+
+# functions
+
+def addUserData(bodyUniqueId:int, key, value, linkIndex=-1, visualShapeIndex=-1, physicsClientId=0): # real signature unknown; restored from __doc__
+    """
+    addUserData(bodyUniqueId, key, value, linkIndex=-1, visualShapeIndex=-1, physicsClientId=0)
+    Adds or updates a user data entry. Returns user data identifier.
+    """
+    pass
+
+def addUserDebugLine(lineFromXYZ:list[float],lineToXYZ:list[float],lineColorRGB:list[float],lineWidth:float,lifeTime:float,parentObjectUniqueId:int,parentLinkIndex:int,replaceItemUniqueId:int,physicsClientId:int,*args, **kwargs): # real signature unknown
+    """ Add a user debug draw line with lineFrom[3], lineTo[3], lineColorRGB[3], lineWidth, lifeTime. A lifeTime of 0 means permanent until removed. Returns a unique id for the user debug item. """
+    pass
+
+def addUserDebugParameter(paramName:str,rangeMin:float,rangeMax:float,startValue:float,physicsClientId:int,*args, **kwargs): # real signature unknown
+    """ Add a user debug parameter, such as a slider, that can be controlled using a GUI. """
+    pass
+
+def addUserDebugText(text:str,textPosition:list[float],textColorRGB:list[float],textSize:float,lifeTime:float,textOrientation:list[float],parentObjectUniqueId:int,parentLinkIndex:int,replaceItemUniqueId:int,physicsClientId:int,*args, **kwargs): # real signature unknown
+    """ Add a user debug draw line with text, textPosition[3], textSize and lifeTime in seconds A lifeTime of 0 means permanent until removed. Returns a unique id for the user debug item. """
+    pass
+
+def applyExternalForce(objectUniqueId:int,linkIndex:int,forceObj:list[float],posObj:list[float],flags:int,physicsClientId:int,*args, **kwargs): # real signature unknown
+    """ for objectUniqueId, linkIndex (-1 for base/root link), apply a force [x,y,z] at the a position [x,y,z], flag to select FORCE_IN_LINK_FRAME or WORLD_FRAME coordinates """
+    pass
+
+def applyExternalTorque(objectUniqueId:int,linkIndex:int,forceObj:list[float],posObj:list[float],flags:int,physicsClientId:int,*args, **kwargs): # real signature unknown
+    """ for objectUniqueId, linkIndex (-1 for base/root link), apply a force [x,y,z] at the a position [x,y,z], flag to select FORCE_IN_LINK_FRAME or WORLD_FRAME coordinates """
+    pass
+
+def calculateInverseDynamics(bodyUniqueId:int,objPositions:list[float],objVelocities:list[float],objAccelerations:list[float],physicsClientId:int,*args, **kwargs): # real signature unknown
+    """ Given an object id, joint positions, joint velocities and joint accelerations, compute the joint forces using Inverse Dynamics """
+    pass
+
+def calculateInverseKinematics(bodyUniqueId:int,endEffectorLinkIndex:int,targetPosition:list[float],targetOrientation:list[float],lowerLimits:list[float],upperLimits:list[float],jointRanges:list[float],restPoses:list[float],jointDamping:list[float],solver:int,currentPosition:list[float],maxNumIterations:int,residualThreshold:int,physicsClientId:int,*args, **kwargs): # real signature unknown
+    """ Inverse Kinematics bindings: Given an object id, current joint positions and target position for the end effector,compute the inverse kinematics and return the new joint state """
+    pass
+
+def calculateInverseKinematics2(bodyUniqueId:int,endEffectorLinkIndices:list[int],targetPositions:list[float],targetOrientation:list[float],lowerLimits:list[float],upperLimits:list[float],jointRanges:list[float],restPoses:list[float],jointDamping:list[float],solver:int,currentPosition:list[float],maxNumIterations:int,residualThreshold:int,physicsClientId:int,*args, **kwargs): # real signature unknown
+    """ Inverse Kinematics bindings: Given an object id, current joint positions and target positions for the end effectors,compute the inverse kinematics and return the new joint state """
+    pass
+
+def calculateJacobian(bodyUniqueId:int, linkIndex:int, localPosition:list[float], objPositions:list[float], objVelocities:list[float], objAccelerations:list[float], physicsClientId:int=0): # real signature unknown; restored from __doc__
+    """
+    linearJacobian, angularJacobian = calculateJacobian(bodyUniqueId, linkIndex, localPosition, objPositions, objVelocities, objAccelerations, physicsClientId=0)
+    Compute the jacobian for a specified local position on a body and its kinematics.
+    Args:
+      bodyIndex - a scalar defining the unique object id.
+      linkIndex - a scalar identifying the link containing the local point.
+      localPosition - a list of [x, y, z] of the coordinates defined in the link frame.
+      objPositions - a list of the joint positions.
+      objVelocities - a list of the joint velocities.
+      objAccelerations - a list of the joint accelerations.
+    Returns:
+      linearJacobian - a list of the partial linear velocities of the jacobian.
+      angularJacobian - a list of the partial angular velocities of the jacobian.
+    """
+    pass
+
+def calculateMassMatrix(bodyUniqueId:int, objPositions:list[float], physicsClientId:int=0): # real signature unknown; restored from __doc__
+    """
+    massMatrix = calculateMassMatrix(bodyUniqueId, objPositions, physicsClientId=0)
+    Compute the mass matrix for an object and its chain of bodies.
+    Args:
+      bodyIndex - a scalar defining the unique object id.
+      objPositions - a list of the joint positions.
+    Returns:
+      massMatrix - a list of lists of the mass matrix components.
+    """
+    pass
+
+def calculateVelocityQuaternion(*args, **kwargs): # real signature unknown
+    """ Compute the angular velocity given start and end quaternion and delta time. """
+    pass
+
+def changeConstraint(parentBodyUniqueId:int,parentLinkIndex:int,childBodyUniqueId:int,childLinkIndex:int,jointType:int,jointAxis:list[float],parentFramePosition:list[float],childFramePosition:list[float],parentFrameOrientation:list[float],childFrameOrientation:list[float],physicsClientId:int,*args, **kwargs): # real signature unknown
+    """ Change some parameters of an existing constraint, such as the child pivot or child frame orientation, using its unique id. """
+    pass
+
+def changeDynamics(bodyUniqueId:int,linkIndex:int,mass:float,lateralFriction:float,spinningFriction:float,rollingFriction:float,restitution:float,linearDamping:float,angularDamping:float,contactStiffness:float,contactDamping:float,frictionAnchor:int,localInertiaDiagnoal:list,ccdSweptSphereRadius:float,contactProcessingThreshold:float,activationState:int,jointDamping:float,anisotropicFriction:float,maxJointVelocity:float,collisionMargin:float,jointLowerLimit:float,jointUpperLimit:float,jointLimitForce:float,physicsClientId:int,*args, **kwargs): # real signature unknown
+    """ change dynamics information such as mass, lateral friction coefficient. """
+    pass
+
+def changeTexture(*args, **kwargs): # real signature unknown
+    """ Change a texture file. """
+    pass
+
+def changeVisualShape(objectUniqueId:int,linkIndex:int,shapeIndex:int,textureUniqueId:int,rgbaColor:list[float],specularColor:list[float],physicsClientId:int,*args, **kwargs): # real signature unknown
+    """ Change part of the visual shape information for one object. """
+    pass
+
+def computeDofCount(*args, **kwargs): # real signature unknown
+    """ computeDofCount returns the number of degrees of freedom, including 7 degrees of freedom for the base in case of floating base """
+    pass
+
+def computeProjectionMatrix(left:float,right:float,bottom:float,up:float,near:float,far:float,physicsClientId:int,*args, **kwargs): # real signature unknown
+    """ Compute a camera projection matrix from screen left/right/bottom/top/near/far values """
+    pass
+
+def computeProjectionMatrixFOV(fov:float,aspect:float,nearVal:float,farVal:float,physicsClientId:int,*args, **kwargs): # real signature unknown
+    """ Compute a camera projection matrix from fov, aspect ratio, near, far values """
+    pass
+
+def computeViewMatrix(cameraEyePosition:list[float],cameraTargetPosition:list[float],cameraUpVector:list[float],physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Compute a camera viewmatrix from camera eye,  target position and up vector """
+    pass
+
+def computeViewMatrixFromYawPitchRoll(cameraTargetPosition:list[float],distance:float,yaw:float,pitch:float,roll:float,upAxisIndex:int,physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Compute a camera viewmatrix from camera eye,  target position and up vector """
+    pass
+
+def configureDebugVisualizer(flag:int,enable:int,lightPosition:list[float],shadowMapResolution:int,shadowMapWorldSize:int,shadowMapIntensity:float,rgbBackground:list[float],physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ For the 3D OpenGL Visualizer, enable/disable GUI, shadows. """
+    pass
+
+def connect(method, key=None, options=''): # real signature unknown; restored from __doc__
+    """
+    connect(method, key=SHARED_MEMORY_KEY, options='')
+    options='--background_color_red=1.0 --background_color_green=0.0 --background_color_blue=0.0'
+    connect(method, hostname='localhost', port=1234, options='')
+    Connect to an existing physics server (using shared memory by default).
+    """
+    pass
+
+def createCollisionShape(shapeType:int,radius:float,halfExtents:list[float],height:float,fileName:list[float],meshScale:list[float],planeNormal:list[float],flags:int,collisionFramePosition:list[float],collisionFrameOrientationosition:list[float],vertices:list[float],indices:list[int],heightfieldTextureScaling:float,numHeightfieldRows:int,numHeightfieldColumns:int,replaceHeightfieldIndex:int,physicsClientId:int,*args, **kwargs): # real signature unknown
+    """ Create a collision shape. Returns a non-negative (int) unique id, if successfull, negative otherwise. """
+    pass
+
+def createCollisionShapeArray(*args, **kwargs): # real signature unknown
+    """ Create collision shapes. Returns a non-negative (int) unique id, if successfull, negative otherwise. """
+    pass
+
+def createConstraint(parentBodyUniqueId:int,parentLinkIndex:int,childBodyUniqueId:int,childLinkIndex:int,jointType:int,jointAxis:list[float],parentFramePosition:list[float],childFramePosition:list[float],parentFrameOrientation:list[float],childFrameOrientation:list[float],physicsClientId:int,*args, **kwargs): # real signature unknown
+    """ Create a constraint between two bodies. Returns a (int) unique id, if successfull. """
+    pass
+
+def createMultiBody(baseMass:float,baseCollisionShapeIndex:int,baseVisualShapeIndex:int,basePosition:list[float],baseOrientation:list[float],baseInertialFramePosition:list[float],baseInertialFrameOrientation:list[float],linkMasses:list[float],linkCollisionShapeIndices:list[int],linkVisualShapeIndices:list[int],linkPositions:list[float],linkOrientations:list[float],linkInertialFramePositions:list[float],linkInertialFrameOrientations:list[float],linkParentIndices:list[int],linkJointTypes:list[int],linkJointAxis:list[float],useMaximalCoordinates:int,flags:int,batchPositions:list[float],physicsClientId:int=0,*args, **kwargs) -> int : # real signature unknown
+    """ Create a multi body. Returns a non-negative (int) unique id, if successfull, negative otherwise. """
+    pass
+
+def createSoftBodyAnchor(*args, **kwargs): # real signature unknown
+    """ Create an anchor (attachment) between a soft body and a rigid or multi body. """
+    pass
+
+def createVisualShape(shapeType:int,radius:float,halfExtents:list[float],height:float,fileName:list[float],meshScale:list[float],planeNormal:list[float],flags:int,visualFramePosition:list[float],visualFrameOrientationosition:list[float],vertices:list[float],indices:list[int],heightfieldTextureScaling:float,numHeightfieldRows:int,numHeightfieldColumns:int,replaceHeightfieldIndex:int,physicsClientId:int,*args, **kwargs): # real signature unknown
+    """ Create a visual shape. Returns a non-negative (int) unique id, if successfull, negative otherwise. """
+    pass
+
+def createVisualShapeArray(*args, **kwargs): # real signature unknown
+    """ Create visual shapes. Returns a non-negative (int) unique id, if successfull, negative otherwise. """
+    pass
+
+def disconnect(physicsClientId=0): # real signature unknown; restored from __doc__
+    """
+    disconnect(physicsClientId=0)
+    Disconnect from the physics server.
+    """
+    pass
+
+def enableJointForceTorqueSensor(bodyUniqueId:int,jointIndex:int,enableSensor:int,physicsClientId:int,*args, **kwargs): # real signature unknown
+    """ Enable or disable a joint force/torque sensor measuring the joint reaction forces. """
+    pass
+
+def executePluginCommand(pluginUniqueId:int,textArgument:str,intArgs:list[int],floatArgs:list[float],physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Execute a command, implemented in a plugin. """
+    pass
+
+def getAABB(bodyUniqueId:int,linkIndex:int,physicsClientId:int=0,*args, **kwargs) -> list[float]: # real signature unknown
+    """ Get the axis aligned bound box min and max coordinates in world space. """
+    pass
+
+def getAPIVersion(physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Get version of the API. Compatibility exists for connections using the same API version. Make sure both client and server use the same number of bits (32-bit or 64bit). """
+    pass
+
+def getAxisAngleFromQuaternionquaternion(quaternion:list[float],physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Compute the quaternion from axis and angle representation. """
+    pass
+
+def getAxisDifferenceQuaternion(*args, **kwargs): # real signature unknown
+    """ Compute the velocity axis difference from two quaternions. """
+    pass
+
+def getBasePositionAndOrientation(objectUniqueId:int,physicsClientId:int,*args, **kwargs) -> list[float]: # real signature unknown
+    """ Get the world position and orientation of the base of the object. (x,y,z) position vector and (x,y,z,w) quaternion orientation. """
+    pass
+
+def getBaseVelocity(objectUniqueId:int,physicsClientId:int,*args, **kwargs) -> list[float]: # real signature unknown
+    """ Get the linear and angular velocity of the base of the object  in world space coordinates. (x,y,z) linear velocity vector and (x,y,z) angular velocity vector. """
+    pass
+
+def getBodyInfo(*args, **kwargs): # real signature unknown
+    """ Get the body info, given a body unique id. """
+    pass
+
+def getBodyUniqueId(*args, **kwargs): # real signature unknown
+    """ getBodyUniqueId is used after connecting to server with existing bodies.Get the unique id of the body, given a integer range [0.. number of bodies). """
+    pass
+
+def getCameraImage(width:int,height:int,viewMatrix:float,projectionMatrix:float,lightDirection:list[float],lightColor:list[float],lightDistance:float,shadow:int,lightAmbientCoeff:float,lightDiffuseCoeff:float,lightSpecularCoeff:float,renderer:int,flags:int,physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Render an image (given the pixel resolution width, height, camera viewMatrix , projectionMatrix, lightDirection, lightColor, lightDistance, shadow, lightAmbientCoeff, lightDiffuseCoeff, lightSpecularCoeff, and renderer), and return the 8-8-8bit RGB pixel data and floating point depth values """
+    pass
+
+def getClosestPoints(bodyA:int,bodyB:int,distance:float,linkIndexA:int,linkIndexB:int,physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Compute the closest points between two objects, if the distance is below a given threshold.Input is two objects unique ids and distance threshold. """
+    pass
+
+def getCollisionShapeData(objectUniqueId:int,linkIndex:int,physicsClientId:int,*args, **kwargs): # real signature unknown
+    """ Return the collision shape information for one object. """
+    pass
+
+def getConnectionInfo(physicsClientId=0): # real signature unknown; restored from __doc__
+    """
+    getConnectionInfo(physicsClientId=0)
+    Return if a given client id is connected, and using what method.
+    """
+    pass
+
+def getConstraintInfo(constraintUniqueId:int,physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Get the user-created constraint info, given a constraint unique id. """
+    pass
+
+def getConstraintState(constraintUniqueId:int,physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Get the user-created constraint state (applied forces), given a constraint unique id. """
+    pass
+
+def getConstraintUniqueId(*args, **kwargs): # real signature unknown
+    """ Get the unique id of the constraint, given a integer index in range [0.. number of constraints). """
+    pass
+
+def getContactPoints(*args, **kwargs): # real signature unknown
+    """ Return existing contact points after the stepSimulation command. Optional arguments one or two object unique ids, that need to be involved in the contact. """
+    pass
+
+def getDebugVisualizerCamera(*args, **kwargs): # real signature unknown
+    """ Get information about the 3D visualizer camera, such as width, height, view matrix, projection matrix etc. """
+    pass
+
+def getDifferenceQuaternion(quaternionStart:list[float],quaternionEnd:list[float],physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Compute the quaternion difference from two quaternions. """
+    pass
+
+def getDynamicsInfo(bodyUniqueId:int,linkIndex:int,physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Get dynamics information such as mass, lateral friction coefficient. """
+    pass
+
+def getEulerFromQuaternion(quaternion:list[float],physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Convert quaternion [x,y,z,w] to Euler [roll, pitch, yaw] as in URDF/SDF convention """
+    pass
+
+def getJointInfo(bodyUniqueId:int,jointIndex:int,physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Get the name and type info for a joint on a body. """
+    pass
+
+def getJointState(bodyUniqueId:int,jointIndex:int,physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Get the state (position, velocity etc) for a joint on a body. """
+    pass
+
+def getJointStateMultiDof(bodyUniqueId:int,jointIndex:int,physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Get the state (position, velocity etc) for a joint on a body. (supports planar and spherical joints) """
+    pass
+
+def getJointStates(bodyUniqueId:int,jointIndices:list[int],physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Get the state (position, velocity etc) for multiple joints on a body. """
+    pass
+
+def getJointStatesMultiDof(bodyUniqueId:int,jointIndex:list[int],physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Get the states (position, velocity etc) for multiple joint on a body. (supports planar and spherical joints) """
+    pass
+
+def getKeyboardEvents(physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Get keyboard events, keycode and state (KEY_IS_DOWN, KEY_WAS_TRIGGERED, KEY_WAS_RELEASED) """
+    pass
+
+def getLinkState(bodyUniqueId:int, linkIndex:int, computeLinkVelocity:int=0, computeForwardKinematics:int=0, physicsClientId:int=0): # real signature unknown; restored from __doc__
+    """
+    position_linkcom_world, world_rotation_linkcom,
+    position_linkcom_frame, frame_rotation_linkcom,
+    position_frame_world, world_rotation_frame,
+    linearVelocity_linkcom_world, angularVelocity_linkcom_world
+      = getLinkState(bodyUniqueId, linkIndex, computeLinkVelocity=0,
+                     computeForwardKinematics=0, physicsClientId=0)
+    Provides extra information such as the Cartesian world coordinates center of mass (COM) of the link, relative to the world reference frame.
+    """
+    pass
+
+def getLinkStates(bodyUniqueId:int, linkIndices:list[int], computeLinkVelocity:int=0, computeForwardKinematics:int=0, physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ same as getLinkState except it takes a list of linkIndices """
+    pass
+
+def getMatrixFromQuaternion(quaternion:list[float],*args, **kwargs): # real signature unknown
+    """ Compute the 3x3 matrix from a quaternion, as a list of 9 values (row-major) """
+    pass
+
+def getMeshData(bodyUniqueId:int,linkIndex:int,collisionShapeIndex:int,flags:int,physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Get mesh data. Returns vertices etc from the mesh. """
+    pass
+
+def getMouseEvents(physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Get mouse events, event type and button state (KEY_IS_DOWN, KEY_WAS_TRIGGERED, KEY_WAS_RELEASED) """
+    pass
+
+def getNumBodies(*args, **kwargs): # real signature unknown
+    """ Get the number of bodies in the simulation. """
+    pass
+
+def getNumConstraints(*args, **kwargs): # real signature unknown
+    """ Get the number of user-created constraints in the simulation. """
+    pass
+
+def getNumJoints(bodyUniqueId:int,physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Get the number of joints for an object. """
+    pass
+
+def getNumUserData(bodyUniqueId_physicsClientId=0): # real signature unknown; restored from __doc__
+    """
+    getNumUserData(bodyUniqueId physicsClientId=0)
+    Retrieves the number of user data entries in a body.
+    """
+    pass
+
+def getOverlappingObjects(aabbMin:list[float],aabbMax:list[float],physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Return all the objects that have overlap with a given axis-aligned bounding box volume (AABB).Input are two vectors defining the AABB in world space [min_x,min_y,min_z],[max_x,max_y,max_z]. """
+    pass
+
+def getPhysicsEngineParameters(*args, **kwargs): # real signature unknown
+    """ Get the current values of internal physics engine parameters """
+    pass
+
+def getQuaternionFromAxisAngle(*args, **kwargs): # real signature unknown
+    """ Compute the quaternion from axis and angle representation. """
+    pass
+
+def getQuaternionFromEuler(eulerAngle:list[float],physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Convert Euler [roll, pitch, yaw] as in URDF/SDF convention, to quaternion [x,y,z,w] """
+    pass
+
+def getQuaternionSlerp(*args, **kwargs): # real signature unknown
+    """ Compute the spherical interpolation given a start and end quaternion and an interpolation value in range [0..1] """
+    pass
+
+def getUserData(userDataId, physicsClientId=0): # real signature unknown; restored from __doc__
+    """
+    getUserData(userDataId, physicsClientId=0)
+    Returns the user data value.
+    """
+    pass
+
+def getUserDataId(bodyUniqueId, key, linkIndex=-1, visualShapeIndex=-1, physicsClientId=0): # real signature unknown; restored from __doc__
+    """
+    getUserDataId(bodyUniqueId, key, linkIndex=-1, visualShapeIndex=-1, physicsClientId=0)
+    Retrieves the userDataId given the key and optionally link and visual shape index.
+    """
+    pass
+
+def getUserDataInfo(bodyUniqueId, userDataIndex, physicsClientId=0): # real signature unknown; restored from __doc__
+    """
+    getUserDataInfo(bodyUniqueId, userDataIndex, physicsClientId=0)
+    Retrieves the key and the identifier of a user data as (userDataId, key, bodyUniqueId, linkIndex, visualShapeIndex).
+    """
+    pass
+
+def getVisualShapeData(objectUniqueId:int,flags:int,physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Return the visual shape information for one object. """
+    pass
+
+def getVREvents(deviceTypeFilter:int,allAnalogAxes:int,physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Get Virtual Reality events, for example to track VR controllers position/buttons """
+    pass
+
+def invertTransform(position:list[float],orientation:list[float],*args, **kwargs): # real signature unknown
+    """ Invert a transform, provided as [position], [quaternion]. """
+    pass
+
+def isConnected(physicsClientId=0): # real signature unknown; restored from __doc__
+    """
+    isConnected(physicsClientId=0)
+    Return if a given client id is connected.
+    """
+    pass
+
+def isNumpyEnabled(*args, **kwargs): # real signature unknown
+    """ return True if PyBullet was compiled with NUMPY support. This makes the getCameraImage API faster """
+    pass
+
+def loadBullet(*args, **kwargs): # real signature unknown
+    """ Load a world from a .bullet file. """
+    pass
+
+def loadMJCF(fileName:str,useMaximalCoordinates:int,globalScaling:float,physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Load multibodies from an MJCF file. """
+    pass
+
+def loadPlugin(pluginPath:str,postFix:str,physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Load a plugin, could implement custom commands etc. """
+    pass
+
+def loadSDF(fileName:str,useMaximalCoordinates:int,globalScaling:float,physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Load multibodies from an SDF file. """
+    pass
+
+def loadSoftBody(fileName:str,basePosition:list[float],baseOrientation:list[float],scale:float,mass:float,collisionMargin,useMassSpring:bool,useBendingSprings:bool,useNeoHookean:bool,springElasticStiffness:float,springDampingStiffness:float,springDampingAllDirections:float,springBendingStiffness:float,NeoHookeanMu:float,NeoHookeanLambda:float,NeoHookeanDamping:float,frictionCoeff:float,useFaceContact:bool,useSelfCollision:bool,repulsionStiffness:float,*args, **kwargs): # real signature unknown
+    """ Load a softbody from an obj file. """
+    pass
+
+def loadTexture(*args, **kwargs): # real signature unknown
+    """ Load texture file. """
+    pass
+
+def loadURDF(fileName:str, basePosition:list[float]=None, baseOrientation:list[float]=None, useMaximalCoordinates:int=0, useFixedBase:bool=False, flags:int=0, globalScaling:float=1.0, physicsClientId:int=0): # real signature unknown; restored from __doc__
+    """
+    bodyUniqueId = loadURDF(fileName, basePosition=[0.,0.,0.], baseOrientation=[0.,0.,0.,1.], useMaximalCoordinates=0, useFixedBase=0, flags=0, globalScaling=1.0, physicsClientId=0)
+    Create a multibody by loading a URDF file.
+    """
+    pass
+
+def multiplyTransforms(positionA:list[float],orientationA:list[float],positionB:list[float],orientationB:list[float],physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Multiply two transform, provided as [position], [quaternion]. """
+    pass
+
+def performCollisionDetection(physicsClientId=0): # real signature unknown; restored from __doc__
+    """
+    performCollisionDetection(physicsClientId=0)
+    Update AABBs, compute overlapping pairs and contact points. stepSimulation also includes this already.
+    """
+    pass
+
+def rayTest(rayFromPosition:list[float],rayToPosition:list[float],physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Cast a ray and return the first object hit, if any. Takes two arguments (from_position [x,y,z] and to_position [x,y,z] in Cartesian world coordinates """
+    pass
+
+def rayTestBatch(rayFromPosition:list[float],rayToPosition:list[float],parentObjectUniqueId:int,parentLinkIndex:int,numThreads:int,reportHitNumber:int,collisionFilterMask:int,fractionEpsilon:float,physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Cast a batch of rays and return the result for each of the rays (first object hit, if any. or -1) Takes two required arguments (list of from_positions [x,y,z] and a list of to_positions [x,y,z] in Cartesian world coordinates) and one optional argument numThreads to specify the number of threads to use to compute the ray intersections for the batch. Specify 0 to let Bullet decide, 1 (default) for single core execution, 2 or more to select the number of threads to use. """
+    pass
+
+def readUserDebugParameter(itemUniqueId:int,physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Read the current value of a user debug parameter, given the user debug item unique id. """
+    pass
+
+def removeAllUserDebugItems(*args, **kwargs): # real signature unknown
+    """ remove all user debug draw items """
+    pass
+
+def removeAllUserParameters(physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ remove all user debug parameters (sliders, buttons) """
+    pass
+
+def removeBody(bodyUniqueId:int,*args, **kwargs): # real signature unknown
+    """ Remove a body by its body unique id. """
+    pass
+
+def removeCollisionShape(*args, **kwargs): # real signature unknown
+    """ Remove a collision shape. Only useful when the collision shape is not used in a body (to perform a getClosestPoint query). """
+    pass
+
+def removeConstraint(userConstraintUniqueId:int,physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Remove a constraint using its unique id. """
+    pass
+
+def removeState(*args, **kwargs): # real signature unknown
+    """ Remove a state created using saveState by its state unique id. """
+    pass
+
+def removeUserData(userDataId, physicsClientId:int=0): # real signature unknown; restored from __doc__
+    """
+    removeUserData(userDataId, physicsClientId=0)
+    Removes a user data entry.
+    """
+    pass
+
+def removeUserDebugItem(itemUniqueId:int,physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ remove a user debug draw item, giving its unique id """
+    pass
+
+def renderImage(*args, **kwargs): # real signature unknown
+    """ obsolete, please use getCameraImage and getViewProjectionMatrices instead """
+    pass
+
+def resetBasePositionAndOrientation(bodyUniqueId:int,posObj:list[float],ornObj:list[float],physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Reset the world position and orientation of the base of the object instantaneously, not through physics simulation. (x,y,z) position vector and (x,y,z,w) quaternion orientation. """
+    pass
+
+def resetBaseVelocity(objectUniqueId:int,linearVelocity:list[float],angularVelocity:list[float],physicsClientId:int,*args, **kwargs): # real signature unknown
+    """ Reset the linear and/or angular velocity of the base of the object  in world space coordinates. linearVelocity (x,y,z) and angularVelocity (x,y,z). """
+    pass
+
+def resetDebugVisualizerCamera(cameraDistance:float,cameraYaw:float,cameraPitch:float,cameraTargetPosition:list[float],physicsClientId:int=0) -> None: # real signature unknown
+    """ For the 3D OpenGL Visualizer, set the camera distance, yaw, pitch and target position. 
+    Example: pybullet.resetDebugVisualizerCamera( cameraDistance=3, cameraYaw=30,cameraPitch=52, cameraTargetPosition=[0,0,0])"""
+    pass
+
+def resetJointState(objectUniqueId:int, jointIndex:int, targetValue:float, targetVelocity:int=0, physicsClientId:int=0): # real signature unknown; restored from __doc__
+    """
+    resetJointState(objectUniqueId, jointIndex, targetValue, targetVelocity=0, physicsClientId=0)
+    Reset the state (position, velocity etc) for a joint on a body instantaneously, not through physics simulation.
+    """
+    pass
+
+def resetJointStateMultiDof(objectUniqueId:int, jointIndex:int, targetValue:int, targetVelocity=0, physicsClientId:int=0): # real signature unknown; restored from __doc__
+    """
+    resetJointStateMultiDof(objectUniqueId, jointIndex, targetValue, targetVelocity=0, physicsClientId=0)
+    Reset the state (position, velocity etc) for a joint on a body instantaneously, not through physics simulation.
+    """
+    pass
+
+def resetJointStatesMultiDof(objectUniqueId:int, jointIndices:list[int], targetValues:list[int], targetVelocities:list[int], physicsClientId:int=0): # real signature unknown; restored from __doc__
+    """
+    resetJointStatesMultiDof(objectUniqueId, jointIndices, targetValues, targetVelocities=0, physicsClientId=0)
+    Reset the states (position, velocity etc) for multiple joints on a body instantaneously, not through physics simulation.
+    """
+    pass
+
+def resetMeshData(*args, **kwargs): # real signature unknown
+    """ Reset mesh data. Only implemented for deformable bodies. """
+    pass
+
+def resetSimulation(flags:int,physicsClientId:int=0): # real signature unknown; restored from __doc__
+    """
+    resetSimulation(physicsClientId=0)
+    Reset the simulation: remove all objects and start from an empty world.
+    """
+    pass
+
+def resetVisualShapeData(*args, **kwargs): # real signature unknown
+    """ Obsolete method, kept for backward compatibility, use changeVisualShapeData instead. """
+    pass
+
+def restoreState(fileName:str,stateId:int,clientServerId:int,*args, **kwargs): # real signature unknown
+    """ Restore the full state of an existing world. """
+    pass
+
+def rotateVector(*args, **kwargs): # real signature unknown
+    """ Rotate a vector using a quaternion. """
+    pass
+
+def saveBullet(fileName:str,stateId:int,clientServerId:int,*args, **kwargs): # real signature unknown
+    """ Save the full state of the world to a .bullet file. """
+    pass
+
+def saveState(fileName:str,stateId:int,clientServerId:int,*args, **kwargs): # real signature unknown
+    """ Save the full state of the world to memory. """
+    pass
+
+def saveWorld(filename:str,clientServerId:int): # real signature unknown; restored from __doc__
+    """ Save a approximate Python file to reproduce the current state of the world: saveWorld(filename). (very preliminary and approximately) """
+    pass
+
+def setAdditionalSearchPath(*args, **kwargs): # real signature unknown
+    """ Set an additional search path, used to load URDF/SDF files. """
+    pass
+
+def setCollisionFilterGroupMask(bodyUniqueId:int,linkIndexA:int,collisionFilterGroup:int,collisionFilterMask:int,physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Set the collision filter group and the mask for a body. """
+    pass
+
+def setCollisionFilterPair(bodyUniqueIdA:int,bodyUniqueIdB:int,linkIndexA:int,linkIndexB:int,enableCollision:int,physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Enable or disable collision detection between two object links.Input are two object unique ids and two link indices and an enumto enable or disable collisions. """
+    pass
+
+def setDebugObjectColor(objectUniqueId:int,linkIndex:int,objectDebugColorRGB:list[float],physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Override the wireframe debug drawing color for a particular object unique id / link index.If you ommit the color, the custom color will be removed. """
+    pass
+
+def setDefaultContactERP(defaultContactERP, physicsClientId=0): # real signature unknown; restored from __doc__
+    """
+    setDefaultContactERP(defaultContactERP, physicsClientId=0)
+    Set the amount of contact penetration Error Recovery Paramater (ERP) in each time step. 		This is an tuning parameter to control resting contact stability. This value depends on the time step.
+    """
+    pass
+
+def setGravity(gravX:float, gravY:float, gravZ:float, physicsClientId:int=0): # real signature unknown; restored from __doc__
+    """
+    setGravity(gravX, gravY, gravZ, physicsClientId=0)
+    Set the gravity acceleration (x,y,z).
+    """
+    pass
+
+def setInternalSimFlags(*args, **kwargs): # real signature unknown
+    """ This is for experimental purposes, use at own risk, magic may or not happen """
+    pass
+
+def setJointMotorControl(this_is_obsolete_use_motorctrl_2,*args, **kwargs): # real signature unknown
+    """ This (obsolete) method cannot select non-zero physicsClientId, use setJointMotorControl2 instead.Set a single joint motor control mode and desired target value. There is no immediate state change, stepSimulation will process the motors. """
+    pass
+
+def setJointMotorControl2(bodyIndex:int,jointIndex:int,controlMode:int,targetPosition:float,targetVelocity:float,force:float,positionGain:float,velocityGain:float,maxVelocity:float,physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Set a single joint motor control mode and desired target value. There is no immediate state change, stepSimulation will process the motors. """
+    pass
+
+def setJointMotorControlArray(bodyIndex:int,jointIndices:list[int],controlMode:int,targetPositions:list[float],targetVelocities:list[float],forces:list[float],positionGains:list[float],velocityGains:list[float],maxVelocities:list[float],physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Set an array of motors control mode and desired target value. There is no immediate state change, stepSimulation will process the motors.This is similar to setJointMotorControl2, with jointIndices as a list, and optional targetPositions, targetVelocities, forces, kds and kps as listsUsing setJointMotorControlArray has the benefit of lower calling overhead. """
+    pass
+
+def setJointMotorControlMultiDof(bodyUniqueId:int,jointIndex:int,controlMode:int,targetPosition:list[float],targetVelocity:list[float],force:list[float],positionGain:float,velocityGain:float,maxVelocity:float,physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Set a single joint motor control mode and desired target value. There is no immediate state change, stepSimulation will process the motors.This method sets multi-degree-of-freedom motor such as the spherical joint motor. """
+    pass
+
+def setJointMotorControlMultiDofArray(bodyUniqueId:int,jointIndices:list[int],controlMode:int,targetPositions:list[float],targetVelocities:list[float],forces:list[float],positionGains:list[float],velocityGains:list[float],maxVelocities:list[float],physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Set control mode and desired target values for multiple motors. There is no immediate state change, stepSimulation will process the motors.This method sets multi-degree-of-freedom motor such as the spherical joint motor. """
+    pass
+
+def setPhysicsEngineParameter(fixedTimeStep:float,numSolverIterations:int,useSplitImpulse:int,splitImpulsePenetrationThreshold:float,numSubSteps:int,collisionFilterMode:int,contactBreakingThreshold:float,maxNumCmdPer1ms:int,enableFileCaching:int,restitutionVelocityThreshold:float,erp:float,contactERP:float,frictionERP:float,enableConeFriction:int,deterministicOverlappingPairs:int,allowedCcdPenetration:float,jointFeedbackMode:int,solverResidualThreshold:float,contactSlop:float,enableSAT:int,constraintSolverType:int,globalCFM:float,minimumSolverIslandSize:int,reportSolverAnalytics:int,warmStartingFactor:float,physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Set some internal physics engine parameter, such as cfm or erp etc. """
+    pass
+
+def setRealTimeSimulation(enableRealTimeSimulation:int, physicsClientId:int=0): # real signature unknown; restored from __doc__
+    """
+    setRealTimeSimulation(enableRealTimeSimulation, physicsClientId=0)
+    Enable or disable real time simulation (using the real time clock, RTC) in the physics server. Expects one integer argument, 0 or 1
+    """
+    pass
+
+def setTimeOut(*args, **kwargs): # real signature unknown
+    """ Set the timeOut in seconds, used for most of the API calls. """
+    pass
+
+def setTimeStep(timestep:float, physicsClientId:int=0): # real signature unknown; restored from __doc__
+    """
+    setTimeStep(timestep, physicsClientId=0)
+    Set the amount of time to proceed at each call to stepSimulation. (unit is seconds, typically range is 0.01 or 0.001)
+    """
+    pass
+
+def setVRCameraState(rootPosition:list[float],rootOrientation:list[float],trackObject:list[float],trackObjectFlag:int,physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Set properties of the VR Camera such as its root transform for teleporting or to track objects (camera inside a vehicle for example). """
+    pass
+
+def startStateLogging(loggingType:int,fileName:str,objectUniqueIds:list[int],maxLogDof:int,bodyUniqueIdA:int,bodyUniqueIdB:int,linkIndexA:int,linkIndexB:int,deviceTypeFilter:int,logFlags:int,physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Start logging of state, such as robot base position, orientation, joint positions etc. Specify loggingType (STATE_LOGGING_MINITAUR, STATE_LOGGING_GENERIC_ROBOT, STATE_LOGGING_VR_CONTROLLERS, STATE_LOGGING_CONTACT_POINTS, etc), fileName, optional objectUniqueId, maxLogDof, bodyUniqueIdA, bodyUniqueIdB, linkIndexA, linkIndexB. Function returns int loggingUniqueId """
+    pass
+
+def stepSimulation(physicsClientId:int=0): # real signature unknown; restored from __doc__
+    """
+    stepSimulation(physicsClientId=0)
+    Step the simulation using forward dynamics.
+    """
+    pass
+
+def stopStateLogging(*args, **kwargs): # real signature unknown
+    """ Stop logging of robot state, given a loggingUniqueId. """
+    pass
+
+def submitProfileTiming(*args, **kwargs): # real signature unknown
+    """ Add a custom profile timing that will be visible in performance profile recordings on the physics server.On the physics server (in GUI and VR mode) you can press 'p' to start and/or stop profile recordings """
+    pass
+
+def syncBodyInfo(physicsClientId=0): # real signature unknown; restored from __doc__
+    """
+    syncBodyInfo(physicsClientId=0)
+    Update body and constraint/joint information, in case other clients made changes.
+    """
+    pass
+
+def syncUserData(bodyUniqueIds=list[int], physicsClientId:int=0): # real signature unknown; restored from __doc__
+    """
+    syncUserData(bodyUniqueIds=[], physicsClientId=0)
+    Update user data, in case other clients made changes.
+    """
+    pass
+
+def unloadPlugin(*args, **kwargs): # real signature unknown
+    """ Unload a plugin, given the pluginUniqueId. """
+    pass
+
+def unsupportedChangeScaling(*args, **kwargs): # real signature unknown
+    """ Change the scaling of the base of an object.Warning: unsupported rudimentary feature that has many limitations. """
+    pass
+
+def vhacd(fileNameIn:str,fileNameOut:str,fileNameLog:str,concavity:float,alpha:float,beta:float,gamma:float,minVolumePerCH:float,resolution:int,maxNumVerticesPerCH:int,depth:int,planeDownsampling:int,convexhullDownsampling:int,pca:int,mode:int,convexhullApproximation:int,physicsClientId:int=0,*args, **kwargs): # real signature unknown
+    """ Compute volume hierarchical convex decomposition of an OBJ file. """
+    pass
+
+# classes
+
+class error(Exception):
+    # no doc
+    def __init__(self, *args, **kwargs): # real signature unknown
+        pass
+
+    __weakref__ = property(lambda self: object(), lambda self, v: None, lambda self: None)  # default
+    """list of weak references to the object (if defined)"""
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index bf0ee307..eae49bd7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ requires = ["flit_core >=3.2,<4"]
 build-backend = "flit_core.buildapi"
 
 [project]
-name = "aidesign"
+name = "vai_lab"
 authors = [
     {name = "Chris McGreavy", email = "chris.mcgreavy@aalto.fi"},
     {name = "Carlos Sevilla Salcedo", email = "carlos.sevillasalcedo@aalto.fi"}
@@ -12,19 +12,34 @@ authors = [
 readme = "README.md"
 classifiers = [
     "License :: OSI Approved :: MIT License",
+    'Programming Language :: Python :: 3.10',
+    'Programming Language :: Python :: 3.11'
 ]
 requires-python = ">=3.8"
-version = "1.0"
+version = "0.0.dev4"
 description = "AI assisted Virtual Laboratory framework."
 
 dependencies = [
-    "numpy",
-    "tk", # package name of tkinter
-    "pillow", # package name of PIL
-    "pandas",
-    "sklearn",
-    "ttkwidgets"
+    "numpy >= 1.20.0",
+    "pillow >= 9.0.0", # package name of PIL
+    "pandas >= 1.4.0",
+    "scikit-learn >= 0.0",
+    "ttkwidgets >= 0.12.0",
+    "matplotlib >= 3.5.0",
+    "opencv-python >= 4.6.0.65",
+    "pytest >= 7.2.0",
+    "pytest-cov >= 4.0.0",
+    "pybullet >= 3.2.5",
+    "attrs >= 23.1.0",
+    "tk >= 0.1.0"
 ]
 
 [project.urls]
-Source = "https://github.com/AaltoPML/VAI-labs"
+Source = "https://github.com/AaltoPML/VAI-lab"
+
+[tool.pytest.ini_options]
+minversion = "6.0"
+addopts = "-ra -q"
+
+[project.scripts]
+vai_lab = "vai_lab.run_pipeline:main"
\ No newline at end of file
diff --git a/run_pipeline.py b/run_pipeline.py
deleted file mode 100644
index b63bbaac..00000000
--- a/run_pipeline.py
+++ /dev/null
@@ -1,10 +0,0 @@
-import aidesign as ai
-
-core = ai.Core()
-
-# core.load_config_file(
-#     ("./examples",
-#     "xml_files",
-#     'crystalDesign_v1.xml'))
-
-core.run()
\ No newline at end of file
diff --git a/setup.py b/setup.py
deleted file mode 100644
index a4530e96..00000000
--- a/setup.py
+++ /dev/null
@@ -1,56 +0,0 @@
-import sys
-import subprocess
-import pkg_resources
-
-class SetupPrequisties(object):
-    def __init__(self):
-        self.required_packages = {
-            'numpy',
-            'tk', #package name of tkinter
-            'pillow', #package name of PIL
-            'pandas', 
-            'sklearn',
-            'ttkwidgets',
-            'matplotlib',
-            }
-        self.python_version = sys.executable
-
-    def check_installed_packages(self):
-        print ("\nChecking for required packages...")
-        self.installed = {pkg.key for pkg in pkg_resources.working_set}
-        self.missing = self.required_packages - self.installed
-    
-    def install_missing(self):
-        # subprocess.check_call([self.python_version, '-m', 'pip', 'install', *self.missing], stdout=subprocess.DEVNULL)
-        subprocess.check_call([self.python_version, '-m', 'pip', 'install', *self.missing])
-
-    def get_user_confirmation(self):
-        print ("The following required packages are not installed: ")
-        print ("   - {}".format(",\n   - ".join([*self.missing])))
-        return input ("Install missing packages with pip for {}?  [Y/N] \n".format(self.python_version)).lower()
-
-    def denied_installation(self):
-        print ("No packages installed.")
-
-    def nothing_missing(self):
-        print ("\nAll required packages already installed, no action needed.\n")
-
-    def __call__(self):
-        self.check_installed_packages()
-        if self.missing:
-            user_input = self.get_user_confirmation()
-            if user_input.lower() == 'y':
-                self.install_missing()
-            else:
-                self.denied_installation()
-        else:
-            self.nothing_missing()
-        
-    
-setup = SetupPrequisties()
-setup()
-# print (missing)
-# print (sys.executable)
-# if missing:
-#     python = sys.executable
-#     subprocess.check_call([python, '-m', 'pip', 'install', *missing], stdout=subprocess.DEVNULL)
\ No newline at end of file
diff --git a/aidesign/Core/Assets/UFAIcon.ico b/src/vai_lab/Core/Assets/UFAIcon.ico
similarity index 100%
rename from aidesign/Core/Assets/UFAIcon.ico
rename to src/vai_lab/Core/Assets/UFAIcon.ico
diff --git a/aidesign/Core/Assets/UFAIcon_name.png b/src/vai_lab/Core/Assets/UFAIcon_name.png
similarity index 100%
rename from aidesign/Core/Assets/UFAIcon_name.png
rename to src/vai_lab/Core/Assets/UFAIcon_name.png
diff --git a/aidesign/Core/__init__.py b/src/vai_lab/Core/__init__.py
similarity index 86%
rename from aidesign/Core/__init__.py
rename to src/vai_lab/Core/__init__.py
index 3750ece6..9ff26fa3 100644
--- a/aidesign/Core/__init__.py
+++ b/src/vai_lab/Core/__init__.py
@@ -4,5 +4,5 @@
 
 Let's look for a more automatic way of doing this when we have more modules
 """
-from .AI_design_core import Core
+from .vai_lab_core import Core
 # from .. import *
\ No newline at end of file
diff --git a/aidesign/Core/AI_design_core.py b/src/vai_lab/Core/vai_lab_core.py
similarity index 70%
rename from aidesign/Core/AI_design_core.py
rename to src/vai_lab/Core/vai_lab_core.py
index 6af28e28..3d721ca7 100644
--- a/aidesign/Core/AI_design_core.py
+++ b/src/vai_lab/Core/vai_lab_core.py
@@ -1,29 +1,34 @@
-from aidesign._import_helper import import_module
-from aidesign.Data.xml_handler import XML_handler
-from aidesign.GUI.GUI_core import GUI
-from aidesign.Data.Data_core import Data
-from aidesign._plugin_helpers import PluginSpecs
-from aidesign._types import PluginSpecsInterface, ModuleInterface
 import time
 from sys import exit
-
 from os.path import join
-from typing import Dict, Tuple, Union, List
+from typing import Dict, List, Tuple, Union
+
+from vai_lab._import_helper import import_module
+from vai_lab._plugin_helpers import PluginSpecs
+from vai_lab._types import ModuleInterface, PluginSpecsInterface
+from vai_lab.GUI.GUI_core import GUI
+from vai_lab.Data.Data_core import Data
+from vai_lab.Data.xml_handler import XML_handler
+
+
 class Core:
     def __init__(self) -> None:
+        self.data = Data()
         self._xml_handler = XML_handler()
         self._avail_plugins: PluginSpecsInterface = PluginSpecs()
-        self.data = Data()
+        
         self.loop_level: int = 0
-        self.setup_complete: bool = False
+        self._initialised: bool = False
         self.status_logger:Dict = {}
+        self._debug = False
 
     def _launch(self):
         gui_app = GUI()
+        gui_app._debug = self._debug
         gui_app.set_avail_plugins(self._avail_plugins)
         gui_app.set_gui_as_startpage()
         gui_output = gui_app.launch()
-        if not gui_app.closed:
+        if not self._debug:
             try:
                 self.load_config_file(gui_output["xml_filename"])
             except:
@@ -31,14 +36,18 @@ def _launch(self):
             self._load_data()
 
     def load_config_file(self, filename: Union[str,List,Tuple]):
+        """Loads XML file into XML_handler object.
+        Parses filename first, if needed.
+        """
         if type(filename) == list or type(filename) == tuple:
             filedir:str = join(*filename)
         else:
             filedir = str(filename)
         self._xml_handler.load_XML(filedir)
-        self.setup_complete = True
+        self._initialised = True
 
     def _load_data(self) -> None:
+        """Loads data from XML file into Data object"""
         init_data_fn = self._xml_handler.data_to_load
         self.data.import_data_from_config(init_data_fn)
 
@@ -49,31 +58,32 @@ def _execute_module(self, specs):
         :param specs: dict of module to be executed
         """
         mod: ModuleInterface = import_module(globals(), specs["module_type"]).__call__()
+        mod._debug = self._debug
         mod.set_avail_plugins(self._avail_plugins)
         mod.set_data_in(self.data)
         mod.set_options(specs)
         print("\t"*self.loop_level
                 + specs["module_type"]
-                + " module: \"{}\"".format(specs["name"])
+                + " module: \"{}\" ".format(specs["name"])
                 + "processing..."
               )
         mod.launch()
         self.data = mod.get_result()
 
     def _execute_loop(self, specs):
-        try:
+        if  hasattr(self,"_execute_{}_loop".format(specs["type"])):
             print("\t"*self.loop_level
+                        + "Starting "
                         + specs["type"]
-                        + " loop: "
-                        + "\"{}\"".format(specs["name"])
-                        + " starting...")
+                        + " loop: \"{}\"".format(specs["name"])
+                        + " ...")
             self.loop_level += 1
             getattr(self, "_execute_{}_loop".format(specs["type"]))(specs)
             self.loop_level -= 1
-        except KeyError:
+        else:
             print("\nError: Invalid Loop Type.")
             print("Loop \"{0}\" with type \"{1}\" not recognised".format(
-                specs.key, specs["type"]))
+                specs["name"], specs["type"]))
 
     def _execute_entry_point(self, specs):
         """Placeholder: Will parse the initialiser module when ready"""
@@ -83,7 +93,7 @@ def _execute_exit_point(self, specs):
         """Placeholder: Will parse the Output module when ready"""
         pass
 
-    def _parse_condition(self, condition):
+    def _parse_loop_condition(self, condition):
         try:
             condition = int(condition)
             
@@ -95,20 +105,24 @@ def _parse_condition(self, condition):
                 "Other formats in in development. Only ranged for loops are working currently")
 
     def _execute_for_loop(self, specs):
-        condition = self._parse_condition(specs["condition"])
+        condition = self._parse_loop_condition(specs["condition"])
         for c in condition:
             self._execute(specs)
 
-    def _runTracker(self):
+    def _show_updated_tracker(self):
         self.gui_app = GUI()
+        self.gui_app._debug = self._debug
         self.gui_app.set_avail_plugins(self._avail_plugins)
-        self.gui_app.set_gui(self.status_logger)
+        self.gui_app.set_gui_as_progress_tracker(self.status_logger)
         self.gui_app._append_to_output("xml_filename", self._xml_handler.filename)
+        self.gui_app._load_plugin("progressTracker")
         return self.gui_app.launch()
     
     def _init_status(self, modules):
         for key in [key for key, val in modules.items() if type(val) == dict]:
             self.status_logger[modules[key]['name']] = {}
+            if modules[key]['class'] == "loop":
+                self._init_status(modules[key])
 
     def _add_status(self, module, key, value):
         self.status_logger[module['name']][key] = value
@@ -119,7 +133,6 @@ def _progress_start(self, module):
     def _progress_finish(self, module):
         self._add_status(module, 'finish', self._t)
 
-
     @property
     def _t(self):
         return time.strftime('%H:%M:%S', time.localtime())
@@ -138,18 +151,24 @@ def _execute(self, specs):
             self._progress_finish(specs[key])
 
             if specs[key]["class"] == 'module':
-                term = self._runTracker()
-                if not term['close']:
+                _tracker = self._show_updated_tracker()
+
+                if not _tracker['terminate']:
                     self.load_config_file(self._xml_handler.filename)
+                    # pass
                 else:
                     print('Pipeline terminated')
                     exit()
 
+    def _initialise_with_gui(self):
+        """Launches GUI when no XML file is specified"""
+        print("Loading GUI")
+        print("To load existing config, run core.load_config_file(<path_to_file>)")
+        self._launch()
+
     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()
+        if not self._initialised:
+            self._initialise_with_gui()
         print("Running pipeline...")
         self._load_data()
         
diff --git a/aidesign/Data/Data_core.py b/src/vai_lab/Data/Data_core.py
similarity index 86%
rename from aidesign/Data/Data_core.py
rename to src/vai_lab/Data/Data_core.py
index 50645a50..327ba0fe 100644
--- a/aidesign/Data/Data_core.py
+++ b/src/vai_lab/Data/Data_core.py
@@ -1,5 +1,5 @@
-"""Adds aidesign root folder to pythonpath assuming this script is 2 levels down.
-Hacky patch For testing only, normally you'd have aidesign in your
+"""Adds vai_lab root folder to pythonpath assuming this script is 2 levels down.
+Hacky patch For testing only, normally you'd have vai_lab in your
 OS's PYTHONPATH"""
 from os import path
 
@@ -10,8 +10,9 @@
 
 from typing import Dict, KeysView, TypeVar
 
-from aidesign._import_helper import get_lib_parent_dir
-from aidesign.Data.xml_handler import XML_handler
+from vai_lab._import_helper import get_lib_parent_dir
+from vai_lab.Data.xml_handler import XML_handler
+from vai_lab._import_helper import rel_to_abs
 
 import pandas as pd # type: ignore
 import numpy as np
@@ -72,17 +73,6 @@ def _import_dir(self: DataT,
         for f in files:
             self.import_data(f, data_name)
 
-    def _rel_to_abs(self: DataT, filename: str) -> str:
-        """Checks if path is relative or absolute
-        If absolute, returns original path 
-        If relative, converts path to absolute by appending to base directory
-        """
-        if filename[0] == ".":
-            filename = path.join(self._lib_base_path, filename)
-        elif filename[0] == "/" or (filename[0].isalpha() and filename[0].isupper()):
-            filename = filename
-        return filename
-
     def _get_ext(self: DataT, path_dir: str) -> str:
         """Extracts extension from path_dir, or check if is dir
         :param path_dir: str, path_dir to be checked
@@ -102,7 +92,7 @@ def import_data(self: DataT,
         :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).replace(
+        filename = 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/__init__.py b/src/vai_lab/Data/__init__.py
similarity index 100%
rename from aidesign/Data/__init__.py
rename to src/vai_lab/Data/__init__.py
diff --git a/aidesign/Data/xml_handler.py b/src/vai_lab/Data/xml_handler.py
similarity index 95%
rename from aidesign/Data/xml_handler.py
rename to src/vai_lab/Data/xml_handler.py
index e44c6ce2..0975c962 100644
--- a/aidesign/Data/xml_handler.py
+++ b/src/vai_lab/Data/xml_handler.py
@@ -1,17 +1,15 @@
 from os import path
+from ast import literal_eval
+from typing import Any, Dict, List, Optional, Union
+import xml.etree.ElementTree as ET
 
+# Allows standalone testing 
 if not __package__:
     import sys
     root_mod = path.dirname(path.dirname(path.dirname(__file__)))
     sys.path.append(root_mod)
 
-
-import xml.etree.ElementTree as ET
-from ast import literal_eval
-from os import path
-from typing import Any, Dict, List, Optional, Union
-
-from aidesign._import_helper import get_lib_parent_dir
+from vai_lab._import_helper import get_lib_parent_dir
 
 
 class XML_handler:
@@ -41,6 +39,7 @@ def __init__(self, filename: str = None):
             "Modelling": "module",
             "InputData": "module",
             "DecisionMaking": "module",
+            "Environment": "module",
             "DataStorage": "module",
             "loop": "loop"
         }
@@ -245,6 +244,23 @@ def _load_list(self, element: ET.Element, parent: dict) -> None:
             if len(parent[element.tag]) == 1:
                 parent[element.tag] = parent[element.tag][0]
 
+    def _str_to_num(self, string:str) -> Union[str, int, float]:
+        """Converts a string containing only numeric data to float or int
+            Otherwise returns string
+
+        :param string: The string to be converted if possible
+        :returns out: either a string, float or int
+        """
+        try:
+            return int(string)
+        except ValueError:
+            pass
+
+        try:
+            return float(string)
+        except ValueError:
+            return string
+
     def _parse_text_to_list(self, element: ET.Element) -> List:
         """Formats raw text data
         :param elem: xml.etree.ElementTree.Element to be parsed
@@ -261,6 +277,8 @@ def _parse_text_to_list(self, element: ET.Element) -> List:
                 out[idx] = literal_eval(out[idx])
             if "(" in out[idx] and ")" in out[idx]:
                 out[idx] = list(literal_eval(out[idx]))
+            if type(out[idx]) == str:
+                out[idx] = self._str_to_num(out[idx])
         element.text = raw_elem_text
         return out
 
@@ -462,15 +480,16 @@ def append_input_data(self,
                           xml_parent: Union[ET.Element, 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.
+
+        :param plugin_type: plugin to be loaded into module
+        :type plugin_type: str
+        :param plugin_options: plugin options as key-value pairs
+        :type plugin_options: dict
+        :param xml_parent: Name of parent or pass element directly
+        :type xml_parent: str, ET.Element
+        :param save_dir_as_relative: Replace root path in filename with "./"
+        :type save_dir_as_relative: bool, optional
+        :return: None
         """
         if isinstance(xml_parent, str):
             xml_parent = self._get_element_from_name(xml_parent)
@@ -493,6 +512,7 @@ def append_plugin_to_module(self,
                                 overwrite_existing: Union[bool, int] = False
                                 ):
         """Appened plugin as subelement to existing module element
+        
         :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. 
diff --git a/aidesign/DataProcessing/DataProcessing_core.py b/src/vai_lab/DataProcessing/DataProcessing_core.py
similarity index 90%
rename from aidesign/DataProcessing/DataProcessing_core.py
rename to src/vai_lab/DataProcessing/DataProcessing_core.py
index f036cd77..ae681db3 100644
--- a/aidesign/DataProcessing/DataProcessing_core.py
+++ b/src/vai_lab/DataProcessing/DataProcessing_core.py
@@ -1,5 +1,5 @@
-from aidesign._import_helper import import_plugin_absolute
-from aidesign._types import PluginSpecsInterface, DataInterface, DataProcessingPluginInterface
+from vai_lab._import_helper import import_plugin_absolute
+from vai_lab._types import PluginSpecsInterface, DataInterface, DataProcessingPluginInterface
 
 class DataProcessing(object):
     def __init__(self) -> None:
diff --git a/aidesign/DataProcessing/__init__.py b/src/vai_lab/DataProcessing/__init__.py
similarity index 100%
rename from aidesign/DataProcessing/__init__.py
rename to src/vai_lab/DataProcessing/__init__.py
diff --git a/src/vai_lab/DataProcessing/plugins/binarizer.py b/src/vai_lab/DataProcessing/plugins/binarizer.py
new file mode 100644
index 00000000..14d0e285
--- /dev/null
+++ b/src/vai_lab/DataProcessing/plugins/binarizer.py
@@ -0,0 +1,23 @@
+from vai_lab._plugin_templates import DataProcessingT
+
+import pandas as pd
+from sklearn.preprocessing import Binarizer as model
+
+_PLUGIN_READABLE_NAMES = {"Binarizer":"default","binarizer":"alias"}    # type:ignore  
+_PLUGIN_MODULE_OPTIONS = {"Type": "encoder"}                            # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}                             # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {"threshold": "float"}                      # type:ignore
+_PLUGIN_REQUIRED_DATA = {}                                              # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"X","Y","X_tst", 'Y_tst'}                      # type:ignore
+
+class Binarizer(DataProcessingT):
+    """
+    Binarize data (set feature values to 0 or 1) according to a threshold
+    """
+
+    def __init__(self):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.proc = model()
\ No newline at end of file
diff --git a/src/vai_lab/DataProcessing/plugins/kbinsdiscretizer.py b/src/vai_lab/DataProcessing/plugins/kbinsdiscretizer.py
new file mode 100644
index 00000000..bcc13b9a
--- /dev/null
+++ b/src/vai_lab/DataProcessing/plugins/kbinsdiscretizer.py
@@ -0,0 +1,23 @@
+from vai_lab._plugin_templates import DataProcessingT
+
+import pandas as pd
+from sklearn.preprocessing import KBinsDiscretizer as model
+
+_PLUGIN_READABLE_NAMES = {"KBinsDiscretizer":"default"} # type:ignore
+_PLUGIN_MODULE_OPTIONS = {"Type": "encoder"}            # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}             # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {"n_bins": "int"}           # type:ignore
+_PLUGIN_REQUIRED_DATA = {}                              # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"X","Y","X_tst", 'Y_tst'}      # type:ignore
+
+class KBinsDiscretizer(DataProcessingT):
+    """
+    Bin continuous data into interval
+    """
+
+    def __init__(self):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.proc = model()
\ No newline at end of file
diff --git a/src/vai_lab/DataProcessing/plugins/labelbinarizer.py b/src/vai_lab/DataProcessing/plugins/labelbinarizer.py
new file mode 100644
index 00000000..b9858065
--- /dev/null
+++ b/src/vai_lab/DataProcessing/plugins/labelbinarizer.py
@@ -0,0 +1,21 @@
+from sklearn.preprocessing import LabelBinarizer as model
+from vai_lab._plugin_templates import DataProcessingT
+import pandas as pd
+
+_PLUGIN_READABLE_NAMES = {"LabelBinarizer":"default"}                   # type:ignore
+_PLUGIN_MODULE_OPTIONS = {"Type": "encoder"}                            # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}                             # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {"neg_label": "int", "pos_label": "int"}    # type:ignore
+_PLUGIN_REQUIRED_DATA = {}                                              # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"X","Y","X_tst", 'Y_tst'}                      # type:ignore
+
+class LabelBinarizer(DataProcessingT):
+    """
+    Binarize labels in a one-vs-all fashion
+    """
+    def __init__(self):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.proc = model()
\ No newline at end of file
diff --git a/src/vai_lab/DataProcessing/plugins/labelencoder.py b/src/vai_lab/DataProcessing/plugins/labelencoder.py
new file mode 100644
index 00000000..84bed864
--- /dev/null
+++ b/src/vai_lab/DataProcessing/plugins/labelencoder.py
@@ -0,0 +1,22 @@
+from sklearn.preprocessing import LabelEncoder as model
+from vai_lab._plugin_templates import DataProcessingT
+import pandas as pd
+
+_PLUGIN_READABLE_NAMES = {"LabelEncoder":"default","LE":"alias"}    # type:ignore
+_PLUGIN_MODULE_OPTIONS = {"Type": "encoder"}                        # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}                         # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {}                                      # type:ignore
+_PLUGIN_REQUIRED_DATA = {}                                          # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"X","Y","X_tst", 'Y_tst'}                  # type:ignore
+
+class LabelEncoder(DataProcessingT):
+    """
+    Encode target labels with value between 0 and n_classes-1
+    """
+
+    def __init__(self):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.proc = model()
\ No newline at end of file
diff --git a/src/vai_lab/DataProcessing/plugins/maxabsscaler.py b/src/vai_lab/DataProcessing/plugins/maxabsscaler.py
new file mode 100644
index 00000000..d8e700ce
--- /dev/null
+++ b/src/vai_lab/DataProcessing/plugins/maxabsscaler.py
@@ -0,0 +1,22 @@
+from sklearn.preprocessing import MaxAbsScaler as model
+from vai_lab._plugin_templates import DataProcessingT
+import pandas as pd
+
+_PLUGIN_READABLE_NAMES = {"MaxAbsScaler":"default"}     # type:ignore
+_PLUGIN_MODULE_OPTIONS = {"Type": "scaler"}             # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}             # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {}                          # type:ignore
+_PLUGIN_REQUIRED_DATA = {}                              # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"X","Y","X_tst", 'Y_tst'}      # type:ignore
+
+class MaxAbsScaler(DataProcessingT):
+    """
+    Scale each feature by its maximum absolute value
+    """
+
+    def __init__(self):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.proc = model()
\ No newline at end of file
diff --git a/src/vai_lab/DataProcessing/plugins/minmaxscaler.py b/src/vai_lab/DataProcessing/plugins/minmaxscaler.py
new file mode 100644
index 00000000..d3010e3b
--- /dev/null
+++ b/src/vai_lab/DataProcessing/plugins/minmaxscaler.py
@@ -0,0 +1,23 @@
+from sklearn.preprocessing import MinMaxScaler as model
+from vai_lab._plugin_templates import DataProcessingT
+import pandas as pd
+
+_PLUGIN_READABLE_NAMES = {"MinMaxScaler":"default"}     # type:ignore
+_PLUGIN_MODULE_OPTIONS = {"Type": "scaler"}             # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}             # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {"feature_range": "tuple"}  # type:ignore
+_PLUGIN_REQUIRED_DATA = {}                              # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"X","Y","X_tst", 'Y_tst'}      # type:ignore
+
+class MinMaxScaler(DataProcessingT):
+    """
+    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):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.proc = model()
\ No newline at end of file
diff --git a/src/vai_lab/DataProcessing/plugins/multilabelbinarizer.py b/src/vai_lab/DataProcessing/plugins/multilabelbinarizer.py
new file mode 100644
index 00000000..24be8036
--- /dev/null
+++ b/src/vai_lab/DataProcessing/plugins/multilabelbinarizer.py
@@ -0,0 +1,22 @@
+from sklearn.preprocessing import MultiLabelBinarizer as model
+from vai_lab._plugin_templates import DataProcessingT
+import pandas as pd
+
+_PLUGIN_READABLE_NAMES = {"MultiLabelBinarizer":"default"}  # type:ignore
+_PLUGIN_MODULE_OPTIONS = {"Type": "encoder"}                # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}                 # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {"classes": "array-like"}       # type:ignore
+_PLUGIN_REQUIRED_DATA = {}                                  # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"X","Y","X_tst", 'Y_tst'}          # type:ignore
+
+class MultiLabelBinarizer(DataProcessingT):
+    """
+    Transform between iterable of iterables and a multilabel format
+    """
+
+    def __init__(self):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.proc = model()
\ No newline at end of file
diff --git a/src/vai_lab/DataProcessing/plugins/normalizer.py b/src/vai_lab/DataProcessing/plugins/normalizer.py
new file mode 100644
index 00000000..5b92cfe6
--- /dev/null
+++ b/src/vai_lab/DataProcessing/plugins/normalizer.py
@@ -0,0 +1,26 @@
+from vai_lab._plugin_templates import DataProcessingT
+from vai_lab._types import DataInterface
+
+from sklearn.preprocessing import Normalizer as model
+import pandas as pd
+
+_PLUGIN_READABLE_NAMES = {"Normalizer": "default",
+                          "Norm": "alias", "normalizer": "alias"}   # type:ignore
+_PLUGIN_MODULE_OPTIONS = {"Type": "scaler"}                         # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}                         # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {"norm": "str"}                         # type:ignore
+_PLUGIN_REQUIRED_DATA = {}                                          # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"X", "Y", "X_tst", 'Y_tst'}                # type:ignore
+
+
+class Normalizer(DataProcessingT):
+    """
+    Normalize samples individually to unit norm
+    """
+
+    def __init__(self):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.proc = model()
\ No newline at end of file
diff --git a/src/vai_lab/DataProcessing/plugins/onehotencoder.py b/src/vai_lab/DataProcessing/plugins/onehotencoder.py
new file mode 100644
index 00000000..259574bc
--- /dev/null
+++ b/src/vai_lab/DataProcessing/plugins/onehotencoder.py
@@ -0,0 +1,22 @@
+from sklearn.preprocessing import OneHotEncoder as model
+from vai_lab._plugin_templates import DataProcessingT
+import pandas as pd
+
+_PLUGIN_READABLE_NAMES = {"OneHotEncoder":"default"}        # type:ignore
+_PLUGIN_MODULE_OPTIONS = {"Type": "encoder"}                # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}                 # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {"categories": "array-like"}    # type:ignore
+_PLUGIN_REQUIRED_DATA = {}                                  # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"X","Y","X_tst", 'Y_tst'}          # type:ignore
+
+class OneHotEncoder(DataProcessingT):
+    """
+    Encode categorical features as a one-hot numeric array
+    """
+
+    def __init__(self):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.proc = model()
\ No newline at end of file
diff --git a/src/vai_lab/DataProcessing/plugins/ordinalencoder.py b/src/vai_lab/DataProcessing/plugins/ordinalencoder.py
new file mode 100644
index 00000000..558083cf
--- /dev/null
+++ b/src/vai_lab/DataProcessing/plugins/ordinalencoder.py
@@ -0,0 +1,23 @@
+from sklearn.preprocessing import OrdinalEncoder as model
+from vai_lab._plugin_templates import DataProcessingT
+import pandas as pd
+
+_PLUGIN_READABLE_NAMES = {"OrdinalEncoder": "default"}      # type:ignore
+_PLUGIN_MODULE_OPTIONS = {"Type": "encoder"}                # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}                 # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {"categories": "array-like"}    # type:ignore
+_PLUGIN_REQUIRED_DATA = {}                                  # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"X", "Y", "X_tst", 'Y_tst'}        # type:ignore
+
+
+class OrdinalEncoder(DataProcessingT):
+    """
+    Encode categorical features as an integer array
+    """
+
+    def __init__(self):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.proc = model()
\ No newline at end of file
diff --git a/aidesign/DataProcessing/plugins/polynomialfeatures.py b/src/vai_lab/DataProcessing/plugins/polynomialfeatures.py
similarity index 51%
rename from aidesign/DataProcessing/plugins/polynomialfeatures.py
rename to src/vai_lab/DataProcessing/plugins/polynomialfeatures.py
index 8523feb6..deae011c 100644
--- a/aidesign/DataProcessing/plugins/polynomialfeatures.py
+++ b/src/vai_lab/DataProcessing/plugins/polynomialfeatures.py
@@ -1,4 +1,4 @@
-from aidesign._plugin_templates import DataProcessingT
+from vai_lab._plugin_templates import DataProcessingT
 from sklearn.preprocessing import PolynomialFeatures as model
 import pandas as pd
 
@@ -24,29 +24,4 @@ def __init__(self):
             Passes `globals` dict of all current variables
         """
         super().__init__(globals())
-        self.proc = model()
-
-    def configure(self, config: dict):
-        """Sets and parses plugin configurations options
-        :param config: dict of internal tags set in the XML config file 
-        """
-        super().configure(config)
-
-    def set_data_in(self, data_in):
-        """Sets and parses incoming data
-        :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
-        """
-        super().set_data_in(data_in)
-
-    def fit(self):
-        cleaned_options = self._clean_solver_options()
-        self.proc.set_params(**cleaned_options)
-        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
+        self.proc = model()
\ No newline at end of file
diff --git a/src/vai_lab/DataProcessing/plugins/quantiletransformer.py b/src/vai_lab/DataProcessing/plugins/quantiletransformer.py
new file mode 100644
index 00000000..1e6b1c5b
--- /dev/null
+++ b/src/vai_lab/DataProcessing/plugins/quantiletransformer.py
@@ -0,0 +1,24 @@
+from sklearn.preprocessing import QuantileTransformer as model
+from vai_lab._plugin_templates import DataProcessingT
+import pandas as pd
+
+_PLUGIN_READABLE_NAMES = {
+    "QuantileTransformer": "default", "Quantile": "alias"}  # type:ignore
+_PLUGIN_MODULE_OPTIONS = {"Type": "encoder"}                # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}                 # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {"n_quantiles": "int"}          # type:ignore
+_PLUGIN_REQUIRED_DATA = {}                                  # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"X", "Y", "X_tst", 'Y_tst'}        # type:ignore
+
+
+class QuantileTransformer(DataProcessingT):
+    """
+    Transform features using quantiles information
+    """
+
+    def __init__(self):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.proc = model()
\ No newline at end of file
diff --git a/src/vai_lab/DataProcessing/plugins/standardscaler.py b/src/vai_lab/DataProcessing/plugins/standardscaler.py
new file mode 100644
index 00000000..f44c7d7b
--- /dev/null
+++ b/src/vai_lab/DataProcessing/plugins/standardscaler.py
@@ -0,0 +1,24 @@
+from sklearn.preprocessing import StandardScaler as model
+from vai_lab._plugin_templates import DataProcessingT
+import pandas as pd
+
+_PLUGIN_READABLE_NAMES = {
+    "StandardScaler": "default", "standardscaler": "alias"}     # type:ignore
+_PLUGIN_MODULE_OPTIONS = {"Type": "scaler"}                     # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {"Data": "str"}                     # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {"with_mean": "bool"}               # type:ignore
+_PLUGIN_REQUIRED_DATA = {}                                      # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"X", "Y", "X_tst", 'Y_tst'}            # type:ignore
+
+
+class StandardScaler(DataProcessingT):
+    """
+    Standardize features by removing the mean and scaling to unit variance
+    """
+
+    def __init__(self):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.proc = model()
\ No newline at end of file
diff --git a/aidesign/DataStorage/DataStorage_core.py b/src/vai_lab/DataStorage/DataStorage_core.py
similarity index 100%
rename from aidesign/DataStorage/DataStorage_core.py
rename to src/vai_lab/DataStorage/DataStorage_core.py
diff --git a/aidesign/DataStorage/__init__.py b/src/vai_lab/DataStorage/__init__.py
similarity index 100%
rename from aidesign/DataStorage/__init__.py
rename to src/vai_lab/DataStorage/__init__.py
diff --git a/aidesign/DecisionMaking/Modelling_core.py b/src/vai_lab/DecisionMaking/Modelling_core.py
similarity index 100%
rename from aidesign/DecisionMaking/Modelling_core.py
rename to src/vai_lab/DecisionMaking/Modelling_core.py
diff --git a/aidesign/DecisionMaking/__init__.py b/src/vai_lab/DecisionMaking/__init__.py
similarity index 100%
rename from aidesign/DecisionMaking/__init__.py
rename to src/vai_lab/DecisionMaking/__init__.py
diff --git a/src/vai_lab/Environment/Environment_core.py b/src/vai_lab/Environment/Environment_core.py
new file mode 100644
index 00000000..70bcf13c
--- /dev/null
+++ b/src/vai_lab/Environment/Environment_core.py
@@ -0,0 +1,41 @@
+from vai_lab._import_helper import import_plugin_absolute
+from vai_lab._types import PluginSpecsInterface, DataInterface, EnvironmentPluginInterface
+
+class Environment(object):
+    def __init__(self) -> None:
+        self.output_data: DataInterface
+
+    def set_avail_plugins(self, avail_plugins: PluginSpecsInterface) -> None:
+        self._avail_plugins = avail_plugins
+
+    def set_data_in(self, data_in: DataInterface) -> None:
+        self._data_in = data_in
+
+    def _load_plugin(self, plugin_name: str) -> None:
+        avail_plugins = self._avail_plugins.find_from_readable_name(
+            plugin_name)
+        self._plugin_name = plugin_name
+        self._plugin: EnvironmentPluginInterface = import_plugin_absolute(globals(),
+                                              avail_plugins["_PLUGIN_PACKAGE"],
+                                              avail_plugins["_PLUGIN_CLASS_NAME"])\
+            .__call__()
+
+    def set_options(self, module_config: dict) -> None:
+        """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) -> None:
+        self._plugin.set_data_in(self._data_in)
+        self._plugin.configure(self._module_config["plugin"])
+        self._plugin.connect()
+        self._plugin.load_model()
+        self._plugin.run_simulation()
+        
+
+    def get_result(self) -> DataInterface:
+        # return self.output_data
+        return self._data_in
diff --git a/src/vai_lab/Environment/plugins/PyBulletEnv.py b/src/vai_lab/Environment/plugins/PyBulletEnv.py
new file mode 100644
index 00000000..727f2d79
--- /dev/null
+++ b/src/vai_lab/Environment/plugins/PyBulletEnv.py
@@ -0,0 +1,101 @@
+from attr import attr
+from vai_lab._plugin_templates import EnvironmentPluginT
+from vai_lab._import_helper import rel_to_abs
+from typing import Any, Dict
+import pybullet as p
+from time import sleep
+
+
+_PLUGIN_READABLE_NAMES = {"PyBullet":"default"}                                                 # type:ignore
+_PLUGIN_MODULE_OPTIONS = {}                                                                     # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {"model_dir": "str", "headless": "bool",
+                                "timestep":"float", "max_steps":"int"}                          # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {"gravity":"list"}                                                  # type:ignore
+_PLUGIN_REQUIRED_DATA = {}                                                                      # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"X","Y","X_tst", 'Y_tst'}                                              # type:ignore
+
+
+class PyBulletEnv(EnvironmentPluginT):
+    """
+    Loads the pybullet library as wildcard and exposes all functions
+    """
+
+    def __init__(self) -> None:
+        super().__init__(globals())
+        self.model_ids: Dict = {}
+
+    def set_gui(self, use_gui: bool = True):
+        if type(use_gui) == str:
+            use_gui = eval(use_gui)
+        if use_gui:
+            self.connection_mode = p.GUI  # Use pybullet GUI
+        else:
+            self.connection_mode = p.DIRECT  # Use pybullet without GUI
+
+    def connect(self):
+        self.physicsClient = p.connect(self.connection_mode)
+
+    def disconnect(self):
+        return p.disconnect()
+
+    def reset(self):
+        return p.resetSimulation()
+
+    def load_pb_data(self):
+        import pybullet_data                                        # type:ignore
+        p.setAdditionalSearchPath(pybullet_data.getDataPath())
+
+    def _load_model_by_type(self, model):
+        model = rel_to_abs(model)
+        ext = model.split(".")[-1]
+        name = model.split("/")[-1].split(".")[0]
+        if name == "plane":
+            self.load_pb_data()
+        if ext == "urdf":
+            self.model_ids[name] = p.loadURDF(model)
+        elif ext == "sdf":
+            self.model_ids[name] = p.loadSDF(model, 1, 1.0)
+        elif ext == "xml":
+            self.model_ids[name] = p.loadMJCF(model)
+
+    def load_model(self) -> None:
+        model_paths = self._config["options"]["model_dir"]
+        if type(model_paths) == str:
+            self._load_model_by_type(model_paths)
+        elif type(model_paths) == list:
+            for model in model_paths:
+                self._load_model_by_type(model)
+
+    def _set_options(self):
+        api_list = dir(p)
+        for key, value in self._config["options"].items():
+            if key in api_list:
+                getattr(p,key)(*value)
+        if "timestep" in self._config["options"]:
+            p.setPhysicsEngineParameter(
+                fixedTimeStep=self._config["options"]["timestep"])
+        elif "max_steps" in self._config["options"]:
+            p.setPhysicsEngineParameter(
+                numSolverIterations=self._config["options"]["max_steps"])
+
+    def run_simulation(self):
+        self._set_options()
+        for step in range(1, self._config["options"]["max_steps"]):
+            p.stepSimulation()
+            sleep(self._config["options"]["timestep"])
+        self.disconnect()
+
+    def __getattr__(self, attr: str) -> Any:
+        """Allows calling pybullet functions directly as if they were functions of this class
+
+        TODO: This is probably not the best way to do this, but pybullet is
+        a compiled module and cannot be used as a parent. This is a workaround.
+        """
+        return getattr(p, attr)
+
+
+if __name__ == "__main__":
+    pb = PyBulletEnv()
+    print(pb.ACTIVATION_STATE_DISABLE_SLEEPING)
+    # pb.set_gui(True)
+    # pb.connect()
diff --git a/src/vai_lab/Environment/resources/models/half_cheetah.xml b/src/vai_lab/Environment/resources/models/half_cheetah.xml
new file mode 100644
index 00000000..eb888fd5
--- /dev/null
+++ b/src/vai_lab/Environment/resources/models/half_cheetah.xml
@@ -0,0 +1,82 @@
+<!-- Cheetah Model
+    The state space is populated with joints in the order that they are
+    defined in this file. The actuators also operate on joints.
+    State-Space (name/joint/parameter):
+        - rootx     slider      position (m)
+        - rootz     slider      position (m)
+        - rooty     hinge       angle (rad)
+        - bthigh    hinge       angle (rad)
+        - bshin     hinge       angle (rad)
+        - bfoot     hinge       angle (rad)
+        - fthigh    hinge       angle (rad)
+        - fshin     hinge       angle (rad)
+        - ffoot     hinge       angle (rad)
+        - rootx     slider      velocity (m/s)
+        - rootz     slider      velocity (m/s)
+        - rooty     hinge       angular velocity (rad/s)
+        - bthigh    hinge       angular velocity (rad/s)
+        - bshin     hinge       angular velocity (rad/s)
+        - bfoot     hinge       angular velocity (rad/s)
+        - fthigh    hinge       angular velocity (rad/s)
+        - fshin     hinge       angular velocity (rad/s)
+        - ffoot     hinge       angular velocity (rad/s)
+    Actuators (name/actuator/parameter):
+        - bthigh    hinge       torque (N m)
+        - bshin     hinge       torque (N m)
+        - bfoot     hinge       torque (N m)
+        - fthigh    hinge       torque (N m)
+        - fshin     hinge       torque (N m)
+        - ffoot     hinge       torque (N m)
+-->
+<mujoco model="cheetah">
+  <compiler angle="radian" coordinate="local" inertiafromgeom="true" settotalmass="14"/>
+  <default>
+    <joint armature=".1" damping=".01" limited="true" solimplimit="0 .8 .03" solreflimit=".02 1" stiffness="8"/>
+    <geom conaffinity="0" condim="3" contype="1" friction="0.4 .1 .1" rgba="0.8 0.6 .4 1" solimp="0.0 0.8 0.01" solref="0.02 1"/>
+    <motor ctrllimited="true" ctrlrange="-1 1"/>
+  </default>
+  <size nstack="300000" nuser_geom="1"/>
+  <option gravity="0 0 -9.81" timestep="0.01"/>
+  <worldbody>
+    <body name="torso" pos="0 0 .7">
+      <joint armature="0" axis="1 0 0" damping="0" limited="false" name="ignorex" pos="0 0 0" stiffness="0" type="slide"/>
+      <joint armature="0" axis="0 0 1" damping="0" limited="false" name="ignorez" pos="0 0 0" stiffness="0" type="slide"/>
+      <joint armature="0" axis="0 1 0" damping="0" limited="false" name="ignorey" pos="0 0 0" stiffness="0" type="hinge"/>
+      <geom fromto="-.5 0 0 .5 0 0" name="torso" size="0.046" type="capsule"/>
+      <geom axisangle="0 1 0 .87" name="head" pos=".6 0 .1" size="0.046 .15" type="capsule"/>
+      <!-- <site name='tip'  pos='.15 0 .11'/>-->
+      <body name="bthigh" pos="-.5 0 0">
+        <joint axis="0 1 0" damping="6" name="bthigh" pos="0 0 0" range="-.52 1.05" stiffness="240" type="hinge"/>
+        <geom axisangle="0 1 0 -3.8" name="bthigh" pos=".1 0 -.13" size="0.046 .145" type="capsule"/>
+        <body name="bshin" pos=".16 0 -.25">
+          <joint axis="0 1 0" damping="4.5" name="bshin" pos="0 0 0" range="-.87 .87" stiffness="180" type="hinge"/>
+          <geom axisangle="0 1 0 -2.03" name="bshin" pos="-.14 0 -.07" rgba="0.9 0.6 0.6 1" size="0.046 .15" type="capsule"/>
+          <body name="bfoot" pos="-.28 0 -.14">
+            <joint axis="0 1 0" damping="3" name="bfoot" pos="0 0 0" range="-.87 .87" stiffness="120" type="hinge"/>
+            <geom axisangle="0 1 0 -.27" name="bfoot" pos=".03 0 -.097" rgba="0.9 0.6 0.6 1" size="0.046 .094" type="capsule"/>
+          </body>
+        </body>
+      </body>
+      <body name="fthigh" pos=".5 0 0">
+        <joint axis="0 1 0" damping="4.5" name="fthigh" pos="0 0 0" range="-1.0 0.007" stiffness="180" type="hinge"/>
+        <geom axisangle="0 1 0 .52" name="fthigh" pos="-.07 0 -.12" size="0.046 .133" type="capsule"/>
+        <body name="fshin" pos="-.14 0 -.24">
+          <joint axis="0 1 0" damping="3" name="fshin" pos="0 0 0" range="-1.22 0.87" stiffness="120" type="hinge"/>
+          <geom axisangle="0 1 0 -.6" name="fshin" pos=".065 0 -.09" rgba="0.9 0.6 0.6 1" size="0.046 .106" type="capsule"/>
+          <body name="ffoot" pos=".13 0 -.18">
+            <joint axis="0 1 0" damping="1.5" name="ffoot" pos="0 0 0" range="-0.488 0.488" stiffness="60" type="hinge"/>
+            <geom axisangle="0 1 0 -.6" name="ffoot" pos=".045 0 -.07" rgba="0.9 0.6 0.6 1" size="0.046 .07" type="capsule"/>
+          </body>
+        </body>
+      </body>
+    </body>
+  </worldbody>
+  <actuator>
+    <motor gear="120" joint="bthigh" name="bthigh"/>
+    <motor gear="90" joint="bshin" name="bshin"/>
+    <motor gear="60" joint="bfoot" name="bfoot"/>
+    <motor gear="90" joint="fthigh" name="fthigh"/>
+    <motor gear="60" joint="fshin" name="fshin"/>
+    <motor gear="30" joint="ffoot" name="ffoot"/>
+  </actuator>
+</mujoco>
diff --git a/src/vai_lab/Environment/resources/models/half_cheetah_with_mass.xml b/src/vai_lab/Environment/resources/models/half_cheetah_with_mass.xml
new file mode 100644
index 00000000..3c465ced
--- /dev/null
+++ b/src/vai_lab/Environment/resources/models/half_cheetah_with_mass.xml
@@ -0,0 +1,88 @@
+<!-- Cheetah Model
+    The state space is populated with joints in the order that they are
+    defined in this file. The actuators also operate on joints.
+    State-Space (name/joint/parameter):
+        - rootx     slider      position (m)
+        - rootz     slider      position (m)
+        - rooty     hinge       angle (rad)
+        - bthigh    hinge       angle (rad)
+        - bshin     hinge       angle (rad)
+        - bfoot     hinge       angle (rad)
+        - fthigh    hinge       angle (rad)
+        - fshin     hinge       angle (rad)
+        - ffoot     hinge       angle (rad)
+        - rootx     slider      velocity (m/s)
+        - rootz     slider      velocity (m/s)
+        - rooty     hinge       angular velocity (rad/s)
+        - bthigh    hinge       angular velocity (rad/s)
+        - bshin     hinge       angular velocity (rad/s)
+        - bfoot     hinge       angular velocity (rad/s)
+        - fthigh    hinge       angular velocity (rad/s)
+        - fshin     hinge       angular velocity (rad/s)
+        - ffoot     hinge       angular velocity (rad/s)
+    Actuators (name/actuator/parameter):
+        - bthigh    hinge       torque (N m)
+        - bshin     hinge       torque (N m)
+        - bfoot     hinge       torque (N m)
+        - fthigh    hinge       torque (N m)
+        - fshin     hinge       torque (N m)
+        - ffoot     hinge       torque (N m)
+-->
+<mujoco model="cheetah">
+  <compiler angle="radian" coordinate="local" inertiafromgeom="true" settotalmass="14"/>
+  <default>
+    <joint armature=".1" damping=".01" limited="true" solimplimit="0 .8 .03" solreflimit=".02 1" stiffness="8"/>
+    <geom conaffinity="0" condim="3" contype="1" friction="0.4 .1 .1" rgba="0.8 0.6 .4 1" solimp="0.0 0.8 0.01" solref="0.02 1"/>
+    <motor ctrllimited="true" ctrlrange="-1 1"/>
+  </default>
+  <size nstack="300000" nuser_geom="1"/>
+  <option gravity="0 0 -9.81" timestep="0.01"/>
+  <worldbody>
+    <body name="torso" pos="0 0 .7">
+      <joint armature="0" axis="1 0 0" damping="0" limited="false" name="ignorex" pos="0 0 0" stiffness="0" type="slide"/>
+      <joint armature="0" axis="0 0 1" damping="0" limited="false" name="ignorez" pos="0 0 0" stiffness="0" type="slide"/>
+      <joint armature="0" axis="0 1 0" damping="0" limited="false" name="ignorey" pos="0 0 0" stiffness="0" type="hinge"/>
+      <geom fromto="-.5 0 0 .5 0 0" name="torso" size="0.046" type="capsule"/>
+      <geom axisangle="0 1 0 .87" name="head" pos=".6 0 .1" size="0.046 .15" type="capsule"/>
+      <!-- <site name='tip'  pos='.15 0 .11'/>-->
+      <body name="bthigh" pos="-.5 0 0">
+        <joint axis="0 1 0" damping="6" name="bthigh" pos="0 0 0" range="-.52 1.05" stiffness="240" type="hinge"/>
+        <inertial pos="0 0 0" mass="10"/>
+        <geom axisangle="0 1 0 -3.8" name="bthigh" pos=".1 0 -.13" size="0.046 .145" type="capsule"/>
+        <body name="bshin" pos=".16 0 -.25">
+          <joint axis="0 1 0" damping="4.5" name="bshin" pos="0 0 0" range="-.87 .87" stiffness="180" type="hinge"/>
+          <inertial pos="0 0 0" mass="10"/>
+          <geom axisangle="0 1 0 -2.03" name="bshin" pos="-.14 0 -.07" rgba="0.9 0.6 0.6 1" size="0.046 .15" type="capsule"/>
+          <body name="bfoot" pos="-.28 0 -.14">
+            <joint axis="0 1 0" damping="3" name="bfoot" pos="0 0 0" range="-.87 .87" stiffness="120" type="hinge"/>
+            <inertial pos="0 0 0" mass="10"/>
+            <geom axisangle="0 1 0 -.27" name="bfoot" pos=".03 0 -.097" rgba="0.9 0.6 0.6 1" size="0.046 .094" type="capsule"/>
+          </body>
+        </body>
+      </body>
+      <body name="fthigh" pos=".5 0 0">
+        <joint axis="0 1 0" damping="4.5" name="fthigh" pos="0 0 0" range="-1.0 0.007" stiffness="180" type="hinge"/>
+        <inertial pos="0 0 0" mass="10"/>
+        <geom axisangle="0 1 0 .52" name="fthigh" pos="-.07 0 -.12" size="0.046 .133" type="capsule"/>
+        <body name="fshin" pos="-.14 0 -.24">
+          <joint axis="0 1 0" damping="3" name="fshin" pos="0 0 0" range="-1.22 0.87" stiffness="120" type="hinge"/>
+          <inertial pos="0 0 0" mass="10"/>
+          <geom axisangle="0 1 0 -.6" name="fshin" pos=".065 0 -.09" rgba="0.9 0.6 0.6 1" size="0.046 .106" type="capsule"/>
+          <body name="ffoot" pos=".13 0 -.18">
+            <joint axis="0 1 0" damping="1.5" name="ffoot" pos="0 0 0" range="-0.488 0.488" stiffness="60" type="hinge"/>
+            <inertial pos="0 0 0" mass="10"/>
+            <geom axisangle="0 1 0 -.6" name="ffoot" pos=".045 0 -.07" rgba="0.9 0.6 0.6 1" size="0.046 .07" type="capsule"/>
+          </body>
+        </body>
+      </body>
+    </body>
+  </worldbody>
+  <actuator>
+    <motor gear="120" joint="bthigh" name="bthigh"/>
+    <motor gear="90" joint="bshin" name="bshin"/>
+    <motor gear="60" joint="bfoot" name="bfoot"/>
+    <motor gear="90" joint="fthigh" name="fthigh"/>
+    <motor gear="60" joint="fshin" name="fshin"/>
+    <motor gear="30" joint="ffoot" name="ffoot"/>
+  </actuator>
+</mujoco>
diff --git a/aidesign/GUI/GUI_core.py b/src/vai_lab/GUI/GUI_core.py
similarity index 89%
rename from aidesign/GUI/GUI_core.py
rename to src/vai_lab/GUI/GUI_core.py
index 3ed923a7..867dfab9 100644
--- a/aidesign/GUI/GUI_core.py
+++ b/src/vai_lab/GUI/GUI_core.py
@@ -1,6 +1,6 @@
-from aidesign.Data.xml_handler import XML_handler
-from aidesign._import_helper import import_plugin_absolute
-from aidesign._types import DataInterface, PluginSpecsInterface, DictT
+from vai_lab.Data.xml_handler import XML_handler
+from vai_lab._import_helper import import_plugin_absolute
+from vai_lab._types import DataInterface, PluginSpecsInterface, DictT
 
 from typing import Any
 import tkinter as tk
@@ -25,8 +25,9 @@ def __init__(self, *args, **kwargs):
         self._desired_ui_types = []
         self._top_ui_layer = None
         self._module_config = None
-        self.closed = False
-        self.startpage = False
+        self._debug = False
+        self._closed = False
+        self._is_startpage = False
         self.output = {}
 
     def set_avail_plugins(self, avail_plugins: PluginSpecsInterface):
@@ -44,15 +45,15 @@ def set_options(self, module_config: DictT):
         self._load_plugin(self._module_config["plugin"]["plugin_name"])
 
     def set_gui_as_startpage(self):
-        self.startpage = True
+        self._is_startpage = True
         self._load_plugin("main")
-        self.s = XML_handler()
-        self.s.new_config_file()
+        self.xml_handler = XML_handler()
+        self.xml_handler.new_config_file()
         
-    def set_gui(self, status):
-        self.startpage = True
+    def set_gui_as_progress_tracker(self, status):
+        self._is_startpage = True
         self._load_plugin("progressTracker")
-        self.status = status
+        self._status = status
 
     def _compare_layer_priority(self, ui_specs):
         """Check if a new module should have higher layer priority than the existing one
@@ -124,7 +125,7 @@ def _show_frame(self, page_name):
         frame.tkraise()
 
     def _on_closing(self) -> None:
-        self.closed = True
+        self._closed = True
         self.destroy()
 
     def destroy(self) -> None:
@@ -144,7 +145,7 @@ def launch(self):
             page_name = F.__name__
             frame = F(parent=container, controller=self,
                       config=self._module_config)
-            if not self.startpage:
+            if not self._is_startpage:
                 frame.set_data_in(self._data_in)
             self.frames[page_name] = frame
 
@@ -153,7 +154,10 @@ def launch(self):
 
         self._show_frame(self._top_ui_layer)
         self.protocol("WM_DELETE_WINDOW", self._on_closing)
-        self.mainloop()
+        if not self._debug:
+            self.mainloop()
+        else:
+            self._closed = True
         return self.output
 
     def get_result(self):
diff --git a/aidesign/GUI/User_Feedback_template.py b/src/vai_lab/GUI/User_Feedback_template.py
similarity index 63%
rename from aidesign/GUI/User_Feedback_template.py
rename to src/vai_lab/GUI/User_Feedback_template.py
index 07c9dce5..8b3b2967 100644
--- a/aidesign/GUI/User_Feedback_template.py
+++ b/src/vai_lab/GUI/User_Feedback_template.py
@@ -1,8 +1,8 @@
-from aidesign._plugin_templates import UI
+from vai_lab._plugin_templates import UI
 
 class UserFeedbackTemplate(UI):
     """UserFeedbackTemplate directly duplicates:
-        aidesign.UserInteraction.User_Interaction_template
+        vai_lab.UserInteraction.User_Interaction_template
     """
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
\ No newline at end of file
diff --git a/aidesign/GUI/__init__.py b/src/vai_lab/GUI/__init__.py
similarity index 100%
rename from aidesign/GUI/__init__.py
rename to src/vai_lab/GUI/__init__.py
diff --git a/aidesign/GUI/plugins/__init__.py b/src/vai_lab/GUI/plugins/__init__.py
similarity index 100%
rename from aidesign/GUI/plugins/__init__.py
rename to src/vai_lab/GUI/plugins/__init__.py
diff --git a/aidesign/InputData/InputData_core.py b/src/vai_lab/InputData/InputData_core.py
similarity index 90%
rename from aidesign/InputData/InputData_core.py
rename to src/vai_lab/InputData/InputData_core.py
index 9f2b40a0..f9f4d165 100644
--- a/aidesign/InputData/InputData_core.py
+++ b/src/vai_lab/InputData/InputData_core.py
@@ -1,6 +1,6 @@
-from aidesign.Data.Data_core import Data
-from aidesign._import_helper import import_plugin_absolute
-from aidesign._types import PluginSpecsInterface, DataInterface
+from vai_lab.Data.Data_core import Data
+from vai_lab._import_helper import import_plugin_absolute
+from vai_lab._types import PluginSpecsInterface, DataInterface
 
 class InputData(Data):
     def __init__(self):
diff --git a/aidesign/InputData/__init__.py b/src/vai_lab/InputData/__init__.py
similarity index 100%
rename from aidesign/InputData/__init__.py
rename to src/vai_lab/InputData/__init__.py
diff --git a/aidesign/Modelling/Modelling_core.py b/src/vai_lab/Modelling/Modelling_core.py
similarity index 95%
rename from aidesign/Modelling/Modelling_core.py
rename to src/vai_lab/Modelling/Modelling_core.py
index 7ca67626..81695629 100644
--- a/aidesign/Modelling/Modelling_core.py
+++ b/src/vai_lab/Modelling/Modelling_core.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-from aidesign._import_helper import import_plugin_absolute
+from vai_lab._import_helper import import_plugin_absolute
 class Modelling(object):
     def __init__(self):
         self.output_data = None
diff --git a/aidesign/Modelling/__init__.py b/src/vai_lab/Modelling/__init__.py
similarity index 100%
rename from aidesign/Modelling/__init__.py
rename to src/vai_lab/Modelling/__init__.py
diff --git a/src/vai_lab/Modelling/plugins/affinitypropagation.py b/src/vai_lab/Modelling/plugins/affinitypropagation.py
new file mode 100644
index 00000000..b3dbc666
--- /dev/null
+++ b/src/vai_lab/Modelling/plugins/affinitypropagation.py
@@ -0,0 +1,22 @@
+from vai_lab._plugin_templates import ModellingPluginT
+from sklearn.cluster import AffinityPropagation as model
+
+_PLUGIN_READABLE_NAMES = {"Birch": "default"}       # type:ignore
+_PLUGIN_MODULE_OPTIONS = {"Type": "clustering"}     # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {}                      # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {"damping": "float"}    # type:ignore
+_PLUGIN_REQUIRED_DATA = {"X"}                       # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"Y", "X_tst", 'Y_tst'}     # type:ignore
+
+
+class AffinityPropagation(ModellingPluginT):
+    """
+    Perform Affinity Propagation Clustering of data
+    """
+
+    def __init__(self):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.clf = model()
\ No newline at end of file
diff --git a/src/vai_lab/Modelling/plugins/bayesianridge.py b/src/vai_lab/Modelling/plugins/bayesianridge.py
new file mode 100644
index 00000000..9844f610
--- /dev/null
+++ b/src/vai_lab/Modelling/plugins/bayesianridge.py
@@ -0,0 +1,22 @@
+from vai_lab._plugin_templates import ModellingPluginT
+from sklearn.linear_model import BayesianRidge as model
+
+_PLUGIN_READABLE_NAMES = {"BayesianRidge": "default"}   # type:ignore
+_PLUGIN_MODULE_OPTIONS = {"Type": "regression"}         # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {}                          # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {"n_iter": "int"}           # type:ignore
+_PLUGIN_REQUIRED_DATA = {"X", "Y"}                      # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}              # type:ignore
+
+
+class BayesianRidge(ModellingPluginT):
+    """
+    Bayesian ridge regression
+    """
+
+    def __init__(self):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.clf = model()
\ No newline at end of file
diff --git a/src/vai_lab/Modelling/plugins/birch.py b/src/vai_lab/Modelling/plugins/birch.py
new file mode 100644
index 00000000..c1bd4a01
--- /dev/null
+++ b/src/vai_lab/Modelling/plugins/birch.py
@@ -0,0 +1,23 @@
+from vai_lab._plugin_templates import ModellingPluginT
+from sklearn.cluster import Birch as model
+
+_PLUGIN_READABLE_NAMES = {"Birch": "default"}       # type:ignore
+_PLUGIN_MODULE_OPTIONS = {"Type": "clustering"}     # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {}                      # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {"n_clusters": "int",   # type:ignore
+                             "threshold": "float"}  # type:ignore
+_PLUGIN_REQUIRED_DATA = {"X"}                       # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"Y", "X_tst", 'Y_tst'}     # type:ignore
+
+
+class Birch(ModellingPluginT):
+    """
+    Implements the BIRCH clustering algorithm
+    """
+
+    def __init__(self):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.clf = model()
\ No newline at end of file
diff --git a/src/vai_lab/Modelling/plugins/decisiontreeclassifier.py b/src/vai_lab/Modelling/plugins/decisiontreeclassifier.py
new file mode 100644
index 00000000..32a8b3cc
--- /dev/null
+++ b/src/vai_lab/Modelling/plugins/decisiontreeclassifier.py
@@ -0,0 +1,23 @@
+from vai_lab._plugin_templates import ModellingPluginTClass
+from sklearn.tree import DecisionTreeClassifier as model
+
+_PLUGIN_READABLE_NAMES = {"DecissionTreeClassifier": "default",
+                          "DTClassifier": "alias"}              # type:ignore
+_PLUGIN_MODULE_OPTIONS = {"Type": "classification"}             # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {}                                  # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {"max_depth": "int"}                # type:ignore
+_PLUGIN_REQUIRED_DATA = {"X", "Y"}                              # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}                      # type:ignore
+
+
+class DecisionTreeClassifier(ModellingPluginTClass):
+    """
+    A decision tree classifier
+    """
+
+    def __init__(self):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.clf = model()
\ No newline at end of file
diff --git a/src/vai_lab/Modelling/plugins/decisiontreeregressor.py b/src/vai_lab/Modelling/plugins/decisiontreeregressor.py
new file mode 100644
index 00000000..d3b071f6
--- /dev/null
+++ b/src/vai_lab/Modelling/plugins/decisiontreeregressor.py
@@ -0,0 +1,23 @@
+from vai_lab._plugin_templates import ModellingPluginT
+from sklearn.tree import DecisionTreeRegressor as model
+
+_PLUGIN_READABLE_NAMES = {"DecisionTreeRegressor": "default",
+                          "DTregressor": "alias"}               # type:ignore
+_PLUGIN_MODULE_OPTIONS = {"Type": "regression"}                 # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {}                                  # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {"max_depth": "int"}                # type:ignore
+_PLUGIN_REQUIRED_DATA = {"X", "Y"}                              # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}                      # type:ignore
+
+
+class DecisionTreeRegressor(ModellingPluginT):
+    """
+    A decision tree regressor
+    """
+
+    def __init__(self):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.clf = model()
\ No newline at end of file
diff --git a/src/vai_lab/Modelling/plugins/elasticnet.py b/src/vai_lab/Modelling/plugins/elasticnet.py
new file mode 100644
index 00000000..0e85f49d
--- /dev/null
+++ b/src/vai_lab/Modelling/plugins/elasticnet.py
@@ -0,0 +1,23 @@
+from vai_lab._plugin_templates import ModellingPluginT
+from sklearn.linear_model import ElasticNet as model
+
+_PLUGIN_READABLE_NAMES = {"ElasticNet": "default"}      # type:ignore
+_PLUGIN_MODULE_OPTIONS = {"Type": "regression"}         # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {}                          # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {"alpha": "float",
+                             "l1_ratio": "float"}       # type:ignore
+_PLUGIN_REQUIRED_DATA = {"X", "Y"}                      # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}              # type:ignore
+
+
+class ElasticNet(ModellingPluginT):
+    """
+    Linear regression with combined L1 and L2 priors as regularizer
+    """
+
+    def __init__(self):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.clf = model()
\ No newline at end of file
diff --git a/src/vai_lab/Modelling/plugins/gpclassifier.py b/src/vai_lab/Modelling/plugins/gpclassifier.py
new file mode 100644
index 00000000..b332f673
--- /dev/null
+++ b/src/vai_lab/Modelling/plugins/gpclassifier.py
@@ -0,0 +1,25 @@
+from vai_lab._plugin_templates import ModellingPluginTClass
+from sklearn.gaussian_process import GaussianProcessClassifier as model
+
+_PLUGIN_READABLE_NAMES = {"GPClassifier": "default",
+                          "GPC": "alias",
+                          "GaussianProcessClassifier": "alias"}     # type:ignore
+_PLUGIN_MODULE_OPTIONS = {"Type": "classification"}                 # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {}                                      # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {"n_restarts_optimizer": "int",
+                             "random_state": "int"}                 # type:ignore
+_PLUGIN_REQUIRED_DATA = {"X", "Y"}                                  # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}                          # type:ignore
+
+
+class GPClassifier(ModellingPluginTClass):
+    """
+    Gaussian process Classifier (GPC) based on Laplace approximation
+    """
+
+    def __init__(self):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.clf = model()
\ No newline at end of file
diff --git a/src/vai_lab/Modelling/plugins/gpregressor.py b/src/vai_lab/Modelling/plugins/gpregressor.py
new file mode 100644
index 00000000..edf23cb6
--- /dev/null
+++ b/src/vai_lab/Modelling/plugins/gpregressor.py
@@ -0,0 +1,25 @@
+from vai_lab._plugin_templates import ModellingPluginT
+from sklearn.gaussian_process import GaussianProcessRegressor as model
+
+_PLUGIN_READABLE_NAMES = {"GPRegressor": "default",
+                          "GPR": "alias",
+                          "GaussianProcessRegressor": "alias"}      # type:ignore
+_PLUGIN_MODULE_OPTIONS = {"Type": "regression"}                     # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {}                                      # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {"n_restarts_optimizer": "int",
+                             "random_state": "int"}                 # type:ignore
+_PLUGIN_REQUIRED_DATA = {"X", "Y"}                                  # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}                          # type:ignore
+
+
+class GPRegressor(ModellingPluginT):
+    """
+    Gaussian process regressor
+    """
+
+    def __init__(self):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.clf = model()
\ No newline at end of file
diff --git a/src/vai_lab/Modelling/plugins/kernelridge.py b/src/vai_lab/Modelling/plugins/kernelridge.py
new file mode 100644
index 00000000..95cbdb20
--- /dev/null
+++ b/src/vai_lab/Modelling/plugins/kernelridge.py
@@ -0,0 +1,26 @@
+from vai_lab._plugin_templates import ModellingPluginT
+from sklearn.kernel_ridge import KernelRidge as model
+
+_PLUGIN_READABLE_NAMES = {"KernelRidge": "default",
+                          "KR": "alias"}                # type:ignore
+_PLUGIN_MODULE_OPTIONS = {"Type": "regression"}         # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {}                          # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {"alpha": "float",
+                             "kernel": "str",
+                             "gamma": "float",
+                             "degree": "int"}           # type:ignore
+_PLUGIN_REQUIRED_DATA = {"X", "Y"}                      # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}              # type:ignore
+
+
+class KernelRidge(ModellingPluginT):
+    """
+    Kernel ridge regression.
+    """
+
+    def __init__(self):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.clf = model()
\ No newline at end of file
diff --git a/src/vai_lab/Modelling/plugins/kmeans.py b/src/vai_lab/Modelling/plugins/kmeans.py
new file mode 100644
index 00000000..960512ce
--- /dev/null
+++ b/src/vai_lab/Modelling/plugins/kmeans.py
@@ -0,0 +1,23 @@
+from vai_lab._plugin_templates import ModellingPluginT
+from sklearn.cluster import KMeans as model
+
+_PLUGIN_READABLE_NAMES = {"KMeans": "default"}      # type:ignore
+_PLUGIN_MODULE_OPTIONS = {"Type": "clustering"}     # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {}                      # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {"n_clusters": "int",
+                             "n_init": "int"}       # type:ignore
+_PLUGIN_REQUIRED_DATA = {"X"}                       # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"Y", "X_tst", 'Y_tst'}     # type:ignore
+
+
+class KMeans(ModellingPluginT):
+    """
+    K-Means clustering
+    """
+
+    def __init__(self):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.clf = model()
\ No newline at end of file
diff --git a/src/vai_lab/Modelling/plugins/knnclassifier.py b/src/vai_lab/Modelling/plugins/knnclassifier.py
new file mode 100644
index 00000000..9ae82293
--- /dev/null
+++ b/src/vai_lab/Modelling/plugins/knnclassifier.py
@@ -0,0 +1,24 @@
+from vai_lab._plugin_templates import ModellingPluginTClass
+from sklearn.neighbors import KNeighborsClassifier as model
+
+_PLUGIN_READABLE_NAMES = {"KNNClassifier": "default",
+                          "KNN-C": "alias"}             # type:ignore
+_PLUGIN_MODULE_OPTIONS = {"Type": "classification"}     # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {}                          # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {"n_neighbors": "int",
+                             "weights": "str"}          # type:ignore
+_PLUGIN_REQUIRED_DATA = {"X", "Y"}                      # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}              # type:ignore
+
+
+class KNNclassifier(ModellingPluginTClass):
+    """
+    Classifier implementing the k-nearest neighbors vote
+    """
+
+    def __init__(self):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.clf = model()
\ No newline at end of file
diff --git a/src/vai_lab/Modelling/plugins/knnregressor.py b/src/vai_lab/Modelling/plugins/knnregressor.py
new file mode 100644
index 00000000..8be1bcc8
--- /dev/null
+++ b/src/vai_lab/Modelling/plugins/knnregressor.py
@@ -0,0 +1,24 @@
+from vai_lab._plugin_templates import ModellingPluginT
+from sklearn.neighbors import KNeighborsRegressor as model
+
+_PLUGIN_READABLE_NAMES = {"KNNRegressor": "default",
+                          "KNN-R": "alias"}             # type:ignore
+_PLUGIN_MODULE_OPTIONS = {"Type": "regression"}         # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {}                          # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {"n_neighbors": "int",
+                             "weights": "str"}          # type:ignore  
+_PLUGIN_REQUIRED_DATA = {"X", "Y"}                      # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}              # type:ignore
+
+
+class KNNRegressor(ModellingPluginT):
+    """
+    Regression based on k-nearest neighbors
+    """
+
+    def __init__(self):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.clf = model()
\ No newline at end of file
diff --git a/src/vai_lab/Modelling/plugins/lasso.py b/src/vai_lab/Modelling/plugins/lasso.py
new file mode 100644
index 00000000..10ccbfed
--- /dev/null
+++ b/src/vai_lab/Modelling/plugins/lasso.py
@@ -0,0 +1,21 @@
+from vai_lab._plugin_templates import ModellingPluginT
+from sklearn.linear_model import Lasso as model
+
+_PLUGIN_READABLE_NAMES = {"Lasso":"default"}        # type:ignore
+_PLUGIN_MODULE_OPTIONS = {"Type": "regression"}     # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {}                      # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {"alpha": "float"}      # type:ignore
+_PLUGIN_REQUIRED_DATA = {"X","Y"}                   # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}          # type:ignore
+
+class Lasso(ModellingPluginT):
+    """
+    Linear Model trained with L1 prior as regularizer
+    """
+
+    def __init__(self):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.clf = model()
\ No newline at end of file
diff --git a/src/vai_lab/Modelling/plugins/linearregression.py b/src/vai_lab/Modelling/plugins/linearregression.py
new file mode 100644
index 00000000..c25523cf
--- /dev/null
+++ b/src/vai_lab/Modelling/plugins/linearregression.py
@@ -0,0 +1,23 @@
+from vai_lab._plugin_templates import ModellingPluginT
+from sklearn.linear_model import LinearRegression as model
+
+_PLUGIN_READABLE_NAMES = {"LinearRegression": "default",
+                          "LR": "alias"}                    # type:ignore
+_PLUGIN_MODULE_OPTIONS = {"Type": "regression"}             # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {}                              # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {}                              # type:ignore
+_PLUGIN_REQUIRED_DATA = {"X", "Y"}                          # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}                  # type:ignore
+
+
+class LinearRegression(ModellingPluginT):
+    """
+    Ordinary least squares Linear Regression
+    """
+
+    def __init__(self):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.clf = model()
\ No newline at end of file
diff --git a/src/vai_lab/Modelling/plugins/logisticregression.py b/src/vai_lab/Modelling/plugins/logisticregression.py
new file mode 100644
index 00000000..84390752
--- /dev/null
+++ b/src/vai_lab/Modelling/plugins/logisticregression.py
@@ -0,0 +1,24 @@
+from vai_lab._plugin_templates import ModellingPluginTClass
+from sklearn.linear_model import LogisticRegression as model
+
+_PLUGIN_READABLE_NAMES = {"LogisticRegression": "default",
+                          "logit": "alias",
+                          "MaxEnt": "alias"}                    # type:ignore
+_PLUGIN_MODULE_OPTIONS = {"Type": "classification"}             # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {}                                  # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {"penalty": "str", "C": "float"}    # type:ignore
+_PLUGIN_REQUIRED_DATA = {"X", "Y"}                              # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}                      # type:ignore
+
+
+class LogisticRegression(ModellingPluginTClass):
+    """
+    Logistic regression classification.
+    """
+
+    def __init__(self):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.clf = model()
\ No newline at end of file
diff --git a/src/vai_lab/Modelling/plugins/meanshift.py b/src/vai_lab/Modelling/plugins/meanshift.py
new file mode 100644
index 00000000..d3d8c207
--- /dev/null
+++ b/src/vai_lab/Modelling/plugins/meanshift.py
@@ -0,0 +1,21 @@
+from vai_lab._plugin_templates import ModellingPluginT
+from sklearn.cluster import MeanShift as model
+
+_PLUGIN_READABLE_NAMES = {"MeanShift":"default"}        # type:ignore
+_PLUGIN_MODULE_OPTIONS = {"Type": "clustering"}         # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {}                          # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {"bandwidth": "float"}      # type:ignore
+_PLUGIN_REQUIRED_DATA = {"X"}                           # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"Y", "X_tst", 'Y_tst'}         # type:ignore
+
+class MeanShift(ModellingPluginT):
+    """
+    Mean shift clustering using a flat kernel.
+    """
+
+    def __init__(self):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.clf = model()
\ No newline at end of file
diff --git a/src/vai_lab/Modelling/plugins/passiveaggressiveclassifier.py b/src/vai_lab/Modelling/plugins/passiveaggressiveclassifier.py
new file mode 100644
index 00000000..772ef0e6
--- /dev/null
+++ b/src/vai_lab/Modelling/plugins/passiveaggressiveclassifier.py
@@ -0,0 +1,23 @@
+from vai_lab._plugin_templates import ModellingPluginTClass
+from sklearn.linear_model import PassiveAggressiveClassifier as model
+
+_PLUGIN_READABLE_NAMES = {"PassiveAggressiveClassifier": "default",
+                          "PassiveAgressive": "alias"}                  # type:ignore
+_PLUGIN_MODULE_OPTIONS = {"Type": "classification"}                     # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {}                                          # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {"C": "float"}                              # type:ignore
+_PLUGIN_REQUIRED_DATA = {"X", "Y"}                                      # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}                              # type:ignore
+
+
+class PassiveAggressiveClassifier(ModellingPluginTClass):
+    """
+    Passive aggressive classifier
+    """
+
+    def __init__(self):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.clf = model()
\ No newline at end of file
diff --git a/src/vai_lab/Modelling/plugins/perceptron.py b/src/vai_lab/Modelling/plugins/perceptron.py
new file mode 100644
index 00000000..63155166
--- /dev/null
+++ b/src/vai_lab/Modelling/plugins/perceptron.py
@@ -0,0 +1,24 @@
+from vai_lab._plugin_templates import ModellingPluginTClass
+from sklearn.linear_model import Perceptron as model
+
+_PLUGIN_READABLE_NAMES = {"Perceptron": "default",
+                          "LinearPerceptron": "alias"}      # type:ignore
+_PLUGIN_MODULE_OPTIONS = {"Type": "classification"}         # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {}                              # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {"alpha": "float",
+                             "penalty": "str"}              # type:ignore
+_PLUGIN_REQUIRED_DATA = {"X", "Y"}                          # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}                  # type:ignore
+
+
+class Perceptron(ModellingPluginTClass):
+    """
+    Linear perceptron classification
+    """
+
+    def __init__(self):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.clf = model()
\ No newline at end of file
diff --git a/src/vai_lab/Modelling/plugins/randomforestclassifier.py b/src/vai_lab/Modelling/plugins/randomforestclassifier.py
new file mode 100644
index 00000000..39850f41
--- /dev/null
+++ b/src/vai_lab/Modelling/plugins/randomforestclassifier.py
@@ -0,0 +1,25 @@
+from vai_lab._plugin_templates import ModellingPluginTClass
+from sklearn.ensemble import RandomForestClassifier as model
+
+_PLUGIN_READABLE_NAMES = {"RandomForestClassifier": "default",
+                          "RFclassifier": "alias",
+                          "RFC": "alias"}                       # type:ignore
+_PLUGIN_MODULE_OPTIONS = {"Type": "classification"}             # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {}                                  # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {"max_depth": "int",
+                             "n_estimators": "int"}             # type:ignore
+_PLUGIN_REQUIRED_DATA = {"X", "Y"}                              # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}                      # type:ignore
+
+
+class RandomForestClassifier(ModellingPluginTClass):
+    """
+    A random forest classifier
+    """
+
+    def __init__(self):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.clf = model()
\ No newline at end of file
diff --git a/src/vai_lab/Modelling/plugins/randomforestregressor.py b/src/vai_lab/Modelling/plugins/randomforestregressor.py
new file mode 100644
index 00000000..bea9acbb
--- /dev/null
+++ b/src/vai_lab/Modelling/plugins/randomforestregressor.py
@@ -0,0 +1,25 @@
+from vai_lab._plugin_templates import ModellingPluginT
+from sklearn.ensemble import RandomForestRegressor as model
+
+_PLUGIN_READABLE_NAMES = {"RandomForestRegressor": "default",
+                          "RFRegressor": "alias",
+                          "RFR": "alias"}               # type:ignore
+_PLUGIN_MODULE_OPTIONS = {"Type": "regression"}         # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {}                          # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {"max_depth": "int",
+                             "n_estimators": "int"}     # type:ignore
+_PLUGIN_REQUIRED_DATA = {"X", "Y"}                      # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}              # type:ignore
+
+
+class RandomForestRegressor(ModellingPluginT):
+    """
+    A random forest regression
+    """
+
+    def __init__(self):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.clf = model()
\ No newline at end of file
diff --git a/src/vai_lab/Modelling/plugins/ridgeregression.py b/src/vai_lab/Modelling/plugins/ridgeregression.py
new file mode 100644
index 00000000..ee0a234b
--- /dev/null
+++ b/src/vai_lab/Modelling/plugins/ridgeregression.py
@@ -0,0 +1,23 @@
+from vai_lab._plugin_templates import ModellingPluginT
+from sklearn.linear_model import Ridge as model
+
+_PLUGIN_READABLE_NAMES = {"Ridge": "default",
+                          "RidgeRegression": "alias"}       # type:ignore
+_PLUGIN_MODULE_OPTIONS = {"Type": "regression"}             # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {}                              # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {"alpha": "float"}              # type:ignore
+_PLUGIN_REQUIRED_DATA = {"X", "Y"}                          # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}                  # type:ignore
+
+
+class RidgeRegression(ModellingPluginT):
+    """
+    Linear least squares with l2 regularization
+    """
+
+    def __init__(self):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.clf = model()
\ No newline at end of file
diff --git a/src/vai_lab/Modelling/plugins/svc.py b/src/vai_lab/Modelling/plugins/svc.py
new file mode 100644
index 00000000..7862ae70
--- /dev/null
+++ b/src/vai_lab/Modelling/plugins/svc.py
@@ -0,0 +1,26 @@
+from vai_lab._plugin_templates import ModellingPluginTClass
+from sklearn.svm import SVC as model
+
+_PLUGIN_READABLE_NAMES = {"SVC": "default",
+                          "SupportVectorClassification": "alias"}   # type:ignore
+_PLUGIN_MODULE_OPTIONS = {"Type": "classification"}                 # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {}                                      # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {"C": "float",
+                             "kernel": "str",
+                             "gamma": "float",
+                             "degree": "int"}                       # type:ignore
+_PLUGIN_REQUIRED_DATA = {"X", "Y"}                                  # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}                          # type:ignore
+
+
+class SVC(ModellingPluginTClass):
+    """
+    C-Support Vector Classification
+    """
+
+    def __init__(self):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.clf = model()
\ No newline at end of file
diff --git a/src/vai_lab/Modelling/plugins/svr.py b/src/vai_lab/Modelling/plugins/svr.py
new file mode 100644
index 00000000..9519bf7e
--- /dev/null
+++ b/src/vai_lab/Modelling/plugins/svr.py
@@ -0,0 +1,26 @@
+from vai_lab._plugin_templates import ModellingPluginT
+from sklearn.svm import SVR as model
+
+_PLUGIN_READABLE_NAMES = {"SVR": "default",
+                          "SupportVectorRegression": "alias"}   # type:ignore
+_PLUGIN_MODULE_OPTIONS = {"Type": "regression"}                 # type:ignore
+_PLUGIN_REQUIRED_SETTINGS = {}                                  # type:ignore
+_PLUGIN_OPTIONAL_SETTINGS = {"C": "float",
+                             "kernel": "str",
+                             "gamma": "float",
+                             "degree": "int"}                   # type:ignore
+_PLUGIN_REQUIRED_DATA = {"X", "Y"}                              # type:ignore
+_PLUGIN_OPTIONAL_DATA = {"X_tst", 'Y_tst'}                      # type:ignore
+
+
+class SVR(ModellingPluginT):
+    """
+    Epsilon-Support Vector Regression
+    """
+
+    def __init__(self):
+        """Initialises parent class. 
+            Passes `globals` dict of all current variables
+        """
+        super().__init__(globals())
+        self.clf = model()
\ No newline at end of file
diff --git a/aidesign/UserInteraction/UserInteraction_core.py b/src/vai_lab/UserInteraction/UserInteraction_core.py
similarity index 83%
rename from aidesign/UserInteraction/UserInteraction_core.py
rename to src/vai_lab/UserInteraction/UserInteraction_core.py
index 24db1ed5..588f9111 100644
--- a/aidesign/UserInteraction/UserInteraction_core.py
+++ b/src/vai_lab/UserInteraction/UserInteraction_core.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-from aidesign.GUI.GUI_core import GUI
+from vai_lab.GUI.GUI_core import GUI
 class UserInteraction(GUI):
     """UserFeedback core class directly uses GUI_core.py"""
     def __init__(self, *args, **kwargs):
diff --git a/aidesign/UserInteraction/__init__.py b/src/vai_lab/UserInteraction/__init__.py
similarity index 100%
rename from aidesign/UserInteraction/__init__.py
rename to src/vai_lab/UserInteraction/__init__.py
diff --git a/aidesign/UserInteraction/plugins/CanvasInput.py b/src/vai_lab/UserInteraction/plugins/CanvasInput.py
similarity index 98%
rename from aidesign/UserInteraction/plugins/CanvasInput.py
rename to src/vai_lab/UserInteraction/plugins/CanvasInput.py
index 25811d95..30256eaa 100644
--- a/aidesign/UserInteraction/plugins/CanvasInput.py
+++ b/src/vai_lab/UserInteraction/plugins/CanvasInput.py
@@ -1,6 +1,6 @@
 
 from typing import List
-from aidesign._plugin_templates import UI
+from vai_lab._plugin_templates import UI
 
 import tkinter as tk
 from tkinter import messagebox, ttk
@@ -42,15 +42,18 @@ def __init__(self, parent, controller, config: dict):
         self.save_path = ''
         self.saved = True
 
-    def set_data_in(self, data_in):
-        req_check = [
-            r for r in _PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
+    def set_data_in(self, data_in: dict):
+        """Set data.
+        """
+        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("CanvasInput")
                             + "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
         self._load_classes_from_data()
 
@@ -252,10 +255,7 @@ def draw_dot(self, event):
                     # Update coordinates in corresponding row if exists.
                     if self.tree[ii].selection():
                         n = int(self.tree[ii].selection()[0]) + 1
-                        if n % 2 == 0:
-                            tag = ('even',)
-                        else:
-                            tag = ('odd',)
+                        tag = ('odd' if n%2 == 0 else 'even',)
                         self.tree[ii].insert(
                             parent='', index='end', iid=n, text=n+1,
                             values=tuple(self.dict2mat(
diff --git a/aidesign/UserInteraction/plugins/ManualInput.py b/src/vai_lab/UserInteraction/plugins/ManualInput.py
similarity index 98%
rename from aidesign/UserInteraction/plugins/ManualInput.py
rename to src/vai_lab/UserInteraction/plugins/ManualInput.py
index 85cc826b..94ac0018 100644
--- a/aidesign/UserInteraction/plugins/ManualInput.py
+++ b/src/vai_lab/UserInteraction/plugins/ManualInput.py
@@ -1,6 +1,6 @@
-from aidesign._plugin_templates import UI
-from aidesign._import_helper import get_lib_parent_dir
-from aidesign._types import DictT, DataInterface, GUICoreInterface
+from vai_lab._plugin_templates import UI
+from vai_lab._import_helper import get_lib_parent_dir
+from vai_lab._types import DictT, DataInterface, GUICoreInterface
 
 import os
 import numpy as np
@@ -114,14 +114,18 @@ def _load_images_from_data(self):
         self.grid_columnconfigure(0, weight=2)
 
     def set_data_in(self, data_in):
+        """Set data.
+        """
         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("ManualInput")
                             + "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
         self._load_images_from_data()
         self._load_classes_from_data()
diff --git a/aidesign/UserInteraction/plugins/__init__.py b/src/vai_lab/UserInteraction/plugins/__init__.py
similarity index 100%
rename from aidesign/UserInteraction/plugins/__init__.py
rename to src/vai_lab/UserInteraction/plugins/__init__.py
diff --git a/aidesign/UserInteraction/plugins/resources/Assets/UFAIcon.ico b/src/vai_lab/UserInteraction/plugins/resources/Assets/UFAIcon.ico
similarity index 100%
rename from aidesign/UserInteraction/plugins/resources/Assets/UFAIcon.ico
rename to src/vai_lab/UserInteraction/plugins/resources/Assets/UFAIcon.ico
diff --git a/aidesign/UserInteraction/plugins/resources/Assets/UFAIcon_name.png b/src/vai_lab/UserInteraction/plugins/resources/Assets/UFAIcon_name.png
similarity index 100%
rename from aidesign/UserInteraction/plugins/resources/Assets/UFAIcon_name.png
rename to src/vai_lab/UserInteraction/plugins/resources/Assets/UFAIcon_name.png
diff --git a/aidesign/UserInteraction/plugins/resources/Assets/back_arrow.png b/src/vai_lab/UserInteraction/plugins/resources/Assets/back_arrow.png
similarity index 100%
rename from aidesign/UserInteraction/plugins/resources/Assets/back_arrow.png
rename to src/vai_lab/UserInteraction/plugins/resources/Assets/back_arrow.png
diff --git a/aidesign/UserInteraction/plugins/resources/Assets/forw_arrow.png b/src/vai_lab/UserInteraction/plugins/resources/Assets/forw_arrow.png
similarity index 100%
rename from aidesign/UserInteraction/plugins/resources/Assets/forw_arrow.png
rename to src/vai_lab/UserInteraction/plugins/resources/Assets/forw_arrow.png
diff --git a/src/vai_lab/__init__.py b/src/vai_lab/__init__.py
new file mode 100644
index 00000000..5ecee7eb
--- /dev/null
+++ b/src/vai_lab/__init__.py
@@ -0,0 +1,1 @@
+from .Core import Core
\ No newline at end of file
diff --git a/aidesign/_import_helper.py b/src/vai_lab/_import_helper.py
similarity index 65%
rename from aidesign/_import_helper.py
rename to src/vai_lab/_import_helper.py
index 31afecf5..e083a007 100644
--- a/aidesign/_import_helper.py
+++ b/src/vai_lab/_import_helper.py
@@ -17,7 +17,7 @@ def import_plugin_absolute(script_config,plugin_package,plugin_name):
     return plugin_class
 
 def import_module(script_config, module_name):
-    module_list = __import__('aidesign.' + module_name + "." + module_name + "_core",
+    module_list = __import__('vai_lab.' + module_name + "." + module_name + "_core",
                              script_config,
                              {},
                              [module_name])
@@ -32,4 +32,15 @@ def get_lib_parent_dir():
     return [__file__[:i] \
                 for i,_ in enumerate(__file__)\
                 if __file__[:i].\
-                endswith("{0}aidesign{0}".format(path.sep))][-1]
\ No newline at end of file
+                endswith("{0}vai_lab{0}".format(path.sep))][-1]
+
+def rel_to_abs(filename: str) -> str:
+        """Checks if path is relative or absolute
+        If absolute, returns original path 
+        If relative, converts path to absolute by appending to base directory
+        """
+        if filename[0] == ".":
+            filename = path.join(get_lib_parent_dir(), filename)
+        elif filename[0] == "/" or (filename[0].isalpha() and filename[0].isupper()):
+            filename = filename
+        return filename
\ No newline at end of file
diff --git a/aidesign/_plugin_helpers.py b/src/vai_lab/_plugin_helpers.py
similarity index 85%
rename from aidesign/_plugin_helpers.py
rename to src/vai_lab/_plugin_helpers.py
index 9217a9bd..90b4003d 100644
--- a/aidesign/_plugin_helpers.py
+++ b/src/vai_lab/_plugin_helpers.py
@@ -1,8 +1,8 @@
-from typing import List, Iterator, Union
-from aidesign._types import DictT
-
 import ast
 import os
+from typing import Iterator, List, Union
+
+from vai_lab._types import DictT
 
 
 class PluginSpecs(ast.NodeVisitor):
@@ -26,12 +26,16 @@ def _get_plugin_files(self, root_dir: str = None) -> None:
                 f for f in plugins if f.endswith(".py")]
 
     def _get_clean_module_dirs(self, root_dir: str) -> List[str]:
-        excludes = ["__init__.py", "__pycache__", "resources"]
+        excludes = ["tests", "examples", "resources", "run_pipeline.py"]
         if os.path.exists(root_dir):
             mod_dirs = os.listdir(root_dir)
-            for ex in excludes:
-                if ex in mod_dirs:
-                    mod_dirs.remove(ex)
+            rm_list = []
+            for m in mod_dirs:
+                if m.startswith("_") \
+                    or m in excludes:
+                    rm_list.append(m)
+            for ex in rm_list:
+                mod_dirs.remove(ex)
             return mod_dirs
         else:
             return []
@@ -53,7 +57,7 @@ def _get_module_package(self):
         if __package__:
             parent_package = __package__.split(".")[0]
         else:
-            parent_package = "aidesign"
+            parent_package = "vai_lab"
         scipt_filename = self.curr_plugin.replace(".py", "")
         return "{0}.{1}.plugins.{2}".format(parent_package, self.curr_module, scipt_filename)
 
@@ -96,6 +100,11 @@ def visit_Assign(self, node):
                            "<ast expression>", "eval"))
                 self.available_plugins[self.curr_module][self.curr_plugin][key] = val
 
+    def _get_default_name_from_dict(self, plugin: DictT) -> str:
+        default = list(plugin.keys())\
+            [list(plugin.values()).index("default")]
+        return default
+
     def _get_option_specs(self, option: str) -> dict:
         """Gets either required OR optional settings for each plugin.
 
@@ -106,16 +115,16 @@ def _get_option_specs(self, option: str) -> dict:
 
         :returns output: nested dict of options by [Module][Plugin Class Name]
         """
-        output : DictT= {}
+        output: DictT = {}
         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]:
-                    cn = self.available_plugins[module][plugin]["_PLUGIN_CLASS_NAME"]
-                    output[module][cn] = self.available_plugins[module][plugin][option]
+                    rn = self._get_default_name_from_dict(self.available_plugins[module][plugin]["_PLUGIN_READABLE_NAMES"])
+                    output[module][rn] = self.available_plugins[module][plugin][option]
         return output
 
-    def _find_plugin_by_tag_and_value(self, tag: str, name: str) -> Union[DictT,None]:
+    def _find_plugin_by_tag_and_value(self, tag: str, name: str) -> Union[DictT, None]:
         for module in self.available_plugins.keys():
             for plugin in self.available_plugins[module].keys():
                 if tag in self.available_plugins[module][plugin]:
@@ -153,9 +162,7 @@ def available_plugin_names(self):
         output = []
         for n in names.keys():
             for plugin in names[n]:
-                for variant in names[n][plugin].keys():
-                    if names[n][plugin][variant] == "default":
-                        output.append(variant)
+                output.append(self._get_default_name_from_dict(names[n][plugin]))
         return output
 
     def find_from_class_name(self, value):
@@ -172,7 +179,8 @@ def print(self, value):
 
 if __name__ == "__main__":
     ps = PluginSpecs()
-    ps.print(ps.available_plugin_names)
+    # ps.print(ps.available_plugin_names)
+    ps.print(ps.optional_settings)
     # ps.print(ps.names)
     # ps.print(ps.class_descriptions)
     # print(list(ps.class_descriptions()['GUI'].values()))
diff --git a/aidesign/_plugin_templates.py b/src/vai_lab/_plugin_templates.py
similarity index 53%
rename from aidesign/_plugin_templates.py
rename to src/vai_lab/_plugin_templates.py
index 27e51831..2973574c 100644
--- a/aidesign/_plugin_templates.py
+++ b/src/vai_lab/_plugin_templates.py
@@ -1,7 +1,13 @@
-from aidesign._types import DataInterface
+from typing import Dict
+from vai_lab._types import DataInterface
 from abc import ABC, abstractmethod
 
 import numpy as np
+import pandas as pd
+
+class MissingDataError(Exception):
+    def __init__(self, msg):
+        self.msg = msg
 
 
 class PluginTemplate:
@@ -34,7 +40,7 @@ def configure(self, config: dict) -> None:
     def set_data_in(self, data_in: DataInterface) -> None:
         """Sets and parses incoming data
         :param data_in: saves data as class variable
-                        expected type: aidesign.Data.Data_core.Data
+                        expected type: vai_lab.Data.Data_core.Data
         """
         req_check = [
             r for r in self._PLUGIN_REQUIRED_DATA if r not in data_in.keys()]
@@ -62,7 +68,7 @@ def _get_plugin_deafult_name(self, plugin_globals: dict):
 
     def _data_missing_error(self, req):
         """If incoming data does not match required data, raise Exception and print"""
-        raise Exception("Minimal Data Requirements not met"
+        raise MissingDataError("Minimal Data Requirements not met"
                         + "\n\t{0} ".format(self.default_name)
                         + "requires data: {0}".format(self._PLUGIN_REQUIRED_DATA)
                         + "\n\tThe following data is missing:"
@@ -70,7 +76,7 @@ def _data_missing_error(self, req):
 
     def _parse_config(self):
         """Parse incoming data and args, sets them as class variables"""
-        self.X = self._data_in["X"]
+        self.X = np.array(self._get_data_if_exist(self._data_in, "X"))
         self.Y = np.array(self._get_data_if_exist(self._data_in, "Y")).ravel()
         self.X_tst = self._get_data_if_exist(self._data_in, "X_test")
         self.Y_tst = np.array(self._get_data_if_exist(
@@ -97,20 +103,24 @@ def _reshape(self, data, shape):
         """
         return data.reshape(shape[0], shape[1])
 
+    def _parse_options_dict(self,options_dict:Dict):
+        for key, val in options_dict.items():
+            if type(val) == str and val.replace('.', '').replace(',', '').isnumeric():
+                cleaned_opts = []
+                for el in val.split(","):
+                    val = float(el)
+                    if val.is_integer():
+                        val = int(val)
+                    cleaned_opts.append(val)
+                options_dict[key] = cleaned_opts
+        return options_dict
+
     def _clean_options(self):
         """Parses incoming plugin options in self._config["options"] 
                 and modifies DataInterface in-place
                 str options which only contain numeric data are converted to float OR int
         """
-        for key, val in self._config["options"].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)
-            self._config["options"][key] = val
+        self._parse_options_dict(self._config["options"])
 
     def _test(self, data: DataInterface) -> DataInterface:
         """Run debug tests on data operations
@@ -159,34 +169,116 @@ def _clean_solver_options(self):
         TODO: in future, reverse the direction of this function - instead pull all options
                 that are directly required by the solver
         """
-        return {i[0]: i[1]
+        _cleaned =  {i[0]: i[1]
                 for i in self._config["options"].items()
                 if i[0] not in self._options_to_ignore}
-
-    @abstractmethod
+        for key, val in _cleaned.items():
+            if val == 'True':
+                _cleaned[key] = True
+            elif val == 'False':
+                _cleaned[key] = False
+        return _cleaned
+            
     def fit(self):
-        pass
+        cleaned_options = self._clean_solver_options()
+        try:
+            self.proc.set_params(**cleaned_options)
+        except Exception as exc:
+            print('The plugin encountered an error on the parameters of '
+                     +str(list(self._PLUGIN_READABLE_NAMES.keys())[list(self._PLUGIN_READABLE_NAMES.values()).index('default')])+': '+exc+'.')
+            raise
+        try:
+            self.proc.fit(self.X)
+        except Exception as exc:
+            print('The plugin encountered an error when fitting '
+                     +str(list(self._PLUGIN_READABLE_NAMES.keys())[list(self._PLUGIN_READABLE_NAMES.values()).index('default')])+': '+exc+'.')
+            raise
 
-    @abstractmethod
     def transform(self, data: DataInterface) -> DataInterface:
-        pass
+        try:
+            data.append_data_column("X", pd.DataFrame(self.proc.transform(self.X)))
+        except Exception as exc:
+            print('The plugin encountered an error when transforming the data with '
+                     +str(list(self._PLUGIN_READABLE_NAMES.keys())[list(self._PLUGIN_READABLE_NAMES.values()).index('default')])+': '+exc+'.')
+            raise
+        if self.X_tst is not None:
+            try:
+                data.append_data_column("X_test", pd.DataFrame(self.proc.transform(self.X_tst)))
+            except Exception as exc:
+                print('The plugin encountered an error when transforming the data with '
+                        +str(list(self._PLUGIN_READABLE_NAMES.keys())[list(self._PLUGIN_READABLE_NAMES.values()).index('default')])+': '+exc+'.')
+                raise
+        return data
 
 
 class ModellingPluginT(PluginTemplate, ABC):
     def __init__(self, plugin_globals: dict) -> None:
         super().__init__(plugin_globals)
 
-    @abstractmethod
     def solve(self):
-        pass
-
-    @abstractmethod
-    def predict(self):
-        pass
+        """Sends params to solver, then runs solver"""
+        try:
+            self.clf.set_params(**self._config["options"])
+        except Exception as exc:
+            print('The plugin encountered an error on the parameters of '
+                     +str(list(self._PLUGIN_READABLE_NAMES.keys())[list(self._PLUGIN_READABLE_NAMES.values()).index('default')])+': '+exc+'.')
+            raise
+        try:
+            self.clf.fit(self.X, self.Y)
+        except Exception as exc:
+            print('The plugin encountered an error when fitting '
+                     +str(list(self._PLUGIN_READABLE_NAMES.keys())[list(self._PLUGIN_READABLE_NAMES.values()).index('default')])+': '+exc+'.')
+            raise
+
+    def predict(self, data):
+        """Uses fitted model to predict output of a given Y
+        :param data: array-like or sparse matrix, shape (n_samples, n_features)
+                    Samples
+                    expected type: vai_lab.Data.Data_core.Data
+        :returns: array, shape (n_samples,)
+                    Returns predicted values.
+        """
+        try:
+            return self.clf.predict(data)
+        except Exception as exc:
+            print('The plugin encountered an error when predicting with '
+                     +str(list(self._PLUGIN_READABLE_NAMES.keys())[list(self._PLUGIN_READABLE_NAMES.values()).index('default')])+': '+exc+'.')
+            raise
+
+    def score(self, X, Y, sample_weight):
+        """Return the coefficient of determination
+        :param  X : array-like of shape (n_samples, n_features)
+        :param  Y :  array-like of shape (n_samples,) or (n_samples, n_outputs)
+        :param sample_weight : array-like of shape (n_samples,), default=None
+                    Sample weights.
+
+        :returns: score : float of ``self.predict(X)`` wrt. `y`.
+        """
+        try:
+            return self.clf.score(X, Y, sample_weight)
+        except Exception as exc:
+            print('The plugin encountered an error when calculating the score with '
+                     +str(list(self._PLUGIN_READABLE_NAMES.keys())[list(self._PLUGIN_READABLE_NAMES.values()).index('default')])+'.')
+            raise
+
+class ModellingPluginTClass(ModellingPluginT, ABC):
+    def __init__(self, plugin_globals: dict) -> None:
+        super().__init__(plugin_globals)
 
-    @abstractmethod
-    def score(self):
-        pass
+    def predict_proba(self, data):
+        """Uses fitted model to predict the probability of the output of a given Y
+        :param data: array-like or sparse matrix, shape (n_samples, n_features)
+                    Samples
+                    expected type: vai_lab.Data.Data_core.Data
+        :returns: array, shape (n_samples,)
+                    Returns predicted values.
+        """
+        try:
+            return self.clf.predict_proba(data)
+        except Exception as exc:
+            print('The plugin encountered an error when predicting the probability with '
+                     +str(list(self._PLUGIN_READABLE_NAMES.keys())[list(self._PLUGIN_READABLE_NAMES.values()).index('default')])+'.')
+            raise
 
 
 class UI(PluginTemplate, ABC):
@@ -207,3 +299,31 @@ def save_file(self):
     @abstractmethod
     def save_file_as(self):
         pass
+
+
+class EnvironmentPluginT(PluginTemplate, ABC):
+    
+    @abstractmethod
+    def load_model(self) -> None:
+        pass
+
+    @abstractmethod
+    def connect(self):
+        pass
+
+    @abstractmethod
+    def reset(self):
+        pass
+
+    @abstractmethod
+    def disconnect(self):
+        pass
+
+    @abstractmethod
+    def set_gui(self,use_gui:bool=True) -> None:
+        pass
+
+    def configure(self, config: dict):
+        """Extended from PluginTemplate.configure"""
+        super().configure(config)
+        self.set_gui(self._config["options"]["usegui"])
\ No newline at end of file
diff --git a/aidesign/_types.py b/src/vai_lab/_types.py
similarity index 90%
rename from aidesign/_types.py
rename to src/vai_lab/_types.py
index 5c11fed1..2d9d9470 100644
--- a/aidesign/_types.py
+++ b/src/vai_lab/_types.py
@@ -53,6 +53,22 @@ def fit(self):
     def transform(self, data: DataInterfaceT) -> DataInterface:
         ...
 
+class EnvironmentPluginInterface(PluginInterface, Protocol):
+    def load_model(self) -> None:
+        ...
+
+    def connect(self):
+        ...
+
+    def reset(self):
+        ...
+
+    def disconnect(self):
+        ...
+
+    def run_simulation(self):
+        ...
+
 
 class PluginSpecsInterface(Protocol):
     @property
@@ -94,6 +110,8 @@ def print(self, value):
 
 
 class ModuleInterface(Protocol):
+    _debug: bool
+
     def set_avail_plugins(self, avail_plugins: PluginSpecsInterface):
         ...
 
diff --git a/aidesign/examples/crystalDesign/00001.png b/src/vai_lab/examples/crystalDesign/00001.png
similarity index 100%
rename from aidesign/examples/crystalDesign/00001.png
rename to src/vai_lab/examples/crystalDesign/00001.png
diff --git a/aidesign/examples/crystalDesign/00002.png b/src/vai_lab/examples/crystalDesign/00002.png
similarity index 100%
rename from aidesign/examples/crystalDesign/00002.png
rename to src/vai_lab/examples/crystalDesign/00002.png
diff --git a/aidesign/examples/crystalDesign/00003.png b/src/vai_lab/examples/crystalDesign/00003.png
similarity index 100%
rename from aidesign/examples/crystalDesign/00003.png
rename to src/vai_lab/examples/crystalDesign/00003.png
diff --git a/aidesign/examples/crystalDesign/00005.png b/src/vai_lab/examples/crystalDesign/00005.png
similarity index 100%
rename from aidesign/examples/crystalDesign/00005.png
rename to src/vai_lab/examples/crystalDesign/00005.png
diff --git a/aidesign/examples/crystalDesign/00007.png b/src/vai_lab/examples/crystalDesign/00007.png
similarity index 100%
rename from aidesign/examples/crystalDesign/00007.png
rename to src/vai_lab/examples/crystalDesign/00007.png
diff --git a/aidesign/examples/crystalDesign/00009.png b/src/vai_lab/examples/crystalDesign/00009.png
similarity index 100%
rename from aidesign/examples/crystalDesign/00009.png
rename to src/vai_lab/examples/crystalDesign/00009.png
diff --git a/aidesign/examples/crystalDesign/00011.png b/src/vai_lab/examples/crystalDesign/00011.png
similarity index 100%
rename from aidesign/examples/crystalDesign/00011.png
rename to src/vai_lab/examples/crystalDesign/00011.png
diff --git a/aidesign/examples/crystalDesign/00013.png b/src/vai_lab/examples/crystalDesign/00013.png
similarity index 100%
rename from aidesign/examples/crystalDesign/00013.png
rename to src/vai_lab/examples/crystalDesign/00013.png
diff --git a/aidesign/examples/crystalDesign/00015.png b/src/vai_lab/examples/crystalDesign/00015.png
similarity index 100%
rename from aidesign/examples/crystalDesign/00015.png
rename to src/vai_lab/examples/crystalDesign/00015.png
diff --git a/aidesign/examples/image_classification/Y_data.csv b/src/vai_lab/examples/image_classification/Y_data.csv
similarity index 100%
rename from aidesign/examples/image_classification/Y_data.csv
rename to src/vai_lab/examples/image_classification/Y_data.csv
diff --git a/aidesign/examples/image_classification/training_images/00028173_005.png b/src/vai_lab/examples/image_classification/training_images/00028173_005.png
similarity index 100%
rename from aidesign/examples/image_classification/training_images/00028173_005.png
rename to src/vai_lab/examples/image_classification/training_images/00028173_005.png
diff --git a/aidesign/examples/image_classification/training_images/00028179_000.png b/src/vai_lab/examples/image_classification/training_images/00028179_000.png
similarity index 100%
rename from aidesign/examples/image_classification/training_images/00028179_000.png
rename to src/vai_lab/examples/image_classification/training_images/00028179_000.png
diff --git a/aidesign/examples/image_classification/training_images/00028198_002.png b/src/vai_lab/examples/image_classification/training_images/00028198_002.png
similarity index 100%
rename from aidesign/examples/image_classification/training_images/00028198_002.png
rename to src/vai_lab/examples/image_classification/training_images/00028198_002.png
diff --git a/aidesign/examples/image_classification/training_images/00028208_035.png b/src/vai_lab/examples/image_classification/training_images/00028208_035.png
similarity index 100%
rename from aidesign/examples/image_classification/training_images/00028208_035.png
rename to src/vai_lab/examples/image_classification/training_images/00028208_035.png
diff --git a/aidesign/examples/image_classification/training_images/00028211_021.png b/src/vai_lab/examples/image_classification/training_images/00028211_021.png
similarity index 100%
rename from aidesign/examples/image_classification/training_images/00028211_021.png
rename to src/vai_lab/examples/image_classification/training_images/00028211_021.png
diff --git a/aidesign/examples/image_classification/training_images/00028253_000.png b/src/vai_lab/examples/image_classification/training_images/00028253_000.png
similarity index 100%
rename from aidesign/examples/image_classification/training_images/00028253_000.png
rename to src/vai_lab/examples/image_classification/training_images/00028253_000.png
diff --git a/aidesign/examples/image_classification/training_images/00028279_000.png b/src/vai_lab/examples/image_classification/training_images/00028279_000.png
similarity index 100%
rename from aidesign/examples/image_classification/training_images/00028279_000.png
rename to src/vai_lab/examples/image_classification/training_images/00028279_000.png
diff --git a/aidesign/examples/state-action/X_data.csv b/src/vai_lab/examples/state-action/X_data.csv
similarity index 100%
rename from aidesign/examples/state-action/X_data.csv
rename to src/vai_lab/examples/state-action/X_data.csv
diff --git a/aidesign/examples/supervised_classification/X_tr.csv b/src/vai_lab/examples/supervised_classification/X_tr.csv
similarity index 100%
rename from aidesign/examples/supervised_classification/X_tr.csv
rename to src/vai_lab/examples/supervised_classification/X_tr.csv
diff --git a/aidesign/examples/supervised_classification/X_tst.csv b/src/vai_lab/examples/supervised_classification/X_tst.csv
similarity index 100%
rename from aidesign/examples/supervised_classification/X_tst.csv
rename to src/vai_lab/examples/supervised_classification/X_tst.csv
diff --git a/aidesign/examples/supervised_classification/Y_tr.csv b/src/vai_lab/examples/supervised_classification/Y_tr.csv
similarity index 100%
rename from aidesign/examples/supervised_classification/Y_tr.csv
rename to src/vai_lab/examples/supervised_classification/Y_tr.csv
diff --git a/aidesign/examples/supervised_classification/Y_tst.csv b/src/vai_lab/examples/supervised_classification/Y_tst.csv
similarity index 100%
rename from aidesign/examples/supervised_classification/Y_tst.csv
rename to src/vai_lab/examples/supervised_classification/Y_tst.csv
diff --git a/aidesign/examples/supervised_classification/randomInputData.mat b/src/vai_lab/examples/supervised_classification/randomInputData.mat
similarity index 100%
rename from aidesign/examples/supervised_classification/randomInputData.mat
rename to src/vai_lab/examples/supervised_classification/randomInputData.mat
diff --git a/aidesign/examples/supervised_classification/randomInputDataClassification.py b/src/vai_lab/examples/supervised_classification/randomInputDataClassification.py
similarity index 100%
rename from aidesign/examples/supervised_classification/randomInputDataClassification.py
rename to src/vai_lab/examples/supervised_classification/randomInputDataClassification.py
diff --git a/aidesign/examples/supervised_regression/X_tr.csv b/src/vai_lab/examples/supervised_regression/X_tr.csv
similarity index 100%
rename from aidesign/examples/supervised_regression/X_tr.csv
rename to src/vai_lab/examples/supervised_regression/X_tr.csv
diff --git a/aidesign/examples/supervised_regression/X_tst.csv b/src/vai_lab/examples/supervised_regression/X_tst.csv
similarity index 100%
rename from aidesign/examples/supervised_regression/X_tst.csv
rename to src/vai_lab/examples/supervised_regression/X_tst.csv
diff --git a/aidesign/examples/supervised_regression/Y_tr.csv b/src/vai_lab/examples/supervised_regression/Y_tr.csv
similarity index 100%
rename from aidesign/examples/supervised_regression/Y_tr.csv
rename to src/vai_lab/examples/supervised_regression/Y_tr.csv
diff --git a/aidesign/examples/supervised_regression/Y_tst.csv b/src/vai_lab/examples/supervised_regression/Y_tst.csv
similarity index 100%
rename from aidesign/examples/supervised_regression/Y_tst.csv
rename to src/vai_lab/examples/supervised_regression/Y_tst.csv
diff --git a/aidesign/examples/supervised_regression/data_1D_simple/x_train.csv b/src/vai_lab/examples/supervised_regression/data_1D_simple/x_train.csv
similarity index 100%
rename from aidesign/examples/supervised_regression/data_1D_simple/x_train.csv
rename to src/vai_lab/examples/supervised_regression/data_1D_simple/x_train.csv
diff --git a/aidesign/examples/supervised_regression/data_1D_simple/y_train.csv b/src/vai_lab/examples/supervised_regression/data_1D_simple/y_train.csv
similarity index 100%
rename from aidesign/examples/supervised_regression/data_1D_simple/y_train.csv
rename to src/vai_lab/examples/supervised_regression/data_1D_simple/y_train.csv
diff --git a/aidesign/examples/supervised_regression/randomInputData.mat b/src/vai_lab/examples/supervised_regression/randomInputData.mat
similarity index 100%
rename from aidesign/examples/supervised_regression/randomInputData.mat
rename to src/vai_lab/examples/supervised_regression/randomInputData.mat
diff --git a/aidesign/examples/supervised_regression/randomInputDataRegression.py b/src/vai_lab/examples/supervised_regression/randomInputDataRegression.py
similarity index 100%
rename from aidesign/examples/supervised_regression/randomInputDataRegression.py
rename to src/vai_lab/examples/supervised_regression/randomInputDataRegression.py
diff --git a/aidesign/examples/xml_files/KNN-classification_demo.xml b/src/vai_lab/examples/xml_files/KNN-classification_demo.xml
similarity index 100%
rename from aidesign/examples/xml_files/KNN-classification_demo.xml
rename to src/vai_lab/examples/xml_files/KNN-classification_demo.xml
diff --git a/aidesign/examples/xml_files/SVR_demo.xml b/src/vai_lab/examples/xml_files/SVR_demo.xml
similarity index 100%
rename from aidesign/examples/xml_files/SVR_demo.xml
rename to src/vai_lab/examples/xml_files/SVR_demo.xml
diff --git a/aidesign/examples/xml_files/canvas_demo.xml b/src/vai_lab/examples/xml_files/canvas_demo.xml
similarity index 93%
rename from aidesign/examples/xml_files/canvas_demo.xml
rename to src/vai_lab/examples/xml_files/canvas_demo.xml
index 04028d1c..5ed1f55d 100644
--- a/aidesign/examples/xml_files/canvas_demo.xml
+++ b/src/vai_lab/examples/xml_files/canvas_demo.xml
@@ -24,7 +24,7 @@
             <save_to file="../resources/output_test.csv" />
         </plugin>
         <relationships>
-            <parent name="My Second UserFeedback Module" />
+            <parent name="My First UserFeedback Module" />
         </relationships>
     </Output>
 </pipeline>
diff --git a/aidesign/examples/xml_files/k-mean_clustering_demo.xml b/src/vai_lab/examples/xml_files/k-mean_clustering_demo.xml
similarity index 100%
rename from aidesign/examples/xml_files/k-mean_clustering_demo.xml
rename to src/vai_lab/examples/xml_files/k-mean_clustering_demo.xml
diff --git a/aidesign/examples/xml_files/logistic_regression_demo.xml b/src/vai_lab/examples/xml_files/logistic_regression_demo.xml
similarity index 100%
rename from aidesign/examples/xml_files/logistic_regression_demo.xml
rename to src/vai_lab/examples/xml_files/logistic_regression_demo.xml
diff --git a/src/vai_lab/examples/xml_files/pybullet_env_example.xml b/src/vai_lab/examples/xml_files/pybullet_env_example.xml
new file mode 100644
index 00000000..886051e0
--- /dev/null
+++ b/src/vai_lab/examples/xml_files/pybullet_env_example.xml
@@ -0,0 +1,48 @@
+<pipeline>
+    <Initialiser name="Initialiser">
+        <relationships>
+            <child name="MyEnv" />
+        </relationships>
+        <coordinates>
+            [(300.0,50),0,{}]
+        </coordinates>
+    </Initialiser>
+    <Environment name="MyEnv">
+        <relationships>
+            <parent name="Initialiser" />
+            <child name="Output" />
+        </relationships>
+        <coordinates>
+            [(300.0,300.0),2,{0:'d0-u2'}]
+        </coordinates>
+        <plugin type="PyBullet">
+            <model_dir>
+                 plane.urdf
+                 ./Environment/resources/models/half_cheetah_with_mass.xml
+            </model_dir>
+            <usegui>
+                 False
+            </usegui>
+            <setGravity>
+                    0.0
+                    0.0
+                    -9.81
+            </setGravity>
+            <timestep>
+                    0.01
+            </timestep>
+            <max_steps>
+                    10
+            </max_steps>            
+        </plugin>
+    </Environment>
+    <Output name="Output">
+        <plugin type="" />
+        <relationships>
+            <parent name="MyEnv" />
+        </relationships>
+        <coordinates>
+            [(300.0,550),1,{2:'d2-u1'}]
+        </coordinates>
+    </Output>
+</pipeline>
\ No newline at end of file
diff --git a/aidesign/examples/xml_files/random_forest_class_demo.xml b/src/vai_lab/examples/xml_files/random_forest_class_demo.xml
similarity index 73%
rename from aidesign/examples/xml_files/random_forest_class_demo.xml
rename to src/vai_lab/examples/xml_files/random_forest_class_demo.xml
index 04dd8952..e7c0d450 100644
--- a/aidesign/examples/xml_files/random_forest_class_demo.xml
+++ b/src/vai_lab/examples/xml_files/random_forest_class_demo.xml
@@ -4,10 +4,10 @@
             <child name="My Data Processing" />
         </relationships>
 		<inputdata>
-            <X file="./supervised_classification/X_tr.csv"/>
-            <Y file="./supervised_classification/Y_tr.csv"/>
-            <X_test file="./supervised_classification/X_tst.csv"/>
-            <Y_test file="./supervised_classification/Y_tst.csv"/>
+            <X file="./examples/supervised_classification/X_tr.csv"/>
+            <Y file="./examples/supervised_classification/Y_tr.csv"/>
+            <X_test file="./examples/supervised_classification/X_tst.csv"/>
+            <Y_test file="./examples/supervised_classification/Y_tst.csv"/>
         </inputdata>
     </Initialiser>
   
@@ -35,7 +35,7 @@
         <relationships>
             <parent name="My Modelling Module" />
         </relationships>
-      <outData file="./my_results.csv" />
+      <!-- <outData file="./my_results.csv" /> -->
     </Output>
   
 </pipeline>
diff --git a/aidesign/examples/xml_files/regression_demo.xml b/src/vai_lab/examples/xml_files/regression_demo.xml
similarity index 100%
rename from aidesign/examples/xml_files/regression_demo.xml
rename to src/vai_lab/examples/xml_files/regression_demo.xml
diff --git a/aidesign/examples/xml_files/ridge-scalar-ridge_demo.xml b/src/vai_lab/examples/xml_files/ridge-scalar-ridge_demo.xml
similarity index 98%
rename from aidesign/examples/xml_files/ridge-scalar-ridge_demo.xml
rename to src/vai_lab/examples/xml_files/ridge-scalar-ridge_demo.xml
index b8131122..777ca31c 100644
--- a/aidesign/examples/xml_files/ridge-scalar-ridge_demo.xml
+++ b/src/vai_lab/examples/xml_files/ridge-scalar-ridge_demo.xml
@@ -37,7 +37,7 @@
         </coordinates>
         <plugin type="StandardScaler">
             <with_mean>
-                 
+                 True
             </with_mean>
         </plugin>
     </DataProcessing>
diff --git a/aidesign/examples/xml_files/ridge_regression_demo.xml b/src/vai_lab/examples/xml_files/ridge_regression_demo.xml
similarity index 100%
rename from aidesign/examples/xml_files/ridge_regression_demo.xml
rename to src/vai_lab/examples/xml_files/ridge_regression_demo.xml
diff --git a/aidesign/examples/xml_files/scalar_demo.xml b/src/vai_lab/examples/xml_files/scalar_demo.xml
similarity index 89%
rename from aidesign/examples/xml_files/scalar_demo.xml
rename to src/vai_lab/examples/xml_files/scalar_demo.xml
index e2e7e318..10e3f914 100644
--- a/aidesign/examples/xml_files/scalar_demo.xml
+++ b/src/vai_lab/examples/xml_files/scalar_demo.xml
@@ -21,7 +21,11 @@
         <coordinates>
             [(300.0,300.0),2,{0:'d0-u2'}]
         </coordinates>
-        <plugin type="StandardScaler" />
+        <plugin type="StandardScaler">
+            <with_mean>
+                True
+            </with_mean>
+        </plugin>
     </DataProcessing>
     <Output name="Output">
         <plugin type="" />
diff --git a/aidesign/examples/xml_files/scaler-lasso_demo.xml b/src/vai_lab/examples/xml_files/scaler-lasso_demo.xml
similarity index 100%
rename from aidesign/examples/xml_files/scaler-lasso_demo.xml
rename to src/vai_lab/examples/xml_files/scaler-lasso_demo.xml
diff --git a/aidesign/examples/xml_files/user_feedback_demo.xml b/src/vai_lab/examples/xml_files/user_feedback_demo.xml
similarity index 93%
rename from aidesign/examples/xml_files/user_feedback_demo.xml
rename to src/vai_lab/examples/xml_files/user_feedback_demo.xml
index 16098dff..6d1a21a6 100644
--- a/aidesign/examples/xml_files/user_feedback_demo.xml
+++ b/src/vai_lab/examples/xml_files/user_feedback_demo.xml
@@ -25,7 +25,7 @@
             <save_to file="../resources/output_test.csv" />
         </plugin>
         <relationships>
-            <parent name="My Second UserInteraction Module" />
+            <parent name="My First UserInteraction Module" />
         </relationships>
     </Output>
 </pipeline>
diff --git a/src/vai_lab/run_pipeline.py b/src/vai_lab/run_pipeline.py
new file mode 100644
index 00000000..dfabbd7d
--- /dev/null
+++ b/src/vai_lab/run_pipeline.py
@@ -0,0 +1,71 @@
+"""
+CLI for running a pipeline from a config file 
+or start a GUI if no config file given. 
+"""
+
+import argparse
+from os.path import abspath
+
+import vai_lab as ai
+
+
+
+def parse_args():
+    """
+    Parse command line arguments
+    """
+
+    parser = argparse.ArgumentParser(
+                        prog = 'vai_lab',
+                        description = 'AI-assisted virtual laboratories',
+                        )
+
+    parser.add_argument(
+                        '-f', 
+                        '--file',
+                        type=str,
+                        nargs='+',
+                        default=None,
+                        help='pipeline config file',                        
+                        )
+
+    args = parser.parse_args()
+
+    return args
+
+def _config_files_iter(core,files):
+    """Iterate over config files and load them into core
+    TODO: WIP - need a reset function to avoid instantiating core every time
+    """
+    if files:
+        for f in files:
+            core = ai.Core()
+            core.load_config_file(abspath(f))
+            core.run()
+
+
+def main():
+
+    # Parse command line arguments
+    args = parse_args()
+
+    # Core instance
+    core = ai.Core()
+
+    # Load config file if given
+    if args.file:
+        for i in range(0,len(args.file)):
+            args.file[i] = abspath(args.file[i])
+        core.load_config_file(args.file)
+
+    core.load_config_file(
+        ("./examples",
+        "xml_files",
+        'KNN-classification_demo.xml'))
+
+    # Run pipeline   
+    core.run()
+
+if __name__=='__main__':
+    
+    main()
\ No newline at end of file
diff --git a/aidesign/utils/AID_core.py b/src/vai_lab/utils/AID_core.py
similarity index 80%
rename from aidesign/utils/AID_core.py
rename to src/vai_lab/utils/AID_core.py
index 824724ca..33e89ec3 100644
--- a/aidesign/utils/AID_core.py
+++ b/src/vai_lab/utils/AID_core.py
@@ -1,4 +1,4 @@
-from aidesign.GUI.GUI_core import GUI
+from vai_lab.GUI.GUI_core import GUI
 
 class AID(GUI):
     """UserFeedback core class directly uses GUI_core.py"""
diff --git a/aidesign/utils/UserInterfaceClass.py b/src/vai_lab/utils/UserInterfaceClass.py
similarity index 62%
rename from aidesign/utils/UserInterfaceClass.py
rename to src/vai_lab/utils/UserInterfaceClass.py
index 1e9329ea..da82108f 100644
--- a/aidesign/utils/UserInterfaceClass.py
+++ b/src/vai_lab/utils/UserInterfaceClass.py
@@ -1,8 +1,8 @@
-from aidesign._plugin_templates import UI
+from vai_lab._plugin_templates import UI
 
 class UserInterfaceClass(UI):
     """UserInterfaceClass directly duplicates:
-        aidesign.UserInteraction.User_Interaction_template
+        vai_lab.UserInteraction.User_Interaction_template
     """
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
\ No newline at end of file
diff --git a/aidesign/utils/__init__.py b/src/vai_lab/utils/__init__.py
similarity index 100%
rename from aidesign/utils/__init__.py
rename to src/vai_lab/utils/__init__.py
diff --git a/aidesign/utils/plugins/MainPage.py b/src/vai_lab/utils/plugins/MainPage.py
similarity index 89%
rename from aidesign/utils/plugins/MainPage.py
rename to src/vai_lab/utils/plugins/MainPage.py
index ea0a23b3..92043453 100644
--- a/aidesign/utils/plugins/MainPage.py
+++ b/src/vai_lab/utils/plugins/MainPage.py
@@ -5,9 +5,9 @@
 from tkinter.filedialog import askopenfilename, askdirectory
 import pandas as pd
 
-from aidesign.Data.xml_handler import XML_handler
-from aidesign.utils.plugins.dataLoader import dataLoader
-from aidesign._import_helper import get_lib_parent_dir
+from vai_lab.Data.xml_handler import XML_handler
+from vai_lab.utils.plugins.dataLoader import dataLoader
+from vai_lab._import_helper import get_lib_parent_dir, rel_to_abs
 
 _PLUGIN_READABLE_NAMES = {"main": "default",
                           "main page": "alias",
@@ -31,7 +31,10 @@ def __init__(self, parent, controller, config: dict):
         self.controller.output_type = 'regression'
         self.out_data = pd.DataFrame()
         self.bg = parent['bg']
+        if not self.controller._debug:
+            self._setup_frame()
 
+    def _setup_frame(self):
         self.script_dir = get_lib_parent_dir()
         self.my_img1 = ImageTk.PhotoImage(
             Image.open(
@@ -52,7 +55,7 @@ def __init__(self, parent, controller, config: dict):
 
         self.my_label = tk.Label(frame1,
                                  image=self.my_img1,
-                                 bg=parent['bg'])
+                                 bg=self.bg)
 
         self.my_label.grid(column=0,
                            row=0,
@@ -64,8 +67,8 @@ def __init__(self, parent, controller, config: dict):
         my_label = tk.Label(frame2,
                             text='Indicate the data folder and define the pipeline.',
                             pady=10,
-                            font=controller.title_font,
-                            bg=parent['bg'],
+                            font=self.controller.title_font,
+                            bg=self.bg,
                             fg='white')
         my_label.grid(column=0,
                       row=11,
@@ -74,8 +77,8 @@ def __init__(self, parent, controller, config: dict):
         tk.Button(frame3,
                   text='Upload data matrix',
                   fg='white',
-                  font=controller.title_font,
-                  bg=parent['bg'],
+                  font=self.controller.title_font,
+                  bg=self.bg,
                   height=3,
                   width=20,
                   command=self.upload_data_folder,
@@ -83,8 +86,8 @@ def __init__(self, parent, controller, config: dict):
         tk.Button(frame3,
                   text='Upload data path',
                   fg='white',
-                  font=controller.title_font,
-                  bg=parent['bg'],
+                  font=self.controller.title_font,
+                  bg=self.bg,
                   height=3,
                   width=20,
                   command=self.upload_data_path,
@@ -93,8 +96,8 @@ def __init__(self, parent, controller, config: dict):
                                              text='Optional',
                                              pady=10,
                                              padx=10,
-                                             font=controller.title_font,
-                                             bg=parent['bg'],
+                                             font=self.controller.title_font,
+                                             bg=self.bg,
                                              fg='white')
         self.controller.Datalabel.grid(column=2,
                                        row=12)
@@ -102,8 +105,8 @@ def __init__(self, parent, controller, config: dict):
         self.interactButton = tk.Button(frame3,
                                         text='Interact with canvas',
                                         fg='white',
-                                        font=controller.title_font,
-                                        bg=parent['bg'],
+                                        font=self.controller.title_font,
+                                        bg=self.bg,
                                         height=3,
                                         width=20,
                                         # state=tk.DISABLED,
@@ -115,8 +118,8 @@ def __init__(self, parent, controller, config: dict):
         self. uploadButton = tk.Button(frame3,
                                        text='Upload XML file',
                                        fg='white',
-                                       font=controller.title_font,
-                                       bg=parent['bg'],
+                                       font=self.controller.title_font,
+                                       bg=self.bg,
                                        height=3,
                                        width=20,
                                     #    state=tk.DISABLED,
@@ -128,16 +131,16 @@ def __init__(self, parent, controller, config: dict):
                                             text='Incomplete',
                                             pady=10,
                                             padx=10,
-                                            font=controller.title_font,
-                                            bg=parent['bg'],
+                                            font=self.controller.title_font,
+                                            bg=self.bg,
                                             fg='white')
         self.controller.XMLlabel.grid(column=2,
                                       row=13)
         self.PluginButton = tk.Button(frame3,
                                       text='Modules plugins',
                                       fg='white',
-                                      font=controller.title_font,
-                                      bg=parent['bg'],
+                                      font=self.controller.title_font,
+                                      bg=self.bg,
                                       height=3,
                                       width=20,
                                       state=tk.DISABLED,
@@ -148,8 +151,8 @@ def __init__(self, parent, controller, config: dict):
         self.RunButton = tk.Button(frame3,
                                    text='Run Pipeline',
                                    fg='white',
-                                   font=controller.title_font,
-                                   bg=parent['bg'],
+                                   font=self.controller.title_font,
+                                   bg=self.bg,
                                    height=3,
                                    width=20,
                                    state=tk.DISABLED,
@@ -317,7 +320,7 @@ def start_dataloader(self):
                         # Infers by default, should it be None?
                         data[variable] = pd.read_csv(filename)
                         isVar[i] = 1
-                        self.controller.s.append_input_data(variable, self.rel_path(filename))
+                        self.controller.s.append_input_data(variable, rel_to_abs(filename))
                         if i == 0:
                             self.controller.Data.set(True)
                         if any(isVar[1::2]) and (
@@ -333,21 +336,6 @@ def start_dataloader(self):
             tk.messagebox.showwarning(title='Error - X not specified',
                                       message='You need to specify X before proceeding.')
 
-    def rel_path(self, path):
-        """ Returns relative path if available. """
-        if path[0].lower() == os.getcwd()[0].lower():
-            #Same drive
-            _folder = os.path.relpath(path, os.getcwd())
-            if _folder[:2] == '..':
-                # Absolute path
-                return path
-            else:
-                # Relative path
-                return os.path.join('.',_folder)
-        else: 
-            #Different drive -> Absolute path
-            return path
-
     def upload_data_path(self):
         """ Stores the directory containing the data that will be later loaded 
         """
@@ -355,7 +343,7 @@ def upload_data_path(self):
                               title='Select a folder',
                               mustexist=True)
         if folder is not None and len(folder) > 0:
-            self.controller.s.append_input_data('X', self.rel_path(folder))
+            self.controller.s.append_input_data('X', rel_to_abs(folder))
         
     def upload_data_folder(self):
         """ Stores the directory containing the data that will be later loaded 
diff --git a/aidesign/utils/plugins/__init__.py b/src/vai_lab/utils/plugins/__init__.py
similarity index 100%
rename from aidesign/utils/plugins/__init__.py
rename to src/vai_lab/utils/plugins/__init__.py
diff --git a/aidesign/utils/plugins/aidCanvas.py b/src/vai_lab/utils/plugins/aidCanvas.py
similarity index 96%
rename from aidesign/utils/plugins/aidCanvas.py
rename to src/vai_lab/utils/plugins/aidCanvas.py
index f4030bd7..ced0e04a 100644
--- a/aidesign/utils/plugins/aidCanvas.py
+++ b/src/vai_lab/utils/plugins/aidCanvas.py
@@ -1,5 +1,5 @@
-from aidesign.Data.xml_handler import XML_handler
-from aidesign._types import DictT
+from vai_lab.Data.xml_handler import XML_handler
+from vai_lab._types import DictT
 
 import os
 import numpy as np
@@ -10,7 +10,7 @@
 import tkinter as tk
 from tkinter import messagebox
 from tkinter.filedialog import asksaveasfile, askopenfile, askopenfilename
-from aidesign._import_helper import get_lib_parent_dir
+from vai_lab._import_helper import get_lib_parent_dir
 
 
 _PLUGIN_READABLE_NAMES = {"aid_canvas": "default",
@@ -34,6 +34,21 @@ def __init__(self, parent, controller, config: dict):
         self.bg = parent['bg']
         self.controller = controller
 
+        self.canvas_startxy: List[Tuple] = []
+        self.out_data = pd.DataFrame()
+        self.connections: DictT = {}
+        self.modules = 0
+        self.module_list: List[str] = []
+        self.module_names: List[str] = []
+
+        # Create module
+        self.w, self.h = 100, 50
+        self.cr = 4
+
+        if not self.controller._debug:
+            self._setup_frame()
+
+    def _setup_frame(self):
         script_dir = get_lib_parent_dir()
         self.tk.call('wm', 'iconphoto', self.controller._w, ImageTk.PhotoImage(
             file=os.path.join(os.path.join(
@@ -58,7 +73,7 @@ def __init__(self, parent, controller, config: dict):
         frame3 = tk.Frame(self, bg=self.bg)
         frame4 = tk.Frame(self, bg=self.bg)
 
-        self.my_label = tk.Label(frame2, image=self.my_img1, bg=parent['bg'], anchor = 'center')
+        self.my_label = tk.Label(frame2, image=self.my_img1, bg=self.bg, anchor = 'center')
         self.my_label.grid(column=5, row=0, padx=(10, 10), pady=(10, 10), sticky="nwse")
 
         # Create canvas
@@ -69,17 +84,6 @@ def __init__(self, parent, controller, config: dict):
         #     height=self.height, background="white")
         self.canvas.pack(fill=tk.BOTH, expand=True, padx=(10, 0), pady=10)
 
-        self.canvas_startxy: List[Tuple] = []
-        self.out_data = pd.DataFrame()
-        self.connections: DictT = {}
-        self.modules = 0
-        self.module_list: List[str] = []
-        self.module_names: List[str] = []
-
-        # Create module
-        self.w, self.h = 100, 50
-        self.cr = 4
-
         # Initialiser module
         self.add_module('Initialiser', self.width/2, self.h, ini=True)
         self.add_module('Output', self.width/2, self.height - self.h, out=True)
@@ -96,69 +100,70 @@ def __init__(self, parent, controller, config: dict):
 
         # for m, module in enumerate(modules):
         tk.Button(
-            frame4, text='Data processing', fg='white', bg=parent['bg'],
+            frame4, text='Data processing', fg='white', bg=self.bg,
             height=3, width=25, font=self.controller.pages_font,
             command=lambda: self.add_module('Data Processing',
                                             self.width/2,
                                             self.height/2)
         ).grid(column=5, row=1, padx=(10, 10), sticky="news")
         tk.Button(
-            frame4, text='Modelling', fg='white', bg=parent['bg'],
+            frame4, text='Modelling', fg='white', bg=self.bg,
             height=3, width=25, font=self.controller.pages_font,
             command=lambda: self.add_module('Modelling',
                                             self.width/2,
                                             self.height/2)
         ).grid(column=5, row=2, padx=(10, 10), sticky="news")
         tk.Button(
-            frame4, text='Decision making', fg='white', bg=parent['bg'],
+            frame4, text='Decision making', fg='white', bg=self.bg,
             height=3, width=25, font=self.controller.pages_font,
             command=lambda: self.add_module('Decision Making',
                                             self.width/2,
                                             self.height/2)
         ).grid(column=5, row=3, padx=(10, 10), sticky="news")
         tk.Button(
-            frame4, text='User Interaction', fg='white', bg=parent['bg'],
+            frame4, text='User Interaction', fg='white', bg=self.bg,
             height=3, width=25, font=self.controller.pages_font,
             command=lambda: self.add_module('User Interaction',
                                             self.width/2,
                                             self.height/2)
         ).grid(column=5, row=4, padx=(10, 10), sticky="news")
         tk.Button(
-            frame4, text = 'Input data', fg = 'white', bg = parent['bg'],
+            frame4, text = 'Input data', fg = 'white', bg = self.bg,
             height = 3, width = 25, font = self.controller.pages_font,
             command = lambda: self.add_module('Input Data', 
                                               self.width/2, 
                                               self.height/2)
-            ).grid(column = 5, row = 5, padx=(0,10), sticky="news")        
+            ).grid(column = 5, row = 5, padx=(10,10), sticky="news")        
         tk.Button(
-            frame4, text = 'Data Storage', fg = 'white', bg = parent['bg'],
+            frame4, text = 'Data Storage', fg = 'white', bg = self.bg,
             height = 3, width = 25, font = self.controller.pages_font,
             command = lambda: self.add_module('Data Storage', 
                                               self.width/2, 
                                               self.height/2)
-            ).grid(column = 5, row = 6, padx=(0,10), sticky="news")
+            ).grid(column = 5, row = 6, padx=(10,10), sticky="news")
         tk.Button(
-            frame4, text = 'Delete selection', fg = 'white', bg = parent['bg'],
+            frame4, text = 'Delete selection', fg = 'white', bg = self.bg,
             height = 3, width = 25, font = self.controller.pages_font,
             command = self.delete_sel).grid(column = 5, row = 7, sticky="news"
-                                            , padx=(0,10), pady=(0,10))
+                                            , padx=(10,10), pady=(0,10))
         
+        ww = int(self.width/40)
         tk.Button(
-            frame3, text='Upload', fg='white', bg=parent['bg'],
-            height=3, width=15, font=self.controller.pages_font,
+            frame3, text='Upload', fg='white', bg=self.bg,
+            height=3, width=ww, font=self.controller.pages_font,
             command=self.upload).grid(column=0, row=10, sticky="news",
                                       padx=(10, 0), pady=(0, 10))
         tk.Button(
-            frame3, text='Save', fg='white', bg=parent['bg'],
-            height=3, width=15, font=self.controller.pages_font,
+            frame3, text='Save', fg='white', bg=self.bg,
+            height=3, width=ww, font=self.controller.pages_font,
             command=self.save_file).grid(column=1, row=10, sticky="news", pady=(0, 10))
         tk.Button(
-            frame3, text='Reset', fg='white', bg=parent['bg'],
-            height=3, width=15, font=self.controller.pages_font,
+            frame3, text='Reset', fg='white', bg=self.bg,
+            height=3, width=ww, font=self.controller.pages_font,
             command=self.reset).grid(column=2, row=10, sticky="news", pady=(0, 10))
         tk.Button(
-            frame3, text='Done', fg='white', bg=parent['bg'],
-            height=3, width=15, font=self.controller.pages_font,
+            frame3, text='Done', fg='white', bg=self.bg,
+            height=3, width=ww, font=self.controller.pages_font,
             command=self.check_quit).grid(column=3, row=10, sticky="news", pady=(0, 10))
 
         self.save_path = ''
@@ -169,7 +174,7 @@ def __init__(self, parent, controller, config: dict):
         frame3.grid(column=0, row=2, sticky="swe")
         frame4.grid(column=1, row=1, sticky="nse")
         
-        frame3.grid_columnconfigure(4, weight=1)
+        frame3.grid_columnconfigure(tuple(range(4)), weight=1)
         frame4.grid_rowconfigure(7, weight=1)
 
     def class_list(self, value):
diff --git a/aidesign/utils/plugins/dataLoader.py b/src/vai_lab/utils/plugins/dataLoader.py
similarity index 99%
rename from aidesign/utils/plugins/dataLoader.py
rename to src/vai_lab/utils/plugins/dataLoader.py
index c940b3c4..bdff82a1 100644
--- a/aidesign/utils/plugins/dataLoader.py
+++ b/src/vai_lab/utils/plugins/dataLoader.py
@@ -3,7 +3,7 @@
 from PIL import ImageTk
 from tkinter import ttk
 import numpy as np
-from aidesign._import_helper import get_lib_parent_dir
+from vai_lab._import_helper import get_lib_parent_dir
 # from ttkwidgets import CheckboxTreeview
 # from sys import platform
 
diff --git a/aidesign/utils/plugins/pluginCanvas.py b/src/vai_lab/utils/plugins/pluginCanvas.py
similarity index 93%
rename from aidesign/utils/plugins/pluginCanvas.py
rename to src/vai_lab/utils/plugins/pluginCanvas.py
index 8ec8788e..5eb92fae 100644
--- a/aidesign/utils/plugins/pluginCanvas.py
+++ b/src/vai_lab/utils/plugins/pluginCanvas.py
@@ -1,6 +1,6 @@
-from aidesign.Data.xml_handler import XML_handler
-from aidesign._plugin_helpers import PluginSpecs
-from aidesign._import_helper import get_lib_parent_dir
+from vai_lab.Data.xml_handler import XML_handler
+from vai_lab._plugin_helpers import PluginSpecs
+from vai_lab._import_helper import get_lib_parent_dir
 
 import os
 import numpy as np
@@ -32,7 +32,19 @@ def __init__(self, parent, controller, config: dict):
         self.parent = parent
         self.controller = controller
         self.s = XML_handler()
+        self.m: int
+        self.w, self.h = 100, 50
+        self.cr = 4
+        self.id_done = [0, 1]
+        self.id_mod:List[int] = [0, 1]
+        self.out_data = pd.DataFrame()
+        self.plugin: Dict[int, tk.StringVar] = {}
+        self.dataType: Dict[int, tk.StringVar] = {}
+        self.allWeHearIs: List[tk.Radiobutton] = []
+        if not self.controller._debug:
+            self._setup_frame()
 
+    def _setup_frame(self):
         script_dir = get_lib_parent_dir()
         self.tk.call('wm', 'iconphoto', self.controller._w, ImageTk.PhotoImage(
             file=os.path.join(os.path.join(
@@ -57,16 +69,8 @@ def __init__(self, parent, controller, config: dict):
                                 height=self.height, background="white")
         self.canvas.pack(fill=tk.BOTH, expand=True, padx=(10, 0), pady=10)
 
-        self.m: int
-        self.w, self.h = 100, 50
-        self.cr = 4
         self.canvas.bind('<Button-1>', self.on_click)
-        self.id_done = [0, 1]
-        self.id_mod:List[int] = [0, 1]
-        self.out_data = pd.DataFrame()
-        self.plugin: Dict[int, tk.StringVar] = {}
-        self.dataType: Dict[int, tk.StringVar] = {}
-        self.allWeHearIs: List[tk.Radiobutton] = []
+        
 
         self.my_label = tk.Label(self.frame2,
                                  text='',
@@ -91,14 +95,15 @@ def __init__(self, parent, controller, config: dict):
                          'Assets',
                          'forw_arrow.png')).resize((140, 60)))
 
+        ww = int(self.width/40)
         tk.Button(
-            frame3, text='Load Pipeline', fg='white', bg=parent['bg'],
-            height=3, width=15, font=self.controller.pages_font,
+            frame3, text='Load Pipeline', fg='white', bg=self.bg,
+            height=3, width=ww, font=self.controller.pages_font,
             command=self.upload).grid(column=0, row=0, sticky="news",
                                       padx=(10, 0), pady=(0, 10))
         tk.Button(
-            frame3, text='Back to main', fg='white', bg=parent['bg'],
-            height=3, width=15, font=self.controller.pages_font,
+            frame3, text='Back to main', fg='white', bg=self.bg,
+            height=3, width=ww, font=self.controller.pages_font,
             command=self.check_quit).grid(column=1, row=0, sticky="news", pady=(0, 10))
 
         self.frame_canvas = tk.Canvas(
@@ -113,15 +118,16 @@ def __init__(self, parent, controller, config: dict):
         self.save_path = ''
         self.saved = True
         frame1.grid(column=0, row=0, rowspan=5, sticky="nsew")
-        self.frame2.grid(column=1, row=0, sticky="new")
+        self.frame2.grid(column=1, row=0, sticky="new", padx=(10, 0))
         frame3.grid(column=0, row=5, sticky="nswe")
-        self.frame4.grid(column=1, row=5, sticky="sew")
+        self.frame4.grid(column=1, row=5, sticky="nsew")
 
         # self.frame2.grid_columnconfigure(tuple(range(2)), weight=1)
         self.frame2.grid_columnconfigure(1, weight=1)
-        # frame3.grid_columnconfigure(tuple(range(2)), weight=2)
         frame3.grid_columnconfigure(tuple(range(2)), weight=2)
+        # frame3.grid_columnconfigure(2, weight=2)
         self.frame4.grid_columnconfigure(tuple(range(2)), weight=2)
+        # self.frame4.grid_columnconfigure(2, weight=2)
 
     def class_list(self, value):
         """ Temporary fix """
@@ -223,6 +229,8 @@ def select(self, x: float, y: float):
                 self.my_label.config(text='')
                 for widget in self.allWeHearIs:
                     widget.grid_remove()
+                for widget in self.radio_label:
+                    widget.grid_remove()
                 if hasattr(self, 'button_forw'):
                     self.button_back.config(image=self.back_img, bg=self.bg,
                                             state=tk.DISABLED, text='')
@@ -287,21 +295,21 @@ def display_buttons(self):
             self.framexx = []
         framexx_name = np.array([])
         framexx_i = []
+        self.radio_label = []
         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):
             self.framexx.append(tk.Frame(self.framep, bg=self.bg))
             framexx_name = np.append(
                 framexx_name, t if t is not None else 'other')
-            print(framexx_name)
             framexx_i.append(0)
-            label = tk.Label(self.framexx[-1],
+            self.radio_label.append(tk.Label(self.framexx[-1],
                              text=framexx_name[-1].capitalize(),
                              pady=10,
                              font=self.controller.title_font,
                              bg=self.bg,
-                             fg='white')
-            label.grid(column=0,
+                             fg='white'))
+            self.radio_label[-1].grid(column=0,
                        row=0, padx=(10, 0), sticky='nw')
             self.framexx[-1].grid(column=0, row=i, sticky="nsew")
         unsupervised = ['clustering', 'RL']
@@ -357,7 +365,6 @@ def optionsWindow(self):
             # 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 = []
 
             style = ttk.Style()
             style.configure(
@@ -373,14 +380,12 @@ def optionsWindow(self):
             frame2 = tk.Frame(self.newWindow, bg='green')
             self.r = 1
             self.create_treeView(frame2)
-            self.fill_treeview(frame2, self.req_settings, self.opt_settings)
+            self.fill_treeview(self.req_settings, self.opt_settings)
             frame2.grid(column=0, row=1, sticky="nswe", pady=10, padx=10)
     
             frame2.grid_rowconfigure(tuple(range(self.r)), weight=1)
             frame2.grid_columnconfigure(tuple(range(2)), weight=1)
 
-            # if len(self.entry) > 0:
-            #     self.entry[0].focus()
             self.finishButton = tk.Button(
                 frame4, text='Finish', command=self.removewindow)
             self.finishButton.grid(
@@ -391,22 +396,19 @@ def optionsWindow(self):
 
             frame1.grid(column=0, row=0, sticky="ew")
             frame4.grid(column=0, row=2, sticky="se")
-            # self.newWindow.grid_rowconfigure(0, weight=1)
             self.newWindow.grid_rowconfigure(1, weight=2)
             self.newWindow.grid_columnconfigure(0, weight=1)
 
-    def create_treeView(self, frame):
+    def create_treeView(self, tree_frame):
         """ Function to create a new tree view in the given frame
 
         Parameters
         ----------
-        frame : tk.Frame
-                frame where the tree view will be included
+        tree_frame : tk.Frame
+                    frame where the tree view will be included
         key : str
               key for the tree dictionary
         """
-        tree_frame = frame #tk.Frame(frame)
-        # tree_frame.grid(row=0, column=0, sticky="nsew", pady=10, padx=10)
 
         tree_scrollx = tk.Scrollbar(tree_frame, orient='horizontal')
         tree_scrollx.pack(side=tk.BOTTOM, fill=tk.X)
@@ -425,7 +427,6 @@ def create_treeView(self, frame):
         self.tree['columns'] = columns_names
 
         # Format columns
-        # self.tree['show'] = 'headings'
         self.tree.column("#0", width=20,
                         minwidth=0, stretch=tk.NO)
         for n, cl in enumerate(columns_names):
@@ -443,19 +444,6 @@ def create_treeView(self, frame):
                                 background='#E8E8E8')
         self.tree.tag_configure('func', foreground='black',
                                 background='#DFDFDF')
-
-        # Add data
-        # for n, sample in enumerate(self.out_data):
-        #     if n % 2 == 0:
-        #         self.tree.insert(parent='', index='end', iid=n, text=n+1,
-        #                         values=tuple(sample.astype(int)), tags=('even',))
-        #     else:
-        #         self.tree.insert(parent='', index='end', iid=n, text=n+1,
-        #                         values=tuple(sample.astype(int)), tags=('odd',))
-
-        # Select the current row
-        # self.tree.selection_set(str(int(0)))
-
         # Define double-click on row action
         self.tree.bind("<Double-1>", self.OnDoubleClick)
 
@@ -510,10 +498,11 @@ def on_return(self, event):
         self.entry.destroy()
         self.saved = False
 
-    def fill_treeview(self, frame, req_settings, opt_settings, parent = ''):
+    def fill_treeview(self, req_settings, opt_settings, parent = ''):
         """ Adds an entry for each setting. Displays it in the specified row.
-        :param frame: tkinter frame type of frame
-        :param settings: dict type of plugin setting options
+        :param req_settings: dict type of plugin required setting options
+        :param opt_settings: dict type of plugin optional setting options
+        :param parent: string type of parent name
         """
         self.tree.insert(parent=parent, index='end', iid=parent+'_req', text='',
             values=tuple(['Required settings', '', '']), tags=('type',))
@@ -537,24 +526,35 @@ def fill_treeview(self, frame, req_settings, opt_settings, parent = ''):
     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 c, child in enumerate(self.tree.get_children()):
-            val = self.tree.item(child)["values"]
-            tag = self.tree.item(child)["tags"][0]
-            if val[0] == 'Data':
-                if val[2] == 'Choose X or Y' or len(val[2]) == 0:
-                    self.updateSettings(tag, val[0], 'X')
-                else:
-                    self.updateSettings(tag, val[0], val[2])
-            else:
-                if val[2] == 'default' or len(str(val[2])) == 0:
-                    self.updateSettings(tag, val[0])
-                else:
-                    self.updateSettings(tag, val[0], val[2])
+        children = self.get_all_children()
+        for child in children:
+            tag = self.tree.item(child)["tags"][0]            
+            if tag in ['req', 'opt']:
+                val = self.tree.item(child)["values"]
+                self.settingOptions(tag, val) 
         self.newWindow.destroy()
         self.newWindow = None
         self.focus()
+    
+    def get_all_children(self, item=""):
+        """ Iterates over the treeview to get all childer """
+        children = self.tree.get_children(item)
+        for child in children:
+            children += self.get_all_children(child)
+        return children
+
+    def settingOptions(self, tag, val):
+        """ Identifies how the data should be stored """
+        if val[0] == 'Data':
+            if val[2] == 'Choose X or Y' or len(val[2]) == 0:
+                self.updateSettings(tag, val[0], 'X')
+            else:
+                self.updateSettings(tag, val[0], val[2])
+        else:
+            if val[2] == 'default' or len(str(val[2])) == 0:
+                self.updateSettings(tag, val[0])
+            else:
+                self.updateSettings(tag, val[0], val[2])
 
     def updateSettings(self, tag, key, value = None):
         """ Return the selected settings 
@@ -565,7 +565,10 @@ def updateSettings(self, tag, key, value = None):
               tag for the settings
         """
         if tag == 'req':
-            self.req_settings[key] = value
+            if value is not None or self.req_settings[key] != value:
+                self.req_settings[key] = value
+            else:
+                self.req_settings.pop(key, None)
         elif tag == 'opt':
             if value is not None or self.opt_settings[key] != value:
                 self.opt_settings[key] = value
@@ -729,8 +732,8 @@ def upload(self):
                 yout + self.cr,
                 xins + self.cr,
                 yins + self.cr,
-                fill="red", width = 2,
-                arrow = tk.LAST, arrowshape = (12,10,5),
+                fill = "red",  width = 2,
+                arrow = tk.LAST, arrowshape = (12,10,5), 
                 tags=('o'+str(parent_id),
                       'o'+str(1), modout['coordinates'][2][connect[p]]))
             self.out_data.iloc[int(parent_id)][1] = 1
@@ -832,8 +835,8 @@ def draw_connection(self, modules):
                                     yout + self.cr,
                                     xins + self.cr,
                                     yins + self.cr,
-                                    fill = "red", width = 2,
-                                    arrow = tk.LAST, arrowshape = (12,10,5),
+                                    fill = "red",  width = 2,
+                                    arrow = tk.LAST, arrowshape = (12,10,5), 
                                     tags = ('o'+str(parent_id),
                                           # 'o'+str(self.id_mod[-1]), 
                                           modules[key]['coordinates'][2][connect[p]]))
diff --git a/aidesign/utils/plugins/progressTracker.py b/src/vai_lab/utils/plugins/progressTracker.py
similarity index 51%
rename from aidesign/utils/plugins/progressTracker.py
rename to src/vai_lab/utils/plugins/progressTracker.py
index 8888dcd4..4dc7a6a0 100644
--- a/aidesign/utils/plugins/progressTracker.py
+++ b/src/vai_lab/utils/plugins/progressTracker.py
@@ -1,13 +1,13 @@
 import os
 import tkinter as tk
 from typing import Dict
+from tkinter import ttk
 
 import numpy as np
 import pandas as pd
 from PIL import Image, ImageTk
-from aidesign._plugin_helpers import PluginSpecs
-from aidesign.Data.xml_handler import XML_handler
-from aidesign._import_helper import get_lib_parent_dir
+from vai_lab.Data.xml_handler import XML_handler
+from vai_lab._import_helper import get_lib_parent_dir
 
 _PLUGIN_READABLE_NAMES = {"progress_tracker":"default",
                             "progressTracker":"alias",
@@ -27,27 +27,27 @@ def __init__(self, parent, controller, config: dict):
         super().__init__(parent, bg=parent['bg'])
         self.bg = parent['bg']
         self.controller = controller
-        self.s = XML_handler()
+        self.xml_handler = XML_handler()
         self.controller.title('Progress Tracker')
         
-        script_dir = os.path.dirname(__file__)
-        self.tk.call('wm', 'iconphoto', self.controller._w, ImageTk.PhotoImage(
-            file=os.path.join(os.path.join(
-                get_lib_parent_dir(),
-                'utils',
-                'resources',
-                'Assets',
-                'VAILabsIcon.ico'))))
+        if not self.controller._debug:
+            self.tk.call('wm', 'iconphoto', self.controller._w, ImageTk.PhotoImage(
+                file=os.path.join(os.path.join(
+                    get_lib_parent_dir(),
+                    'utils',
+                    'resources',
+                    'Assets',
+                    'VAILabsIcon.ico'))))
         self.grid_columnconfigure(0, weight=1)
 
-        frame1 = tk.Frame(self, bg=self.bg)
-        self.frame2 = tk.Frame(self, bg=self.bg)
+        self.frame1 = tk.Frame(self, bg=self.bg)
+        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 = 700, 700
-        self.canvas = tk.Canvas(frame1, width=self.width,
+        self.canvas = tk.Canvas(self.frame1, width=self.width,
                                 height=self.height, background="white")
         self.canvas.pack(fill=tk.BOTH, expand=True, padx=(10, 0), pady=10)
 
@@ -61,11 +61,9 @@ def __init__(self, parent, controller, config: dict):
         self.canvas.bind('<Button-1>', self.on_click)
         self.dataType: Dict = {}
         
-        self.upload()
-
         sec = 5
         self.click = False
-        self.my_label = tk.Label(self.frame2, 
+        self.my_label = tk.Label(frame2, 
                     text = 
                     'This window will get\nclosed after '+str(sec)+' seconds\nunless you click on the canvas.',
                     pady= 10,
@@ -75,6 +73,9 @@ def __init__(self, parent, controller, config: dict):
                     anchor = tk.CENTER)
         self.my_label.grid(column = 0,
                             row = 0, columnspan = 2, padx=10)
+        
+        self.upload()
+
         tk.Button(
             self.frame4, text = 'Next step', fg = 'white', bg = parent['bg'], 
             height = 3, width = 15, font = self.controller.pages_font, 
@@ -84,11 +85,11 @@ def __init__(self, parent, controller, config: dict):
             height = 3, width = 15, font = self.controller.pages_font, 
             command = self.terminate).grid(column = 0, row = 26, sticky="news", pady=(0,10))
         
-        self.controller._append_to_output('close', False)
+        self.controller._append_to_output('terminate', False)
         self.save_path = ''
         self.saved = True
-        frame1.grid(column=0, row=0, sticky="nsew")
-        self.frame2.grid(column=1, row=0, sticky="new")
+        self.frame1.grid(column=0, row=0, sticky="nsew")
+        frame2.grid(column=1, row=0, sticky="new")
         frame3.grid(column=0, row=1, sticky="swe")
         self.frame4.grid(column=1, row=1, sticky="sew")
         
@@ -97,10 +98,6 @@ def __init__(self, parent, controller, config: dict):
 
         self.controller.after(sec*1000,lambda:self.check_click())
 
-    def class_list(self,value):
-        """ Temporary fix """
-        return value
-
     def on_click(self, event):
         """ Passes the mouse click coordinates to the select function."""
         self.click = True
@@ -113,7 +110,7 @@ def check_click(self):
 
     def terminate(self):
         """ Terminates window and pipeline. """
-        self.controller._append_to_output('close', True)
+        self.controller._append_to_output('terminate', True)
         self.check_quit()
         
     def select(self, x: float, y:float):
@@ -184,8 +181,8 @@ def add_module(self,boxName: str,x: float,y:float,ini = False,out = False):
             tag = ('n0',)
         text_w = self.controller.pages_font.measure(boxName+'-00') + 10
         # Check module status
-        if 'start' in self.controller.status[boxName]:
-            if 'finish' in self.controller.status[boxName]:
+        if 'start' in self.controller._status[boxName]:
+            if 'finish' in self.controller._status[boxName]:
                 colour = '#46da63'
             else:
                 colour = '#dbaa21'
@@ -213,9 +210,9 @@ def add_module(self,boxName: str,x: float,y:float,ini = False,out = False):
             justify = tk.CENTER)
         
         CanvasTooltip(self.canvas, self.canvas.find_withtag('p'+str(self.modules))[0], 
-                           text = 'Model progress status:\n'+self.pretty_status(self.controller.status[boxName])) # Link box
+                           text = 'Model progress status:\n'+self.pretty_status(self.controller._status[boxName])) # Link box
         CanvasTooltip(self.canvas, self.canvas.find_withtag('t'+str(self.modules))[0], 
-                           text = 'Model progress status:\n'+self.pretty_status(self.controller.status[boxName])) # Link text
+                           text = 'Model progress status:\n'+self.pretty_status(self.controller._status[boxName])) # Link text
         
         if not out:
             self.canvas.create_oval(
@@ -259,128 +256,253 @@ def add_module(self,boxName: str,x: float,y:float,ini = False,out = False):
         self.canvas_startxy.append((x, y))
         self.connections[self.modules] = {}
         self.module_out(boxName)
-        self.module_list.append(boxName)
+        # self.module_list.append(boxName)
         self.modules += 1
         
     def optionsWindow(self):
         """ Function to create a new window displaying the available options 
         of the selected plugin."""
         
-        self.module = np.array(self.module_list)[self.m == np.array(self.id_mod)][0]
-        self.plugin = self.s.loaded_modules[self.module]['plugin']['plugin_name']
-        module_type = self.s.loaded_modules[self.module]['module_type']
-        ps = PluginSpecs()
-        self.opt_settings = ps.optional_settings[module_type][self.plugin]
-        self.req_settings = ps.required_settings[module_type][self.plugin]
+        mod_idx = np.where(self.m == np.array(self.id_mod))[0][0]
+        self.module = np.array(self.module_list)[mod_idx]
+        self.plugin = np.array(self.plugin_list)[mod_idx]
+        module_type = np.array(self.type_list)[mod_idx]
+        
+        self.opt_settings = self.controller._avail_plugins.optional_settings[module_type][self.plugin]
+        self.req_settings = self.controller._avail_plugins.required_settings[module_type][self.plugin]
         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+' 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'))))
+            script_dir = get_lib_parent_dir()
+            self.tk.call('wm', 'iconphoto', self.newWindow, ImageTk.PhotoImage(
+                file=os.path.join(os.path.join(
+                    script_dir,
+                    'utils',
+                    'resources',
+                    'Assets',
+                    'VAILabsIcon.ico'))))
             self.newWindow.geometry("350x400")
-
+            self.raise_above_all(self.newWindow)
             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+" 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
-            if len(self.entry) > 0:
-                self.entry[0].focus()
+
+            style = ttk.Style()
+            style.configure(
+                "Treeview", background='white', foreground='white',
+                rowheight=25, fieldbackground='white',
+                # font=self.controller.pages_font
+                )
+            style.configure("Treeview.Heading", 
+            # font=self.controller.pages_font
+            )
+            style.map('Treeview', background=[('selected', 'grey')])
+
+            frame2 = tk.Frame(self.newWindow, bg='green')
+            self.r = 1
+            self.create_treeView(frame2, ['Name', 'Type', 'Value'])
+            self.fill_treeview(frame2, self.req_settings, self.opt_settings)
+            frame2.grid(column=0, row=1, sticky="nswe", pady=10, padx=10)
+
+            frame2.grid_rowconfigure(tuple(range(self.r)), weight=1)
+            frame2.grid_columnconfigure(tuple(range(2)), weight=1)
+
             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())
+                frame4, text='Finish', command=self.removewindow)
+            self.finishButton.grid(
+                column=1, row=0, 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)
+            frame1.grid(column=0, row=0, sticky="ew")
+            frame4.grid(column=0, row=2, sticky="se")
+            self.newWindow.grid_rowconfigure(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
+    def raise_above_all(self, window):
+        window.attributes('-topmost', 1)
+        window.attributes('-topmost', 0)
+
+    def create_treeView(self, tree_frame, columns_names):
+        """ Function to create a new tree view in the given frame
+
+        Parameters
+        ----------
+        tree_frame : tk.Frame
+                    frame where the tree view will be included
+        key : str
+              key for the tree dictionary
+        """
+
+        tree_scrollx = tk.Scrollbar(tree_frame, orient='horizontal')
+        tree_scrollx.pack(side=tk.BOTTOM, fill=tk.X)
+        tree_scrolly = tk.Scrollbar(tree_frame)
+        tree_scrolly.pack(side=tk.RIGHT, fill=tk.Y)
+
+        self.tree = ttk.Treeview(tree_frame,
+                                yscrollcommand=tree_scrolly.set,
+                                xscrollcommand=tree_scrollx.set)
+        self.tree.pack(fill='both', expand=True)
+
+        tree_scrollx.config(command=self.tree.xview)
+        tree_scrolly.config(command=self.tree.yview)
+
+        self.tree['columns'] = columns_names
+
+        # Format columns
+        self.tree.column("#0", width=20,
+                        minwidth=0, stretch=tk.NO)
+        for n, cl in enumerate(columns_names):
+            self.tree.column(
+                cl, width=int(self.controller.pages_font.measure(str(cl)))+20,
+                minwidth=50, anchor=tk.CENTER)
+        # Headings
+        for cl in columns_names:
+            self.tree.heading(cl, text=cl, anchor=tk.CENTER)
+        self.tree.tag_configure('req', foreground='black',
+                                background='#9fc5e8')
+        self.tree.tag_configure('opt', foreground='black',
+                                background='#cfe2f3')
+        self.tree.tag_configure('type', foreground='black',
+                                background='#E8E8E8')
+        self.tree.tag_configure('func', foreground='black',
+                                background='#DFDFDF')
+        # Define double-click on row action
+        self.tree.bind("<Double-1>", self.OnDoubleClick)
+
+    def OnDoubleClick(self, event):
+        """ Executed when a row of the treeview is double clicked.
+        Opens an entry box to edit a cell. """
+
+        self.treerow = self.tree.identify_row(event.y)
+        self.treecol = self.tree.identify_column(event.x)
+        tags = self.tree.item(self.treerow)["tags"]
+        if len(tags) > 0 and tags[0] in ['opt', 'req']:
+            # get column position info
+            x, y, width, height = self.tree.bbox(self.treerow, self.treecol)
+
+            # y-axis offset
+            pady = height // 2
+            # pady = 0
+
+            if hasattr(self, 'entry'):
+                self.entry.destroy()
+
+            self.entry = tk.Entry(self.tree, justify='center')
+
+            if int(self.treecol[1:]) > 0:
+                value = self.tree.item(self.treerow)['values'][int(str(self.treecol[1:]))-1] 
+                value = str(value) if str(value) not in ['default', 'Choose X or Y'] else ''
+                self.entry.insert(0, value)
+                # self.entry['selectbackground'] = '#123456'
+                self.entry['exportselection'] = False
+
+                self.entry.focus_force()
+                self.entry.bind("<Return>", self.on_return)
+                self.entry.bind("<Escape>", lambda *ignore: self.entry.destroy())
+
+                self.entry.place(x=x,
+                                y=y + pady,
+                                anchor=tk.W, width=width)
+
+    def on_return(self, event):
+        """ Executed when the entry is edited and pressed enter.
+        Saves the edited value"""
+
+        val = self.tree.item(self.treerow)['values']
+        val[int(self.treecol[1:])-1] = self.entry.get()
+        if self.entry.get() != '':
+            self.tree.item(self.treerow, values=tuple([val[0], val[1], self.entry.get()]))
+        elif val[2] == '':
+            self.tree.item(self.treerow, values=tuple([val[0], val[1], 'default']))
+        else:
+            self.tree.item(self.treerow, values=val)
+        self.entry.destroy()
+        self.saved = False
+
+    def fill_treeview(self, frame, req_settings, opt_settings, parent = ''):
+        """ Adds an entry for each setting. Displays it in the specified row.
+        :param req_settings: dict type of plugin required setting options
+        :param opt_settings: dict type of plugin optional setting options
+        :param parent: string type of parent name
         """
-        r = 1
-        options = self.s.loaded_modules[self.module]['plugin']['options']
-        for arg, val in settings.items():
-            tk.Label(frame,
-              text = arg).grid(row=r,column=0)
+        self.tree.insert(parent=parent, index='end', iid=parent+'_req', text='',
+            values=tuple(['Required settings', '', '']), tags=('type',))
+        self.r+=1
+        for arg, val in req_settings.items():
             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)
+                self.tree.insert(parent=parent+'_req', index='end', iid=str(self.r), text='',
+                    values=tuple([arg, val, 'Choose X or Y']), tags=('req',))
             else:
-                self.entry.append(tk.Entry(frame))
-                value = options[arg] if arg in options else 'default'
-                self.entry[-1].insert(0, str(value))
-                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)
+                self.tree.insert(parent=parent+'_req', index='end', iid=str(self.r), text='',
+                                    values=tuple([arg, val, '']), tags=('req',))
+            self.r+=1
+        self.tree.insert(parent=parent, index='end', iid=parent+'_opt', text='',
+            values=tuple(['Optional settings', '', '']), tags=('type',))
+        self.r+=1
+        for arg, val in opt_settings.items():
+            self.tree.insert(parent=parent+'_opt', index='end', iid=str(self.r), text='',
+                                 values=tuple([arg, val, 'default']), tags=('opt',))
+            self.r+=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):
-                if len(ent.get()) == 0 or ent.get() == 'default':
-                    self.req_settings.pop(req_keys[e], None)
-                else:
-                    self.req_settings[req_keys[e]] = ent.get()
-            else:
-                if len(ent.get()) == 0 or ent.get() == 'default':
-                    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()
+        children = self.get_all_children()
+        for child in children:
+            tag = self.tree.item(child)["tags"][0]            
+            if tag in ['req', 'opt']:
+                val = self.tree.item(child)["values"]
+                self.settingOptions(tag, val) 
         self.newWindow.destroy()
         self.newWindow = None
-        self.s.update_plugin_options(self.module,
-                                       {**self.req_settings, **self.opt_settings},
-                                       True)
         self.focus()
+    
+    def get_all_children(self, item=""):
+        """ Iterates over the treeview to get all childer """
+        children = self.tree.get_children(item)
+        for child in children:
+            children += self.get_all_children(child)
+        return children
+
+    def settingOptions(self, tag, val):
+        """ Identifies how the data should be stored """
+        if val[0] == 'Data':
+            if val[2] == 'Choose X or Y' or len(val[2]) == 0:
+                self.updateSettings(tag, val[0], 'X')
+            else:
+                self.updateSettings(tag, val[0], val[2])
+        else:
+            if val[2] == 'default' or len(str(val[2])) == 0:
+                self.updateSettings(tag, val[0])
+            else:
+                self.updateSettings(tag, val[0], val[2])
+
+    def updateSettings(self, tag, key, value = None):
+        """ Return the selected settings 
+
+        Parameters
+        ----------
+        tag : str
+              tag for the settings
+        """
+        if tag == 'req':
+            if value is not None or self.req_settings[key] != value:
+                self.req_settings[key] = value
+            else:
+                self.req_settings.pop(key, None)
+        elif tag == 'opt':
+            if value is not None or self.opt_settings[key] != value:
+                self.opt_settings[key] = value
+            else:
+                self.opt_settings.pop(key, None)
 
     def on_return_entry(self, r):
         """ Changes focus to the next available entry. When no more, focuses 
@@ -392,6 +514,41 @@ def on_return_entry(self, r):
         else:
             self.finishButton.focus()
 
+    def isKey(self, d, k, key_list, cond_list):
+        """ Checks if the elements of a dictionary hava a specific key 
+        : param d: dict type.
+        : param k: string type.      
+        : param key_list: list type with keys that are not loop.
+        : param cond_list: list type with whether k is in a module in key_list.
+        """
+        for key in [key for key, val in d.items() if type(val) == dict]:
+            if d[key]['class'] == 'loop':
+                key_list, cond_list = self.isKey(d[key],k,key_list,cond_list)
+            else:
+                if d[key]['class'].lower() == 'entry_point':
+                    self.init_module = key
+                elif d[key]['class'].lower()  == 'exit_point':
+                    self.out_module = key
+                key_list.append(key)
+                cond_list.append(k in d[key].keys())
+        return key_list, cond_list
+    
+    def allKeysinDict(self, d, k, out_list):
+        """ Stores the elements of a dictionary for a specific key 
+        : param d: dict type.
+        : param k: string type.      
+        : param out_list: list type to store the keys that fulfill the condition.
+        """
+        for key in [key for key, val in d.items() if type(val) == dict]:
+            if d[key]['class'] == 'loop':
+                out_list = self.allKeysinDict(d[key],k,out_list)
+            else:
+                if k in d[key].keys():
+                    out_list.append(d[key][k])
+                else:
+                    out_list.append({})
+        return out_list
+    
     def upload(self):
         """ Opens the XML file that was previously uploaded and places the 
         modules, loops and connections in the canvas."""
@@ -400,39 +557,133 @@ def upload(self):
 
         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)
+        self.xml_handler = XML_handler()
+        self.xml_handler.load_XML(filename)
+        modules = self.xml_handler.loaded_modules
+        self.module_list, self.isCoords = self.isKey(modules, 'coordinates', [], [])
+        self.p_list = self.allKeysinDict(modules, 'plugin', [])
+        self.plugin_list = []
+        for e in self.p_list:
+            if type(e) == dict and 'plugin_name' in e.keys():
+                self.plugin_list.append(e['plugin_name'])
+            else:
+                self.plugin_list.append(0)
+        self.type_list = self.allKeysinDict(modules, 'module_type', [])
+
+        if all(self.isCoords):
+
+            # Place Initialiser and Output
+            self.add_module(self.init_module, self.width/2, self.h, ini=True)
+            self.add_module(self.out_module, self.width/2, self.height - self.h, out=True)
+
+            modout = modules[self.out_module]
+            del modules[self.init_module], modules[self.out_module] # They are generated when resetting
+            self.disp_mod = [self.init_module, self.out_module]
+            self.id_mod = [0, 1]
+            # Reorder to have input and output in first two positions
+            self.plugin_list.insert(1, self.plugin_list.pop(len(self.plugin_list)-1))
+            self.type_list.insert(1, self.type_list.pop(len(self.type_list)-1))
+
+            # Place the modules
+            self.place_modules(modules)
+
+            #Output module
+            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]
+
+        else: # There are no coordinates for some modules.
+            self.my_label.config(text = " ".join(self.my_label.cget('text').split(' ')[:-1] + ['list.']))
+
+            self.update_output(modules)
+            self.id_mod = list(range(len(self.module_list)))
+            self.disp_mod = self.module_list
+            self.canvas.pack_forget()
+            frame_tree = tk.Frame(self.frame1, bg='green')
+            self.create_treeView(frame_tree, ['Module'])
+            self.r=0
+            for i, module in enumerate(self.module_list):
+                self.tree.insert(parent='', index='end', iid=str(self.r), text='',
+                                    values=tuple([module]), tags=('odd' if i%2 == 0 else 'even',))
+                self.r+=1
+            self.tree.column(
+                'Module', width=int(self.controller.pages_font.measure(str(max(self.module_list, key=len))))+20,
+                minwidth=50, anchor=tk.CENTER)
+            self.tree.tag_configure('odd', foreground='black',
+                                    background='#9fc5e8')
+            self.tree.tag_configure('even', foreground='black',
+                                    background='#cfe2f3')
+
+            frame_tree.grid(column=0, row=1, sticky="nswe", pady=10, padx=10)
+            frame_tree.grid_rowconfigure(tuple(range(len(self.module_list))), weight=1)
+            frame_tree.grid_columnconfigure(tuple(range(2)), weight=1)
+            self.tree.bind('<Button-1>', self.on_click_noCanvas)
+
+    def on_click_noCanvas(self, event):
+        """ Passes the mouse click coordinates to the select function when there is no Canvas."""
+        self.click = True
+        self.select_noCanvas(event.x, event.y)
+        
+    def select_noCanvas(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.treerow = self.tree.identify_row(y)
+        self.treecol = self.tree.identify_column(x)
+        tags = self.tree.item(self.treerow)["tags"]
+
+        if len(tags) > 0:
+            self.m = int(self.treerow)
+            if int(self.m) > 0 and int(self.m) < len(self.module_list)-1:
+                self.optionsWindow()
+
+    def update_output(self, modules: dict):
+        """Update the output.
+        :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'])))))
+                self.loops.append({'type': modules[key]['type'],
+                                   'condition': modules[key]['condition'],
+                                   'mod': []})
+                self.update_output(modules[key])
+            else:
+                self.module_out(modules[key]['name'])
+                # Connect modules
+                for p, parent in enumerate(modules[key]['parents']):
+                    if not (parent[:4] == 'loop'):
+                        self.out_data.loc[parent].loc[modules[key]['name']] = 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)
 
     def place_modules(self, modules: dict):
         """Places the modules in the dictionary in the canvas.
@@ -521,11 +772,10 @@ def reset(self):
         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.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 = []
@@ -672,4 +922,4 @@ def hide(self):
 
 if __name__ == "__main__":
     app = progressTracker()
-    app.mainloop()
+    app.mainloop()
\ No newline at end of file
diff --git a/aidesign/utils/plugins/startpage.py b/src/vai_lab/utils/plugins/startpage.py
similarity index 99%
rename from aidesign/utils/plugins/startpage.py
rename to src/vai_lab/utils/plugins/startpage.py
index c71f65e5..21feb79b 100644
--- a/aidesign/utils/plugins/startpage.py
+++ b/src/vai_lab/utils/plugins/startpage.py
@@ -1,4 +1,4 @@
-from aidesign._import_helper import get_lib_parent_dir
+from vai_lab._import_helper import get_lib_parent_dir
 
 import tkinter as tk
 import os
diff --git a/aidesign/utils/resources/Assets/ADDIcon.ico b/src/vai_lab/utils/resources/Assets/ADDIcon.ico
similarity index 100%
rename from aidesign/utils/resources/Assets/ADDIcon.ico
rename to src/vai_lab/utils/resources/Assets/ADDIcon.ico
diff --git a/aidesign/utils/resources/Assets/ADDIcon_name.png b/src/vai_lab/utils/resources/Assets/ADDIcon_name.png
similarity index 100%
rename from aidesign/utils/resources/Assets/ADDIcon_name.png
rename to src/vai_lab/utils/resources/Assets/ADDIcon_name.png
diff --git a/aidesign/utils/resources/Assets/AIDIcon.ico b/src/vai_lab/utils/resources/Assets/AIDIcon.ico
similarity index 100%
rename from aidesign/utils/resources/Assets/AIDIcon.ico
rename to src/vai_lab/utils/resources/Assets/AIDIcon.ico
diff --git a/aidesign/utils/resources/Assets/AIDIcon_name.png b/src/vai_lab/utils/resources/Assets/AIDIcon_name.png
similarity index 100%
rename from aidesign/utils/resources/Assets/AIDIcon_name.png
rename to src/vai_lab/utils/resources/Assets/AIDIcon_name.png
diff --git a/aidesign/utils/resources/Assets/AIFRED.png b/src/vai_lab/utils/resources/Assets/AIFRED.png
similarity index 100%
rename from aidesign/utils/resources/Assets/AIFRED.png
rename to src/vai_lab/utils/resources/Assets/AIFRED.png
diff --git a/aidesign/utils/resources/Assets/Hospital.xml b/src/vai_lab/utils/resources/Assets/Hospital.xml
similarity index 100%
rename from aidesign/utils/resources/Assets/Hospital.xml
rename to src/vai_lab/utils/resources/Assets/Hospital.xml
diff --git a/aidesign/utils/resources/Assets/UFAIcon.ico b/src/vai_lab/utils/resources/Assets/UFAIcon.ico
similarity index 100%
rename from aidesign/utils/resources/Assets/UFAIcon.ico
rename to src/vai_lab/utils/resources/Assets/UFAIcon.ico
diff --git a/aidesign/utils/resources/Assets/UFAIcon_name.png b/src/vai_lab/utils/resources/Assets/UFAIcon_name.png
similarity index 100%
rename from aidesign/utils/resources/Assets/UFAIcon_name.png
rename to src/vai_lab/utils/resources/Assets/UFAIcon_name.png
diff --git a/aidesign/utils/resources/Assets/VAIL.ico b/src/vai_lab/utils/resources/Assets/VAIL.ico
similarity index 100%
rename from aidesign/utils/resources/Assets/VAIL.ico
rename to src/vai_lab/utils/resources/Assets/VAIL.ico
diff --git a/aidesign/utils/resources/Assets/VAILabs.png b/src/vai_lab/utils/resources/Assets/VAILabs.png
similarity index 100%
rename from aidesign/utils/resources/Assets/VAILabs.png
rename to src/vai_lab/utils/resources/Assets/VAILabs.png
diff --git a/aidesign/utils/resources/Assets/VAILabsIcon.ico b/src/vai_lab/utils/resources/Assets/VAILabsIcon.ico
similarity index 100%
rename from aidesign/utils/resources/Assets/VAILabsIcon.ico
rename to src/vai_lab/utils/resources/Assets/VAILabsIcon.ico
diff --git a/aidesign/utils/resources/Assets/back_arrow.png b/src/vai_lab/utils/resources/Assets/back_arrow.png
similarity index 100%
rename from aidesign/utils/resources/Assets/back_arrow.png
rename to src/vai_lab/utils/resources/Assets/back_arrow.png
diff --git a/aidesign/utils/resources/Assets/forw_arrow.png b/src/vai_lab/utils/resources/Assets/forw_arrow.png
similarity index 100%
rename from aidesign/utils/resources/Assets/forw_arrow.png
rename to src/vai_lab/utils/resources/Assets/forw_arrow.png
diff --git a/aidesign/utils/resources/example_radiography_images/00028173_005.png b/src/vai_lab/utils/resources/example_radiography_images/00028173_005.png
similarity index 100%
rename from aidesign/utils/resources/example_radiography_images/00028173_005.png
rename to src/vai_lab/utils/resources/example_radiography_images/00028173_005.png
diff --git a/aidesign/utils/resources/example_radiography_images/00028179_000.png b/src/vai_lab/utils/resources/example_radiography_images/00028179_000.png
similarity index 100%
rename from aidesign/utils/resources/example_radiography_images/00028179_000.png
rename to src/vai_lab/utils/resources/example_radiography_images/00028179_000.png
diff --git a/aidesign/utils/resources/example_radiography_images/00028198_002.png b/src/vai_lab/utils/resources/example_radiography_images/00028198_002.png
similarity index 100%
rename from aidesign/utils/resources/example_radiography_images/00028198_002.png
rename to src/vai_lab/utils/resources/example_radiography_images/00028198_002.png
diff --git a/aidesign/utils/resources/example_radiography_images/00028208_035.png b/src/vai_lab/utils/resources/example_radiography_images/00028208_035.png
similarity index 100%
rename from aidesign/utils/resources/example_radiography_images/00028208_035.png
rename to src/vai_lab/utils/resources/example_radiography_images/00028208_035.png
diff --git a/aidesign/utils/resources/example_radiography_images/00028211_021.png b/src/vai_lab/utils/resources/example_radiography_images/00028211_021.png
similarity index 100%
rename from aidesign/utils/resources/example_radiography_images/00028211_021.png
rename to src/vai_lab/utils/resources/example_radiography_images/00028211_021.png
diff --git a/aidesign/utils/resources/example_radiography_images/00028253_000.png b/src/vai_lab/utils/resources/example_radiography_images/00028253_000.png
similarity index 100%
rename from aidesign/utils/resources/example_radiography_images/00028253_000.png
rename to src/vai_lab/utils/resources/example_radiography_images/00028253_000.png
diff --git a/aidesign/utils/resources/example_radiography_images/00028279_000.png b/src/vai_lab/utils/resources/example_radiography_images/00028279_000.png
similarity index 100%
rename from aidesign/utils/resources/example_radiography_images/00028279_000.png
rename to src/vai_lab/utils/resources/example_radiography_images/00028279_000.png


Test patch:
diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml
new file mode 100644
index 00000000..c23a16c5
--- /dev/null
+++ b/.github/workflows/pytest.yml
@@ -0,0 +1,45 @@
+name: pytest
+
+on:
+  pull_request:
+  workflow_dispatch:
+    inputs:
+        logLevel:
+          description: 'Log level'     
+          required: true
+          default: 'warning'
+        tags:
+          description: 'Test scenario tags' 
+
+defaults:
+  run:
+    shell: bash -l {0}
+
+jobs:
+  run-matrix:
+    runs-on: ${{ matrix.os }}
+
+    strategy:
+      matrix:
+        os: [ubuntu-latest, macos-latest, windows-latest]
+        python-version: ["3.10", "3.11"]
+
+    steps:
+      - uses: actions/checkout@v3
+
+      # pip cache
+      - name: pip cache
+        uses: actions/setup-python@v4
+        with:
+          python-version: ${{ matrix.python-version }}
+          cache: 'pip'
+      
+      # pip install
+      - name: pip install
+        run: python3 -m pip install -U pip wheel && python3 -m pip install .
+
+      # pytest
+      - name: Run pytest
+        uses: coactions/setup-xvfb@v1
+        with:
+          run: pytest --cov=./src/vai_lab -v --cov-report=xml:./coverage.xml --cov-report term-missing
\ No newline at end of file
diff --git a/src/vai_lab/Core/tests/__init__.py b/src/vai_lab/Core/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/vai_lab/Core/tests/test_vai_lab_core.py b/src/vai_lab/Core/tests/test_vai_lab_core.py
new file mode 100644
index 00000000..ad00500d
--- /dev/null
+++ b/src/vai_lab/Core/tests/test_vai_lab_core.py
@@ -0,0 +1,11 @@
+"""
+Tests for vai_lab.Core.vai_lab_core.py
+"""
+
+from vai_lab import Core
+
+def test_core_init():
+
+    core = Core()
+
+    assert core
diff --git a/src/vai_lab/Data/tests/__init__.py b/src/vai_lab/Data/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/vai_lab/DataProcessing/tests/__init__.py b/src/vai_lab/DataProcessing/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/vai_lab/DataStorage/tests/__init__.py b/src/vai_lab/DataStorage/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/vai_lab/DecisionMaking/tests/__init__.py b/src/vai_lab/DecisionMaking/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/vai_lab/Environment/tests/__init__.py b/src/vai_lab/Environment/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/vai_lab/GUI/tests/__init__.py b/src/vai_lab/GUI/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/vai_lab/InputData/tests/__init__.py b/src/vai_lab/InputData/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/vai_lab/Modelling/tests/__init__.py b/src/vai_lab/Modelling/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/vai_lab/UserInteraction/tests/__init__.py b/src/vai_lab/UserInteraction/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/aidesign/examples/supervised_regression/import_test user_interface.csv b/src/vai_lab/examples/supervised_regression/import_test user_interface.csv
similarity index 100%
rename from aidesign/examples/supervised_regression/import_test user_interface.csv
rename to src/vai_lab/examples/supervised_regression/import_test user_interface.csv
diff --git a/aidesign/examples/supervised_regression/import_test.csv b/src/vai_lab/examples/supervised_regression/import_test.csv
similarity index 100%
rename from aidesign/examples/supervised_regression/import_test.csv
rename to src/vai_lab/examples/supervised_regression/import_test.csv
diff --git a/src/vai_lab/tests/__init__.py b/src/vai_lab/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/vai_lab/tests/test_examples.py b/src/vai_lab/tests/test_examples.py
new file mode 100644
index 00000000..b23eece1
--- /dev/null
+++ b/src/vai_lab/tests/test_examples.py
@@ -0,0 +1,18 @@
+from glob import glob
+
+import vai_lab as ai
+from vai_lab._import_helper import get_lib_parent_dir
+
+def test_examples():
+    """
+    Test launching GUI
+    """
+    scripts = glob(get_lib_parent_dir() + "/examples/xml_files/" + "*")
+    for file in scripts:
+        """Temp removal of userfeedback test - relies too heavily on GUI for github actions"""
+        if "user_feedback" not in file:
+            print (file)
+            core = ai.Core()
+            core.load_config_file(file)
+            core._debug = True
+            core.run()
\ No newline at end of file
diff --git a/src/vai_lab/tests/test_launch.py b/src/vai_lab/tests/test_launch.py
new file mode 100644
index 00000000..07d70d5e
--- /dev/null
+++ b/src/vai_lab/tests/test_launch.py
@@ -0,0 +1,9 @@
+import vai_lab as ai
+
+def test_launch():
+    """
+    Test launching GUI
+    """
+    core = ai.Core()    
+    core._debug = True
+    core.run()
diff --git a/src/vai_lab/utils/tests/__init__.py b/src/vai_lab/utils/tests/__init__.py
new file mode 100644
index 00000000..e69de29b