Repository: GiacomoValliPhD/openhdemg (language: python)

Issue:
Towards v0.1.0



Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index eeb908e..0ba1c27 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,5 @@ openhdemg.egg-info/
 openhdemg/.DS_Store
 prove.py
 prove_storage.py
+openhdemg/tests/fixtures/*
+!openhdemg/tests/fixtures/__init__.py
diff --git a/MANIFEST.in b/MANIFEST.in
index 53f47b0..d5e3106 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -4,13 +4,16 @@ include LICENSE
 include requirements.txt
 
 # Add all the non .py files in openhdemg
-include openhdemg/gui/gui_files/Icon.ico
+include openhdemg/gui/gui_files/Icon_transp.ico
 include openhdemg/gui/gui_files/Cite.png
 include openhdemg/gui/gui_files/Contact.png
+include openhdemg/gui/gui_files/Gear.png
 include openhdemg/gui/gui_files/Info.png
-include openhdemg/gui/gui_files/logo.png
+include openhdemg/gui/gui_files/Logo_high_res.png
 include openhdemg/gui/gui_files/Matrix.png
 include openhdemg/gui/gui_files/Online.png
 include openhdemg/gui/gui_files/Redirect.png
+include openhdemg/gui/gui_files/Error.png
+include openhdemg/gui/gui_files/gui_color_theme.json
 
 include openhdemg/library/decomposed_test_files/otb_testfile.mat
diff --git a/README.md b/README.md
index cc579f3..9b50bff 100644
--- a/README.md
+++ b/README.md
@@ -42,12 +42,6 @@ If you already know how to use Python, that's the way to go! Otherwise, have a l
 pip install openhdemg
 ```
 
-or conda:
-
-```shell
-conda install -c conda-forge openhdemg
-```
-
 If you want an overview of what you can do with the *openhdemg* library, have a look at the [Quick Start section](https://www.giacomovalli.com/openhdemg/quick-start/).
 
 ## Good to know
@@ -61,7 +55,7 @@ python -m openhdemg.gui.openhdemg_gui
 
 Once opened, it will look like this. It is cool, isn't it?
 
-![gui_preview](https://www.giacomovalli.com/openhdemg/md_graphics/index/gui_preview.png)
+![gui_preview](https://www.giacomovalli.com/openhdemg/md_graphics/index/gui_preview_v2.png)
 
 ## Why openhdemg
 The *openhdemg* project was born in 2022 with the aim to provide the HD-EMG community with a free and open-source framework to analyse motor units' properties.
diff --git a/docs/api_muap.md b/docs/api_muap.md
index f8c25c7..b7e8fb3 100644
--- a/docs/api_muap.md
+++ b/docs/api_muap.md
@@ -82,6 +82,13 @@ This module contains functions to produce and analyse MUs anction potentials
 
 <br/>
 
+::: openhdemg.library.muap.estimate_cv_via_mle
+    options:
+        show_root_full_path: False
+        show_root_heading: True
+
+<br/>
+
 ::: openhdemg.library.muap.MUcv_gui
     options:
         show_root_full_path: False
diff --git a/docs/cite-us.md b/docs/cite-us.md
index 0e3d83e..0917449 100644
--- a/docs/cite-us.md
+++ b/docs/cite-us.md
@@ -1,4 +1,4 @@
-If you use *openhdemg* for your reaserch, please cite our [tutorial article](/isek_jek_tutorials/#jek-tutorial-article). Any citation will help us to continue our work.
+If you use *openhdemg* for your reaserch, please cite our [tutorial article](isek_jek_tutorials.md#jek-tutorial-article). Any citation will help us to continue our work.
 
 Cite us as:
 
diff --git a/docs/gui_advanced.md b/docs/gui_advanced.md
index 4c84d3d..c2999b7 100644
--- a/docs/gui_advanced.md
+++ b/docs/gui_advanced.md
@@ -1,10 +1,12 @@
 # Graphical Interface
 
-This is the toturial for the `Advanced Tools` in the *openhdemg* GUI. Great that you made it this far! In the next few sections we will take a look at the more advanced functions implemented in the GUI. But first of all, you need to click the `Advanced Tools`button in the main window of the GUI to get to the respective adavanced analysis. The `Advanced Tools Window` will open.
+This is the toturial for the `Advanced Tools` in the *openhdemg* GUI. Great that you made it this far! In the next few sections we will take a look at the more advanced functions implemented in the GUI. But first of all, you need to click the `Advanced Tools` button in the main window of the GUI to get to the respective adavanced analysis. The `Advanced Tools Window` will open.
+
+![advanced_analysis](md_graphics/gui/advanced_tools_window_v2.png)
 
 Please note, the `Advanced Tools` might not be available for all the files, as some of them might not have a sufficient number of electrodes to directly perform the advanced analyses. If you want to use the advanced tools anyway, you can still do so from the library.
 
-![advanced_analysis](md_graphics/gui/advanced_analysis_window.png)
+## Start a Specific Tool
 
 So far, we have included three advanced analyses in the *openhdemg* GUI.
 
@@ -12,37 +14,43 @@ So far, we have included three advanced analyses in the *openhdemg* GUI.
 - `Duplicate Removal`
 - `Conduction Velocity Estimation`
 
-For all of those, the specification of a `Matrix Orientation` and a `Matrix Code` is required. The `Matrix Orientaion` must match the one of your matrix during acquisition. You can find a reference image for the `Orientation` at the bottom in the right side of the `Plot Window` when using the `Plot EMG`function. The `Matrix Orientation` can be either **0** or **180** and must be chosen from the dropdown list.
+For all of those, the specification of a `Matrix Code` and a `Matrix Orientation` is required.
+
+The `Matrix Code` must be specified according to the one you used during acquisition. So far, the implemented codes are:
 
-The `Matrix Code` must be specified according to the one you used during acquisition. So far, the codes
+    - `Custom order`
+    - `None`
+    - `GR08MM1305`
+    - `GR04MM1305`
+    - `GR10MM0808`
 
-- `GR08MM1305`
-- `GR04MM1305`
-- `GR10MM0808`
-- `None`
+In case you selected `Custom order`, you must also specify the custom order in the GUI settings. Please refer to [this tutorial](gui_settings.md/#electrodes) for further instructions on how to do so.
 
-are implemented. You must choose one from the respective dropdown list. In case you selected `None`, the entrybox `Rows, Columns` will appear. Please specify the number of rows and columns of your used matrix since you now bypass included matrix codes. `Orientation` is ignored when `Matrix Code` is `None`. In example, specifying
+In case you selected `None`, the entrybox `Rows, Columns` will appear. Please specify the number of rows and columns of your used matrix since you now bypass included matrix codes. In example, specifying
 
 ```Python
 Rows, Columns: 13, 5
 ```
-means that your File has 65 channels.
+means that your File has 65 channels organised over 13 rows and 5 columns.
+
+If you selected one of the built-in sorting orders (e.g., `GR08MM1305`, `GR04MM1305`, `GR10MM0808`), you need to specify also the `Orientation` in row two and column four in the left side of the `Plot Window`. The `Orientaion` must match the one of your matrix during acquisition. You can find a reference image for the `Orientation` at the bottom in the right side of the `Plot Window`. `Orientation` is ignored when `Matrix Code` is `None` or `Custom order`.
 
 Once you specified these parameter, you can click the `Advaned Analysis` button to start your analysis.
 
 -----------------------------------------
 
 ## Motor Unit Tracking
-When you want to track MUs across two different files, you need to select the `Motor Unit Tracking` options and specify the `Matrix Code` and `Matrix Orentation` in the `Advanced Tools Window`. Once you clicked the `Advanced Analysis` button, the `MUs Tracking Window` will pop-up.
 
-![mus_tracking](md_graphics/gui/mu_tracking_window.png)
+When you want to track MUs across two different files, you need to select the `Motor Unit Tracking` option and specify the `Matrix Code` and `Matrix Orentation` in the `Advanced Tools Window`. Once you clicked the `Advanced Analysis` button, the `MUs Tracking Window` will pop-up.
+
+![mus_tracking](md_graphics/gui/mu_tracking_window_v2.png)
 
 1. You need to specify the `Type of file` you want to track MUs across in the respective dropdown. The available filetypes are:
 
-    - `OTB` (.mat file exportable by OTBiolab+)
-    - `DEMUSE` (.mat file used in DEMUSE)
     - `OPENHDEMG` (emgfile or reference signal stored in .json format)
     - `CUSTOMCSV` (custom data from a .csv file)
+    - `OTB` (.mat file exportable by OTBiolab+)
+    - `DEMUSE` (.mat file used in DEMUSE)
 
     Each filetype corresponds to a distinct datatype that should match the file you want to analyse. So, select the **Type of file** corresponding to the type of your file. In case you selected `OTB`, specify the `extension factor` in the dropdown.
 
@@ -61,9 +69,10 @@ When you want to track MUs across two different files, you need to select the `M
 8. By clicking the `Track` button, you can start the analysis. The tracking results will be displayed in the `MUs Tracking Resul` output in the right side of the `MUs Tracking Window`.
 
 ## Duplicate Removal
+
 When you want to remove MUs duplicates across different files, you need to select the `Duplicate Removal` options and specify the `Matrix Code` and `Matrix Orentation` in the `Advanced Tools Window`. Once you clicked the `Advanced Analysis` button, the `Duplicate Removal Window` will pop-up. `Duplicate Removal` requires similar input as `Motor Unit Tracking`, so please take a look at the [`Motor Unit Tracking`](#motor-unit-tracking) section. However, you need to do two more things. 
 
-![duplicate_removal](md_graphics/gui/duplicate_removal_window.png)
+![duplicate_removal](md_graphics/gui/duplicate_removal_window_v2.png)
 
 1. You should specify How to remove the duplicated MUs in the `Which` dropdown. You can choose between
 
diff --git a/docs/gui_basics.md b/docs/gui_basics.md
index ad6ed17..fbc17ff 100644
--- a/docs/gui_basics.md
+++ b/docs/gui_basics.md
@@ -5,42 +5,57 @@ This is the basic introduction to the *openhdemg* GUI. In the next few sections,
 --------------------------------------------
 
 ## Motor Unit Sorting
+
 To sort the MUs included in your analysis file in order of their recruitement, we implemented a sorting algorithm. The MUs are sorted based on their recruitement order in an ascending manner.
 
 1. Load a file. Take a look at the [intro](gui_intro.md#specifying-an-analysis-file) section on how to do so.
 
-2. Pay attention to view the MUs first, using the `View MUs` button (we explained this button in the [intro](gui_intro.md) chapter). The MUs will be sorted anyways, but without viewing them you won't see what is happening. 
-
-3. On the left hand side in the main window of the GUI, you can find the `Sort MUs` button. It is located in row three, column two. Once you press the button, the MUs will be sorted. 
+2. On the left hand side in the main window of the GUI, you can find the `Sort MUs` button. It is located in row three, column two. Once you press the button, the MUs will be sorted based on recruitment order. 
 
 ## Remove Motor Units
-To remove MUs included in your analysis file, you can click the `Remove MUs` button. The button is located on the left hand side in the main window of the GUI in column one of row four.
 
-![remove_mus](md_graphics/gui/remove_mu_window.png)
+To remove MUs included in your analysis file, you can click the `Remove MUs` button. The button is located on the left hand side in the main window of the GUI in column one of row four.
 
-1. View the MUs using the `View MUs` button prior to MU removal, you can directly see what is happening.
+![remove_mus](md_graphics/gui/remove_mu_window_v2.png)
 
-2. Click the `Remove MUs` button, and a file is loaded, a pop-up window will open. 
+1. Click the `Remove MUs` button, and a file is loaded, a pop-up window will open. 
 
-3. Select the MU you want to delete from the analysis file from the `Select MU:`dropdown.
+2. Select the MU you want to delete from the analysis file from the `Select MU:`dropdown.
 
     ```Python
     Select MU: 1
     ```
     will result in the second MU to be deleted (Python is base 0).
 
-4. Click the `Remove MU` button to remove the MU.
+3. Click the `Remove MU` button to remove the MU.
+
+Alternatively, you can click the `Remove empty MUs` button to delete all the MUs without discharge times. These can be present in the *emgfile* as the result of decomposed duplicates that have not been fully removed.
+
+## Signal Editing
+
+It is often necessary to edit (e.g., filter or convert) the signals. In the *openhdemg* GUI this can be done from the `Signal Editing Window`. In order to open this window, click the `Signal Editing` button.
+
+![signal_editing](md_graphics/gui/signal_editing_window.png)
 
-Alternatively, you can click the `Remove empty MUs` button to delete all the MUs without discharge times. These can be present in the emgfile as the result of decomposed duplicates that have not been fully removed.
+### EMG Signal Filtering
 
-## Reference Signal Editing
-The *openhdemg* GUI also allows you to edit and filter reference signals corresponding to your analysis file (this can be either a file containing both the MUs and the reference signal or a file containing only the reference signal).
+It is common practice to filter the EMG signal before decomposition. However, if your *emgfile* contains the raw (unfiltered) signal, this can be easily filtered from the `Signal Editing Window`.
 
-![reference_sig](md_graphics/gui/refsig_filter_window.png)
+1. Click the `Signal Editing` button located in row five and column one, a new pop-up window opens. In the `Signal Editing Window`, you can band-pass filter the EMG signal and process the reference signal (see next paragraph).
 
-1. View the MUs using the `View MUs` button prior to reference signal editing, so you can see what is happening.
+3. When you click the `Filter EMG signal` button, the EMG signal is band-pass filtered (Zero-lag, Butterworth) according to values specified in the `Filter Order` and `BandPass Freq` textboxes. In example, specifiying 
 
-2. Click the `RefSig Editing` button located in row five and column one, a new pop-up window opens. In the `Reference Signal Editing Window`, you can low-pass filter the reference signal as well as remove any signal offset. Additionally, you can also convert your reference signal by a specific factor (amplification factor) or convert it from absolute to percentage (relative or normalised) values.
+    ```Python
+    Filter Order: 2
+    BandPass Freq: 20-500
+    ```
+    will allow only frequencies between 20 and 500 Hz to pass trough.
+
+### Reference Signal Editing
+
+The *openhdemg* GUI also allows you to edit and filter reference signals contained in your analysis file (this can be either a file containing both the MUs and the reference signal or a file containing only the reference signal).
+
+1. Click the `Signal Editing` button located in row five and column one, a new pop-up window opens. In the `Signal Editing Window`, you can low-pass filter the reference signal as well as remove any signal offset. Additionally, you can also convert your reference signal by a specific factor (amplification factor) or convert it from absolute to percentage (relative or normalised) values.
 
 3. When you click the `Filter RefSig` button, the reference signal is low-pass filtered (Zero-lag, Butterworth) according to values specified in the `Filter Order` and `Cutoff Freq` textboxes. In example, specifiying 
 
@@ -48,7 +63,7 @@ The *openhdemg* GUI also allows you to edit and filter reference signals corresp
     Filter Order: 4
     Cutoff Freq: 15
     ```
-    will allow only frequencies below 15 Hz to pass trough. The filter order of 4 indicates a fourth degree polynomial transfer function.
+    will allow only frequencies below 15 Hz to pass trough.
 
 4. When you click the `Remove Offset` button, the reference signal's offset will be removed according to the values specified in the `Offset Value` and `Automatic Offset` textboxes. In example, specifying
 
@@ -78,6 +93,14 @@ The *openhdemg* GUI also allows you to edit and filter reference signals corresp
     ```
     will amplify the reference signal 2.5 times.
 
+    While:
+
+    ```Python
+    Operator : "Multiply"
+    Factor: -1
+    ```
+    will make your negative reference signal become positive.
+
 6. When you click the `To Percent` button, the reference signal in absolute values is converted to percentage (relative or normalised) values based on the provided `MVC Value`. **This step should be performed before any analysis, because *openhdemg* is designed to work with a normalised reference signal.** In example, a file with a reference signal in absolute values ranging from 0 to 100 will be normalised from 0 to 20 if
 
     ```Python
@@ -93,12 +116,12 @@ Sometimes, resizing of your analysis file is unevitable. Luckily, *openhdemg* pr
 
 2. Clicking the `Resize File` button will open a new pop-up plot of your analysis file. 
 
-3. Follow the instructions in the plot to resize the file. Simply click in the signal twice (once for start-point, once for end-point) to specify the resizing region and press enter to confirm your coice.
+3. Follow the instructions in the plot to resize the file. Simply click in the signal twice (once for start-point, once for end-point) to specify the resizing region and press enter to confirm your choice.
 
 ## Analyse Force Signal
 In order to analyse the force signal in your analysis file, you can press the `Analyse Force` button located in row six and column one in the left side of the GUI. A new pop-up window will open where you can analyse the maximum voluntary contraction (MVC) value as well as the rate of force development (RFD). 
 
-![force_analysis](md_graphics/gui/force_analysis_window.png)
+![force_analysis](md_graphics/gui/force_analysis_window_v2.png)
 
 ### Maximum voluntary contraction
 1. In order to get the MVC value, simply press the `Get MVC` button. A pop-up plot opens and you can select the area where you suspect the MVC to be. 
@@ -114,14 +137,14 @@ In order to analyse the force signal in your analysis file, you can press the `A
     ```Python
     RFD miliseconds: 50,100,150,200
     ```
-    will result in RFD value calculation between the intervals 0-50ms, 50-100ms, 100-150ms and 150-200ms. You can also specify less or more values in the `RFD miliseconds` textbox. 
+    will result in RFD value calculation between the intervals 0-50ms, 0-100ms, 0-150ms and 0-200ms. You can also specify less or more values in the `RFD miliseconds` textbox. 
 
 4. You can edit or copy any value in the `Result Output`, however, you need to close the top-level `Force Analysis Window` first.
 
 ## Motor Unit Properties
 When you press the `MU Properties` button in row six and column two, the `Motor Unit Properties` Window will pop up. In this window, you have the option to analyse several MUs propierties such as the MUs recruitement threshold or the MUs discharge rate. 
 
-![mus_properties](md_graphics/gui/mu_properties_window.png)
+![mus_properties](md_graphics/gui/mu_properties_window_v2.png)
 
 1. Specify your priorly calculated MVC in the `Enter MVC [N]:` textbox, like
 
@@ -156,7 +179,7 @@ Subsequently to specifying the MVC, you can compute the MUs recruitement thresho
 Subsequently to specifying the MVC, you can compute the MUs discharge rate by entering the respective firing rates and event.
 
 1. Specify the number of firings at recruitment and derecruitment to consider for the calculation in the `Firings at Rec` textbox. 
-2. Enter the number of firings over which to calculate the DR at the start and end of the steady-state phase in the `Firings Start/End Steady` textbox.
+2. Enter the number of firings over which to calculate the discharge rate at the start and end of the steady-state phase in the `Firings Start/End Steady` textbox.
     
     For example:
     ```Python
@@ -189,8 +212,8 @@ Subsequently to specifying the MVC, you can calculate a number of basic MUs prop
 
 and are all displayed in the `Result Output` once the analysis in completed. 
 
-1. Specify the number of firings at recruitment and derecruitment to consider for the calculation in the `Firings at Rec` textbox. 
-2. Enter the start and end point (in samples) of the steady-state phase in the `Firings Start/End Steady` textbox. For example, 
+1. Specify the number of firings at recruitment and derecruitment to consider for the calculation of discharge rate in the `Firings at Rec` textbox. 
+2. Enter the number of firings over which to calculate the discharge rate at the start and end of the steady-state phase in the `Firings Start/End Steady` textbox. For example,
 
     ```Python
     Firings at Rec: 4
@@ -202,9 +225,9 @@ and are all displayed in the `Result Output` once the analysis in completed.
 
 ## Plot Motor Units
 In *openhdemg* we have implemented options to plot your analysis file ... a lot of options!
-Upon clicking the `Plot MUs` button, the `Plot Window` will pop up. In the top right corner of the window, you can find an information button forwarding you directly to some tutorials. 
+Upon clicking the `Plot MUs` button, the `Plot Window` will pop up. In the top right corner of the window, you can find an information button forwarding you directly to this tutorial. 
 
-![plot_mus](md_graphics/gui/plot_window.png)
+![plot_mus](md_graphics/gui/plot_window_v2.png)
 
 You can choose between the follwing plotting options:
 
@@ -214,7 +237,7 @@ You can choose between the follwing plotting options:
 - Plot the decomposed source. (Plot Source)
 - Plot the instantaneous discharge rate (IDR). (Plot IDR)
 - Plot the differential derivation of the raw emg signal by matrix column. (Plot Derivation)
-- Plot motor unit action potentials (MUAPs) obtained from spike-triggered average from one or multiple MUs. (Plot MUAPs)
+- Plot motor unit action potentials (MUAPs) obtained from spike-triggered average (STA) from one or multiple MUs. Please note that for Delsys files, the displayed MUAPs are those computed in the Delsys software and not those obtained via STA. (Plot MUAPs)
 
 Prior to plotting you can **optionally** select a few options on the left side of the `Plot Window`. 
 
@@ -229,44 +252,56 @@ Prior to plotting you can **optionally** select a few options on the left side o
 These three setting options are universally used in all plots. There are two more specification options on the right side of the `Plot Window` only relevant when using the `Plot Derivation` or `Plot MUAP` buttons. 
 
 1. The `Matrix Code` must be specified in row one and column four in the right side of the `Plot Window` according to the one you used during acquisition. So far, implemented codes are:
+
+    - `Custom order`
+    - `None`
     - `GR08MM1305`
     - `GR04MM1305`
     - `GR10MM0808`
-    - `None`
+    - `Trigno Galileo Sensor`
+
+    In case you selected `Custom order`, you must also specify the custom order in the GUI settings. Please refer to [this tutorial](gui_settings.md/#electrodes) for further instructions on how to do so.
 
     In case you selected `None`, the entrybox `Rows, Columns` will appear. Please specify the number of rows and columns of your used matrix since you now bypass included matrix codes. In example, specifying
+
     ```Python
     Rows, Columns: 13, 5
     ```
-    means that your File has 65 channels.
+    means that your File has 65 channels organised over 13 rows and 5 columns.
 
-2. You need to specify the `Orientation` in row two and column four in the left side of the `Plot Window`. The `Orientaion` must match the one of your matrix during acquisition. You can find a reference image for the `Orientation` at the bottom in the right side of the `Plot Window`. `Orientation` is ignored when `Matrix Code` is `None`.
+2. If you selected one of the built-in sorting orders (e.g., `GR08MM1305`, `GR04MM1305`, `GR10MM0808`), you need to specify also the `Orientation` in row two and column four in the left side of the `Plot Window`. The `Orientaion` must match the one of your matrix during acquisition. You can find a reference image for the `Orientation` at the bottom in the right side of the `Plot Window`. `Orientation` is ignored when `Matrix Code` is `None`, `Custom order` or `Trigno Galileo Sensor`.
 
 ### Plot Raw EMG Signal
+
 1. Click the `Plot EMGsig` button in row four and column one in the left side of the `Plot Window`, to plot the raw emg signal of your analysis file. 
 2. Enter or select a `Channel Number` in / from the dropdown list. For example, if you want to plot `Channel Number` one enter *0* in the dropdown. If you want to plot `Channel Numbers` one, two and three enter *0,1,2* in the dropdown. 
 3. Once you have clicked the `Plot EMGsig` button, a pop-up plot will appear. 
 
 ### Plot Reference Signal 
+
 1. Click the `Plot RefSig` button in row five and column one in the left side of the `Plot Window`, to plot the reference signal. 
 2. Once you have clicked the `Plot RefSig` button, a pop-up plot will appear. 
 
 ### Plot Motor Unit Pulses
+
 1. Click the `Plot MUpulses` button in row six and column one in the left side of the `Plot Window`, to plot the single pulses of the MUs in your analysis file.
 2. Enter/select a pulse `Linewidth` in/from the dropdown list. For example, if you want to use a `Linewidth` of one, enter *1* in the dropdown. 
 3. Once you have clicked the `Plot MUpulses` button, a pop-up plot will appear. 
 
 ### Plot the Decomposed Source
+
 1. Click the `Plot Source` button in row seven and column one in the left side of the `Plot Window`, to plot the Source of the decomposed MUs in your analysis file. 
 2. Enter/select a `MU Number` in/from the dropdown list. For example, if you want to plot the source for `MU Number` one enter *0* in the dropdown. If you want to plot the sources for `MU Number` one, two and three enter *0,1,2,* in the dropdown. You can also set `MU Number` to "all" to plot the sources for all included MUs in the analysis file.
 3. Once you have clicked the `Plot Source` button, a pop-up plot will appear. 
 
 ### Plot Instanteous Discharge rate
+
 1. Click the `Plot IDR` button in row eight and column one in the left side of the `Plot Window`, to plot the IDR of the MUs in your analysis file. 
 2. Enter/select a `MU Number` in/from the dropdown list. For example, if you want to plot the IDR of `MU Number` one enter *0* in the dropdown. If you want to plot the IDR of `MU Number` one, two and three enter *0,1,2* in the dropdown. You can also set `MU Number` to "all" to plot the IDR of all included MUs in the analysis file.
 3. Once you have clicked the `Plot IDR` button, a pop-up plot will appear. 
 
 ### Plot Differential Derivation
+
 1. Click the `Plot Derivation` button in row four and column three in the right side of the `Plot Window`, to plot the differential derivation of the MUs in your analysis file. 
 2. Specify the `Configuration` for the calculation first. You can choose from:
     - `Single differential` (Calculate single differential of raw signal on matrix rows)
@@ -282,9 +317,11 @@ These three setting options are universally used in all plots. There are two mor
     - `Double differential`(Calculate double differential of raw signal on matrix rows)
 3. Specify the respective `MU Number` you want to plot. You can choose one from the `MU Number` dropdown list. 
 4. Specify the `Timewindow` of the plots. You can choose from the `Timewindow` dropdown list or enter any integer. 
-5. Once you have clicked the `Plot MUAPs` button, a new pop-up plot appears. 
+5. Once you have clicked the `Plot MUAPs` button, a new pop-up plot appears.
 
-## Saving Your Analysis File 
+Please note, if you are using the Delsys decomposition outcome, the plotted MUAPs will be those calculated bu the Delsys software.
+
+## Saving Your Analysis File
 Subsequently to analysing your emg-file in the *openhdemg* GUI, it is beneficial to save it. Otherwise, all changes will be lost when the GUI is closed. 
 
 1. Click the `Save File` button in row two and column two in the left side of the main window. 
@@ -296,8 +333,9 @@ Some analyses included in the *openhdemg* GUI return values that are displayed i
 1. click the `Save Results` button in row two and column two in the left side of the main window. 
 2. Specify a location where to save the file and confirm. You can find the file there with the name of your analysis file. 
 
-## Resetting Your Analysis 
-We all make mistakes! But, most likely, we are also able to correct them. In case you have made a mistake in the analysis of you emg-file in the *openhdemg* GUI, we have implemented a reset button for you. Click the `Reset Analysis` button in row eight and column two in the lef side of the main window to  reset any analysis you previously performed since opening the GUI and inputting an analysis file. Your analysis file is reset to the original file and all the changes are discarded. So, no need to be perfect!
+## Resetting Your Analysis
+
+We all make mistakes! But, most likely, we are also able to correct them. In case you have made a mistake in the analysis of your emgfile in the *openhdemg* GUI, we have implemented a reset button for you. Click the `Reset Analysis` button in row eight and column two in the lef side of the main window to  reset any analysis you previously performed since opening the GUI and inputting an analysis file. Your analysis file is reset to the original file and all the changes are discarded. So, no need to be perfect!
 
 --------------------------------------------
 
diff --git a/docs/gui_intro.md b/docs/gui_intro.md
index 83467cd..04ee4a2 100644
--- a/docs/gui_intro.md
+++ b/docs/gui_intro.md
@@ -14,9 +14,9 @@ python -m openhdemg.gui.openhdemg_gui
 
 Let us shortly walk you through the main window of the GUI. An image of the starting page of the GUI is displayed below.
 
-![gui_preview](md_graphics/index/gui_preview.png)
+![gui_preview](md_graphics/index/gui_preview_v2.png)
 
-This is your starting point for every analysis. On the left hand side you can find all the entryboxes and buttons relevant for the analyses you want to perform. In the middle you can see the plotting canvas where plots of the HD-EMG data analysis are displayed. On the right hand side you can find information buttons leading you directly to more information, tutorials, and more. And, with a little swoosh of magic, the results window appears at the bottom of the GUI once an analysis is finished. 
+This is your starting point for every analysis. On the left hand side you can find all the entryboxes and buttons relevant for the analyses you want to perform. In the middle you can see the plotting canvas where plots of the HD-EMG data analysis are displayed. On the right hand side you can find information buttons leading you directly to more information, tutorials, and more. Very important, at the top of this section, there is the settings icon (Gear icon). By clicking this icon, you will be able to [change some of the GUI settings](gui_settings.md). Finally, at the bottom of the GUI there is a window used to display the results of the analyses. 
 
 -------------------------------------------------
 
@@ -33,17 +33,20 @@ This is your starting point for every analysis. On the left hand side you can fi
     - `CUSTOMCSV` (custom data from a .csv file)
     - `CUSTOMCSV_REFSIG` (Reference signal in a custom .csv file)
 
-    Each filetype corresponds to a distinct datatype that should match the file you want to analyse. So, select the `Type of file` corresponding to the type of your file. In case you selected `OTB`, specify the `extension factor` in the dropdown.
+    Each filetype corresponds to a distinct datatype that should match the file you want to analyse. So, select the `Type of file` corresponding to the type of your file.
 
-2. To actually load the file, click the **Load File** button and select the file you want to analyse. In case of occurence, follow the error messages and repeat this and the previos step.
+2. Before loading a file, verify that the [GUI settings](gui_settings.md) are correct for your needs.
 
-3. Once the file is successfully loaded, the specifications of the file you want to analyse will be displayed next to the **Load File** button. 
+3. To actually load the file, click the **Load File** button and select the file you want to analyse. In case of occurence, follow the error messages and repeat this and the previos step.
+
+4. Once the file is successfully loaded, the specifications of the file you want to analyse will be displayed next to the **Load File** button. 
 
 ## Viewing an analysis file
 
 It doesn't get any simpler than this! 
 
-Once a file is successfully loaded as described above, you can click the `View MUs` button to plot/view your file. In the middle section of the GUI, a plot containing your data should appear.
+Once a file is successfully loaded as described above, a plot containing your data should appear in the middle section of the GUI, allowing you to
+view the content of your file. This can also be force by clicking the `View MUs` button.
 
 ----------------------------------------
 
diff --git a/docs/gui_settings.md b/docs/gui_settings.md
new file mode 100644
index 0000000..ec04841
--- /dev/null
+++ b/docs/gui_settings.md
@@ -0,0 +1,194 @@
+Since the release of version 0.1.0-beta.4, we have introduced GUI settings. These allow you to customise functions behaviour when using the GUI, making it more flexible and adaptable to specific users needs.
+
+## Access GUI settings
+
+Accessing these GUI settings is simple: just click on the Gear icon located at the top-right corner of the GUI window.
+
+![gui_preview](md_graphics/index/gui_preview_v2.png)
+
+Upon clicking the settings icon, a Python file will open in your default text editor. Here, you'll be able to modify the settings according to your requirements.
+
+## Modify GUI settings
+
+The GUI settings file (named *settings.py*) is organised into distinct topic sections.
+
+All the variables that can be modified are labelled as:
+
+- Name of the calling function + _ _ + Name of the variable
+
+In this way, the values that each variable can assume can be discovered from the API section of this website by navigating into the topic, then into the Name of the function and then looking at the specific variable.
+
+The variables that can be modified from the GUI settings, as well as their values, can differ between different *openhdemg* releases. Therefore, the user is always encouraged to check the specifics APIs, and not to rely on this guide, which only serves didactical purposes.
+
+### openfiles
+
+The first section is named *openfiles* and controls how files are loaded into *openhdemg* or saved in .json format.
+
+The API section for this topic is [here](api_openfiles.md).
+
+For example, the GUI behaviour when loading Custom .csv files can be adjusted in the following code snippet:
+
+```Python
+# in emg_from_customcsv()
+emg_from_customcsv__ref_signal = "REF_SIGNAL"
+emg_from_customcsv__raw_signal = "RAW_SIGNAL"
+emg_from_customcsv__ipts = "IPTS"
+emg_from_customcsv__mupulses = "MUPULSES"
+emg_from_customcsv__binary_mus_firing = "BINARY_MUS_FIRING"
+emg_from_customcsv__accuracy = "ACCURACY"
+emg_from_customcsv__extras = "EXTRAS"
+emg_from_customcsv__fsamp = 2048
+emg_from_customcsv__ied = 8
+```
+
+In the first line of code, *emg_from_customcsv__ref_signal = "REF_SIGNAL"* indicates that the variable *ref_signal* belonging to the function *emg_from_customcsv* is set to "REF_SIGNAL".
+
+The complete documentation of the function *emg_from_customcsv()* can be accessed [here](api_openfiles.md#openhdemg.library.openfiles.emg_from_customcsv). Reading the specific APIs, you will always know what parameters can be passed to each variable!
+
+Similarly, the GUI behaviour when saving .json files can be adjusted in the following code snippet:
+
+```Python
+# in save_json_emgfile()
+save_json_emgfile__compresslevel = 4
+```
+
+And the corresponding APIs can be accessed [here](api_openfiles.md#openhdemg.library.openfiles.save_json_emgfile).
+
+### analysis
+
+The section named *analysis* controls how basic MUs properties (i.e., MUs recruitment/derecruitment thresholds, discharge rate and accuracy measures) are calculated.
+
+The API section for this topic is [here](api_analysis.md).
+
+For example, in the following code snippet we can adjust how many firings are used to calculate the recruitment/derecruitment thresholds or how the accuracy measure is computed.
+
+```Python
+# in basic_mus_properties()
+basic_mus_properties__n_firings_rt_dert = 1
+basic_mus_properties__accuracy = "default"
+basic_mus_properties__ignore_negative_ipts = False
+basic_mus_properties__constrain_pulses = [True, 3]
+```
+
+The corresponding APIs for the function *basic_mus_properties* can be accessed [here](api_analysis.md/#openhdemg.library.analysis.basic_mus_properties).
+
+### tools
+
+The section named *tools* controls additional functionalities that are necessary for the usability of the library. The functions contained in this section can be considered as "tools" or shortcuts necessary to operate with the HD-EMG recordings.
+
+The API section for this topic is [here](api_tools.md).
+
+For example, in the following code snippet we can adjust how to resize the emgfile and how to deal with the accuracy measure in the new resized file.
+
+```Python
+# in resize_emgfile()
+resize_emgfile__how = "ref_signal"
+resize_emgfile__accuracy = "recalculate"
+resize_emgfile__ignore_negative_ipts = False
+```
+
+The corresponding APIs for the function *resize_emgfile* can be accessed [here](api_tools.md/#openhdemg.library.tools.resize_emgfile).
+
+### muap
+
+The section named *muap* controls all the functionalities that require MU action potentials. Currently, it controls the behaviour of functionalities such as MU tracking, duplicate removal and conduction velocity estimation, among others.
+
+The API section for this topic is [here](api_muap.md).
+
+For example, in the following code snippet we can adjust how to perform MU tracking. As you might have noticed using the GUI, it is already possibile to specify many parameters for MU tracking directly in the GUI, and these settings here offer an additional level of customisability.
+
+```Python
+# in tracking()
+tracking__firings = "all"
+tracking__derivation = "sd"
+```
+
+The corresponding APIs for the function *tracking* can be accessed [here](api_muap.md/#openhdemg.library.muap.tracking).
+
+### electrodes
+
+The section named *electrodes* allows to specify a custom order for electrodes sorting. The sorting order is used any time the grid channels need to be oriented in a specific manner, wich usually reflects how the operator positioned the grid on the skin. A number of common sorting orders are already present the library. However, if the needed sorting order is not available, the user can specify any custom order in this section. This allows for maximum flexibility of the sorting functionalities.
+
+The API section for this topic is [here](api_electrodes.md).
+
+In the following example, we show how the sorting order of a grid can be obtained and specified.
+
+Here you can see the channels order as displayed on the grid. This example refers to the commercially available grid GR08MM1305.
+
+```
+Channel Order GR08MM1305
+       0   1   2   3   4
+0     64  39  38  13  12
+1     63  40  37  14  11
+2     62  41  36  15  10
+3     61  42  35  16   9
+4     60  43  34  17   8
+5     59  44  33  18   7
+6     58  45  32  19   6
+7     57  46  31  20   5
+8     56  47  30  21   4
+9     55  48  29  22   3
+10    54  49  28  23   2
+11    53  50  27  24   1
+12    52  51  26  25 NaN
+```
+
+As you can see, this grid of electrodes is composed of 5 columns (from 0 to 4) and 13 rows (from 0 to 12) for a total of 64 channels. Given that the channels are represented in a quadrilateral structure, one channels will result empty.
+
+Given that Python is in base 0, we first need to convert the channels in base 0, with the final matrix looking like this:
+
+```
+Channel Order in base 0
+       0   1   2   3   4
+0     63  38  37  12  11
+1     62  39  36  13  10
+2     61  40  35  14   9
+3     60  41  34  15   8
+4     59  42  33  16   7
+5     58  43  32  17   6
+6     57  44  31  18   5
+7     56  45  30  19   4
+8     55  46  29  20   3
+9     54  47  28  21   2
+10    53  48  27  22   1
+11    52  49  26  23   0
+12    51  50  25  24 NaN
+```
+
+After the conversion from base 1 to base 0, we can proceed creating a list of lists, where each internal list represents a column of the grid, as follows:
+
+```Python
+custom_sorting_order = [
+    [63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52,     51],
+    [38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,     50],
+    [37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26,     25],
+    [12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,     24],
+    [11, 10,  9,  8,  7,  6,  5,  4,  3,  2,  1,  0, np.nan],
+]
+```
+
+The empty channel can be indicated with *np.nan*, which stands for "not a number". Similarly, we can mark more missing channels with the same approach.
+
+```Python
+custom_sorting_order = [
+    [63, 62, 61,     60, 59, 58, np.nan, 56, 55, 54, 53, 52,     51],
+    [38, 39, 40,     41, 42, 43,     44, 45, 46, 47, 48, 49,     50],
+    [37, 36, 35,     34, 33, 32,     31, 30, 29, 28, 27, 26,     25],
+    [12, 13, 14,     15, 16, 17,     18, 19, 20, 21, 22, 23,     24],
+    [11, 10,  9, np.nan,  7,  6,      5,  4,  3,  2,  1,  0, np.nan],
+]
+```
+
+## What variables can be modified?
+
+Please note that **only** the variables present in the *settings.py* file can be modified. If you would like to modify additional variables, please [get in touch](contacts.md) with us.
+
+Additionally, you cannot remove the unused variables from the settings file! You can only modify their value.
+
+## Restore GUI settings
+
+If you accidentally modify some variables and the GUI stops working properly, you can restore the original settings by copying and pasting the content of the *backup_settings.py* file. This will be visible in the file explorer of your editor next to the *settings.py* file.
+
+## More questions?
+
+We hope that this tutorial was useful. If you need any additional information, do not hesitate to read the answers or ask a question in the [*openhdemg* discussion section](https://github.com/GiacomoValliPhD/openhdemg/discussions){:target="_blank"}. If you are not familiar with GitHub discussions, please read this [post](https://github.com/GiacomoValliPhD/openhdemg/discussions/42){:target="_blank"}. This will allow the *openhdemg* community to answer your questions.
\ No newline at end of file
diff --git a/docs/index.md b/docs/index.md
index f9fbb8c..4df027e 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -39,12 +39,6 @@ If you already know how to use Python, that's the way to go! Otherwise, have a l
 pip install openhdemg
 ```
 
-or conda:
-
-```shell
-conda install -c conda-forge openhdemg
-```
-
 If you want an overview of what you can do with the *openhdemg* library, have a look at the [Quick Start](quick-start.md) section and then explore all the functions in the **API reference**.
 
 ## Good to know
@@ -58,7 +52,7 @@ python -m openhdemg.gui.openhdemg_gui
 
 Once opened, it will look like this. It is cool, isn't it?
 
-![gui_preview](md_graphics/index/gui_preview.png)
+![gui_preview](md_graphics/index/gui_preview_v2.png)
 
 ## Why openhdemg
 The *openhdemg* project was born in 2022 with the aim to provide the HD-EMG community with a free and open-source framework to analyse motor units' properties.
diff --git a/docs/isek_jek_tutorials.md b/docs/isek_jek_tutorials.md
index 170cc0c..c69dec6 100644
--- a/docs/isek_jek_tutorials.md
+++ b/docs/isek_jek_tutorials.md
@@ -18,7 +18,11 @@ You can download the sample files and the sample scripts [here](https://drive.go
 
 <br>
 
-<embed src="https://www.giacomovalli.com/openhdemg/online_pdfs/pre-proof_valli_et_al_2023_jek.pdf" width="100%" height="800px" allowfullscreen type="application/pdf"></iframe>
+<a href="https://www.giacomovalli.com/openhdemg/online_pdfs/valli_et_al_2023_tutorial_jek.pdf" target="_blank" rel="noopener noreferrer">
+  <object data="https://www.giacomovalli.com/openhdemg/online_pdfs/valli_et_al_2023_tutorial_jek.pdf" type="application/pdf" width="100%" height="800px">
+    <p>Your web browser doesn't have a PDF plugin. Instead, you can click here to download the PDF file.</p>
+  </object>
+</a>
 
 <br> <!-- links in HTML should be full links-->
 
@@ -26,7 +30,7 @@ You can download the sample files and the sample scripts [here](https://drive.go
 
 **Workshop: Simplified analysis of motor unit properties with *openhdemg*.**
 
-On the 26th of June 2024, we will present the *openhdemg* framework at the ISEK congress, which will be held in Nagoya, Japan.
+On the 26th of June 2024, we will present the *openhdemg* framework at the [ISEK congress](https://isek.org/workshops/){:target="_blank"}, which will be held in Nagoya, Japan.
 
 Below, you can find a preliminary design of the workshop and the fantastic speakers who will participate. We look forward to seeing you there!
 
diff --git a/docs/md_graphics/gui/advanced_analysis_window.png b/docs/md_graphics/gui/advanced_analysis_window.png
deleted file mode 100644
index f8db494..0000000
Binary files a/docs/md_graphics/gui/advanced_analysis_window.png and /dev/null differ
diff --git a/docs/md_graphics/gui/advanced_tools_window_v2.png b/docs/md_graphics/gui/advanced_tools_window_v2.png
new file mode 100644
index 0000000..cae1620
Binary files /dev/null and b/docs/md_graphics/gui/advanced_tools_window_v2.png differ
diff --git a/docs/md_graphics/gui/duplicate_removal_window.png b/docs/md_graphics/gui/duplicate_removal_window.png
deleted file mode 100644
index dc2307e..0000000
Binary files a/docs/md_graphics/gui/duplicate_removal_window.png and /dev/null differ
diff --git a/docs/md_graphics/gui/duplicate_removal_window_v2.png b/docs/md_graphics/gui/duplicate_removal_window_v2.png
new file mode 100644
index 0000000..e8fb7bc
Binary files /dev/null and b/docs/md_graphics/gui/duplicate_removal_window_v2.png differ
diff --git a/docs/md_graphics/gui/force_analysis_window.png b/docs/md_graphics/gui/force_analysis_window.png
deleted file mode 100644
index af434d2..0000000
Binary files a/docs/md_graphics/gui/force_analysis_window.png and /dev/null differ
diff --git a/docs/md_graphics/gui/force_analysis_window_v2.png b/docs/md_graphics/gui/force_analysis_window_v2.png
new file mode 100644
index 0000000..33300ed
Binary files /dev/null and b/docs/md_graphics/gui/force_analysis_window_v2.png differ
diff --git a/docs/md_graphics/gui/mu_properties_window.png b/docs/md_graphics/gui/mu_properties_window.png
deleted file mode 100644
index 7dee165..0000000
Binary files a/docs/md_graphics/gui/mu_properties_window.png and /dev/null differ
diff --git a/docs/md_graphics/gui/mu_properties_window_v2.png b/docs/md_graphics/gui/mu_properties_window_v2.png
new file mode 100644
index 0000000..4d02998
Binary files /dev/null and b/docs/md_graphics/gui/mu_properties_window_v2.png differ
diff --git a/docs/md_graphics/gui/mu_tracking_window.png b/docs/md_graphics/gui/mu_tracking_window.png
deleted file mode 100644
index 02eaca6..0000000
Binary files a/docs/md_graphics/gui/mu_tracking_window.png and /dev/null differ
diff --git a/docs/md_graphics/gui/mu_tracking_window_v2.png b/docs/md_graphics/gui/mu_tracking_window_v2.png
new file mode 100644
index 0000000..569f6d6
Binary files /dev/null and b/docs/md_graphics/gui/mu_tracking_window_v2.png differ
diff --git a/docs/md_graphics/gui/plot_window.png b/docs/md_graphics/gui/plot_window.png
deleted file mode 100644
index 8753830..0000000
Binary files a/docs/md_graphics/gui/plot_window.png and /dev/null differ
diff --git a/docs/md_graphics/gui/plot_window_v2.png b/docs/md_graphics/gui/plot_window_v2.png
new file mode 100644
index 0000000..75667f4
Binary files /dev/null and b/docs/md_graphics/gui/plot_window_v2.png differ
diff --git a/docs/md_graphics/gui/refsig_filter_window.png b/docs/md_graphics/gui/refsig_filter_window.png
deleted file mode 100644
index 2fc33e0..0000000
Binary files a/docs/md_graphics/gui/refsig_filter_window.png and /dev/null differ
diff --git a/docs/md_graphics/gui/remove_mu_window.png b/docs/md_graphics/gui/remove_mu_window.png
deleted file mode 100644
index a1b0f2a..0000000
Binary files a/docs/md_graphics/gui/remove_mu_window.png and /dev/null differ
diff --git a/docs/md_graphics/gui/remove_mu_window_v2.png b/docs/md_graphics/gui/remove_mu_window_v2.png
new file mode 100644
index 0000000..d2f9ef7
Binary files /dev/null and b/docs/md_graphics/gui/remove_mu_window_v2.png differ
diff --git a/docs/md_graphics/gui/signal_editing_window.png b/docs/md_graphics/gui/signal_editing_window.png
new file mode 100644
index 0000000..d5561da
Binary files /dev/null and b/docs/md_graphics/gui/signal_editing_window.png differ
diff --git a/docs/md_graphics/index/gui_preview.png b/docs/md_graphics/index/gui_preview.png
deleted file mode 100644
index 4dc0c77..0000000
Binary files a/docs/md_graphics/index/gui_preview.png and /dev/null differ
diff --git a/docs/md_graphics/index/gui_preview_v2.png b/docs/md_graphics/index/gui_preview_v2.png
new file mode 100644
index 0000000..ebc2638
Binary files /dev/null and b/docs/md_graphics/index/gui_preview_v2.png differ
diff --git a/docs/online_pdfs/pre-proof_valli_et_al_2023_jek.pdf b/docs/online_pdfs/pre-proof_valli_et_al_2023_jek.pdf
deleted file mode 100644
index 2b3595f..0000000
Binary files a/docs/online_pdfs/pre-proof_valli_et_al_2023_jek.pdf and /dev/null differ
diff --git a/docs/online_pdfs/valli_et_al_2023_tutorial_jek.pdf b/docs/online_pdfs/valli_et_al_2023_tutorial_jek.pdf
new file mode 100644
index 0000000..07ca11f
Binary files /dev/null and b/docs/online_pdfs/valli_et_al_2023_tutorial_jek.pdf differ
diff --git a/docs/overrides/main.html b/docs/overrides/main.html
index f2915b1..43eddac 100644
--- a/docs/overrides/main.html
+++ b/docs/overrides/main.html
@@ -7,9 +7,12 @@
 {% block announce %}
   <div class="announcement-banner">
     <div class="announcement-content">
-      <h2>🎉🎉 Exciting News!</h2>
-      <p>We are thrilled to announce the publication of our Tutorial article!</p>
-      <a href="https://www.giacomovalli.com/openhdemg/isek_jek_tutorials/" class="btn btn-primary">Read it Now</a>
+      <h2 style="display: block;">2 X 🎉 == Double News!</h2>
+      <p style="display: inline;">The 4th openhdemg beta release is now available! => </p>
+      <a href="https://www.giacomovalli.com/openhdemg/what%27s-new/" class="btn btn-secondary" style="display: inline;">See what's new</a>
+      <br> <!-- New line -->
+      <p style="display: inline;">We are thrilled to announce the publication of our Tutorial article! => </p>
+      <a href="https://www.giacomovalli.com/openhdemg/isek_jek_tutorials/" class="btn btn-primary" style="display: inline;">Read it Now</a>
     </div>
   </div>
 {% endblock %}
diff --git a/docs/tutorials/setup_working_env.md b/docs/tutorials/setup_working_env.md
index d344c31..44fde9c 100644
--- a/docs/tutorials/setup_working_env.md
+++ b/docs/tutorials/setup_working_env.md
@@ -137,7 +137,7 @@ python -m openhdemg.gui.openhdemg_gui
 
 And the GUI will start:
 
-![openhdemg GUI](../md_graphics/index/gui_preview.png)
+![openhdemg GUI](../md_graphics/index/gui_preview_v2.png)
 
 If you instead want to write your own script using the functions contained in *openhdemg*, follow these steps:
 
@@ -189,7 +189,7 @@ This should solve the issue and you should now be able to activate your Virtual
 
 ### *openhdemg* Installation Issues
 
-Windows:
+Windows (1):
 
 If trying to install *openhdemg* via pip you see this (or a similar) message in the terminal:
 
@@ -199,6 +199,31 @@ ImportError: DLL load failed while importing _cext: The specified module could n
 
 the problem might be that you do not have the necessary Visual C++ Redistributable. This can be simply solved by visitng the [Microsoft website](https://learn.microsoft.com/en-US/cpp/windows/latest-supported-vc-redist?view=msvc-170){:target="_blank"} and dowloading and installing the latest Visual Studio 2015, 2017, 2019 and 2022 redistributable. Please note, this is a single redistributable, you don't need to perform multiple dowloads. This should solve the issue and you should now be able to pip install *openhdemg*. If that's not the case, continue reading.
 
+Windows (2):
+
+If trying to install *openhdemg* via pip you see this (or a similar) message in the terminal:
+
+```
+Microsoft Visual C++ 14.0 or greater is required.
+```
+
+The problem might be that you are missing the C++ build tools necessary to compile some code present in libraries such as pandas. To solve this, follow the steps below:
+
+1. visit [https://visualstudio.microsoft.com/visual-cpp-build-tools/](https://visualstudio.microsoft.com/visual-cpp-build-tools/){:target="_blank"}
+2. click the button "download build tools"
+3. install the tools
+4. once the installation is completed, you should see a window like this:
+
+![Microsoft_Visual_C_step_1](setup_working_env/screen_visualC14_1.png)
+
+If the window is different, there should be a button like "install". Clicking "install" or "modify", if present. This should bring you to the next window (it is also possible that you directly visualise the next window if no previous installation of these components is present in your computer):
+
+![Microsoft_Visual_C_step_2](setup_working_env/screen_visualC14_1.png)
+
+Here, make sure that you select C++ build tool (as in the figure) and install them. If you have windows 10, you may want to select "Windows 10 SDK 10.0.20348" on the right panel.
+
+This should solve the issue and you should now be able to pip install *openhdemg*. If that's not the case, continue reading.
+
 ## More questions?
 
 We hope that this tutorial was useful. If you need any additional information, do not hesitate to read the answers or ask a question in the [*openhdemg* discussion section](https://github.com/GiacomoValliPhD/openhdemg/discussions){:target="_blank"}. If you are not familiar with GitHub discussions, please read this [post](https://github.com/GiacomoValliPhD/openhdemg/discussions/42){:target="_blank"}. This will allow the *openhdemg* community to answer your questions.
diff --git a/docs/tutorials/setup_working_env/screen_visualC14_1.png b/docs/tutorials/setup_working_env/screen_visualC14_1.png
new file mode 100644
index 0000000..09b0d85
Binary files /dev/null and b/docs/tutorials/setup_working_env/screen_visualC14_1.png differ
diff --git a/docs/tutorials/setup_working_env/screen_visualC14_2.png b/docs/tutorials/setup_working_env/screen_visualC14_2.png
new file mode 100644
index 0000000..6f32cae
Binary files /dev/null and b/docs/tutorials/setup_working_env/screen_visualC14_2.png differ
diff --git a/docs/what's-new.md b/docs/what's-new.md
index 582c6fa..4224d8e 100644
--- a/docs/what's-new.md
+++ b/docs/what's-new.md
@@ -1,8 +1,68 @@
+## :octicons-tag-24: 0.1.0-beta.4
+:octicons-clock-24: April 2024 
+
+This release is aimed at increasing the robustness of the implemented functions and at improving the usability of the graphical user interface (GUI).
+
+### Backward Compatibility
+
+This version is fully backward compatible (with v0.1.0-beta.3) although the PNR (pulse to noise ratio) estimation might slightly differ from the previous versions.
+
+### Major Achievements
+- **Much faster**
+- **More accurate**
+- **More robust**
+- **More user-friendly**
+
+### Major Changes
+
+- **Restyled, debugged and more flexible GUI**:
+
+    - Almost all the functioning issues affecting the GUI have been solved. Enjoy a much better experience with this new release.
+    - New settings file: it is now possible to customise the functions behaviour directly from the GUI. This increases the number of possible analyses that can be performed from the GUI.
+    - New modern look
+    - Better responsiveness
+    - Expandable main window and figure
+    - New modular structure of the source code for easier implementation of new functionalities.
+
+- **Awesome performance improvements**:
+
+    - The estimation of MUs conduction velocity is 96% faster compared to the previous implementation, making it suitable also for the estimation of global conduction velocity.
+
+- **New functions**:
+
+    - The function `estimate_cv_via_mle` has been created to allow the user to estimate CV of any given signal with only 1 line of code.
+
+- **Updated functions**:
+    - The function `compute_sil` has a new argument that allows to include or exclude negative source values from the SIL estimation. This increases the estimation robustness when the source has large negative components.
+    - The function `compute_pnr` has a new argument that allows to select whether to cluster firings/noise via a heuristic penalty function or by using the provided discharge times.
+    - It is now possible to specify how to estimate accuracy in `emg_from_demuse`, `emg_from_otb` and `basic_mus_properties` based on the new implementation of `compute_sil` and `compute_pnr`.
+    - The function `sort_rawemg` now allows to sort channels based on custom orders with a custom number of empty channels.
+    - The function `resize_emgfile` now allows to select the area to resize based on the reference signal or on the mean EMG signal.
+    - The function `delete_mus` now allows to delete also the MUAPs computed in the Delsys software (stored in emgfile[“EXTRAS”]) simultaneously with the removal of MUs firing. This is now the default in the GUI.
+
+- **New modules**: With this release, we introduce dedicated test modules. These will progressively allow for automated extensive testing of the implemented functions and to exit the beta phase. Currently, test functions have been created for the `openfiles` module.
+
+### Other changes
+
+- It is now possible to pass OTB EXTRAS to the function `askopenfile`.
+- Improved readability of the string values in the docstrings and in the online documentation.
+
+### Tutorials
+
+- Added new troubleshooting in setup working env
+- Added new tutorial explaining how to use the GUI settings
+
+<br>
+
 ## :octicons-tag-24: 0.1.0-beta.3
 :octicons-clock-24: November 2023
 
 This release is focused on expanding the range of supported input files (decomposition outcomes) and to increase the speed and efficiency of the code. Furthermore, the introduction of a new backward compatibility module brings *openhdemg* a step closer to exiting the beta phase.
 
+### Backward Compatibility
+
+By default, the .json files saved from *openhdemg* version 0.1.0-beta.2 (released in September 2023) cannot be opened in *openhdemg* version 0.1.0-beta.3. However, these files can be easily converted to the newer file format thanks to the new backward compatibility module. We also created a [tutorials section](tutorials/convert_old_json_files.md) where the users are guided to the migration towards newer versions of the library. This will ensure easy migration to the latest *openhdemg* release.
+
 ### Major Achievements
 
 - **Extended input file compatibility**: now supporting a wider range of input files, making it easily accessible to anybody.
@@ -46,10 +106,6 @@ Added new tutorials explaining:
 - How to use the backward compatibility module to easily migrate to the newer *openhdemg* releases.
 - In the tutorial “Setup working environment,” we specified that *openhdemg* is currently working with Python up to the 3.11.x version. We are working to make it compatible with Python 3.12.
 
-### Backward Compatibility
-
-By default, the .json files saved from *openhdemg* version 0.1.0-beta.2 (released in September 2023) cannot be opened in *openhdemg* version 0.1.0-beta.3. However, these files can be easily converted to the newer file format thanks to the new backward compatibility module. We also created a [tutorials section](tutorials/convert_old_json_files.md) where the users are guided to the migration towards newer versions of the library. This will ensure easy migration to the latest *openhdemg* release.
-
 <br>
 
 ## :octicons-tag-24: 0.1.0-beta.2
diff --git a/mkdocs.yml b/mkdocs.yml
index 8d818aa..b537729 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -8,7 +8,7 @@ site_description: >-
 repo_name: openhdemg
 repo_url: https://github.com/GiacomoValliPhD/openhdemg
 
-copyright: Copyright &copy; 2022 - 2023. The openhdemg community
+copyright: Copyright &copy; 2022 - 2024. The openhdemg community
 
 theme:
   name: material
@@ -73,6 +73,7 @@ nav:
     - Intro: gui_intro.md
     - Basics: gui_basics.md
     - Advanced: gui_advanced.md
+    - Settings: gui_settings.md
   - What's New: what's-new.md
   - Contacts: contacts.md
   - Cite us: cite-us.md
@@ -108,8 +109,8 @@ markdown_extensions:
   - pymdownx.caret
   - pymdownx.details
   - pymdownx.emoji:
-      emoji_generator: !!python/name:materialx.emoji.to_svg
-      emoji_index: !!python/name:materialx.emoji.twemoji
+      emoji_index: !!python/name:material.extensions.emoji.twemoji
+      emoji_generator: !!python/name:material.extensions.emoji.to_svg
   - pymdownx.highlight:
       anchor_linenums: true
       line_spans: __span
@@ -118,8 +119,8 @@ markdown_extensions:
   - pymdownx.keys
   - pymdownx.magiclink:
       repo_url_shorthand: true
-      user: squidfunk
-      repo: mkdocs-material
+      user: GiacomoValliPhD
+      repo: openhdemg
   - pymdownx.mark
   - pymdownx.smartsymbols
   - pymdownx.superfences:
diff --git a/openhdemg/__init__.py b/openhdemg/__init__.py
index 3f54c02..578cf8c 100644
--- a/openhdemg/__init__.py
+++ b/openhdemg/__init__.py
@@ -1,3 +1,3 @@
 __all__ = ["__version__"]
 
-__version__ = "0.1.0-beta.3"
+__version__ = "0.1.0-beta.4"
diff --git a/openhdemg/gui/__init__.py b/openhdemg/gui/__init__.py
index 108304c..8b13789 100644
--- a/openhdemg/gui/__init__.py
+++ b/openhdemg/gui/__init__.py
@@ -1,1 +1,1 @@
-__all__ = ["openhdemg_gui"]
+
diff --git a/openhdemg/gui/backup_settings.py b/openhdemg/gui/backup_settings.py
new file mode 100644
index 0000000..fb335ea
--- /dev/null
+++ b/openhdemg/gui/backup_settings.py
@@ -0,0 +1,102 @@
+"""
+Module docstring explaining how to change the GUI settings.
+
+The settings can be related to both the GUI appearence and the analyses
+functions. Parameters for the analyses functions are clustered based on the
+openhdemg library's modules (here described as #----- MODULE NAME -----) and
+can be better known from the API section of the openhdemg website.
+
+Each parameter for the analyses functions is named FunctionName__Parameter.
+An extensive explanation of each "Parameter" can be found in the specific
+API module and in the specific "FunctionName".
+
+A tutorial on how to use this settings file is available at:
+https://www.giacomovalli.com/openhdemg/gui_settings/
+
+If you mess up with the settings, you can restore the default ones from
+backup_settings.py
+"""
+
+import numpy as np
+
+# --------------------------------- openfiles ---------------------------------
+
+# in emg_from_demuse()
+emg_from_demuse__ignore_negative_ipts = False
+
+# in emg_from_otb()
+emg_from_otb__ext_factor = 8
+emg_from_otb__refsig = [True, "fullsampled"]
+emg_from_otb__extras = None
+emg_from_otb__ignore_negative_ipts = False
+
+# in refsig_from_otb()
+refsig_from_otb__refsig = "fullsampled"
+refsig_from_otb__extras = None
+
+# in emg_from_delsys()
+emg_from_delsys__emg_sensor_name = "Galileo sensor"
+emg_from_delsys__refsig_sensor_name = "Trigno Load Cell"
+emg_from_delsys__filename_from = "mus_directory"
+
+# in refsig_from_delsys()
+refsig_from_delsys__refsig_sensor_name = "Trigno Load Cell"
+
+# in emg_from_customcsv()
+emg_from_customcsv__ref_signal = "REF_SIGNAL"
+emg_from_customcsv__raw_signal = "RAW_SIGNAL"
+emg_from_customcsv__ipts = "IPTS"
+emg_from_customcsv__mupulses = "MUPULSES"
+emg_from_customcsv__binary_mus_firing = "BINARY_MUS_FIRING"
+emg_from_customcsv__accuracy = "ACCURACY"
+emg_from_customcsv__extras = "EXTRAS"
+emg_from_customcsv__fsamp = 2048
+emg_from_customcsv__ied = 8
+
+# in refsig_from_customcsv()
+refsig_from_customcsv__ref_signal = "REF_SIGNAL"
+refsig_from_customcsv__extras = "EXTRAS"
+refsig_from_customcsv__fsamp = 2048
+
+# in save_json_emgfile()
+save_json_emgfile__compresslevel = 4
+
+
+# ---------------------------------- analysis ---------------------------------
+
+# in compute_thresholds()
+compute_thresholds__n_firings = 1
+
+# in basic_mus_properties()
+basic_mus_properties__n_firings_rt_dert = 1
+basic_mus_properties__accuracy = "default"
+basic_mus_properties__ignore_negative_ipts = False
+basic_mus_properties__constrain_pulses = [True, 3]
+
+
+# ----------------------------------- tools -----------------------------------
+
+# in resize_emgfile()
+resize_emgfile__how = "ref_signal"
+resize_emgfile__accuracy = "recalculate"
+resize_emgfile__ignore_negative_ipts = False
+
+
+# ------------------------------------ muap -----------------------------------
+# in tracking()
+tracking__firings = "all"
+tracking__derivation = "sd"
+
+# in remove_duplicates_between()
+remove_duplicates_between__firings = "all"
+remove_duplicates_between__derivation = "sd"
+
+# in MUcv_gui()
+MUcv_gui__n_firings = [0, 50]
+MUcv_gui__muaps_timewindow = 50
+MUcv_gui__figsize = [25, 20]
+
+# --------------------------------- electrodes --------------------------------
+# This custom sorting order is valid for all the GUI windows, although the
+# documentation is accessible in the api of the electrodes module.
+custom_sorting_order = None
diff --git a/openhdemg/gui/gui_files/Error.png b/openhdemg/gui/gui_files/Error.png
new file mode 100644
index 0000000..7d7700f
Binary files /dev/null and b/openhdemg/gui/gui_files/Error.png differ
diff --git a/openhdemg/gui/gui_files/Icon.ico b/openhdemg/gui/gui_files/Icon.ico
deleted file mode 100644
index 490e0ff..0000000
Binary files a/openhdemg/gui/gui_files/Icon.ico and /dev/null differ
diff --git a/openhdemg/gui/gui_files/Icon_transp.ico b/openhdemg/gui/gui_files/Icon_transp.ico
new file mode 100644
index 0000000..a5a8522
Binary files /dev/null and b/openhdemg/gui/gui_files/Icon_transp.ico differ
diff --git a/openhdemg/gui/gui_files/Logo_high_res.png b/openhdemg/gui/gui_files/Logo_high_res.png
new file mode 100644
index 0000000..191d8c6
Binary files /dev/null and b/openhdemg/gui/gui_files/Logo_high_res.png differ
diff --git a/openhdemg/gui/gui_files/gear.png b/openhdemg/gui/gui_files/gear.png
new file mode 100644
index 0000000..cea51cb
Binary files /dev/null and b/openhdemg/gui/gui_files/gear.png differ
diff --git a/openhdemg/gui/gui_files/gui_color_theme.json b/openhdemg/gui/gui_files/gui_color_theme.json
new file mode 100644
index 0000000..18c3da8
--- /dev/null
+++ b/openhdemg/gui/gui_files/gui_color_theme.json
@@ -0,0 +1,359 @@
+{
+  "CTk": {
+    "fg_color": [
+      "LightBlue4",
+      "LightBlue4"
+    ]
+  },
+  "CTkToplevel": {
+    "fg_color": [
+      "LightBlue4",
+      "LightBlue4"
+    ]
+  },
+  "CTkFrame": {
+    "corner_radius": 0,
+    "border_width": 0,
+    "fg_color": [
+      "LightBlue4",
+      "LightBlue4"
+    ],
+    "top_fg_color": [
+      "LightBlue4",
+      "LightBlue4"
+    ],
+    "border_color": [
+      "LightBlue4",
+      "LightBlue4"
+    ]
+  },
+  "CTkButton": {
+    "corner_radius": 6,
+    "border_width": 1,
+    "fg_color": [
+      "#E5E4E2",
+      "#E5E4E2"
+    ],
+    "hover_color": [
+      "#325882",
+      "#325882"
+    ],
+    "border_color": [
+      "#000000",
+      "#000000"
+    ],
+    "text_color": [
+      "#000000",
+      "#000000"
+    ],
+    "text_color_disabled": [
+      "gray74",
+      "gray60"
+    ]
+  },
+  "CTkLabel": {
+    "corner_radius": 0,
+    "fg_color": "transparent",
+    "text_color": [
+      "gray14",
+      "gray84"
+    ]
+  },
+  "CTkEntry": {
+    "corner_radius": 6,
+    "border_width": 2,
+    "fg_color": [
+      "#F9F9FA",
+      "#343638"
+    ],
+    "border_color": [
+      "#979DA2",
+      "#565B5E"
+    ],
+    "text_color": [
+      "gray14",
+      "gray84"
+    ],
+    "placeholder_text_color": [
+      "gray52",
+      "gray62"
+    ]
+  },
+  "CTkCheckBox": {
+    "corner_radius": 6,
+    "border_width": 3,
+    "fg_color": [
+      "#3a7ebf",
+      "#1f538d"
+    ],
+    "border_color": [
+      "#3E454A",
+      "#949A9F"
+    ],
+    "hover_color": [
+      "#325882",
+      "#14375e"
+    ],
+    "checkmark_color": [
+      "#DCE4EE",
+      "gray90"
+    ],
+    "text_color": [
+      "gray14",
+      "gray84"
+    ],
+    "text_color_disabled": [
+      "gray60",
+      "gray45"
+    ]
+  },
+  "CTkSwitch": {
+    "corner_radius": 1000,
+    "border_width": 3,
+    "button_length": 0,
+    "fg_color": [
+      "#939BA2",
+      "#4A4D50"
+    ],
+    "progress_color": [
+      "#3a7ebf",
+      "#1f538d"
+    ],
+    "button_color": [
+      "gray36",
+      "#D5D9DE"
+    ],
+    "button_hover_color": [
+      "gray20",
+      "gray100"
+    ],
+    "text_color": [
+      "gray14",
+      "gray84"
+    ],
+    "text_color_disabled": [
+      "gray60",
+      "gray45"
+    ]
+  },
+  "CTkRadioButton": {
+    "corner_radius": 1000,
+    "border_width_checked": 6,
+    "border_width_unchecked": 3,
+    "fg_color": [
+      "#3a7ebf",
+      "#1f538d"
+    ],
+    "border_color": [
+      "#3E454A",
+      "#949A9F"
+    ],
+    "hover_color": [
+      "#325882",
+      "#14375e"
+    ],
+    "text_color": [
+      "gray14",
+      "gray84"
+    ],
+    "text_color_disabled": [
+      "gray60",
+      "gray45"
+    ]
+  },
+  "CTkProgressBar": {
+    "corner_radius": 1000,
+    "border_width": 0,
+    "fg_color": [
+      "#585858",
+      "#585858"
+    ],
+    "progress_color": [
+      "#FFBF00",
+      "#FFBF00"
+    ],
+    "border_color": [
+      "black",
+      "black"
+    ]
+  },
+  "CTkSlider": {
+    "corner_radius": 1000,
+    "button_corner_radius": 1000,
+    "border_width": 6,
+    "button_length": 0,
+    "fg_color": [
+      "#939BA2",
+      "#4A4D50"
+    ],
+    "progress_color": [
+      "gray40",
+      "#AAB0B5"
+    ],
+    "button_color": [
+      "#3a7ebf",
+      "#1f538d"
+    ],
+    "button_hover_color": [
+      "#325882",
+      "#14375e"
+    ]
+  },
+  "CTkOptionMenu": {
+    "corner_radius": 6,
+    "fg_color": [
+      "#3a7ebf",
+      "#1f538d"
+    ],
+    "button_color": [
+      "#325882",
+      "#14375e"
+    ],
+    "button_hover_color": [
+      "#234567",
+      "#1e2c40"
+    ],
+    "text_color": [
+      "#DCE4EE",
+      "#DCE4EE"
+    ],
+    "text_color_disabled": [
+      "gray74",
+      "gray60"
+    ]
+  },
+  "CTkComboBox": {
+    "corner_radius": 6,
+    "border_width": 2,
+    "fg_color": [
+      "#F9F9FA",
+      "#343638"
+    ],
+    "border_color": [
+      "#979DA2",
+      "#565B5E"
+    ],
+    "button_color": [
+      "#979DA2",
+      "#565B5E"
+    ],
+    "button_hover_color": [
+      "#6E7174",
+      "#7A848D"
+    ],
+    "text_color": [
+      "gray14",
+      "gray84"
+    ],
+    "text_color_disabled": [
+      "gray50",
+      "gray45"
+    ]
+  },
+  "CTkScrollbar": {
+    "corner_radius": 1000,
+    "border_spacing": 4,
+    "fg_color": "transparent",
+    "button_color": [
+      "gray55",
+      "gray41"
+    ],
+    "button_hover_color": [
+      "gray40",
+      "gray53"
+    ]
+  },
+  "CTkSegmentedButton": {
+    "corner_radius": 6,
+    "border_width": 2,
+    "fg_color": [
+      "#979DA2",
+      "gray29"
+    ],
+    "selected_color": [
+      "#3a7ebf",
+      "#1f538d"
+    ],
+    "selected_hover_color": [
+      "#325882",
+      "#14375e"
+    ],
+    "unselected_color": [
+      "#979DA2",
+      "gray29"
+    ],
+    "unselected_hover_color": [
+      "gray70",
+      "gray41"
+    ],
+    "text_color": [
+      "#DCE4EE",
+      "#DCE4EE"
+    ],
+    "text_color_disabled": [
+      "gray74",
+      "gray60"
+    ]
+  },
+  "CTkTextbox": {
+    "corner_radius": 6,
+    "border_width": 0,
+    "fg_color": [
+      "gray100",
+      "gray20"
+    ],
+    "border_color": [
+      "#979DA2",
+      "#565B5E"
+    ],
+    "text_color": [
+      "gray14",
+      "gray84"
+    ],
+    "scrollbar_button_color": [
+      "gray55",
+      "gray41"
+    ],
+    "scrollbar_button_hover_color": [
+      "gray40",
+      "gray53"
+    ]
+  },
+  "CTkScrollableFrame": {
+    "label_fg_color": [
+      "gray80",
+      "gray21"
+    ]
+  },
+  "DropdownMenu": {
+    "fg_color": [
+      "gray90",
+      "gray20"
+    ],
+    "hover_color": [
+      "gray75",
+      "gray28"
+    ],
+    "text_color": [
+      "gray14",
+      "gray84"
+    ]
+  },
+  "CTkFont": {
+    "macOS": {
+      "family": "SF Display",
+      "size": 18,
+      "weight": "normal"
+    },
+    "Windows": {
+      "family": "Roboto",
+      "size": 18,
+      "weight": "normal"
+    },
+    "Linux": {
+      "family": "Roboto",
+      "size": 18,
+      "weight": "normal"
+    }
+  }
+}
\ No newline at end of file
diff --git a/openhdemg/gui/gui_files/logo.png b/openhdemg/gui/gui_files/logo.png
deleted file mode 100644
index b889201..0000000
Binary files a/openhdemg/gui/gui_files/logo.png and /dev/null differ
diff --git a/openhdemg/gui/gui_modules/__init__.py b/openhdemg/gui/gui_modules/__init__.py
new file mode 100644
index 0000000..847e263
--- /dev/null
+++ b/openhdemg/gui/gui_modules/__init__.py
@@ -0,0 +1,12 @@
+__all__ = ["edit_mus", "edit_refsig", "gui_helpers", "analyse_force",
+           "mu_properties", "gui_plotting", "advanced_analyses",
+           "error_handler"]
+
+from openhdemg.gui.gui_modules.edit_mus import *
+from openhdemg.gui.gui_modules.edit_sig import *
+from openhdemg.gui.gui_modules.gui_helpers import *
+from openhdemg.gui.gui_modules.analyse_force import *
+from openhdemg.gui.gui_modules.mu_properties import *
+from openhdemg.gui.gui_modules.gui_plotting import *
+from openhdemg.gui.gui_modules.advanced_analyses import *
+from openhdemg.gui.gui_modules.error_handler import *
diff --git a/openhdemg/gui/gui_modules/advanced_analyses.py b/openhdemg/gui/gui_modules/advanced_analyses.py
new file mode 100644
index 0000000..a4e95cc
--- /dev/null
+++ b/openhdemg/gui/gui_modules/advanced_analyses.py
@@ -0,0 +1,945 @@
+"""Module containing the advanced analysis options"""
+
+from tkinter import ttk, W, E, N, S, StringVar, BooleanVar
+import customtkinter as ctk
+import os
+from sys import platform
+from pandastable import Table
+import openhdemg.library as openhdemg
+from openhdemg.gui.gui_modules.error_handler import show_error_dialog
+
+
+class AdvancedAnalysis:
+    """
+    A class to manage advanced analysis tools in an openhdemg GUI application.
+
+    This class provides a window for conducting advanced analyses on EMG data.
+    It allows users to select and utilize different analysis tools and set
+    parameters for these tools. The class supports functionalities like motor
+    unit tracking, duplicate removal, and conduction velocity analysis.
+
+    Attributes
+    ----------
+    parent : object
+        The parent widget, typically the main application window, to which this
+        AdvancedAnalysis instance belongs.
+    matrix_rc_adv : StringVar
+        Tkinter StringVar for storing matrix rows and columns information.
+    filetype_adv : StringVar
+        Tkinter StringVar for storing the file type information.
+    threshold_adv : StringVar
+        Tkinter StringVar for storing the threshold value for analysis.
+    time_window : StringVar
+        Tkinter StringVar for storing the time window for analysis.
+    exclude_thres : BooleanVar
+        Tkinter BooleanVar to indicate if a threshold is to be excluded.
+    filter_adv : BooleanVar
+        Tkinter BooleanVar to indicate if filtering is applied.
+    show_adv : BooleanVar
+        Tkinter BooleanVar to indicate if advanced results are to be shown.
+    which_adv : StringVar
+        Tkinter StringVar for specifying which advanced analysis to perform.
+    emgfile1 : dict
+        Dictionary to store the first EMG file's data.
+    emgfile2 : dict
+        Dictionary to store the second EMG file's data.
+    extension_factor_adv : StringVar
+        Tkinter StringVar for storing the extension factor for analysis.
+
+    Methods
+    -------
+    __init__(self, parent)
+        Initialize a new instance of the AdvancedAnalysis class.
+    advanced_analysis(self)
+        Perform the selected advanced analysis based on user-defined
+        parameters.
+    on_matrix_none_adv(self, *args)
+        Callback function for handling changes in matrix code selection.
+
+    Examples
+    --------
+    >>> main_window = Tk()
+    >>> advanced_analysis = AdvancedAnalysis(main_window)
+    # Usage of advanced_analysis methods as required
+
+    Notes
+    -----
+    The class is designed to be a part of a larger GUI application and
+    interacts with EMG data and analysis tools. It depends on the state and
+    data of the `parent` widget.
+    """
+
+    def __init__(self, parent):
+        """
+        Initialize a new instance of the AdvancedAnalysis class.
+
+        Sets up a window with various controls for performing advanced EMG
+        data analyses. The method configures and places widgets for tool
+        selection, matrix orientation, matrix code, and an analysis button in
+        a grid layout. It also initializes several Tkinter StringVars and
+        BooleanVars for user inputs and settings.
+
+        Parameters
+        ----------
+        parent : object
+            The parent widget, usually the main application window, which
+            provides necessary context and data for the analysis functions.
+
+        Raises
+        ------
+        AttributeError
+            If certain operations are attempted without a loaded file or
+            necessary data.
+        """
+
+        # Define attributes for later usage not defined in init
+        self.matrix_rc_adv = StringVar()
+        self.filetype_adv = StringVar()
+        self.threshold_adv = StringVar()
+        self.time_window = StringVar()
+        self.exclude_thres = BooleanVar()
+        self.filter_adv = BooleanVar()
+        self.show_adv = BooleanVar()
+        self.which_adv = StringVar()
+        self.emgfile1 = {}
+        self.emgfile2 = {}
+        self.extension_factor_adv = StringVar()
+
+        # Initialize parent and load parent settings
+        self.parent = parent
+        self.parent.load_settings()
+
+        # Disable config for DELSYS files
+        try:
+            if self.parent.resdict["SOURCE"] == "DELSYS":
+                show_error_dialog(
+                    parent=self,
+                    error=None,
+                    solution=str(
+                        "Advanced Tools for Delsys are only accessible from "
+                        + "the library."
+                    ),
+                )
+                return
+        except AttributeError:
+            pass  # Allow to open advanced tools even if no file is loaded
+        except Exception as e:
+            show_error_dialog(
+                parent=self, error=e,
+                solution=str("An unexpected error occurred."),
+            )
+            return
+
+        # Open window
+        self.a_window = ctk.CTkToplevel(fg_color="LightBlue4")
+        self.a_window.title("Advanced Tools Window")
+
+        # Set window icon
+        head_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+        iconpath = head_path + "/gui_files/Icon_transp.ico"
+        self.a_window.iconbitmap(default=iconpath)
+        if platform.startswith("win"):
+            self.a_window.after(200, lambda: self.a_window.iconbitmap(iconpath))
+
+        self.a_window.grab_set()
+
+        # Set resizable window
+        # Configure columns with a loop
+        for col in range(3):
+            self.a_window.columnconfigure(col, weight=1)
+
+        # Configure rows with a loop
+        for row in range(8):
+            self.a_window.rowconfigure(row, weight=1)
+
+        # Add Label
+        ctk.CTkLabel(
+            self.a_window,
+            text="Select tool and matrix:",
+            font=("Segoe UI", 20, "underline"),
+            anchor="w",
+        ).grid(row=0, column=0)
+
+        # Analysis Tool
+        ctk.CTkLabel(
+            self.a_window, text="Analysis Tool", font=("Segoe UI", 18, "bold")
+        ).grid(row=2, column=0, sticky=(W, E))
+
+        # Add Selection Combobox
+        adv_box_values = (
+            "Motor Unit Tracking",
+            "Duplicate Removal",
+            "Conduction Velocity",
+        )
+        self.advanced_method = StringVar()
+        adv_box = ctk.CTkComboBox(
+            self.a_window,
+            width=200,
+            variable=self.advanced_method,
+            values=adv_box_values,
+            state="readonly",
+        )
+        adv_box.grid(row=2, column=1, sticky=(W, E))
+        self.advanced_method.set("Motor Unit Tracking")
+
+        # Matrix Orientation
+        ctk.CTkLabel(
+            self.a_window, text="Matrix Orientation",
+            font=("Segoe UI", 18, "bold"),
+        ).grid(row=3, column=0, sticky=(W, E))
+        self.mat_orientation_adv = StringVar()
+        orientation = ctk.CTkComboBox(
+            self.a_window,
+            width=100,
+            variable=self.mat_orientation_adv,
+            values=("0", "180"),
+            state="readonly",
+        )
+        orientation.grid(row=3, column=1, sticky=(W, E))
+        self.mat_orientation_adv.set("180")
+
+        # Matrix code
+        ctk.CTkLabel(
+            self.a_window, text="Matrix Code", font=("Segoe UI", 18, "bold")
+        ).grid(row=4, column=0, sticky=(W, E))
+        self.mat_code_adv = StringVar()
+        matrix_code_vals = (
+            "Custom order",
+            "None",
+            "GR08MM1305",
+            "GR04MM1305",
+            "GR10MM0808",
+        )
+        matrix_code = ctk.CTkComboBox(
+            self.a_window,
+            width=150,
+            variable=self.mat_code_adv,
+            values=matrix_code_vals,
+            state="readonly",
+        )
+        matrix_code.grid(row=4, column=1, sticky=(W, E))
+        self.mat_code_adv.set("GR08MM1305")
+
+        # Trace variable for updating window
+        self.mat_code_adv.trace_add("write", self.on_matrix_none_adv)
+
+        # Analysis Button
+        adv_button = ctk.CTkButton(
+            self.a_window,
+            text="Advanced Analysis",
+            command=self.advanced_analysis,
+            fg_color="#000000",
+            text_color="white",
+            border_color="white",
+            border_width=1,
+            hover_color="#FFBF00",
+        )
+        adv_button.grid(column=0, row=7)
+
+        # Add padding to widgets
+        for child in self.a_window.winfo_children():
+            child.grid_configure(padx=5, pady=5)
+
+    # -----------------------------------------------------------------------------------------------
+    # Advanced Analysis functionalities
+
+    def on_matrix_none_adv(self, *args):
+        """
+        Handle changes in the matrix code selection in the AdvancedAnalysis
+        GUI.
+
+        This callback function is triggered when the `mat_code_adv` variable
+        changes. It dynamically updates the GUI to add or remove an entry box
+        for specifying matrix rows and columns. When 'None' is selected for
+        the matrix code, it creates an entry box for the user to input the
+        rows and columns. Otherwise, it removes this entry box.
+
+        Parameters
+        ----------
+        *args : tuple
+            The arguments passed to the callback function. Not used in the
+            function but required for compatibility with Tkinter's trace
+            mechanism.
+
+        Notes
+        -----
+        The method is part of the AdvancedAnalysis class and interacts with
+        the GUI elements specific to advanced analysis options. It ensures
+        that the GUI is responsive to user selections and updates the
+        interface accordingly.
+        """
+
+        # Necessary to distinguish between None and other
+        if self.mat_code_adv.get() == "None":
+
+            # Set label for matrix rows and columns
+            self.mat_label_adv = ctk.CTkLabel(
+                self.a_window, text="Rows,Columns:",
+                font=("Segoe UI", 18, "bold"),
+            )
+            self.mat_label_adv.grid(row=5, column=1, sticky=W)
+
+            self.row_cols_entry_adv = ctk.CTkEntry(
+                self.a_window, width=80, textvariable=self.matrix_rc_adv
+            )
+            self.row_cols_entry_adv.grid(
+                row=6, column=1, sticky=W, padx=5, pady=2,
+            )
+            self.matrix_rc_adv.set("13,5")
+        else:
+            # Remove the elements
+            if hasattr(self, "row_cols_entry_adv"):
+                self.row_cols_entry_adv.grid_forget()
+                self.mat_label_adv.grid_forget()
+
+        # Update main advanced window based on selection
+        self.a_window.update_idletasks()
+
+    def advanced_analysis(self):
+        """
+        Open a top-level window based on the selected advanced analysis method.
+
+        This method is responsible for generating different GUI windows
+        depending on the advanced analysis option chosen by the user. It
+        dynamically creates GUI elements like dropdowns, buttons, and
+        checkboxes specific to the selected analysis tool, such as 'Motor Unit
+        Tracking', 'Conduction Velocity', or 'Duplicate Removal'.
+
+        Raises
+        ------
+        AttributeError
+            If a required file is not loaded prior to performing the analysis
+            or if invalid rows and columns arguments are entered for the
+            'Conduction Velocity' analysis.
+
+        Notes
+        -----
+        The method is part of the AdvancedAnalysis class and interacts with
+        other GUI elements and functionalities of the application. It ensures
+        that the GUI adapts to the user's choice of analysis, providing
+        relevant options and settings for each analysis type.
+        """
+
+        # Set head window
+        self.head = ctk.CTkToplevel(fg_color="LightBlue4")
+        self.head.title(self.advanced_method.get())
+
+        # Set window icon
+        head_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+        iconpath = head_path + "/gui_files/Icon_transp.ico"
+        self.head.iconbitmap(default=iconpath)
+        if platform.startswith("win"):
+            self.head.after(200, lambda: self.head.iconbitmap(iconpath))
+
+        self.head.grab_set()
+
+        # Set resizable window
+        # Configure columns with a loop
+        for col in range(3):
+            self.head.columnconfigure(col, weight=1)
+
+        # Configure rows with a loop
+        for row in range(17):
+            self.head.rowconfigure(row, weight=1)
+
+        # Specify Signal
+        signal_value = ("OTB", "DEMUSE", "OPENHDEMG", "CUSTOMCSV")
+        signal_entry = ctk.CTkComboBox(
+            self.head,
+            width=150,
+            variable=self.filetype_adv,
+            values=signal_value,
+            state="readonly",
+        )
+        signal_entry.grid(column=0, row=1, sticky=(W, E))
+        self.filetype_adv.set("Type of file")
+        self.filetype_adv.trace_add("write", self.on_filetype_change_adv)
+
+        # Load file
+        load1 = ctk.CTkButton(
+            self.head,
+            text="Load File 1",
+            command=self.open_emgfile1,
+        )
+        load1.grid(column=0, row=2, sticky=(W, E))
+
+        # Load file
+        load2 = ctk.CTkButton(
+            self.head,
+            text="Load File 2",
+            command=self.open_emgfile2,
+        )
+        load2.grid(column=0, row=3, sticky=(W, E))
+
+        # Threshold label
+        threshold_label = ctk.CTkLabel(
+            self.head, text="Threshold:", font=("Segoe UI", 18, "bold")
+        )
+        threshold_label.grid(column=0, row=9)
+
+        # Combobox for threshold
+        threshold_combobox = ctk.CTkComboBox(
+            self.head,
+            values=("0.6", "0.7", "0.8", "0.9"),
+            variable=self.threshold_adv,
+            state="readonly",
+            width=100,
+        )
+        threshold_combobox.grid(column=1, row=9)
+        self.threshold_adv.set("0.8")
+
+        # Time Label
+        time_window_label = ctk.CTkLabel(
+            self.head, text="Time window (ms):", font=("Segoe UI", 18, "bold")
+        )
+        time_window_label.grid(column=0, row=10)
+
+        # Time Combobox
+        time_combobox = ctk.CTkComboBox(
+            self.head,
+            values=("25", "30", "40", "50", "75", "100"),
+            variable=self.time_window,
+            state="readonly",
+            width=100,
+        )
+        time_combobox.grid(column=1, row=10)
+        self.time_window.set("50")
+
+        # Exclude below threshold
+        exclude_label = ctk.CTkLabel(
+            self.head, text="Exclude below threshold",
+            font=("Segoe UI", 18, "bold"),
+        )
+        exclude_label.grid(column=0, row=11)
+
+        # Add exclude checkbox
+        exclude_checkbox = ctk.CTkCheckBox(
+            self.head,
+            variable=self.exclude_thres,
+            bg_color="LightBlue4",
+            onvalue=True,
+            offvalue=False,
+            text="",
+        )
+        exclude_checkbox.grid(column=1, row=11)
+        self.exclude_thres.set(True)
+
+        # Filter
+        filter_label = ctk.CTkLabel(
+            self.head, text="Filter", font=("Segoe UI", 18, "bold")
+        )
+        filter_label.grid(column=0, row=12)
+
+        # Add filter checkbox
+        filter_checkbox = ctk.CTkCheckBox(
+            self.head,
+            variable=self.filter_adv,
+            bg_color="LightBlue4",
+            onvalue=True,
+            offvalue=False,
+            text="",
+        )
+        filter_checkbox.grid(column=1, row=12)
+        self.filter_adv.set(True)
+
+        # Show
+        show_label = ctk.CTkLabel(
+            self.head, text="Show", font=("Segoe UI", 18, "bold"),
+        )
+        show_label.grid(column=0, row=13)
+
+        # Add show checkbox
+        show_checkbox = ctk.CTkCheckBox(
+            self.head,
+            variable=self.show_adv,
+            bg_color="LightBlue4",
+            onvalue=True,
+            offvalue=False,
+            text="",
+        )
+        show_checkbox.grid(column=1, row=13)
+
+        # Add button to execute MU tracking
+        track_button = ctk.CTkButton(
+            self.head,
+            text="Track",
+            command=self.track_mus,
+        )
+        track_button.grid(column=0, row=15, columnspan=2, sticky=(W, E))
+
+        # Add padding
+        for child in self.head.winfo_children():
+            child.grid_configure(padx=5, pady=5)
+
+        # Add Which widget and update the track button
+        # to match functionalities required for duplicate removal
+        if self.advanced_method.get() == "Duplicate Removal":
+
+            # Add Which label
+            ctk.CTkLabel(
+                self.head, text="Which", font=("Segoe UI", 18, "bold")
+            ).grid(column=0, row=14)
+
+            # Combobox for Which option
+            which_combobox = ctk.CTkComboBox(
+                self.head,
+                values=["munumber", "accuracy"],
+                variable=self.which_adv,
+                state="readonly",
+                width=150,
+            )
+            which_combobox.grid(row=14, column=1, padx=5, pady=5)
+            self.which_adv.set("munumber")
+
+            # Add button to execute MU tracking
+            track_button.configure(
+                text="Remove Duplicates",
+                command=self.remove_duplicates_between,
+            )
+
+        if self.advanced_method.get() == "Conduction Velocity":
+            try:
+                # Destroy unnecessary pop-ups
+                self.head.destroy()
+                self.a_window.destroy()
+
+                if self.mat_code_adv.get() == "None":
+
+                    # Get rows and columns and turn into list
+                    list_rcs = [int(i) for i in self.matrix_rc_adv.get().split(",")]
+
+                    try:
+                        # Sort emg file
+                        sorted_rawemg = openhdemg.sort_rawemg(
+                            self.parent.resdict,
+                            code="None",
+                            n_rows=list_rcs[0],
+                            n_cols=list_rcs[1],
+                        )
+                    except ValueError as e:
+                        show_error_dialog(
+                            parent=self,
+                            error=e,
+                            solution=str(
+                                "Number of specified rows and columns must "
+                                + "match the number of channels."
+                            ),
+                        )
+                        return
+
+                elif self.mat_code_adv.get() == "Custom order":
+                    try:
+                        # Sort emg file
+                        sorted_rawemg = openhdemg.sort_rawemg(
+                            self.parent.resdict,
+                            code="Custom order",
+                            custom_sorting_order=self.parent.settings.custom_sorting_order,
+                        )
+                    except ValueError as e:
+                        show_error_dialog(
+                            parent=self,
+                            error=e,
+                            solution=str(
+                                "Number of rows * columns must match the "
+                                + "number of channels. Verify "
+                                + "custom_sorting_order in settings."
+                            ),
+                        )
+                        return
+
+                else:
+                    # Sort emg file
+                    sorted_rawemg = openhdemg.sort_rawemg(
+                        self.parent.resdict,
+                        code=self.mat_code_adv.get(),
+                        orientation=int(self.mat_orientation_adv.get()),
+                    )
+
+                openhdemg.MUcv_gui(
+                    emgfile=self.parent.resdict,
+                    sorted_rawemg=sorted_rawemg,
+                    n_firings=self.parent.settings.MUcv_gui__n_firings,
+                    muaps_timewindow=self.parent.settings.MUcv_gui__muaps_timewindow,
+                    figsize=self.parent.settings.MUcv_gui__figsize,
+                )
+
+            except AttributeError as e:
+                show_error_dialog(
+                    parent=self,
+                    error=e,
+                    solution=str(
+                        "Please make sure to load a file  in the main window "
+                        + "for Conduction velocity calculation."
+                    ),
+                )
+                self.head.destroy()
+
+            except ValueError as e:
+                show_error_dialog(
+                    parent=self,
+                    error=e,
+                    solution=str(
+                        "Please make sure to enter valid Rows,Columns arguments."
+                        + " Arguments must be non-negative and seperated by `,`."
+                    ),
+                )
+                self.head.destroy()
+
+        # Destroy first window to avoid too many pop-ups
+        self.a_window.withdraw()  # TODO check for reference as destroy will evoke tcl error upon continuing, whereas withdraw creates issue when closing.
+
+    ### Define function for advanced analysis tools
+    def open_emgfile1(self):
+        """
+        Open EMG file based on the selected file type.
+
+        This function is used to open and store the first emgfile that is
+        required for the MU tracking. As both files required are loaded by
+        different buttons, two functions storing the two files were created.
+
+        See Also
+        --------
+        open_emgfile1(), openhdemg.askopenfile()
+        """
+
+        try:
+            if self.filetype_adv.get() == "OTB":
+                self.emgfile1 = openhdemg.askopenfile(
+                    filesource=self.filetype_adv.get(),
+                    ignore_negative_ipts=self.parent.settings.emg_from_otb__ignore_negative_ipts,
+                    otb_ext_factor=self.parent.settings.emg_from_otb__ext_factor,
+                    otb_refsig_type=self.parent.settings.emg_from_otb__refsig,
+                    otb_extras=self.parent.settings.emg_from_otb__extras,
+                )
+            elif self.filetype_adv.get() == "DEMUSE":
+                self.emgfile1 = openhdemg.askopenfile(
+                    filesource=self.filetype_adv.get(),
+                    ignore_negative_ipts=self.parent.settings.emg_from_demuse__ignore_negative_ipts,
+                )
+            elif self.filetype_adv.get() == "CUSTOMCSV":
+                self.emgfile1 = openhdemg.askopenfile(
+                    filesource=self.filetype_adv.get(),
+                    custom_ref_signal=self.parent.settings.emg_from_customcsv__ref_signal,
+                    custom_raw_signal=self.parent.settings.emg_from_customcsv__raw_signal,
+                    custom_ipts=self.parent.settings.emg_from_customcsv__ipts,
+                    custom_mupulses=self.parent.settings.emg_from_customcsv__mupulses,
+                    custom_binary_mus_firing=self.parent.settings.emg_from_customcsv__binary_mus_firing,
+                    custom_accuracy=self.parent.settings.emg_from_customcsv__accuracy,
+                    custom_extras=self.parent.settings.emg_from_customcsv__extras,
+                    custom_fsamp=self.parent.settings.emg_from_customcsv__fsamp,
+                    custom_ied=self.parent.settings.emg_from_customcsv__ied,
+                )
+            else:  # OPENHDEMG
+                self.emgfile1 = openhdemg.askopenfile(
+                    filesource=self.filetype_adv.get(),
+                )
+
+            # Add filename to GUI
+            ctk.CTkLabel(
+                self.head, text="File 1 loaded", font=("Segoe UI", 15, "bold")
+            ).grid(column=1, row=2)
+
+        except Exception as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str(
+                    "Make sure to specify a valid filetype or correct settings"
+                ),
+            )
+
+    def open_emgfile2(self):
+        """
+        Open EMG file based on the selected file type.
+
+        This function is used to open and store the first emgfile that is
+        required for the MU tracking. As both files required are loaded by
+        different buttons, two functions storing the two files were created.
+
+        See Also
+        --------
+        open_emgfile1(), openhdemg.askopenfile()
+        """
+
+        try:
+            if self.filetype_adv.get() == "OTB":
+                self.emgfile2 = openhdemg.askopenfile(
+                    filesource=self.filetype_adv.get(),
+                    ignore_negative_ipts=self.parent.settings.emg_from_otb__ignore_negative_ipts,
+                    otb_ext_factor=self.parent.settings.emg_from_otb__ext_factor,
+                    otb_refsig_type=self.parent.settings.emg_from_otb__refsig,
+                    otb_extras=self.parent.settings.emg_from_otb__extras,
+                )
+            elif self.filetype_adv.get() == "DEMUSE":
+                self.emgfile2 = openhdemg.askopenfile(
+                    filesource=self.filetype_adv.get(),
+                    ignore_negative_ipts=self.parent.settings.emg_from_demuse__ignore_negative_ipts,
+                )
+            elif self.filetype_adv.get() == "CUSTOMCSV":
+                self.emgfile2 = openhdemg.askopenfile(
+                    filesource=self.filetype_adv.get(),
+                    custom_ref_signal=self.parent.settings.emg_from_customcsv__ref_signal,
+                    custom_raw_signal=self.parent.settings.emg_from_customcsv__raw_signal,
+                    custom_ipts=self.parent.settings.emg_from_customcsv__ipts,
+                    custom_mupulses=self.parent.settings.emg_from_customcsv__mupulses,
+                    custom_binary_mus_firing=self.parent.settings.emg_from_customcsv__binary_mus_firing,
+                    custom_accuracy=self.parent.settings.emg_from_customcsv__accuracy,
+                    custom_extras=self.parent.settings.emg_from_customcsv__extras,
+                    custom_fsamp=self.parent.settings.emg_from_customcsv__fsamp,
+                    custom_ied=self.parent.settings.emg_from_customcsv__ied,
+                )
+            else:  # OPENHDEMG
+                self.emgfile2 = openhdemg.askopenfile(
+                    filesource=self.filetype_adv.get(),
+                )
+
+            # Add filename to GUI
+            ctk.CTkLabel(
+                self.head, text="File 2 loaded", font=("Segoe UI", 15, "bold")
+            ).grid(column=1, row=3)
+
+        except Exception as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str(
+                    "Make sure to specify a valid filetype or correct settings"
+                ),
+            )
+
+    def on_filetype_change_adv(self, *args):
+        """
+        Handle changes in the file type selection in the AdvancedAnalysis GUI.
+
+        This callback function is triggered when the `filetype_adv` variable
+        changes. If filetype is set to != "OPENHDEMG", it will create a second
+        combobox on the grid at column 0 and row 2 and when the filetype is
+        set to "OPENHDEMG" it will remove the second combobox from the grid.
+
+        Parameters
+        ----------
+        *args : tuple
+            The arguments passed to the callback function. Not used in the
+            function but required for compatibility with Tkinter's trace
+            mechanism.
+
+        Notes
+        -----
+        The method is part of the AdvancedAnalysis class and interacts with
+        the GUI elements specific to file type selection. It ensures that the
+        GUI is responsive to user selections and updates the interface
+        accordingly.
+        """
+
+        # Make sure to forget all the previous labels
+        if hasattr(self, "adv_verify_settings_text"):
+            self.adv_verify_settings_text.grid_forget()
+
+        if self.filetype_adv.get() != "OPENHDEMG":
+            # Display the text: Verify openfiles settings!
+            self.adv_verify_settings_text = ctk.CTkLabel(
+                self.head,
+                text="Verify openfiles settings!",
+                font=("Segoe UI", 12, "bold"),
+                text_color="black",
+            )
+            self.adv_verify_settings_text.grid(
+                column=1, row=1, sticky=(W, E), padx=5,
+            )
+
+    def track_mus(self):
+        """
+        Perform MUs tracking on the loaded EMG files.
+
+        Notes
+        -----
+        The function uses the openhdemg.tracking
+        function to perform the tracking of MUs.
+
+        Raises
+        ------
+        AttributeError
+            If the required EMG files have not been loaded.
+        ValueError
+            If the input parameters are not valid.
+
+        See Also
+        --------
+        openhdemg.tracking()
+        """
+
+        # Reload settings for tracking
+        self.parent.load_settings()
+
+        try:
+            if self.mat_code_adv.get() == "None":
+                # Get rows and columns and turn into list
+                list_rcs = [int(i) for i in self.matrix_rc_adv.get().split(",")]
+                n_rows = list_rcs[0]
+                n_cols = list_rcs[1]
+            else:
+                n_rows = None
+                n_cols = None
+        except ValueError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Verify that Rows and Columns are separated by ','"),
+            )
+
+        try:
+            # Track motor units
+            tracking_res = openhdemg.tracking(
+                emgfile1=self.emgfile1,
+                emgfile2=self.emgfile2,
+                firings=self.parent.settings.tracking__firings,
+                derivation=self.parent.settings.tracking__derivation,
+                threshold=float(self.threshold_adv.get()),
+                timewindow=int(self.time_window.get()),
+                matrixcode=self.mat_code_adv.get(),
+                orientation=int(self.mat_orientation_adv.get()),
+                n_rows=n_rows,
+                n_cols=n_cols,
+                custom_sorting_order=self.parent.settings.custom_sorting_order,
+                exclude_belowthreshold=self.exclude_thres.get(),
+                filter=self.filter_adv.get(),
+                show=self.show_adv.get(),
+            )
+
+            # Add result terminal
+            track_terminal = ttk.LabelFrame(
+                self.head, text="MUs Tracking Result", height=100,
+                relief="ridge",
+            )
+            track_terminal.grid(
+                column=2,
+                row=0,
+                columnspan=2,
+                rowspan=14,
+                pady=8,
+                padx=10,
+                sticky=(N, S, W, E),
+            )
+
+            # Add table containing results to the label frame
+            track_table = Table(track_terminal, dataframe=tracking_res)
+            track_table.show()
+
+        except AttributeError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str(
+                    "Make sure to load all required EMG files prior to tracking."
+                ),
+            )
+
+        except ValueError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str(
+                    "Enter valid input parameters."
+                    + "\nPotenital error sources:"
+                    + "\n - Extension Factor (in case of OTB file)"
+                    + "\n - Matrix Code"
+                    + "\n - Matrix Orientation"
+                    + "\n - Threshold"
+                    + "\n - Rows,Columns"
+                    + "\n - custom_sorting_order in settings"
+                ),
+            )
+
+    def remove_duplicates_between(self):
+        """
+        Perform duplicate removal between two EMG files.
+
+        Notes
+        -----
+        The function uses the openhdemg.remove_duplicates_between function to
+        remove duplicates between two EMG files. If the required parameters
+        are not provided, the function will raise an AttributeError or
+        ValueError.
+
+        Raises
+        ------
+        AttributeError
+            If the required EMG files have not been loaded.
+        ValueError
+            If the input parameters are not valid.
+
+        See Also
+        --------
+        openhdemg.remove_duplicates_between(), openhdemg.asksavefile()
+        """
+
+        try:
+            if self.mat_code_adv.get() == "None":
+                # Get rows and columns and turn into list
+                list_rcs = [int(i) for i in self.matrix_rc_adv.get().split(",")]
+                n_rows = list_rcs[0]
+                n_cols = list_rcs[1]
+            else:
+                n_rows = None
+                n_cols = None
+        except ValueError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Verify that Rows and Columns are separated by ','"),
+            )
+
+        try:
+            # Remove motor unit duplicates
+            emg_file1, emg_file2, _ = openhdemg.remove_duplicates_between(
+                emgfile1=self.emgfile1,
+                emgfile2=self.emgfile2,
+                firings=self.parent.settings.remove_duplicates_between__firings,
+                derivation=self.parent.settings.remove_duplicates_between__derivation,
+                timewindow=int(self.time_window.get()),
+                threshold=float(self.threshold_adv.get()),
+                matrixcode=self.mat_code_adv.get(),
+                orientation=int(self.mat_orientation_adv.get()),
+                n_rows=n_rows,
+                n_cols=n_cols,
+                custom_sorting_order=self.parent.settings.custom_sorting_order,
+                filter=self.filter_adv.get(),
+                show=self.show_adv.get(),
+                which=self.which_adv.get(),
+            )
+
+            # Save files
+            openhdemg.asksavefile(
+                emg_file1,
+                compresslevel=self.parent.settings.save_json_emgfile__compresslevel,
+            )
+            openhdemg.asksavefile(
+                emg_file2,
+                compresslevel=self.parent.settings.save_json_emgfile__compresslevel,
+            )
+
+        except AttributeError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str(
+                    "Make sure to load all required EMG files prior to tracking."
+                ),
+            )
+
+        except ValueError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str(
+                    "Enter valid input parameters."
+                    + "\nPotenital error sources:"
+                    + "\n - Extension Factor (in case of OTB file)"
+                    + "\n - Matrix Code"
+                    + "\n - Matrix Orientation"
+                    + "\n - Threshold"
+                    + "\n - Which"
+                    + "\n - Rows,Columns"
+                    + "\n - custom_sorting_order in settings"
+                ),
+            )
diff --git a/openhdemg/gui/gui_modules/analyse_force.py b/openhdemg/gui/gui_modules/analyse_force.py
new file mode 100644
index 0000000..bd8fde1
--- /dev/null
+++ b/openhdemg/gui/gui_modules/analyse_force.py
@@ -0,0 +1,202 @@
+"""Module containing the force analysis GUI"""
+
+from tkinter import ttk, W, E, StringVar
+from sys import platform
+import os
+import customtkinter as ctk
+import pandas as pd
+import openhdemg.library as openhdemg
+from openhdemg.gui.gui_modules.error_handler import show_error_dialog
+
+
+class AnalyseForce:
+    """
+    A class for conducting force analysis in an openhdemg GUI application.
+
+    This class provides a window for analyzing force signals. It includes
+    functionalities for calculating Maximum Voluntary Contraction (MVC) and
+    Rate of Force Development (RFD). The class is activated through the
+    "Analyse Force" button in the main GUI window.
+
+    Attributes
+    ----------
+    parent : object
+        The parent widget, typically the main application window, to which
+        this AnalyseForce instance belongs.
+    head : CTkToplevel
+        The top-level widget for the Force Analysis window.
+    rfdms : StringVar
+        Tkinter StringVar to store the milliseconds value for Rate of Force
+        Development (RFD) analysis.
+
+    Methods
+    -------
+    __init__(self, parent)
+        Initialize a new instance of the AnalyseForce class.
+    get_mvc(self)
+        Calculate and display the Maximum Voluntary Contraction (MVC).
+    get_rfd(self)
+        Calculate and display the Rate of Force Development (RFD) over
+        specified milliseconds.
+
+    Examples
+    --------
+    >>> main_window = Tk()
+    >>> force_analysis = AnalyseForce(main_window)
+    >>> force_analysis.head.mainloop()
+
+    Notes
+    -----
+    The class is designed to be a part of a larger GUI application and
+    interacts with force signal data accessible via the `parent` widget.
+    """
+
+    def __init__(self, parent):
+        """
+        Initialize a new instance of the AnalyseForce class.
+
+        This method sets up the GUI components for the Force Analysis Window.
+        It includes buttons for calculating MVC and RFD, and an entry field
+        for specifying RFD milliseconds. The method configures and places
+        various widgets such as labels, buttons, and entry fields in a grid
+        layout for user interaction.
+
+        Parameters
+        ----------
+        parent : object
+            The parent widget, typically the main application window, to which
+            this AnalyseForce instance belongs. The parent is used for
+            accessing shared resources and data.
+
+        Raises
+        ------
+        AttributeError
+            If certain widgets or properties are not properly instantiated due
+            to missing parent configurations or resources.
+        """
+
+        # Initialize parent and load parent settings
+        self.parent = parent
+        self.parent.load_settings()
+
+        # Create new window
+        self.head = ctk.CTkToplevel()
+        self.head.title("Force Analysis Window")
+
+        # Set window icon
+        head_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+        iconpath = head_path + "/gui_files/Icon_transp.ico"
+        self.head.iconbitmap(iconpath)
+        if platform.startswith("win"):
+            self.head.after(200, lambda: self.head.iconbitmap(iconpath))
+
+        self.head.grab_set()
+
+        # Set resizable window
+        # Configure columns with a loop
+        for col in range(3):
+            self.head.columnconfigure(col, weight=1)
+
+        # Configure rows with a loop
+        for row in range(10):
+            self.head.rowconfigure(row, weight=1)
+
+        # Get MVC
+        get_mvf = ctk.CTkButton(
+            self.head, text="Get MVC", command=self.get_mvc,
+        )
+        get_mvf.grid(column=0, row=1, sticky=(W, E), padx=5, pady=5)
+
+        # Get RFD
+        separator1 = ttk.Separator(self.head, orient="horizontal")
+        separator1.grid(
+            column=0, columnspan=3, row=2, sticky=(W, E), padx=5, pady=5,
+        )
+
+        ctk.CTkLabel(
+            self.head, text="RFD miliseconds", font=("Segoe UI", 18, "bold")
+        ).grid(column=1, row=3, sticky=(W, E), padx=5, pady=5)
+
+        get_rfd = ctk.CTkButton(
+            self.head, text="Get RFD", command=self.get_rfd,
+        )
+        get_rfd.grid(column=0, row=4, sticky=(W, E), padx=5, pady=5)
+
+        self.rfdms = StringVar()
+        milisecond = ctk.CTkEntry(
+            self.head, width=100, textvariable=self.rfdms,
+        )
+        milisecond.grid(column=1, row=4, sticky=(W, E), padx=5, pady=5)
+        self.rfdms.set("50,100,150,200")
+
+    ### Define functions for force analysis
+
+    def get_mvc(self):
+        """
+        Instance methof to retrieve calculated MVC based on user selection.
+
+        Executed when button "Get MVC" in Analyze Force window is pressed.
+        The Results of the analysis are displayed in the results terminal.
+
+        Raises
+        ------
+        AttributeError
+            When no file is loaded prior to analysis.
+
+        See Also
+        --------
+        get_mvc in the library
+        """
+
+        try:
+            # get MVC
+            mvc = openhdemg.get_mvc(emgfile=self.parent.resdict)
+            # Define dictionary for pandas
+            mvc_dic = {"MVC": [mvc]}
+            self.parent.mvc_df = pd.DataFrame(data=mvc_dic)
+            # Display results
+            self.parent.display_results(input_df=self.parent.mvc_df)
+
+        except AttributeError as e:
+            show_error_dialog(
+                parent=self, error=e,
+                solution=str("Make sure a file is loaded."),
+            )
+
+    def get_rfd(self):
+        """
+        Instance method to calculate RFD at specified timepoints based on user
+        selection.
+
+        Executed when button "Get RFD" in Analyze Force window is pressed.
+        The Results of the analysis are displayed in the results terminal.
+
+        Raises
+        ------
+        AttributeError
+            When no file is loaded prior to analysis.
+
+        See Also
+        --------
+        get_rfd in library
+        """
+
+        try:
+            # Define list for RFD computation
+            ms = str(self.rfdms.get())
+            # Split the string at ,
+            ms_list = ms.split(",")
+            # Use comprehension to iterate through
+            ms_list = [int(i) for i in ms_list]
+            # Calculate rfd
+            self.parent.rfd = openhdemg.compute_rfd(
+                emgfile=self.parent.resdict, ms=ms_list
+            )
+            # Display results
+            self.parent.display_results(input_df=self.parent.rfd)
+
+        except AttributeError as e:
+            show_error_dialog(
+                parent=self, error=e,
+                solution=str("Make sure a file is loaded."),
+            )
diff --git a/openhdemg/gui/gui_modules/edit_mus.py b/openhdemg/gui/gui_modules/edit_mus.py
new file mode 100644
index 0000000..0a5d8fa
--- /dev/null
+++ b/openhdemg/gui/gui_modules/edit_mus.py
@@ -0,0 +1,257 @@
+"""Module containing the MU Removal GUI class"""
+
+import os
+from sys import platform
+from tkinter import E, StringVar, W
+
+import customtkinter as ctk
+
+import openhdemg.library as openhdemg
+from openhdemg.gui.gui_modules.error_handler import show_error_dialog
+
+
+class MURemovalWindow:
+    """
+    A class for managing the removal of motor units (MUs) in a GUI application.
+
+    This class creates a window that offers options to select and remove
+    specific MUs. It is activated from the main GUI window and is intended to
+    provide functionalities for manipulating motor unit data. The class raises
+    an AttributeError if it is instantiated without a loaded file for analysis.
+
+    Attributes
+    ----------
+    parent : object
+        The parent widget, typically the main application window, to which
+        this MURemovalWindow instance belongs.
+    resdict : dict
+        A dictionary containing relevant data and settings, including the
+        number of MUs.
+    head : CTkToplevel
+        The top-level widget for the Motor Unit Removal window.
+    mu_to_remove : StringVar
+        Tkinter StringVar to store the ID of the motor unit selected for
+        removal.
+
+    Methods
+    -------
+    __init__(self, parent, resdict)
+        Initialize a new instance of the MURemovalWindow class.
+    remove(self)
+        Remove the selected motor unit from the analysis.
+    remove_empty(self)
+        Remove all motor units that are empty or have no data.
+
+    Examples
+    --------
+    >>> main_window = Tk()
+    >>> resdict = {"NUMBER_OF_MUS": 10}  # Example resdict
+    >>> mu_removal_window = MURemovalWindow(main_window, resdict)
+    >>> mu_removal_window.head.mainloop()
+
+    Raises
+    ------
+    AttributeError
+        When no file is loaded prior to analysis.
+
+    Notes
+    -----
+    The class is designed to interact with the data structure provided by the
+    `resdict` attribute, which is expected to contain specific keys and values
+    relevant to the MU analysis.
+    """
+
+    def __init__(self, parent):
+        """
+        Initialize a new instance of the MURemovalWindow class.
+
+        This method sets up the GUI components for the Motor Unit Removal
+        Window. It includes a dropdown menu to select a motor unit (MU) for
+        removal and buttons to remove either the selected MU or all empty MUs.
+        The method configures and places various widgets such as labels,
+        comboboxes, and buttons in a grid layout for user interaction.
+
+        Parameters
+        ----------
+        parent : object
+            The parent widget, typically the main application window, to which
+            this MURemovalWindow instance belongs. The parent is used for
+            accessing shared resources and data.
+
+        Raises
+        ------
+        AttributeError
+            If certain widgets or properties are not properly instantiated due
+            to missing parent configurations or resources.
+        """
+
+        try:
+            # Initialize parent and load parent settings
+            self.parent = parent
+            self.parent.load_settings()
+
+            # Create new window
+            self.head = ctk.CTkToplevel()
+            # Set the background color of the top-level window
+            self.head.title("Motor Unit Removal Window")
+
+            # Set the icon for the window
+            head_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+            iconpath = head_path + "/gui_files/Icon_transp.ico"
+            self.head.iconbitmap(iconpath)
+            if platform.startswith("win"):
+                self.head.after(200, lambda: self.head.iconbitmap(iconpath))
+
+            self.head.grab_set()
+
+            # Set resizable window
+            # Configure columns with a loop
+            for col in range(3):
+                self.head.columnconfigure(col, weight=1)
+
+            # Configure rows with a loop
+            for row in range(10):
+                self.head.rowconfigure(row, weight=1)
+
+            # Select Motor Unit
+            ctk.CTkLabel(
+                self.head, text="Select MU:", font=("Segoe UI", 15, "bold")
+            ).grid(column=1, row=0, padx=5, pady=5, sticky=W)
+
+            self.mu_to_remove = StringVar()
+            removed_mu_value = [*range(0, self.parent.resdict["NUMBER_OF_MUS"])]
+            removed_mu_value = list(map(str, removed_mu_value))
+            removed_mu = ctk.CTkComboBox(
+                self.head,
+                width=10,
+                variable=self.mu_to_remove,
+                values=removed_mu_value,
+                state="readonly",
+            )
+            removed_mu.grid(
+                column=1, row=1, columnspan=2, sticky=(W, E), padx=5, pady=5
+            )
+
+            # Remove Motor unit
+            remove = ctk.CTkButton(
+                self.head,
+                text="Remove MU",
+                command=self.remove,
+            )
+            remove.grid(column=1, row=2, sticky=(W, E), padx=5, pady=5)
+
+            # Remove empty MUs
+            remove_empty = ctk.CTkButton(
+                self.head,
+                text="Remove empty MUs",
+                command=self.remove_empty,
+            )
+            remove_empty.grid(column=2, row=2, padx=5, pady=5)
+
+        except AttributeError as e:
+            self.head.destroy()
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Make sure a file is loaded."),
+            )
+
+    def remove(self):
+        """
+        Instance method that actually removes a selected motor unit based on
+        user specification.
+
+        Executed when button "Remove MU" in Motor Unit Removal Window is
+        pressed. The emgfile and the plot are subsequently updated.
+
+        See Also
+        --------
+        delete_mus in library.
+        """
+
+        try:
+            # Get resdict with MU removed
+            self.parent.resdict = openhdemg.delete_mus(
+                emgfile=self.parent.resdict,
+                munumber=int(self.mu_to_remove.get()),
+            )
+            # Upate MU number
+            self.parent.n_of_mus.configure(
+                text="N of MUs: " + str(self.parent.resdict["NUMBER_OF_MUS"]),
+                font=("Segoe UI", 15, "bold"),
+            )
+
+            # Update selection field
+            self.mu_to_remove = StringVar()
+            removed_mu_value = [*range(0, self.parent.resdict["NUMBER_OF_MUS"])]
+            removed_mu_value = list(map(str, removed_mu_value))
+            removed_mu = ctk.CTkComboBox(
+                self.head,
+                width=10,
+                variable=self.mu_to_remove,
+                values=removed_mu_value,
+            )
+            removed_mu.configure(state="readonly")
+            removed_mu.grid(
+                column=1, row=1, columnspan=2, sticky=(W, E), padx=5, pady=5
+            )
+
+            # Update plot
+            if hasattr(self.parent, "fig"):
+                self.parent.in_gui_plotting(resdict=self.parent.resdict)
+
+        except AttributeError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Make sure a file is loaded."),
+            )
+
+    def remove_empty(self):
+        """
+        Instance method that removes all empty MUs.
+
+        Executed when button "Remove empty MUs" in Motor Unit Removal Window
+        is pressed. The emgfile and the plot are subsequently updated.
+
+        See Also
+        --------
+        delete_empty_mus in library.
+        """
+
+        try:
+            # Get resdict with MU removed
+            self.parent.resdict = openhdemg.delete_empty_mus(
+                self.parent.resdict,
+            )
+
+            # Upate MU number
+            self.parent.n_of_mus.configure(
+                text="N of MUs: " + str(self.parent.resdict["NUMBER_OF_MUS"]),
+                font=("Segoe UI", 15, "bold"),
+            )
+            # Update selection field
+            self.mu_to_remove = StringVar()
+            removed_mu_value = [*range(0, self.parent.resdict["NUMBER_OF_MUS"])]
+            removed_mu_value = list(map(str, removed_mu_value))
+            removed_mu = ctk.CTkComboBox(
+                self.head,
+                width=10,
+                variable=self.mu_to_remove,
+                values=removed_mu_value,
+            )
+            removed_mu.configure(state="readonly")
+            removed_mu.grid(
+                column=1, row=1, columnspan=2, sticky=(W, E), padx=5, pady=5
+            )
+
+            # Update plot
+            if hasattr(self.parent, "fig"):
+                self.parent.in_gui_plotting(resdict=self.parent.resdict)
+
+        except AttributeError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Make sure a file is loaded."),
+            )
diff --git a/openhdemg/gui/gui_modules/edit_sig.py b/openhdemg/gui/gui_modules/edit_sig.py
new file mode 100644
index 0000000..d23e562
--- /dev/null
+++ b/openhdemg/gui/gui_modules/edit_sig.py
@@ -0,0 +1,521 @@
+"""Module containing the Resif editing class"""
+
+import os
+from sys import platform
+from tkinter import DoubleVar, E, StringVar, W, ttk
+
+import customtkinter as ctk
+
+import openhdemg.library as openhdemg
+from openhdemg.gui.gui_modules.error_handler import show_error_dialog
+
+
+class EditSig:
+    """
+    A class to manage editing of the signals in a GUI application.
+
+    This class creates a window that offers various options for editing the
+    reference and EMG signal. It includes functionalities for filtering the
+    signal, removing offset, converting the signal, and transforming it to a
+    percentage value. The class is instantiated when the "RefSig Editing"
+    button in the master GUI window is pressed.
+
+    Attributes
+    ----------
+    parent : object
+        The parent widget, typically the main application window, to which
+        this EditSig instance belongs.
+    head : CTkToplevel
+        The top-level widget for the Reference Signal Editing window.
+    filter_order : StringVar
+        Tkinter StringVar to store the filter order for reference signal
+        filtering.
+    cutoff_freq : StringVar
+        Tkinter StringVar to store the cutoff frequency for reference signal
+        filtering.
+    offsetval : StringVar
+        Tkinter StringVar to store the offset value to be removed from the
+        reference signal.
+    auto_eval : StringVar
+        Tkinter StringVar to store the value for automatic offset evaluation.
+    convert : StringVar
+        Tkinter StringVar to store the operation (Multiply/Divide) for
+        reference signal conversion.
+    convert_factor : DoubleVar
+        Tkinter DoubleVar to store the factor for reference signal conversion.
+    mvc_value : DoubleVar
+        Tkinter DoubleVar to store the MVC (Maximum Voluntary Contraction)
+        value for percentage conversion.
+
+    Methods
+    -------
+    __init__(self, parent)
+        Initialize a new instance of the EditSig class.
+    filter_refsig(self)
+        Apply filtering to the reference signal based on the specified order
+        and cutoff frequency.
+    remove_offset(self)
+        Remove or adjust the offset of the reference signal based on the
+        specified value or automatic evaluation.
+    convert_refsig(self)
+        Convert the reference signal using the specified operation
+        (Multiply/Divide) and factor.
+    to_percent(self)
+        Convert the reference signal to a percentage value based on the
+        specified MVC value.
+
+    Examples
+    --------
+    >>> main_window = Tk()
+    >>> edit_sig = EditSig(main_window)
+    >>> edit_sig.head.mainloop()
+
+    Notes
+    -----
+    This class relies on the `ctk` and `ttk` modules from the `tkinter`
+    library. The class is designed to be instantiated from within a larger GUI
+    application and operates on the reference signal data that is accessible
+    via the `parent` widget.
+    """
+
+    def __init__(self, parent):
+        """
+        Initialize a new instance of the EditSig class.
+
+        This method sets up the GUI components for the Signal Editing Window.
+        It includes controls for filtering the signal, removing its offset,
+        converting it, and transforming it to a percentage value. The method
+        configures and places various widgets such as labels, entries, buttons,
+        and combo boxes in a grid layout for user interaction.
+
+        Parameters
+        ----------
+        parent : object
+            The parent widget, typically the main application window, to which
+            this EditSig instance belongs. The parent is used for accessing
+            shared resources and data.
+
+        Raises
+        ------
+        AttributeError
+            If certain widgets or properties are not properly instantiated due
+            to missing parent configurations or resources.
+        """
+
+        # Initialize parent and load parent settings
+        self.parent = parent
+        self.parent.load_settings()
+
+        # Create new window
+        self.head = ctk.CTkToplevel()
+        self.head.title("Signal Editing Window")
+
+        head_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+        iconpath = head_path + "/gui_files/Icon_transp.ico"
+        self.head.iconbitmap(iconpath)
+        if platform.startswith("win"):
+            self.head.after(200, lambda: self.head.iconbitmap(iconpath))
+
+        self.head.grab_set()
+
+        # Set resizable window
+        # Configure columns with a loop
+        for col in range(3):
+            self.head.columnconfigure(col, weight=1)
+
+        # Configure rows with a loop
+        for row in range(12):
+            self.head.rowconfigure(row, weight=1)
+
+        # Filter RAW EMG signal
+        ctk.CTkLabel(
+            self.head,
+            text="EMG Signal",
+            font=("Segoe UI", 18, "bold"),
+        ).grid(column=0, row=0, sticky=W)
+        ctk.CTkLabel(
+            self.head,
+            text="Filter Order",
+            font=("Segoe UI", 18, "bold"),
+        ).grid(column=1, row=1, sticky=(W, E))
+        ctk.CTkLabel(
+            self.head,
+            text="BandPass Freq",
+            font=("Segoe UI", 18, "bold"),
+        ).grid(column=2, row=1, sticky=(W, E))
+        ctk.CTkButton(
+            self.head,
+            text="Filter EMG signal",
+            command=self.filter_emgsig,
+        ).grid(column=0, row=2, sticky=W)
+        self.emg_filter_order = StringVar()
+        ctk.CTkEntry(
+            self.head,
+            width=100,
+            textvariable=self.emg_filter_order,
+        ).grid(column=1, row=2)
+        self.emg_filter_order.set(2)
+        self.emg_bandpass_freq = StringVar()
+        ctk.CTkEntry(
+            self.head,
+            width=100,
+            textvariable=self.emg_bandpass_freq,
+        ).grid(column=2, row=2)
+        self.emg_bandpass_freq.set("20-500")
+        ttk.Separator(self.head, orient="horizontal").grid(
+            column=0,
+            columnspan=3,
+            row=3,
+            sticky=(W, E),
+            padx=5,
+            pady=5,
+        )
+
+        # Filter Refsig
+        # Define Labels
+        ctk.CTkLabel(
+            self.head,
+            text="Reference Signal",
+            font=("Segoe UI", 18, "bold"),
+        ).grid(column=0, row=4, sticky=W)
+        ctk.CTkLabel(
+            self.head,
+            text="Filter Order",
+            font=("Segoe UI", 18, "bold"),
+        ).grid(column=1, row=5, sticky=(W, E))
+        ctk.CTkLabel(
+            self.head,
+            text="Cutoff Freq",
+            font=("Segoe UI", 18, "bold"),
+        ).grid(column=2, row=5, sticky=(W, E))
+        # Fiter button
+        basic = ctk.CTkButton(
+            self.head,
+            text="Filter Refsig",
+            command=self.filter_refsig,
+        )
+        basic.grid(column=0, row=6, sticky=W)
+        self.refsig_filter_order = StringVar()
+        ref_order = ctk.CTkEntry(
+            self.head,
+            width=100,
+            textvariable=self.refsig_filter_order,
+        )
+        ref_order.grid(column=1, row=6)
+        self.refsig_filter_order.set(4)
+
+        self.cutoff_freq = StringVar()
+        cutoff = ctk.CTkEntry(
+            self.head,
+            width=100,
+            textvariable=self.cutoff_freq,
+        )
+        cutoff.grid(column=2, row=6)
+        self.cutoff_freq.set(15)
+
+        # Remove offset of reference signal
+        ctk.CTkLabel(
+            self.head, text="Offset Value", font=("Segoe UI", 18, "bold")
+        ).grid(column=1, row=7, sticky=(W, E))
+        ctk.CTkLabel(
+            self.head, text="Automatic Offset", font=("Segoe UI", 18, "bold")
+        ).grid(column=2, row=7, sticky=(W, E))
+
+        # Offset removal button
+        basic2 = ctk.CTkButton(
+            self.head,
+            text="Remove Offset",
+            command=self.remove_offset,
+        )
+        basic2.grid(column=0, row=8, sticky=W)
+
+        self.offsetval = StringVar()
+        offset = ctk.CTkEntry(
+            self.head,
+            width=100,
+            textvariable=self.offsetval,
+        )
+        offset.grid(column=1, row=8)
+        self.offsetval.set(4)
+
+        self.auto_eval = StringVar()
+        auto = ctk.CTkEntry(self.head, width=100, textvariable=self.auto_eval)
+        auto.grid(column=2, row=8)
+        self.auto_eval.set(0)
+
+        # Convert Reference signal
+        ctk.CTkLabel(
+            self.head,
+            text="Operator",
+            font=("Segoe UI", 18, "bold"),
+        ).grid(column=1, row=9, sticky=(W, E))
+        ctk.CTkLabel(
+            self.head,
+            text="Factor",
+            font=("Segoe UI", 18, "bold"),
+        ).grid(column=2, row=9, sticky=(W, E))
+
+        self.convert = StringVar()
+        convert = ctk.CTkComboBox(
+            self.head,
+            width=100,
+            variable=self.convert,
+            values=("Multiply", "Divide"),
+        )
+        convert.configure(state="readonly")
+        convert.grid(column=1, row=10)
+        self.convert.set("Multiply")
+
+        # DoubleVar does not support - sign, use StringVar
+        self.convert_factor = StringVar()
+        factor = ctk.CTkEntry(
+            self.head,
+            width=100,
+            textvariable=self.convert_factor,
+        )
+        factor.grid(column=2, row=10)
+        self.convert_factor.set(2.5)
+
+        convert_button = ctk.CTkButton(
+            self.head,
+            text="Convert",
+            command=self.convert_refsig,
+        )
+        convert_button.grid(column=0, row=10, sticky=W)
+
+        # Convert to percentage
+        ctk.CTkLabel(
+            self.head,
+            text="MVC Value",
+            font=("Segoe UI", 18, "bold"),
+        ).grid(column=1, row=11, sticky=(W, E))
+
+        percent_button = ctk.CTkButton(
+            self.head,
+            text="To Percent*",
+            command=self.to_percent,
+        )
+        percent_button.grid(column=0, row=12, sticky=W)
+
+        self.mvc_value = DoubleVar()
+        mvc = ctk.CTkEntry(self.head, width=100, textvariable=self.mvc_value)
+        mvc.grid(column=1, row=12)
+
+        ctk.CTkLabel(
+            self.head,
+            text="*Use this button \nonly if your Refsig \nis in absolute values!",
+            font=("Arial", 8),
+        ).grid(column=2, row=11, rowspan=2)
+
+        # Add padding to all children widgets of head
+        for child in self.head.winfo_children():
+            child.grid_configure(padx=5, pady=5)
+
+    ### Define functions for signal editing
+
+    def filter_emgsig(self):
+        """
+        Instance method that filters the EMG signal based on user selected
+        specs.
+
+        Raises
+        ------
+        AttributeError
+            When no emgfile is available.
+
+        See Also
+        --------
+        filter_rawemg in library.
+        """
+
+        # Get the bandpass frequency string
+        bandpass_freq = self.emg_bandpass_freq.get()
+        # Split the string into lowcut and highcut values
+        lowcut, highcut = map(int, bandpass_freq.split("-"))
+
+        try:
+            # Filter RAW EMG
+            self.parent.resdict = openhdemg.filter_rawemg(
+                emgfile=self.parent.resdict,
+                order=int(self.emg_filter_order.get()),
+                lowcut=lowcut,
+                highcut=highcut,
+            )
+            # Plot filtered Refsig
+            self.parent.in_gui_plotting(
+                resdict=self.parent.resdict,
+            )  # Re-plot main plot to indicate that something happened
+
+        except AttributeError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Make sure a file is loaded."),
+            )
+
+    def filter_refsig(self):
+        """
+        Instance method that filters the refig based on user selected specs.
+
+        Executed when button "Filter Refsig" in Reference Signal Editing
+        Window is pressed. The emgfile and the GUI plot are updated.
+
+        Raises
+        ------
+        AttributeError
+            When no reference signal file is available.
+
+        See Also
+        --------
+        filter_refsig in library.
+        """
+
+        try:
+            # Filter refsig
+            self.parent.resdict = openhdemg.filter_refsig(
+                emgfile=self.parent.resdict,
+                order=int(self.refsig_filter_order.get()),
+                cutoff=int(self.cutoff_freq.get()),
+            )
+            # Plot filtered Refsig
+            self.parent.in_gui_plotting(
+                resdict=self.parent.resdict,
+                plot="refsig_fil",
+            )
+
+        except AttributeError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Make sure a Refsig file is loaded."),
+            )
+
+    def remove_offset(self):
+        """
+        Instance Method that removes user specified/selected Refsig offset.
+
+        Executed when button "Remove offset" in Reference Signal Editing
+        Window is pressed. The emgfile and the GUI plot are updated.
+
+        Raises
+        ------
+        AttributeError
+            When no reference signal file is available
+
+        See Also
+        --------
+        remove_offset in library.
+        """
+
+        try:
+            # Remove offset
+            self.parent.resdict = openhdemg.remove_offset(
+                emgfile=self.parent.resdict,
+                offsetval=int(self.offsetval.get()),
+                auto=int(self.auto_eval.get()),
+            )
+            # Update Plot
+            self.parent.in_gui_plotting(
+                resdict=self.parent.resdict,
+                plot="refsig_off",
+            )
+
+        except AttributeError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Make sure a Refsig file is loaded."),
+            )
+
+        except ValueError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Make sure to specify valid filtering or offset values."),
+            )
+
+    def convert_refsig(self):
+        """
+        Instance Method that converts Refsig by multiplication or division.
+
+        Executed when button "Convert" in Reference Signal Editing Window is
+        pressed. The emgfile and the GUI plot are updated.
+
+        Raises
+        ------
+        AttributeError
+            When no reference signal file is available
+        ValueError
+            When invalid conversion factor is specified
+        """
+
+        try:
+            convert_factor = float(self.convert_factor.get())
+            if self.convert.get() == "Multiply":
+                self.parent.resdict["REF_SIGNAL"] = (
+                    self.parent.resdict["REF_SIGNAL"] * convert_factor
+                )
+            elif self.convert.get() == "Divide":
+                self.parent.resdict["REF_SIGNAL"] = (
+                    self.parent.resdict["REF_SIGNAL"] / convert_factor
+                )
+
+            # Update Plot
+            self.parent.in_gui_plotting(
+                resdict=self.parent.resdict,
+                plot="refsig_off",
+            )
+
+        except AttributeError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Make sure a Refsig file is loaded."),
+            )
+
+        except ValueError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Make sure to specify valid conversion factor."),
+            )
+
+    def to_percent(self):
+        """
+        Instance Method that converts Refsig to a percentag value. Should only
+        be used when the Refsig is in absolute values.
+
+        Executed when button "To Percen" in Reference Signal Editing Window is
+        pressed. The emgfile and the GUI plot are updated.
+
+        Raises
+        ------
+        AttributeError
+            When no reference signal file is available
+        ValueError
+            When invalid conversion factor is specified
+        """
+
+        try:
+            self.parent.resdict["REF_SIGNAL"] = (
+                self.parent.resdict["REF_SIGNAL"] * 100
+            ) / self.mvc_value.get()
+            # Update Plot
+            self.parent.in_gui_plotting(
+                resdict=self.parent.resdict,
+                plot="refsig_off",
+            )
+
+        except AttributeError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Make sure a Refsig file is loaded."),
+            )
+
+        except ValueError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Make sure to specify valid conversion factor."),
+            )
diff --git a/openhdemg/gui/gui_modules/error_handler.py b/openhdemg/gui/gui_modules/error_handler.py
new file mode 100644
index 0000000..a3a6ca4
--- /dev/null
+++ b/openhdemg/gui/gui_modules/error_handler.py
@@ -0,0 +1,179 @@
+"""Module containing the error message designs"""
+
+import os
+import traceback
+from sys import platform
+
+import customtkinter as ctk
+from PIL import Image
+
+
+class ErrorDialog:
+    """
+    A dialog window for displaying error messages and solutions to the user.
+
+    This class creates a custom dialog window using customtkinter (ctk)
+    components to show an error message and a potential solution. The dialog
+    includes an information icon, labels for the error and solution, and is
+    styled with specific foreground and background colors.
+
+    Parameters
+    ----------
+    parent : tk.Tk or ctk.CTk
+        The parent window to which this dialog is attached. It can be either a
+        Tkinter root window or another customtkinter component.
+    error : str
+        The error message to be displayed in the dialog. This should describe
+        what went wrong.
+    solution : str
+        A message providing a potential solution or workaround for the error
+        described.
+
+    Attributes
+    ----------
+    parent : tk.Tk or ctk.CTk
+        The parent window of the dialog.
+    head : ctk.CTkToplevel
+        The top-level window component of the dialog.
+    content_frame : ctk.CTkFrame
+        A frame widget that holds the content of the dialog, including the
+        error and solution messages.
+    info_photo : ctk.CTkImage
+        The photo image of the information icon displayed in the dialog.
+    icon : ctk.CTkLabel
+        A label widget used to display the information icon.
+    icon_info : ctk.CTkLabel
+        A label widget displaying the text "INFORMATION" below the icon.
+    solution_label : ctk.CTkLabel
+        A label widget used to display the solution message.
+    error_label : ctk.CTkLabel
+        A label widget used to display the error message.
+
+    Methods
+    -------
+    __init__(self, parent, error, solution)
+        Initializes the ErrorDialog with the parent window, error message, and
+        solution message.
+
+    Example
+    -------
+    >>> import tkinter as tk
+    >>> import customtkinter as ctk
+    >>> root = tk.Tk()
+    >>> error = "This is a sample error message."
+    >>> solution = "Try restarting the application."
+    >>> ErrorDialog(root, error, solution)
+    >>> root.mainloop()
+    """
+
+    def __init__(self, parent, error, solution):
+        self.parent = parent
+        self.head = ctk.CTkToplevel(fg_color="#FFBF00")
+        self.head.title("Error Dialog")
+        self.head.geometry("500x300")  # Adjust the size as needed
+
+        # Set window icon
+        head_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+        iconpath = head_path + "/gui_files/Icon_transp.ico"
+        self.head.iconbitmap(iconpath)
+        if platform.startswith("win"):
+            self.head.after(200, lambda: self.head.iconbitmap(iconpath))
+
+        # Create a frame for the content with blue background, placed in the
+        # middle.
+        self.content_frame = ctk.CTkFrame(
+            self.head,
+            corner_radius=10,
+            fg_color="LightBlue4",
+            bg_color="#FFBF00",
+        )
+        self.content_frame.pack(padx=50, expand=True, fill="both")
+
+        # Load an information icon and display it
+        self.info_photo = ctk.CTkImage(
+            light_image=Image.open(head_path + "/gui_files/Error.png"),
+            size=(50, 50),
+        )
+        self.icon = ctk.CTkLabel(
+            self.content_frame,
+            text="",
+            image=self.info_photo,
+            bg_color="LightBlue4",
+        )
+        self.icon.pack(pady=5)
+        self.icon_info = ctk.CTkLabel(
+            self.content_frame,
+            text="INFORMATION",
+            font=("Arial", 16, "bold"),
+            text_color="#123456",
+        )
+        self.icon_info.pack(pady=5)
+
+        # Error solution label (larger, bold), placed below the icon
+        self.solution_label = ctk.CTkLabel(
+            self.content_frame,
+            text=solution,
+            font=("Arial", 14, "bold"),
+            wraplength=400,
+            fg_color="LightBlue4",
+        )
+        self.solution_label.pack(pady=(10, 5))
+
+        # Error traceback label (smaller)
+        self.error_label = ctk.CTkLabel(
+            self.content_frame,
+            text=error,
+            font=("Arial", 10),
+            wraplength=400,
+            fg_color="LightBlue4",
+        )
+        self.error_label.pack(pady=(5, 10), expand=True, fill="both")
+
+        # Make window modal
+        self.head.grab_set()  # Redirect all events to this window
+
+
+def show_error_dialog(parent, error, solution):
+    """
+    Displays an ErrorDialog with a formatted error traceback and a solution
+    message.
+
+    This function takes an exception as input, formats its traceback, and then
+    creates an ErrorDialog to display the formatted traceback along with a
+    solution message.
+
+    Parameters
+    ----------
+    parent : tk.Tk or ctk.CTk
+        The parent window to which this dialog will be attached.
+    error : Exception
+        The exception object from which the error message will be derived.
+    solution : str
+        A message providing a potential solution or workaround for the error.
+
+    Example
+    -------
+    >>> import tkinter as tk
+    >>> import customtkinter as ctk
+    >>> root = tk.Tk()
+    >>> try:
+    ...     # Some operation that raises an exception
+    ...     raise ValueError("A sample error.")
+    ... except Exception as e:
+    ...     show_error_dialog(root, e, "Check the value and try again.")
+    >>> root.mainloop()
+    """
+    if error is None:
+        error_message = "".join(
+            traceback.format_exception(type(error), value=error, tb=None)
+        )
+    else:
+        error_message = "".join(
+            traceback.format_exception(
+                type(error),
+                value=error,
+                tb=error.__traceback__,
+            )
+        )
+
+    ErrorDialog(parent, error_message, solution)
diff --git a/openhdemg/gui/gui_modules/gui_helpers.py b/openhdemg/gui/gui_modules/gui_helpers.py
new file mode 100644
index 0000000..415aa26
--- /dev/null
+++ b/openhdemg/gui/gui_modules/gui_helpers.py
@@ -0,0 +1,251 @@
+"""Module that contains all helper functions for the GUI"""
+
+from tkinter import filedialog
+import pandas as pd
+
+import openhdemg.library as openhdemg
+from openhdemg.gui.gui_modules.error_handler import show_error_dialog
+
+
+class GUIHelpers:
+    """
+    A utility class to provide additional functionalities in an openhdemg GUI
+    application.
+
+    This class includes helper functions to enhance user interaction with the
+    application, such as resizing files based on user input. It is designed to
+    work in conjunction with the main GUI components and relies on the parent
+    widget for accessing shared data and resources.
+
+    Attributes
+    ----------
+    parent : object
+        The parent widget, typically the main application window, to which
+        this GUIHelpers instance belongs.
+
+    Methods
+    -------
+    __init__(self, parent)
+        Initialize a new instance of the GUIHelpers class.
+    resize_file(self)
+        Resize the EMG file based on user-defined areas selected in the GUI
+        plot.
+    export_to_excel(self)
+        Save an analaysis dataframe to a csv file.
+    sort_mus(self)
+        Sort motor units according to recriuitement order.
+
+    Examples
+    --------
+    >>> main_window = Tk()
+    >>> gui_helpers = GUIHelpers(main_window)
+    # Usage of gui_helpers methods as required
+
+    Raises
+    ------
+    AttributeError
+        When no file is loaded prior to analysis or certain operations are
+        attempted without the necessary context or data.
+
+    Notes
+    -----
+    The class's methods interact with other components of the openhdemg
+    application, such as file handling and plotting utilities, and are
+    dependent on the state of the parent widget.
+
+    """
+
+    def __init__(self, parent):
+        """
+        Initialize a new instance of the GUIHelpers class.
+
+        Sets up a reference to the parent widget, which is used for accessing
+        shared resources and functionalities within the application.
+
+        Parameters
+        ----------
+        parent : object
+            The parent widget, usually the main application window, which
+            provides necessary context and data for the helper functions.
+
+        """
+
+        # Initialize parent and load parent settings
+        self.parent = parent
+        self.parent.load_settings()
+
+    def resize_file(self):
+        """
+        Instance method to get resize area from user specification on plot and
+        resize emgfile.
+
+        Executed when button "Select Resize" is pressed in Resize file window.
+        The emgfile and the GUI plot are updated.
+
+        Raises
+        ------
+        AttributeError
+            When no file is loaded prior to analysis.
+
+        See Also
+        --------
+        showselect, resize_emgfile in library.
+        """
+
+        # TODO this try/except is not catching when the user closes the plot to
+        # select the resizing area (from the X button without selecting points).
+        # Instead, the error appears when closing the GUI. Same happens for mu
+        # properties analysis.
+        try:
+            # Open selection window for user
+            points = openhdemg.showselect(
+                emgfile=self.parent.resdict,
+                how=self.parent.settings.resize_emgfile__how,
+                title="Select the start/end area to resize by hovering the mouse\nand pressing the 'a'-key. Wrong points can be removed with right click.\nWhen ready, press enter.",
+                titlesize=10,
+            )
+            start, end = points[0], points[1]
+
+            # Delsys requires different handling for resize
+            if self.parent.resdict["SOURCE"] == "DELSYS":
+                self.parent.resdict, _, _ = openhdemg.resize_emgfile(
+                    emgfile=self.parent.resdict, area=[start, end],
+                    accuracy="maintain",
+                )
+            else:
+                self.parent.resdict, _, _ = openhdemg.resize_emgfile(
+                    emgfile=self.parent.resdict,
+                    area=[start, end],
+                    accuracy=self.parent.settings.resize_emgfile__accuracy,
+                    ignore_negative_ipts=self.parent.settings.resize_emgfile__ignore_negative_ipts,
+                )
+            # Update Plot
+            self.parent.in_gui_plotting(resdict=self.parent.resdict)
+
+            # Update filelength
+            self.parent.file_length.configure(
+                text="N of MUs: " + str(self.parent.resdict["EMG_LENGTH"]),
+                font=("Segoe UI", 15, "bold"),
+            )
+
+        except AttributeError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Make sure a file is loaded."),
+            )
+
+        except ValueError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str(
+                    "Wrong number of inputs or verify settings for " +
+                    "resize_emgfile()."),
+            )
+
+    def export_to_excel(self):
+        """
+        Instnace method to export any prior analysis results. Results are
+        saved in an excel sheet in a directory specified by the user.
+
+        Executed when button "Save Results" in master GUI window is pressed.
+
+        Raises
+        ------
+        IndexError
+            When no analysis has been performed prior to attempted savig.
+        AttributeError
+            When no file was loaded in the GUI.
+        """
+
+        try:
+            # Ask user to select the directory
+            path = filedialog.askdirectory()
+
+            # Define excel writer
+            writer = pd.ExcelWriter(path + "/Results_" + self.parent.filename + ".xlsx")
+
+            # Check for attributes and write sheets
+            if hasattr(self.parent, "mvc_df"):
+                self.parent.mvc_df.to_excel(writer, sheet_name="MVC")
+
+            if hasattr(self.parent, "rfd"):
+                self.parent.rfd.to_excel(writer, sheet_name="RFD")
+
+            if hasattr(self.parent, "mu_prop_df"):
+                self.parent.mu_prop_df.to_excel(
+                    writer, sheet_name="Basic MU Properties"
+                )
+
+            if hasattr(self.parent, "mus_dr"):
+                self.parent.mus_dr.to_excel(writer, sheet_name="MU Discharge Rate")
+
+            if hasattr(self.parent, "mu_thresholds"):
+                self.parent.mu_thresholds.to_excel(writer, sheet_name="MU Thresholds")
+
+            writer.close()
+
+        except IndexError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Please conduct at least one analysis before saving."),
+            )
+
+        except AttributeError as e:
+            show_error_dialog(
+                parent=self, error=e, solution=str("Make sure a file is loaded.")
+            )
+
+        except PermissionError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("If /Results.xlsx already opened, please close."),
+            )
+
+    def sort_mus(self):
+        """
+        Instance method to sort motor units ascending according to
+        recruitement order.
+
+        Executed when button "Sort MUs" in master GUI window is pressed. The
+        plot of the MUs and the emgfile are subsequently updated.
+
+        Raises
+        ------
+        AttributeError
+            When no file was loaded in the GUI.
+
+        See Also
+        --------
+        sort_mus in library.
+        """
+
+        try:
+            # Sort emgfile
+            self.parent.resdict = openhdemg.sort_mus(
+                emgfile=self.parent.resdict,
+            )
+
+            # Update plot
+            if hasattr(self.parent, "fig"):
+                self.parent.in_gui_plotting(resdict=self.parent.resdict)
+
+        except AttributeError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Make sure a file is loaded."),
+            )
+
+        except KeyError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str(
+                    "Sorting not possible when ≤ 1"
+                    + "\nMU is present in the File (i.e. Refsigs)"
+                ),
+            )
diff --git a/openhdemg/gui/gui_modules/gui_plotting.py b/openhdemg/gui/gui_modules/gui_plotting.py
new file mode 100644
index 0000000..bffe838
--- /dev/null
+++ b/openhdemg/gui/gui_modules/gui_plotting.py
@@ -0,0 +1,1041 @@
+"""This modules contains all plotting functionalities for the GUI"""
+
+import os
+import webbrowser
+from sys import platform
+from tkinter import E, N, PhotoImage, S, StringVar, W, ttk
+
+import customtkinter as ctk
+from PIL import Image
+
+import openhdemg.library as openhdemg
+from openhdemg.gui.gui_modules.error_handler import show_error_dialog
+
+
+class PlotEmg:
+    """
+    A class to manage and display EMG (Electromyography) signal plots in a GUI
+    application.
+
+    This class creates a plotting window with various options to create and
+    display plots from an EMG file. It provides functionalities to plot EMG
+    signals, reference signals, motor unit pulses, and more, with customizable
+    settings.
+
+    Attributes
+    ----------
+    parent : object
+        The parent widget, typically the main application window that this
+        PlotEmg instance belongs to.
+    head : CTkToplevel
+        The top-level widget for the plotting window.
+    matrix_rc : StringVar
+        Tkinter StringVar for storing matrix rows and columns information.
+    mat_label : ttk.Label
+        Label widget for displaying matrix rows and columns information.
+    row_cols_entry : ttk.Entry
+        Entry widget for inputting matrix rows and columns.
+    ref_but : StringVar
+        Variable to track the state of the reference signal checkbox.
+    time_sec : StringVar
+        Variable to track the time selection for the plot.
+    size_fig : StringVar
+        Variable to store the specified figure size.
+    channels : StringVar
+        Variable to store the selected EMG channels.
+    linewidth : StringVar
+        Variable to store the selected line width for plotting.
+    mu_numb : StringVar
+        Variable to store the selected motor unit number.
+    mat_code : StringVar
+        Variable to store the selected matrix code.
+    mat_orientation : StringVar
+        Variable to store the orientation of the matrix.
+    deriv_config : StringVar
+        Variable to store the configuration for the derivation plot.
+    deriv_matrix : StringVar
+        Variable to store the selected matrix column for derivation.
+    muap_config : StringVar
+        Variable to store the configuration for the MUAP plot.
+    muap_munum : StringVar
+        Variable to store the selected motor unit number for MUAP plot.
+    muap_time : StringVar
+        Variable to store the time window for MUAP plot.
+    matrix : PhotoImage
+        Image variable to store and display the matrix illustration graphic.
+    info : CTkImage
+        Image variable for the information button.
+
+    Methods
+    -------
+    __init__(self, parent)
+        Initialize a new instance of the PlotEmg class.
+    plt_emgsignal(self)
+        Plot the EMG signal based on the current settings.
+    plt_refsignal(self)
+        Plot the reference signal.
+    plt_mupulses(self)
+        Plot motor unit pulses.
+    plt_ipts(self)
+        Plot the impulse train.
+    plt_idr(self)
+        Plot the instantaneous discharge rate.
+    plot_derivation(self)
+        Plot the derivation based on the selected configuration.
+    plot_muaps(self)
+        Plot the motor unit action potentials.
+    on_matrix_none(self, *args)
+        Handle changes in the matrix code selection.
+
+    Examples
+    --------
+    >>> main_window = Tk()
+    >>> plot_emg = PlotEmg(main_window)
+    >>> plot_emg.head.mainloop()
+
+    Notes
+    -----
+    This class is dependent on the `ctk` and `ttk` modules from the `tkinter`
+    library.
+    Some attributes and methods are conditional based on the `parent`'s
+    properties.
+
+    """
+
+    def __init__(self, parent):
+        """
+        Initialize a new instance of the PlotEmg class.
+
+        This method sets up the GUI components of the PlotEmg window,
+        including labels, entries, checkboxes, and buttons for various plot
+        settings and options.
+
+        Parameters
+        ----------
+        parent : object
+            The parent widget, typically the main application window, to which
+            this PlotEmg instance belongs.
+
+        Raises
+        ------
+        AttributeError
+            If certain widgets are not properly instantiated due to missing
+            parent resdict.
+
+        """
+
+        # Try is block is necessary as some widgets depend on parent resdict
+        try:
+            # Initialize parent and load parent settings
+            self.parent = parent
+            self.parent.load_settings()
+
+            self.head = ctk.CTkToplevel()
+            self.head.title("Plot Window")
+
+            # Set window icon
+            head_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+            iconpath = head_path + "/gui_files/Icon_transp.ico"
+            self.head.iconbitmap(iconpath)
+            if platform.startswith("win"):
+                self.head.after(200, lambda: self.head.iconbitmap(iconpath))
+
+            # Set resizable window
+            # Configure columns with a loop
+            for col in range(7):
+                self.head.columnconfigure(col, weight=1)
+
+            # Configure rows with a loop
+            for row in range(21):
+                self.head.rowconfigure(row, weight=1)
+
+            # Define tk variables for later use
+            self.matrix_rc = StringVar()  # Matrix rows columns
+            self.mat_label = ttk.Label()  # Label for matriy rows columns
+            self.row_cols_entry = ttk.Entry()  # Entry for matrix rows columns
+
+            # Reference signal
+            ctk.CTkLabel(
+                self.head,
+                text="Reference signal",
+                font=("Segoe UI", 18, "bold"),
+            ).grid(column=0, row=0, sticky=W)
+            self.ref_but = StringVar()
+            ref_button = ctk.CTkCheckBox(
+                self.head,
+                variable=self.ref_but,
+                bg_color="LightBlue4",
+                onvalue="True",
+                offvalue="False",
+                text="",
+            )
+            ref_button.grid(column=1, row=0, sticky=(W))
+            self.ref_but.set(False)
+
+            # Time
+            ctk.CTkLabel(
+                self.head,
+                text="Time in seconds",
+                font=("Segoe UI", 18, "bold"),
+            ).grid(column=0, row=1, sticky=W)
+            self.time_sec = StringVar()
+            time_button = ctk.CTkCheckBox(
+                self.head,
+                variable=self.time_sec,
+                bg_color="LightBlue4",
+                onvalue="True",
+                offvalue="False",
+                text="",
+            )
+            time_button.grid(column=1, row=1, sticky=W)
+            self.time_sec.set(False)
+
+            # Figure Size
+            ctk.CTkLabel(
+                self.head,
+                text="Figure size in cm (h,w)",
+                font=("Segoe UI", 18, "bold"),
+            ).grid(column=0, row=2, sticky=W)
+            self.size_fig = StringVar()
+            fig_entry = ctk.CTkEntry(self.head, width=100, textvariable=self.size_fig)
+            self.size_fig.set("20,15")
+            fig_entry.grid(column=1, row=2, sticky=W)
+
+            # Plot emgsig
+            plt_emgsig = ctk.CTkButton(
+                self.head,
+                text="Plot EMGsig",
+                command=self.plt_emgsignal,
+            )
+            plt_emgsig.grid(column=0, row=3, sticky=W)
+
+            self.channels = StringVar()
+            channel_entry_values = ("0", "0,1,2", "0,1,2,3")
+            channel_entry = ctk.CTkComboBox(
+                self.head,
+                width=150,
+                variable=self.channels,
+                values=channel_entry_values,
+            )
+            channel_entry.grid(column=1, row=3, sticky=(W, E))
+            self.channels.set("Channel Numbers")
+
+            # Plot refsig
+            plt_refsig = ctk.CTkButton(
+                self.head,
+                text="Plot RefSig",
+                command=self.plt_refsignal,
+            )
+            plt_refsig.grid(column=0, row=4, sticky=W)
+
+            # Plot motor unit pulses
+            plt_pulses = ctk.CTkButton(
+                self.head,
+                text="Plot MUpulses",
+                command=self.plt_mupulses,
+            )
+            plt_pulses.grid(column=0, row=5, sticky=W)
+
+            # Define Linewidth for plot
+            self.linewidth = StringVar()
+            linewidth_entry_values = ("0.25", "0.5", "0.75", "1")
+            linewidth_entry = ctk.CTkComboBox(
+                self.head,
+                width=15,
+                variable=self.linewidth,
+                values=linewidth_entry_values,
+            )
+            linewidth_entry.grid(column=1, row=5, sticky=(W, E))
+            self.linewidth.set("Linewidth")
+
+            # Plot impulse train
+            plt_ipts_but = ctk.CTkButton(
+                self.head,
+                text="Plot Source",
+                command=self.plt_ipts,
+            )
+            plt_ipts_but.grid(column=0, row=6, sticky=W)
+
+            self.mu_numb = StringVar()
+            munumb_entry_values = ("0", "0,1,2", "0,1,2,3", "all")
+            munumb_entry = ctk.CTkComboBox(
+                self.head,
+                width=15,
+                variable=self.mu_numb,
+                values=munumb_entry_values,
+            )
+            munumb_entry.grid(column=1, row=6, sticky=(W, E))
+            self.mu_numb.set("MU Number")
+
+            # Plot instantaneous discharge rate
+            plt_idr_but = ctk.CTkButton(
+                self.head,
+                text="Plot IDR",
+                command=self.plt_idr,
+            )
+            plt_idr_but.grid(column=0, row=7, sticky=W)
+
+            self.mu_numb_idr = StringVar()
+            munumb_entry_idr_values = ("0", "0,1,2", "0,1,2,3", "all")
+            munumb_entry_idr = ctk.CTkComboBox(
+                self.head,
+                width=15,
+                variable=self.mu_numb_idr,
+                values=munumb_entry_idr_values,
+            )
+            munumb_entry_idr.grid(column=1, row=7, sticky=(W, E))
+            self.mu_numb_idr.set("MU Number")
+
+            # This section containes the code for column 3++
+            # Separator
+            ttk.Separator(self.head, orient="vertical").grid(
+                row=3, column=2, rowspan=6, ipady=120
+            )
+
+            # Matrix code
+            ctk.CTkLabel(
+                self.head, text="Matrix Code", font=("Segoe UI", 18, "bold")
+            ).grid(row=0, column=3, sticky=(W))
+
+            self.mat_code = StringVar()
+            matrix_code_values = (
+                "Custom order",
+                "None",
+                "GR08MM1305",
+                "GR04MM1305",
+                "GR10MM0808",
+                "Trigno Galileo Sensor",
+            )
+            matrix_code = ctk.CTkComboBox(
+                self.head,
+                width=100,
+                variable=self.mat_code,
+                values=matrix_code_values,
+                state="readonly",
+            )
+            matrix_code.grid(row=0, column=4, sticky=(W, E))
+            self.mat_code.set("Custom order")
+
+            # Trace matrix code value
+            self.mat_code.trace_add("write", self.on_matrix_none)
+
+            # Matrix Orientation
+            ctk.CTkLabel(
+                self.head,
+                text="Orientation",
+                font=("Segoe UI", 18, "bold"),
+            ).grid(row=1, column=3, sticky=(W))
+            self.mat_orientation = StringVar()
+            orientation_values = ("0", "180")
+            orientation = ctk.CTkComboBox(
+                self.head,
+                width=15,
+                variable=self.mat_orientation,
+                values=orientation_values,
+                state="readonly",
+            )
+            orientation.grid(row=1, column=4, sticky=(W, E))
+            self.mat_orientation.set("180")
+            # Disable the orientation setting for DELSYS files
+            if self.parent.resdict["SOURCE"] == "DELSYS":
+                orientation.configure(state="disabled")
+
+            # Plot derivation
+            # Button
+            deriv_button = ctk.CTkButton(
+                self.head,
+                text="Plot Derivation",
+                command=self.plot_derivation,
+            )
+            deriv_button.grid(row=3, column=3, sticky=W)
+
+            # Combobox Config
+            self.deriv_config = StringVar()
+            configuration_values = ("Single differential", "Double differential")
+            configuration = ctk.CTkComboBox(
+                self.head,
+                width=15,
+                variable=self.deriv_config,
+                values=configuration_values,
+                state="readonly",
+            )
+            configuration.grid(row=3, column=4, sticky=(W, E))
+            self.deriv_config.set("Configuration")
+
+            # Combobox Matrix
+            self.deriv_matrix = StringVar()
+            mat_column_values = (
+                "col0", "col1", "col2", "col3", "col4", "col5", "col6",
+                "col7", "col8", "col9", "col10", "col11",
+                )
+            mat_column = ctk.CTkComboBox(
+                self.head,
+                width=100,
+                variable=self.deriv_matrix,
+                values=mat_column_values,
+                state="readonly",
+            )
+            mat_column.grid(row=3, column=5, sticky=(W, E))
+            self.deriv_matrix.set("Matrix Column")
+
+            # Motor unit action potential
+            # Button
+            muap_button = ctk.CTkButton(
+                self.head,
+                text="Plot MUAPs",
+                command=self.plot_muaps,
+            )
+            muap_button.grid(row=4, column=3, sticky=W)
+
+            # Combobox Config
+            self.muap_config = StringVar()
+            config_muap_values = (
+                "Monopolar",
+                "Single differential",
+                "Double differential",
+            )
+            config_muap = ctk.CTkComboBox(
+                self.head,
+                width=15,
+                variable=self.muap_config,
+                values=config_muap_values,
+                state="readonly",
+            )
+            config_muap.grid(row=4, column=4, sticky=(W, E))
+            self.muap_config.set("Configuration")
+            # Disable config for DELSYS files
+            if self.parent.resdict["SOURCE"] == "DELSYS":
+                config_muap.configure(state="disabled")
+                # NOTE config does not exist for CTk widgets. Use configure
+
+            # Combobox MU Number
+            self.muap_munum = StringVar()
+            if self.parent.resdict["SOURCE"] in [
+                "DEMUSE", "OTB", "CUSTOMCSV", "DELSYS",
+            ]:
+                mu_numbers = tuple(
+                    str(number) for number in range(0, self.parent.resdict["NUMBER_OF_MUS"])
+                )
+            else:
+                mu_numbers = ()  # Exception of refsig only files
+            muap_munum = ctk.CTkComboBox(
+                self.head,
+                width=15,
+                variable=self.muap_munum,
+                values=mu_numbers,
+                state="readonly",
+            )
+            muap_munum.grid(row=4, column=5, sticky=(W, E))
+            self.muap_munum.set("MU Number")
+
+            # Combobox Timewindow
+            self.muap_time = StringVar()
+            timewindow_values = ("25", "50", "100", "200")
+            timewindow = ctk.CTkComboBox(
+                self.head,
+                width=120,
+                variable=self.muap_time,
+                values=timewindow_values,
+            )
+            timewindow.grid(row=4, column=6, sticky=(W, E))
+            self.muap_time.set("Timewindow (ms)")
+            # Disable Timewindow for DELSYS files
+            if self.parent.resdict["SOURCE"] == "DELSYS":
+                timewindow.configure(state="disabled")
+
+            # Matrix Illustration Graphic
+            matrix_canvas = ctk.CTkCanvas(
+                self.head,
+                height=150,
+                width=600,
+                bg="white",
+            )
+            matrix_canvas.grid(row=5, column=3, rowspan=5, columnspan=5)
+            parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+            self.matrix = PhotoImage(file=parent_dir + "/gui_files/Matrix.png")
+            matrix_canvas.create_image(0, 0, anchor="nw", image=self.matrix)
+            # Information Button
+            self.info = ctk.CTkImage(
+                light_image=Image.open(parent_dir + "/gui_files/Info.png"),
+                size=(30, 30),
+            )
+            info_button = ctk.CTkButton(
+                self.head,
+                image=self.info,
+                text="",
+                width=30,
+                height=30,
+                bg_color="LightBlue4",
+                fg_color="LightBlue4",
+                border_width=0,
+                command=lambda: (
+                    (
+                        webbrowser.open(
+                            "https://www.giacomovalli.com/openhdemg/gui_basics/#plot-motor-units"
+                        )
+                    ),
+                ),
+            )
+            info_button.grid(row=0, column=6, sticky=E)
+
+            for child in self.head.winfo_children():
+                child.grid_configure(
+                    padx=5,
+                    pady=5,
+                )
+
+        except AttributeError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Make sure a file is loaded."),
+            )
+            self.head.destroy()
+
+    ### Define functions for motor unit plotting
+    def on_matrix_none(self, *args):
+        """
+        This function is called when the value of the mat_code variable is
+        changed.
+
+        When the variable is set to "None" it will create an Entrybox on the
+        grid at column 1 and row 6, and when the mat_code is set to something
+        else it will remove the entrybox from the grid.
+        """
+
+        if self.mat_code.get() == "None":
+            # Place label defined in init
+            # Label for matriy rows columns
+            self.mat_label = ctk.CTkLabel(
+                self.head, text="Rows,Columns:", font=("Segoe UI", 18, "bold")
+            )
+            self.mat_label.grid(row=0, column=5, sticky=E)
+
+            # Column entry only when specified matrix code
+            self.row_cols_entry = ctk.CTkEntry(
+                self.head,
+                width=80,
+                textvariable=self.matrix_rc,
+            )
+            self.row_cols_entry.grid(row=0, column=6, sticky=W, padx=5)
+            self.matrix_rc.set("13,5")
+
+        else:
+            if hasattr(self, "row_cols_entry"):
+                self.row_cols_entry.grid_forget()
+                self.mat_label.grid_forget()
+
+        self.head.update_idletasks()
+
+    def plt_emgsignal(self):
+        """
+        Instance method to plot the raw emg signal in an seperate plot window.
+        The channels selected by the user are plotted. The plot can be saved
+        and partly edited using the matplotlib options.
+
+        Executed when button "Plot EMGsig" in Plot Window is pressed.
+
+        Raises
+        ------
+        ValueError
+            When entered channel number is not valid (inexistent).
+        KeyError
+            When entered channel number is out of bounds.
+        IndexError
+            When negative figure size is specified.
+        See Also
+        --------
+        plot_emgsignal in library.
+        """
+
+        try:
+            # Create list of channels to be plotted
+            channels = self.channels.get()
+            # Create list of figsize
+            figsize = [int(i) for i in self.size_fig.get().split(",")]
+
+            if len(channels) > 1:
+                chan_list = channels.split(",")
+                chan_list = [int(i) for i in chan_list]
+
+                # Plot raw emg signal
+                openhdemg.plot_emgsig(
+                    emgfile=self.parent.resdict,
+                    channels=chan_list,
+                    addrefsig=eval(self.ref_but.get()),
+                    timeinseconds=eval(self.time_sec.get()),
+                    figsize=figsize,
+                )
+
+            else:
+                # Plot raw emg signal
+                openhdemg.plot_emgsig(
+                    emgfile=self.parent.resdict,
+                    channels=int(channels),
+                    addrefsig=eval(self.ref_but.get()),
+                    timeinseconds=eval(self.time_sec.get()),
+                    figsize=figsize,
+                )
+
+        except ValueError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Enter valid channel number or non-negative figure size."),
+            )
+
+        except KeyError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Enter valid channel number."),
+            )
+
+        except IndexError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str(
+                    "Enter valid figure size. Must be non negative and tuple of (heigth, width)."
+                ),
+            )
+
+    def plt_refsignal(self):
+        """
+        Instance method to plot the reference signal in an seperate plot
+        window.
+        The plot can be saved and partly edited using the matplotlib options.
+
+        Executed when button "Plot REFsig" in Plot Window is pressed.
+
+        See Also
+        --------
+        plot_refsig in library.
+        """
+
+        # Create list of figsize
+        figsize = [int(i) for i in self.size_fig.get().split(",")]
+
+        # Plot reference signal
+        openhdemg.plot_refsig(
+            emgfile=self.parent.resdict,
+            timeinseconds=eval(self.time_sec.get()),
+            figsize=figsize,
+        )
+
+    def plt_mupulses(self):
+        """
+        Instance method to plot the mu pulses in an seperate plot window.
+        The linewidth selected by the user is used. The plot can be saved and
+        partly edited using the matplotlib options.
+
+        Executed when button "Plot MUpulses" in Plot Window is pressed.
+
+        Raises
+        ------
+        ValueError
+            When entered channel number is not valid (inexistent).
+
+        See Also
+        --------
+        plot_mupulses in library.
+        """
+
+        try:
+            # Create list of figsize
+            figsize = [int(i) for i in self.size_fig.get().split(",")]
+
+            # Plot motor unit pulses
+            openhdemg.plot_mupulses(
+                emgfile=self.parent.resdict,
+                linewidths=float(self.linewidth.get()),
+                addrefsig=eval(self.ref_but.get()),
+                timeinseconds=eval(self.time_sec.get()),
+                figsize=figsize,
+            )
+
+        except ValueError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Enter valid linewidth number."),
+            )
+
+    def plt_ipts(self):
+        """
+        Instance method to plot the motor unit pulse train in an seperate plot
+        window. The motor units selected by the user are plotted. The plot can
+        be saved and partly edited using the matplotlib options.
+
+        Executed when button "Plot Source" in Plot Window is pressed.
+
+        Raises
+        ------
+        ValueError
+            When entered motor unit number is not valid (inexistent).
+        KeyError
+            When entered motor number is out of bounds.
+
+        See Also
+        --------
+        plot_ipts in library.
+        """
+
+        try:
+            # Create list contaning motor units to be plotted
+            mu_numb = self.mu_numb.get()
+            # Create list of figsize
+            figsize = [int(i) for i in self.size_fig.get().split(",")]
+
+            if mu_numb == "all":
+                # Plot motor unit puls train in default
+                openhdemg.plot_ipts(
+                    emgfile=self.parent.resdict,
+                    addrefsig=eval(self.ref_but.get()),
+                    timeinseconds=eval(self.time_sec.get()),
+                    figsize=figsize,
+                )
+
+            elif len(mu_numb) > 2:
+                # Split at ,
+                mu_list = mu_numb.split(",")
+                # Use comprehension to loop troug mu_list
+                mu_list = [int(i) for i in mu_list]
+                # Plot motor unit puls train in default
+                openhdemg.plot_ipts(
+                    emgfile=self.parent.resdict,
+                    munumber=mu_list,
+                    addrefsig=eval(self.ref_but.get()),
+                    timeinseconds=eval(self.time_sec.get()),
+                    figsize=figsize,
+                )
+
+            else:
+                # Plot motor unit puls train in default
+                openhdemg.plot_ipts(
+                    emgfile=self.parent.resdict,
+                    munumber=int(mu_numb),
+                    addrefsig=eval(self.ref_but.get()),
+                    timeinseconds=eval(self.time_sec.get()),
+                    figsize=figsize,
+                )
+
+        except ValueError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Enter valid motor unit number."),
+            )
+
+        except KeyError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Enter valid motor unit number."),
+            )
+
+    def plt_idr(self):
+        """
+        Instance method to plot the instanteous discharge rate in an seperate
+        plot window. The motor units selected by the user are plotted. The
+        plot can be saved and partly edited using the matplotlib options.
+
+        Executed when button "Plot IDR" in Plot Window is pressed.
+
+        Raises
+        ------
+        ValueError
+            When entered channel number is not valid (inexistent).
+        KeyError
+            When entered channel number is out of bounds.
+
+        See Also
+        --------
+        plot_idr in library.
+        """
+
+        try:
+            mu_idr = self.mu_numb_idr.get()
+            # Create list of figsize
+            figsize = [int(i) for i in self.size_fig.get().split(",")]
+
+            if mu_idr == "all":
+                # Plot instanteous discharge rate
+                openhdemg.plot_idr(
+                    emgfile=self.parent.resdict,
+                    addrefsig=eval(self.ref_but.get()),
+                    timeinseconds=eval(self.time_sec.get()),
+                    figsize=figsize,
+                )
+
+            elif len(mu_idr) > 2:
+                mu_list_idr = mu_idr.split(",")
+                mu_list_idr = [int(mu) for mu in mu_list_idr]
+                # Plot instanteous discharge rate
+                openhdemg.plot_idr(
+                    emgfile=self.parent.resdict,
+                    munumber=mu_list_idr,
+                    addrefsig=eval(self.ref_but.get()),
+                    timeinseconds=eval(self.time_sec.get()),
+                    figsize=figsize,
+                )
+
+            else:
+                # Plot instanteous discharge rate
+                openhdemg.plot_idr(
+                    emgfile=self.parent.resdict,
+                    munumber=int(mu_idr),
+                    addrefsig=eval(self.ref_but.get()),
+                    timeinseconds=eval(self.time_sec.get()),
+                    figsize=figsize,
+                )
+
+        except ValueError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Enter valid motor unit number."),
+            )
+
+        except KeyError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Enter valid motor unit number."),
+            )
+
+    def plot_derivation(self):
+        """
+        Instance method to plot the differential derivation of the RAW_SIGNAL
+        by matrix column.
+
+        Both the single and the double differencials can be plotted.
+        This function is used to plot also the sorted RAW_SIGNAL.
+
+        Raises
+        ------
+        ValueError
+            When entered channel number is not valid (inexistent).
+        UnboundLocalError
+            When matrix configuration and / or column number are invalid.
+        KeyError
+            When entered channel number is out of bounds.
+
+        See Also
+        --------
+        plot_differentials in library.
+        """
+
+        try:
+            if self.mat_code.get() == "None":
+                # Get rows and columns and turn into list
+                list_rcs = [int(i) for i in self.matrix_rc.get().split(",")]
+
+                try:
+                    # Sort emg file
+                    sorted_file = openhdemg.sort_rawemg(
+                        emgfile=self.parent.resdict,
+                        code=self.mat_code.get(),
+                        orientation=int(self.mat_orientation.get()),
+                        n_rows=list_rcs[0],
+                        n_cols=list_rcs[1],
+                    )
+
+                except ValueError as e:
+                    show_error_dialog(
+                        parent=self,
+                        error=e,
+                        solution=str(
+                            "Number of specified rows and columns must match number of channels."
+                        ),
+                    )
+                    return
+
+            else:
+                # Sort emg file
+                sorted_file = openhdemg.sort_rawemg(
+                    emgfile=self.parent.resdict,
+                    code=self.mat_code.get(),
+                    orientation=int(self.mat_orientation.get()),
+                )
+
+            # calcualte derivation
+            if self.deriv_config.get() == "Single differential":
+                diff_file = openhdemg.diff(sorted_rawemg=sorted_file)
+
+            elif self.deriv_config.get() == "Double differential":
+                diff_file = openhdemg.double_diff(sorted_rawemg=sorted_file)
+
+            # Create list of figsize
+            figsize = [int(i) for i in self.size_fig.get().split(",")]
+
+            # Plot derivation
+            openhdemg.plot_differentials(
+                emgfile=self.parent.resdict,
+                differential=diff_file,
+                column=self.deriv_matrix.get(),
+                addrefsig=eval(self.ref_but.get()),
+                timeinseconds=eval(self.time_sec.get()),
+                figsize=figsize,
+            )
+        except ValueError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str(
+                    "Enter valid input parameters."
+                    + "\nPotenital error sources:"
+                    + "\n - Matrix Code"
+                    + "\n - Matrix Orientation"
+                    + "\n - Figure size arguments"
+                    + "\n - Rows,Columns arguments"
+                ),
+            )
+
+        except UnboundLocalError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Enter valid Configuration and Matrix Column."),
+            )
+
+        except KeyError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Enter valid Matrix Column."),
+            )
+
+    def plot_muaps(self):
+        """
+        Instance methos to plot motor unit action potenital obtained from STA
+        from one or multiple MUs. Except for DELSYS files, where the STA is
+        not comupted.
+
+        There is no limit to the number of MUs and STA files that can be
+        overplotted.
+        ``Remember: the different STAs should be matched`` with same number of
+        electrode, processing (i.e., differential) and computed on the same
+        timewindow.
+
+        Raises
+        ------
+        ValueError
+            When entered channel number is not valid (inexistent).
+        UnboundLocalError
+            When matrix configuration and / or column number are invalid.
+        KeyError
+            When entered channel number is out of bounds.
+
+        See Also
+        --------
+        plot_muaps in library.
+
+        """
+        try:
+            # DELSYS requires different MUAPS plot
+            if self.parent.resdict["SOURCE"] == "DELSYS":
+                figsize = [int(i) for i in self.size_fig.get().split(",")]
+                muaps_dict = openhdemg.extract_delsys_muaps(
+                    self.parent.resdict,
+                )
+                openhdemg.plot_muaps(
+                    muaps_dict[int(self.muap_munum.get())],
+                    figsize=figsize,
+                )
+
+            else:
+                if self.mat_code.get() == "None":
+                    # Get rows and columns and turn into list
+                    list_rcs = [int(i) for i in self.matrix_rc.get().split(",")]
+
+                    try:
+                        # Sort emg file
+                        sorted_file = openhdemg.sort_rawemg(
+                            emgfile=self.parent.resdict,
+                            code=self.mat_code.get(),
+                            orientation=int(self.mat_orientation.get()),
+                            n_rows=list_rcs[0],
+                            n_cols=list_rcs[1],
+                        )
+
+                    except ValueError as e:
+                        show_error_dialog(
+                            parent=self,
+                            error=e,
+                            solution=str(
+                                "Number of specified rows and columns must "
+                                + "match the number of channels."
+                            ),
+                        )
+                        return
+
+                else:
+                    # Sort emg file
+                    sorted_file = openhdemg.sort_rawemg(
+                        emgfile=self.parent.resdict,
+                        code=self.mat_code.get(),
+                        orientation=int(self.mat_orientation.get()),
+                        custom_sorting_order=self.parent.settings.custom_sorting_order,
+                    )
+
+                # calcualte derivation
+                if self.muap_config.get() == "Single differential":
+                    diff_file = openhdemg.diff(sorted_rawemg=sorted_file)
+
+                elif self.muap_config.get() == "Double differential":
+                    diff_file = openhdemg.double_diff(
+                        sorted_rawemg=sorted_file,
+                    )
+
+                elif self.muap_config.get() == "Monopolar":
+                    diff_file = sorted_file
+
+                # Calculate STA dictionary
+                # Plot deviation
+                sta_dict = openhdemg.sta(
+                    emgfile=self.parent.resdict,
+                    sorted_rawemg=diff_file,
+                    firings="all",
+                    timewindow=int(self.muap_time.get()),
+                )
+
+                # Create list of figsize
+                figsize = [int(i) for i in self.size_fig.get().split(",")]
+
+                # Plot MUAPS
+                openhdemg.plot_muaps(
+                    sta_dict[int(self.muap_munum.get())],
+                    figsize=figsize,
+                )
+
+        except ValueError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str(
+                    "Enter valid input parameters."
+                    + "\nPotenital error sources:"
+                    + "\n - Matrix Code"
+                    + "\n - Matrix Orientation"
+                    + "\n - Figure size arguments"
+                    + "\n - Timewindow"
+                    + "\n - MU Number"
+                    + "\n - Rows,Columns arguments"
+                    + "\n - custom_sorting_order in settings"
+                ),
+            )
+
+        except UnboundLocalError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Enter valid Configuration."),
+            )
+
+        except KeyError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Enter valid Matrix Column."),
+            )
diff --git a/openhdemg/gui/gui_modules/mu_properties.py b/openhdemg/gui/gui_modules/mu_properties.py
new file mode 100644
index 0000000..ea75c24
--- /dev/null
+++ b/openhdemg/gui/gui_modules/mu_properties.py
@@ -0,0 +1,475 @@
+"""Module containing MU propterty analysis"""
+
+from tkinter import ttk, W, E, StringVar
+from sys import platform
+import os
+import customtkinter as ctk
+import openhdemg.library as openhdemg
+from openhdemg.gui.gui_modules.error_handler import show_error_dialog
+
+
+class MuAnalysis:
+    """
+    A class for analyzing motor unit (MU) properties within a GUI application.
+
+    This class creates a window for analyzing various MU properties such as
+    recruitment threshold, discharge rate, and other basic properties. It is
+    activated from the main GUI window and allows for input and computation of
+    MU-related metrics.
+
+    Attributes
+    ----------
+    parent : object
+        The parent widget, typically the main application window that this
+        MuAnalysis instance belongs to.
+    head : CTkToplevel
+        The top-level widget for the MU properties analysis window.
+    mvc_value : StringVar
+        Tkinter StringVar for storing the Maximum Voluntary Contraction (MVC)
+        value.
+    ct_event : StringVar
+        Variable to store the chosen event type for computing MU thresholds.
+    ct_type : StringVar
+        Variable to store the type of computation (absolute, relative, or both)
+        for MU thresholds.
+    firings_rec : StringVar
+        Variable to store the number of firings at recruitment.
+    firings_ste : StringVar
+        Variable to store the number of firings at the start/end of steady
+        phase.
+    dr_event : StringVar
+        Variable to store the chosen event type for computing MU discharge
+        rate.
+    b_firings_rec : StringVar
+        Variable to store the number of firings at recruitment for basic MU
+        properties computation.
+    b_firings_ste : StringVar
+        Variable to store the number of firings at the start/end of steady
+        phase for basic MU properties computation.
+
+    Methods
+    -------
+    __init__(self, parent)
+        Initialize a new instance of the MuAnalysis class.
+    compute_mu_threshold(self)
+        Compute the motor unit recruitment/derecruitment threshold.
+    compute_mu_dr(self)
+        Compute the motor unit discharge rate.
+    basic_mus_properties(self)
+        Compute basic motor unit properties.
+
+    Examples
+    --------
+    >>> main_window = Tk()
+    >>> mu_analysis = MuAnalysis(main_window)
+    >>> mu_analysis.head.mainloop()
+
+    Notes
+    -----
+    This class is dependent on the `ctk` and `ttk` modules from the `tkinter`
+    library. Some attributes and methods are conditional based on the
+    `parent`'s properties.
+
+    """
+
+    def __init__(self, parent):
+        """
+        Initialize a new instance of the MuAnalysis class.
+
+        This method sets up the GUI components of the Motor Unit Properties
+        window. It includes input fields for MVC (Maximum Voluntary
+        Contraction) value, buttons and dropdown menus to compute MU
+        thresholds, discharge rates, and basic MU properties. Each component is
+        configured and placed in the window grid.
+
+        Parameters
+        ----------
+        parent : object
+            The parent widget, typically the main application window, to which
+            this MuAnalysis instance belongs. The parent is used for accessing
+            shared resources and data.
+
+        Raises
+        ------
+        AttributeError
+            If certain widgets or properties are not properly instantiated due
+            to missing parent configurations or resources.
+
+        Notes
+        -----
+        The creation of the GUI components involves setting up various Tkinter
+        and custom widgets (like CTkLabel, CTkEntry, CTkButton, CTkComboBox).
+        Each widget is configured with specific properties like size, color,
+        and variable bindings and placed in a grid layout.
+        """
+
+        # Initialize parent and load parent settings
+        self.parent = parent
+        self.parent.load_settings()
+        # Create new window
+        self.head = ctk.CTkToplevel()
+        self.head.title("Motor Unit Properties Window")
+
+        # Set window icon
+        head_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+        iconpath = head_path + "/gui_files/Icon_transp.ico"
+        self.head.iconbitmap(iconpath)
+        if platform.startswith("win"):
+            self.head.after(200, lambda: self.head.iconbitmap(iconpath))
+        self.head.grab_set()
+
+        # Set resizable window
+        # Configure columns with a loop
+        for col in range(3):
+            self.head.columnconfigure(col, weight=1)
+
+        # Configure rows with a loop
+        for row in range(21):
+            self.head.rowconfigure(row, weight=1)
+
+        # MVC Entry
+        ctk.CTkLabel(
+            self.head,
+            text="Enter MVC[n]:",
+            font=("Segoe UI", 18, "bold"),
+        ).grid(column=0, row=0, sticky=(W))
+        self.mvc_value = StringVar()
+        enter_mvc = ctk.CTkEntry(
+            self.head,
+            width=100,
+            textvariable=self.mvc_value,
+        )
+        enter_mvc.grid(column=1, row=0, sticky=(W, E))
+
+        # Compute MU re-/derecruitement threshold
+        separator = ttk.Separator(self.head, orient="horizontal")
+        separator.grid(
+            column=0, columnspan=2, row=2, sticky=(W, E), padx=5, pady=5,
+        )  # TODO one row has been skipped between enter_mvc and separator
+
+        thresh = ctk.CTkButton(
+            self.head,
+            text="Compute threshold",
+            command=self.compute_mu_threshold,
+        )
+        thresh.grid(column=0, row=3, sticky=W)
+
+        self.ct_event = StringVar()
+        ct_event_values = ("rt", "dert", "rt_dert")
+        ct_events_entry = ctk.CTkComboBox(
+            self.head,
+            width=100,
+            variable=self.ct_event,
+            values=ct_event_values,
+            state="readonly",
+        )
+        ct_events_entry.grid(column=1, row=3)
+        self.ct_event.set("Event")
+
+        self.ct_type = StringVar()
+        ct_types_values = ("abs", "rel", "abs_rel")
+        ct_types_entry = ctk.CTkComboBox(
+            self.head,
+            width=100,
+            variable=self.ct_type,
+            values=ct_types_values,
+            state="readonly",
+        )
+        ct_types_entry.grid(column=2, row=3)
+        self.ct_type.set("Type")
+
+        # Compute motor unit discharge rate
+        separator1 = ttk.Separator(self.head, orient="horizontal")
+        separator1.grid(
+            column=0,
+            columnspan=4,
+            row=4,
+            sticky=(W, E),
+            padx=5,
+            pady=5,
+        )
+
+        ctk.CTkLabel(
+            self.head,
+            text="Firings at Rec",
+            font=("Segoe UI", 18, "bold"),
+        ).grid(column=1, row=5, sticky=(W, E))
+        ctk.CTkLabel(
+            self.head,
+            text="Firings Start/End Steady",
+            font=("Segoe UI", 18, "bold"),
+        ).grid(column=2, row=5, sticky=(W, E))
+
+        dr_rate = ctk.CTkButton(
+            self.head,
+            text="Compute discharge rate",
+            command=self.compute_mu_dr,
+        )
+        dr_rate.grid(column=0, row=6, sticky=W)
+
+        self.firings_rec = StringVar()
+        firings_1 = ctk.CTkEntry(
+            self.head,
+            width=100,
+            textvariable=self.firings_rec,
+        )
+        firings_1.grid(column=1, row=6)
+        self.firings_rec.set(4)
+
+        self.firings_ste = StringVar()
+        firings_2 = ctk.CTkEntry(
+            self.head,
+            width=100,
+            textvariable=self.firings_ste,
+        )
+        firings_2.grid(column=2, row=6)
+        self.firings_ste.set(10)
+
+        self.dr_event = StringVar()
+        dr_events_values = (
+            "rec",
+            "derec",
+            "rec_derec",
+            "steady",
+            "rec_derec_steady",
+        )
+        dr_events_entry = ctk.CTkComboBox(
+            self.head,
+            width=100,
+            variable=self.dr_event,
+            values=dr_events_values,
+            state="readonly",
+        )
+        dr_events_entry.grid(column=3, row=6, sticky=E)
+        self.dr_event.set("Event")
+
+        # Compute basic motor unit properties
+        separator2 = ttk.Separator(self.head, orient="horizontal")
+        separator2.grid(
+            column=0,
+            columnspan=4,
+            row=7,
+            sticky=(W, E),
+            padx=5,
+            pady=5,
+        )
+
+        ctk.CTkLabel(
+            self.head,
+            text="Firings at Rec",
+            font=("Segoe UI", 18, "bold"),
+        ).grid(column=1, row=8, sticky=(W, E))
+        ctk.CTkLabel(
+            self.head,
+            text="Firings Start/End Steady",
+            font=("Segoe UI", 18, "bold"),
+        ).grid(column=2, row=8, sticky=(W, E))
+
+        basic = ctk.CTkButton(
+            self.head,
+            text="Basic MU properties",
+            command=self.basic_mus_properties,
+        )
+        basic.grid(column=0, row=9, sticky=W)
+
+        self.b_firings_rec = StringVar()
+        b_firings_1 = ctk.CTkEntry(
+            self.head,
+            width=100,
+            textvariable=self.b_firings_rec,
+        )
+        b_firings_1.grid(column=1, row=9)
+        self.b_firings_rec.set(4)
+
+        self.b_firings_ste = StringVar()
+        b_firings_2 = ctk.CTkEntry(
+            self.head,
+            width=100,
+            textvariable=self.b_firings_ste,
+        )
+        b_firings_2.grid(column=2, row=9)
+        self.b_firings_ste.set(10)
+
+        for child in self.head.winfo_children():
+            child.grid_configure(padx=5, pady=5)
+
+    ### Define functions for motor unit property calculation
+
+    def compute_mu_threshold(self):
+        """
+        Instance method to compute the motor unit recruitement thresholds
+        based on user selection of events and types.
+
+        Executed when button "Compute threshold" in Motor Unit Properties
+        Window is pressed. The analysis results are displayed in the result
+        terminal.
+
+        Raises
+        ------
+        AttributeError
+            When no file is loaded prior to calculation.
+        ValueError
+            When entered MVC is not valid (inexistent).
+        AssertionError
+            When types/events are not specified.
+
+        See Also
+        --------
+        compute_thresholds in library.
+        """
+
+        try:
+            # Compute thresholds
+            self.parent.mu_thresholds = openhdemg.compute_thresholds(
+                emgfile=self.parent.resdict,
+                event_=self.ct_event.get(),
+                type_=self.ct_type.get(),
+                n_firings=self.parent.settings.compute_thresholds__n_firings,
+                mvc=float(self.mvc_value.get()),
+            )
+            # Display results
+            self.parent.display_results(self.parent.mu_thresholds)
+
+        except AttributeError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Make sure a file is loaded."),
+            )
+
+        except ValueError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Enter valid MVC, Event or Type."),
+            )
+
+        except AssertionError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Specify Event and/or Type."),
+            )
+
+    def compute_mu_dr(self):
+        """
+        Instance method to compute the motor unit discharge rate
+        based on user selection of Firings and Events.
+
+        Executed when button "Compute discharge rate" in Motor Unit Properties
+        Window is pressed. The analysis results are displayed in the result
+        terminal.
+
+        Raises
+        ------
+        AttributeError
+            When no file is loaded prior to calculation.
+        ValueError
+            When entered Firings values are not valid (inexistent).
+        AssertionError
+            When types/events are not specified.
+
+        See Also
+        --------
+        compute_dr in library.
+        """
+
+        try:
+            # Compute discharge rates
+            self.parent.mus_dr = openhdemg.compute_dr(
+                emgfile=self.parent.resdict,
+                n_firings_RecDerec=int(self.firings_rec.get()),
+                n_firings_steady=int(self.firings_ste.get()),
+                event_=self.dr_event.get(),
+            )
+            # Display results
+            self.parent.display_results(self.parent.mus_dr)
+
+        except AttributeError as e:
+            show_error_dialog(
+                parent=self, error=e,
+                solution=str("Make sure a file is loaded."),
+            )
+
+        except ValueError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str(
+                    "Enter valid Firings value or select a correct number of "
+                    + "points."
+                ),
+            )
+
+        except AssertionError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Specify Event and/or Type."),
+            )
+
+    def basic_mus_properties(self):
+        """
+        Instance method to compute basic motor unit properties based in user
+        selection in plot.
+
+        Executed when button "Basic MU properties" in Motor Unit Properties
+        Window is pressed. The analysis results are displayed in the result
+        terminal.
+
+        Raises
+        ------
+        AttributeError
+            When no file is loaded prior to calculation.
+        ValueError
+            When entered Firings values are not valid (inexistent).
+        AssertionError
+            When types/events are not specified.
+        UnboundLocalError
+            When start/end area for calculations are specified wrongly.
+
+        See Also
+        --------
+        basic_mus_properties in library.
+        """
+
+        try:
+            # Calculate properties
+            self.parent.mu_prop_df = openhdemg.basic_mus_properties(
+                emgfile=self.parent.resdict,
+                n_firings_rt_dert=self.parent.settings.basic_mus_properties__n_firings_rt_dert,
+                n_firings_RecDerec=int(self.b_firings_rec.get()),
+                n_firings_steady=int(self.b_firings_ste.get()),
+                accuracy=self.parent.settings.basic_mus_properties__accuracy,
+                ignore_negative_ipts=self.parent.settings.basic_mus_properties__ignore_negative_ipts,
+                constrain_pulses=self.parent.settings.basic_mus_properties__constrain_pulses,
+                mvc=float(self.mvc_value.get()),
+                start_steady=-1,
+                end_steady=-1,
+            )
+            # Display results
+            self.parent.display_results(self.parent.mu_prop_df)
+
+        except AttributeError as e:
+            show_error_dialog(
+                parent=self, error=e, solution=str("Make sure a file is loaded.")
+            )
+
+        except ValueError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str(
+                    "Enter valid MVC value or select a correct number of points."
+                ),
+            )
+
+        except AssertionError as e:
+            show_error_dialog(
+                parent=self, error=e, solution=str("Specify Event and/or Type.")
+            )
+
+        except UnboundLocalError as e:
+            show_error_dialog(
+                parent=self, error=e, solution=str("Select start/end area again.")
+            )
diff --git a/openhdemg/gui/openhdemg_gui.py b/openhdemg/gui/openhdemg_gui.py
index c416d3e..7ca8a1e 100644
--- a/openhdemg/gui/openhdemg_gui.py
+++ b/openhdemg/gui/openhdemg_gui.py
@@ -2,75 +2,58 @@
 This file contains the gui functionalities of openhdemg.
 """
 
+import importlib
 import os
+import copy
+import subprocess
+import sys
 import tkinter as tk
-import customtkinter
-import threading
 import webbrowser
-from tkinter import ttk, filedialog, Canvas
-from tkinter import StringVar, Tk, N, S, W, E, DoubleVar
-from pandastable import Table, config
+from tkinter import Canvas, E, N, S, StringVar, Tk, W, filedialog, messagebox, ttk
 
-from PIL import Image
-
-import matplotlib.pyplot as plt
+import customtkinter as ctk
 import matplotlib
+import matplotlib.pyplot as plt
 from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
-from matplotlib.figure import Figure
-import pandas as pd
-
-matplotlib.use("TkAgg")
+from pandastable import Table
+from PIL import Image
 
+import openhdemg.gui.settings as settings
 import openhdemg.library as openhdemg
+from openhdemg.gui.gui_modules import (
+    AdvancedAnalysis,
+    AnalyseForce,
+    EditSig,
+    GUIHelpers,
+    MuAnalysis,
+    MURemovalWindow,
+    PlotEmg,
+    show_error_dialog,
+)
+
+matplotlib.use("TkAgg")
+ctk.set_default_color_theme(
+    os.path.dirname(os.path.abspath(__file__)) + "/gui_files/gui_color_theme.json"
+)
 
 
-class emgGUI:
+class emgGUI(ctk.CTk):
     """
-    A class representing a Tkinter TK instance.
+    This class is used to create a graphical user interface for the openhdemg
+    library.
 
-    This class is used to create a graphical user interface for
-    the openhdemg library.
+    Within this class and corresponding childs, most functionalities of the
+    openhdemg library are packed in a GUI. However, the library is more
+    comprehensive and much more adaptable to the users needs.
 
     Attributes
     ----------
-    self.auto_eval : int, default 0
-        If auto > 0, the script automatically removes the offset based on the number
-        of samples passed in input.
-    self.b_firings_rec : int, default 4
-        The number of firings at recruitment and derecruitment to consider for the
-        calculation of the DR.
-    self.b_firings_ste : int, default 10
-        The number of firings to consider for the calculation of the DR at the start and at the end
-        of the steady-state phase.
     self.canvas : matplotlib.backends.backend_tkagg
         Canvas for plotting figures inside the GUI.
     self.channels : int or list
         The channel (int) or channels (list of int) to plot.
         The list can be passed as a manually-written with: "0,1,2,3,4,5...,n",
         channels is expected to be with base 0.
-    self.convert : str, default Multiply
-        The kind of conversion applied to the Refsig during Refsig conversion. Can be "Multiply" or "Divide".
-    self.convert_factor : float, default 2.5
-        Factore used during Refsig converison when multiplication or division is applied.
-    self.cutoff_freq : int, default 20
-        The cut-off frequency in Hz.
-    self.ct_event : str, default "rt_dert"
-        When to calculate the thresholds. Input parameters for event_ are:
-            "rt_dert" means that both recruitment and derecruitment tresholds will be calculated.
-            "rt" means that only recruitment tresholds will be calculated.
-            "dert" means that only derecruitment tresholds will be calculated.
-    self.dr_event : str, default "rec_derec_steady"
-        When to calculate the DR. Input parameters for event_ are:
-            "rec_derec_steady" means that the DR is calculated at recruitment, derecruitment and
-            during the steady-state phase.
-            "rec" means that the DR is calculated at recruitment.
-            "derec" means that the DR is calculated at derecruitment.
-            "rec_derec" means that the DR is calculated at recruitment and derecruitment.
-            "steady" means that the DR is calculated during the steady-state phase.
-    self.end_area, self.start_area : int
-        The start and end of the selection for file resizing (can be used for code automation).
-    self.exportable_df : pd.DataFrame
-        A pd.DataFrame containing the results of the analysis.
     self.fig : matplotlib.figure
         Figure to be plotted on Canvas.
     self.filename : str
@@ -79,97 +62,27 @@ class emgGUI:
         String containing the path to EMG file selected for analysis.
     self.filetype : str
         String containing the filetype of import EMG file.
-        Filetype can be "OPENHDEMG", "OTB", "DEMUSE", "OTB_REFSIG", "CUSTOMCSV", "CUSTOMCSV_REFSIG".
-    self.filter_order : int, default 4
-        The filter order.
-    self.firings_rec : int, default 4
-        The number of firings at recruitment and derecruitment to consider for the calculation
-        of the DR.
-    self.firings_ste : int, default 10
-        The number of firings to consider for the calculation of the DR at the start and at the end
-        of the steady-state phase.
-    self.first_fig : matplotlib.figure
-        Figure frame determinining size of all figures that are plotted on canvas.
-    self.head : tk.toplevel
-        New tk.toplevel instance created everytime upon opnening a new window. This is needed
-        for having a seperate window open.
+        Filetype can be "OPENHDEMG", "OTB", "DEMUSE", "OTB_REFSIG",
+        "CUSTOMCSV", "CUSTOMCSV_REFSIG".
     self.left : tk.frame
-        Left frame inside of master that contains all buttons and filespecs.
-    self.linewidth : float, default 0.5
-        The width of the vertical lines representing the MU firing.
+        Left frame inside of self that contains all buttons and filespecs.
     self.logo :
         String containing the path to image file containing logo of openhdemg.
     self.logo_canvas : tk.canvas
         Canvas to display logo of Open_HG-EMG when openend.
-    self.master: tk
-        TK master window containing all widget children for this GUI.
-    self.mus_dr: pd.DataFrame
-        A pd.DataFrame containing the requested DR.
-    self.mu_numb : int or list, default "all"
-        By default, IPTS of all the MUs is plotted.
-        Otherwise, a single MU (int) or multiple MUs (list of int) can be specified.
-        The list can be passed as a manually-written list: "0,1,2,3,4,5,...,n",
-        self.m_numb is expected to be with base 0 (i.e., the first MU in the file is the number 0).
-    self.mu_numb_idr : int or list, default "all"
-        By default, IDR of all the MUs is plotted.
-        Otherwise, a single MU (int) or multiple MUs (list of int) can be specified.
-        The list can be passed as a manually-written list: "0,1,2,3,4,5,...,n",
-        self.m_numb_idr is expected to be with base 0 (i.e., the first MU in the
-        file is the number 0).
-    self.mu_to_remove : int
-        The MUs to remove. If a single MU has to be removed, this should be an
-        int (number of the MU).
-        self.mu_to_remove is expected to be with base 0
-        (i.e., the first MU in the file is the number 0).
-    self.mu_to_edit : int
-        The MUs to edit singularly. If a single MU has to be edited, this should be an
-        int (number of the MU).
-        self.mu_to_edit is expected to be with base 0
-        (i.e., the first MU in the file is the number 0).
-    self.mu_thresholds : pd.DataFrame
-        A DataFrame containing the requested thresholds.
-    self.mvc : float
-        The MVC value in the original unit of measurement.
-    self.mvc_df : pd.DataFrame
-        A Dataframe containing the detected MVC value.
-    self.mvc_value : float
-        The MVC value specified during Refsig conversion.
-    self.offsetval: float, default 0
-        Value of the offset. If offsetval is 0 (default), the user will be asked to manually
-        select an aerea to compute the offset value.
-        Otherwise, the value passed to offsetval will be used. Negative offsetval can be passed.
+    self.self: tk
+        TK self window containing all widget children for this GUI.
     self.resdict : dict
         Dictionary derived from input EMG file for further analysis.
-    self.rfd_df : pd.DataFrame
-        A Dataframe containing the calculated RFD values.
-    self.rfdms : list, default [50, 100, 150, 200]
-        Milliseconds (ms). A list containing the ranges in ms to calculate the RFD.
+    self.resdict_copy_of_original : dict
+        A deepcopy of self.resdict stored for resetting the analyses.
     self.right : tk.frame
-        Left frame inside of master that contains plotting canvas.
+        Left frame inside of self that contains plotting canvas.
     self.terminal : ttk.Labelframe
-        Tkinter labelframe that is used to display the results table in the GUI.
-    self.mat_code : str
-        The code containing the matrix identification number.
-    self.mat_orientation : int
-        The orientation of the matrix in degrees. Can be 0, 180.
-    self.deriv_config : str
-        The Method used to calculate the MUs deviation.
-    self.muap_config : str
-        The Method used to calculate the MUs deviation.
-    self.deriv_matrix : str
-        Column of the matrix to be plotted.
-    self.size_fig : str, default [20,15]
-        Size of the figure to be plotted in centimeter.
-    self.ref_but : str, default "False"
-        String value used to determine if reference signal should be
-        added to the plot.
-    self.time_but : str, default "False"
-        String value used to determine if time in seconds should be used
-        in x-axis of plotting.
-    muap_munum : int
-        Number of motor unit to be plotted.
-    muap_time : int
-        Time window to be plotted.
+        Tkinter labelframe that is used to display the results table in the
+        GUI.
+    self.processing_indicator : ctk.CTkButton
+        Button used to indicate that the file is loading/saving.
     self.info : tk.PhotoImage
         Information Icon displayed in GUI.
     self.online : tk.Photoimage
@@ -180,364 +93,340 @@ class emgGUI:
         Contact Icon displayed in GUI.
     self.cite : tk.PhotoImage
         Citation Icon displayed in GUI.
-    self.otb_combobox : ttk.Combobox
-        Combobox appearing in main GUI window or advanced
-        analysis window when OTB files are loaded. Contains
-        the extension factor for OTB files.
-        Stringvariable containing the
-    self.extension_factor : tk.StringVar()
-        Stringvariable containing the OTB extension factor value.
-    self.advanced_method : tk.Stringvar()
-        Stringvariable containing the selected method of advanced
-        analysis.
-    self.matrix_rc : tk.StringVar()
-        String containing the channel number of emgfile when
-        matri codes are bypassed. Used in plot window.
-    self.matrix_rc_adv : tk.StringVar()
-        String containing the channel number of emgfile when
-        matri codes are bypassed. Used in advanced window.
-    self.emgfile1 : pd.Dataframe
-        Dataframe object containing the loaded first emgfile used
-        for MU tracking.
-    self.emgfile2 : pd.Dataframe
-        Dataframe object containing the loaded first emgfile used
-        for MU tracking.
-    self.thresh_adv : tk.Stringvar()
-        Stringvariable containing the selected threshold for MU tracking.
-    self.filter_adv : tk.Boolenvar()
-        Boolean determining whether filtering should be applied during MU
-        tracking.
-    self.show_adv : tk.Boolenvar()
-        Boolean determining whether results of MU tracking should be plotted.
-    self.exclude_thres : tk.Boolenvar()
-        Boolean determining whether values below treshold should be excluded
-        during MU tracking.
-    self.which_adv : tk.Stringvar()
-        Stringvariable determining how MU duplicates are removed.
-    self.time_window : tk.Stringvar()
-        Stringvariable determining the time window for duplicate removal
-        and tracking.
 
     Methods
     -------
-    __init__(master)
-        Initializes GUI class and main GUI window (master).
+    __init__(self)
+        Initializes GUI class and main GUI window (self).
     get_file_input()
         Gets emgfile location and respective file is loaded.
-        Executed when button "Load File" in master GUI window pressed.
+        Executed when button "Load File" in self GUI window pressed.
     save_emgfile()
         Saves the edited emgfile dictionary to a .json file.
-        Executed when button "Save File" in master GUI window pressed.
-    export_to_excel()
-        Saves the analysis results to a .xlsx file.
-        Executed when button "Save Results" in master GUI window pressed.
+        Executed when button "Save File" in self GUI window pressed.
     reset_analysis()
-        Resets the whole analysis, restores the original input file and the graph.
-        Executed when button "Reset analysis" in master GUI window pressed.
+        Resets the whole analysis, restores the original input file and the
+        graph.
+        Executed when button "Reset analysis" in self GUI window pressed.
     in_gui_plotting()
         Method used for creating plot inside the GUI (on the GUI canvas).
-        Executed when button "View MUs" in master GUI window pressed.
-    sort_mus()
-        Method used to sort motor units in Plot according to recruitement order.
-        Executed when button "Sort MUs" in master GUI window pressed.
-    remove_mus()
-        Opens seperate window to select motor units to be removed.
-        Executed when button "Remove MUs" in master GUI window pressed.
-    remove()
-        Method used to remove single motor units.
-    remove_empty()
-        Method that removes all empty MUs.
-    edit_refsig()
-        Opens seperate window to edit emg reference signal.
-        Executed when button "RefSig Editing" in master GUI window pressed.
-    filter_refsig()
-        Method used to filter the emg reference signal.
-    remove_offset()
-        Method used to remove offset of emg reference signal.
-    resize_file()
-        Opens seperate window to resize emg file / reference signal.
-        Executed when button "Resize File" in master GUI window pressed.
-    analyze_force()
-        Opens seperate window to analyze force signal/values.
-        Executed when button "Analyze force" in master GUI window pressed.
-    get_mvc()
-        Method used to calculate/select MVC.
-    det_rfd()
-        Method used to calculated RFD based on selected startpoint.
+        Executed when button "View MUs" in self GUI window pressed.
     mu_analysis()
         Opens seperate window to calculated specific motor unit properties.
-        Executed when button "MU properties" in master GUI window pressed.
-    compute_mu_threshold()
-        Method used to calculate motor unit recruitement thresholds.
-    compute_mu_dr()
-       Method used to calculate motor unit discharge rate.
-    basic_mus_analysis()
-        Method used to calculate basic motor unit properties.
-    plot_emg()
-        Opens seperate window to plot emgsignal/motor unit properties.
-        Executed when button "Plot EMG" in master GUI window pressed.
-    plt_emgsignal()
-        Method used to plot emgsignal.
-    plt_idr()
-        Method used to plot instanteous discharge rate.
-    plt_ipts()
-        Method used to plot the motor unit puls train (i.e., non-binary firing)
-    plt_refsignal()
-        Method used to plot the motor unit reference signal.
-    plt_mupulses()
-        Method used to plot the motor unit pulses.
-    plot_derivation()
-        Method to plot the differential derivation of the RAW_SIGNAL
-        by matrix column.
-    plot_muaps()
-        Method to plot motor unit action potenital obtained from STA
-        from one or multiple MUs.
-    advanced_analysis()
-        Method to open top-level windows based on the selected advanced method.
-    on_filetype_change()
-        Method do display extension factor combobx when filetype loaded is
-        OTB.
-    open_emgfile1()
-        Method to open EMG file based on the selected file type and extension factor.
-    open_emgfile2()
-        Method to open EMG file based on the selected file type and extension factor.
-    track_mus()
-        Method to perform MUs tracking on the loaded EMG files.
+        Executed when button "MU properties" in self GUI window pressed.
     display_results()
         Method used to display result table containing analysis results.
-    to_percent()
-        Method that converts Refsig to a percentag value. Should only be used when the Refsig is in absolute values.
-    convert_refsig()
-        Method that converts Refsig by multiplication or division.
 
     Notes
     -----
     Please note that altough we created a GUI class, the included methods/
-    instances are highly specific. We did not conceptualize the methods/instances
-    to be used seperately. Similar functionalities are available in the library
-    and were specifically coded to be used seperately/singularly.
-
-    Most instance methods of this class heavily rely on the functions provided in
-    the library. In the section "See Also" at each instance method, the reader is
-    referred to the corresponding function and extensive documentation in the library.
+    instances are highly specific. We did not conceptualize the
+    methods/instances to be used seperately. Similar functionalities are
+    available in the library and were specifically coded to be used
+    seperately/singularly.
+
+    Most instance methods of this class heavily rely on the functions provided
+    in the library. In the section "See Also" at each instance method, the
+    reader is referred to the corresponding function and extensive
+    documentation in the library.
     """
 
-    def __init__(self, master):
+    def __init__(self, *args, **kwargs):
         """
-        Initialization of master GUI window upon calling.
+        Initialization of  GUI window upon calling.
 
         Parameters
         ----------
-        master: tk
+        : tk
             tk class object
         """
+
+        super().__init__(*args, **kwargs)
+
+        # Load settings
+        self.load_settings()
+
         # Set up GUI
-        self.master = master
-        self.master.title("openhdemg")
+        self.title("openhdemg")
         master_path = os.path.dirname(os.path.abspath(__file__))
-        iconpath = master_path + "/gui_files/Icon.ico"
-        self.master.iconbitmap(iconpath)
-        self.master.columnconfigure(0, weight=1)
-        self.master.rowconfigure(0, weight=1)
+        ctk.set_default_color_theme(master_path + "/gui_files/gui_color_theme.json")
+
+        iconpath = master_path + "/gui_files/Icon_transp.ico"
+        self.iconbitmap(iconpath)
+
+        # Necessary for resizing
+        self.columnconfigure(0, weight=1)
+        self.columnconfigure(1, weight=5)
+        self.rowconfigure(0, weight=1)
+        self.rowconfigure(1, weight=1)  # Output
+        self.minsize(width=600, height=400)
 
         # Create left side framing for functionalities
-        self.left = ttk.Frame(self.master, padding="10 10 12 12")
-        self.left.grid(column=0, row=0, sticky="nsew")
+        self.left = ctk.CTkFrame(
+            self,
+        )
+        self.left.grid(column=0, row=0, sticky=(N, S, E, W))
+
+        # Configure columns with a loop
         self.left.columnconfigure(0, weight=1)
         self.left.columnconfigure(1, weight=1)
-        self.left.columnconfigure(2, weight=1)
-        self.left.columnconfigure(3, weight=1)
-        self.left.rowconfigure(0, weight=1)
-        self.left.rowconfigure(1, weight=1)
-        self.left.rowconfigure(2, weight=1)
-        self.left.rowconfigure(3, weight=1)
-        self.left.rowconfigure(4, weight=1)
-        self.left.rowconfigure(5, weight=1)
-        self.left.rowconfigure(6, weight=1)
-        self.left.rowconfigure(7, weight=1)
-        self.left.rowconfigure(8, weight=1)
-        self.left.rowconfigure(9, weight=1)
-        self.left.rowconfigure(10, weight=1)
-        self.left.rowconfigure(11, weight=1)
-        self.left.rowconfigure(12, weight=1)
-        self.left.rowconfigure(13, weight=1)
-        self.left.rowconfigure(14, weight=1)
-        self.left.rowconfigure(15, weight=1)
-        self.left.rowconfigure(16, weight=1)
-        self.left.rowconfigure(17, weight=1)
-        self.left.rowconfigure(18, weight=1)
-
-        # Style
-        style = ttk.Style()
-        style.theme_use("clam")
-        style.configure("TToplevel", background="LightBlue4")
-        style.configure("TFrame", background="LightBlue4")
-        style.configure(
-            "TLabel",
-            font=("Lucida Sans", 12),
-            foreground="black",
-            background="LightBlue4",
-        )
-        style.configure("TButton", foreground="black", font=("Lucida Sans", 11))
-        style.configure("TEntry", font=("Lucida Sans", 12), foreground="black")
-        style.configure("TCombobox", background="LightBlue4", foreground="black")
-        style.configure("TLabelFrame", foreground="black", font=("Lucida Sans", 16))
-        style.configure("TProgressbar", foreground="#FFBF00", background="#FFBF00")
-        # Specify Signal
+
+        # Configure rows with a loop
+        for row in range(21):
+            self.left.rowconfigure(row, weight=1)
+
+        # Specify filetype
         self.filetype = StringVar()
-        signal_value = ("OPENHDEMG", "DEMUSE","OTB",  "OTB_REFSIG", "DELSYS", "DELSYS_REFSIG", "CUSTOMCSV", "CUSTOMCSV_REFSIG")
-        signal_entry = ttk.Combobox(
-            self.left, text="Signal", width=10, textvariable=self.filetype
+        signal_value = [
+            "OPENHDEMG",
+            "DEMUSE",
+            "OTB",
+            "OTB_REFSIG",
+            "DELSYS",
+            "DELSYS_REFSIG",
+            "CUSTOMCSV",
+            "CUSTOMCSV_REFSIG",
+        ]
+        signal_entry = ctk.CTkComboBox(
+            self.left,
+            width=8,
+            variable=self.filetype,
+            values=signal_value,
+            state="readonly",
         )
-        signal_entry["values"] = signal_value
-        signal_entry["state"] = "readonly"
         signal_entry.grid(column=0, row=1, sticky=(W, E))
         self.filetype.set("Type of file")
-        # Trace filetype to apply function when changeing
-        self.filetype.trace("w", self.on_filetype_change)
+        # Trace filetype to apply function when changing
+        self.filetype.trace_add("write", self.on_filetype_change)
 
         # Load file
-        load = ttk.Button(self.left, text="Load File", command=self.get_file_input)
-        load.grid(column=0, row=3, sticky=W)
+        load = ctk.CTkButton(
+            self.left,
+            text="Load File",
+            command=self.get_file_input,
+        )
+        load.grid(column=0, row=3, sticky=(N, S, E, W))
+
+        # Button to indicate that the file is loading
+        self.processing_indicator = ctk.CTkButton(
+            self.left,
+            text="Processing!",
+            fg_color="#FFA500",  # Orange
+        )
+        self.processing_indicator.grid(column=0, row=4, sticky=(N, S, E, W))
+        # Hide, don't forget to preserve rowconfigure settings
+        self.processing_indicator.lower()
 
         # File specifications
-        ttk.Label(self.left, text="Filespecs:").grid(column=1, row=1, sticky=(W, E))
-        ttk.Label(self.left, text="N Channels:").grid(column=1, row=2, sticky=(W, E))
-        ttk.Label(self.left, text="N of MUs:").grid(column=1, row=3, sticky=(W, E))
-        ttk.Label(self.left, text="File length:").grid(column=1, row=4, sticky=(W, E))
+        ctk.CTkLabel(
+            self.left,
+            text="Filespecs",
+            font=("Segoe UI", 18, "underline"),
+        ).grid(column=1, row=1, sticky=W)
+        self.n_channels = ctk.CTkLabel(
+            self.left,
+            text="N Channels:",
+            font=("Segoe UI", 15, "bold"),
+        )
+        self.n_channels.grid(column=1, row=2, sticky=W)
+        self.n_of_mus = ctk.CTkLabel(
+            self.left,
+            text="N of MUs:",
+            font=("Segoe UI", 15, "bold"),
+        )
+        self.n_of_mus.grid(column=1, row=3, sticky=W)
+        self.file_length = ctk.CTkLabel(
+            self.left,
+            text="File Length:",
+            font=("Segoe UI", 15, "bold"),
+        )
+        self.file_length.grid(column=1, row=4, sticky=W)
         separator0 = ttk.Separator(self.left, orient="horizontal")
-        separator0.grid(column=0, columnspan=3, row=5, sticky=(W, E))
-
-        # COMMENT: This is commented out because it is not yet functional.
-        # Decompose file
-        # decompose = ttk.Button(self.left,
-        #                        text="Decompose",
-        #                        command=self.decompose_file)
-        # decompose.grid(row=3, column=0, sticky=W)
+        separator0.grid(column=0, columnspan=2, row=5, sticky=(E, W))
 
         # Save File
-        save = ttk.Button(self.left, text="Save File", command=self.save_emgfile)
-        save.grid(column=0, row=6, sticky=W)
+        save = ctk.CTkButton(
+            self.left,
+            text="Save File",
+            command=self.save_emgfile,
+        )
+        save.grid(column=0, row=6, sticky=(N, S, E, W))
         separator1 = ttk.Separator(self.left, orient="horizontal")
-        separator1.grid(column=0, columnspan=3, row=7, sticky=(W, E))
+        separator1.grid(column=0, columnspan=2, row=7, sticky=(E, W))
 
         # Export to Excel
-        export = ttk.Button(
-            self.left, text="Save Results", command=self.export_to_excel
+        export = ctk.CTkButton(
+            self.left,
+            text="Save Results",
+            command=lambda: (GUIHelpers(parent=self).export_to_excel()),
         )
-        export.grid(column=1, row=6, sticky=(W, E))
+        export.grid(column=1, row=6, sticky=(N, S, E, W))
 
         # View Motor Unit Firings
-        firings = ttk.Button(self.left, text="View MUs", command=self.in_gui_plotting)
-        firings.grid(column=0, row=8, sticky=W)
+        firings = ctk.CTkButton(
+            self.left,
+            text="View MUs",
+            command=lambda: (self.in_gui_plotting(resdict=self.resdict)),
+        )
+        firings.grid(column=0, row=8, sticky=(N, S, E, W))
 
         # Sort Motor Units
-        sorting = ttk.Button(self.left, text="Sort MUs", command=self.sort_mus)
-        sorting.grid(column=1, row=8, sticky=(W, E))
+        sorting = ctk.CTkButton(
+            self.left,
+            text="Sort MUs",
+            command=lambda: (GUIHelpers(parent=self).sort_mus()),
+        )
+        sorting.grid(column=1, row=8, sticky=(N, S, E, W))
         separator2 = ttk.Separator(self.left, orient="horizontal")
-        separator2.grid(column=0, columnspan=3, row=9, sticky=(W, E))
+        separator2.grid(column=0, columnspan=2, row=9, sticky=(E, W))
 
         # Remove Motor Units
-        remove_mus = ttk.Button(self.left, text="Remove MUs", command=self.remove_mus)
-        remove_mus.grid(column=0, row=10, sticky=W)
-
-        # COMMENT: This is commented because it is not fully functional
-        # Edit Motor Units
-        # edit_mus = ttk.Button(self.left,
-        #                      text="Edit MUs",
-        #                      command=self.editing_mus)
-        # edit_mus.grid(column=1, row=10, sticky=W)
+        remove_mus = ctk.CTkButton(
+            self.left,
+            text="Remove MUs",
+            command=lambda: (MURemovalWindow(parent=self)),
+        )
+        remove_mus.grid(column=0, row=10, sticky=(N, S, E, W))
 
         separator3 = ttk.Separator(self.left, orient="horizontal")
-        separator3.grid(column=0, columnspan=3, row=11, sticky=(W, E))
+        separator3.grid(column=0, columnspan=2, row=11, sticky=(E, W))
 
         # Filter Reference Signal
-        reference = ttk.Button(
-            self.left, text="RefSig Editing", command=self.edit_refsig
+        reference = ctk.CTkButton(
+            self.left,
+            text="Signal Editing",
+            command=lambda: (EditSig(parent=self)),
         )
-        reference.grid(column=0, row=12, sticky=W)
+        reference.grid(column=0, row=12, sticky=(N, S, E, W))
 
         # Resize File
-        resize = ttk.Button(self.left, text="Resize File", command=self.resize_file)
-        resize.grid(column=1, row=12, sticky=(W, E))
+        resize = ctk.CTkButton(
+            self.left,
+            text="Resize File",
+            command=lambda: (GUIHelpers(parent=self).resize_file()),
+        )
+        resize.grid(column=1, row=12, sticky=(N, S, E, W))
         separator4 = ttk.Separator(self.left, orient="horizontal")
-        separator4.grid(column=0, columnspan=3, row=13, sticky=(W, E))
+        separator4.grid(column=0, columnspan=2, row=13, sticky=(E, W))
 
         # Force Analysis
-        force = ttk.Button(self.left, text="Analyse Force", command=self.analyze_force)
-        force.grid(column=0, row=14, sticky=W)
+        force = ctk.CTkButton(
+            self.left,
+            text="Analyse Force",
+            command=lambda: (AnalyseForce(parent=self)),
+        )
+        force.grid(column=0, row=14, sticky=(N, S, E, W))
         separator5 = ttk.Separator(self.left, orient="horizontal")
-        separator5.grid(column=0, columnspan=3, row=15, sticky=(W, E))
+        separator5.grid(column=0, columnspan=2, row=15, sticky=(E, W))
 
         # Motor Unit properties
-        mus = ttk.Button(self.left, text="MU Properties", command=self.mu_analysis)
-        mus.grid(column=1, row=14, sticky=W)
+        mus = ctk.CTkButton(
+            self.left,
+            text="MU Properties",
+            command=lambda: (MuAnalysis(parent=self)),
+        )
+        mus.grid(column=1, row=14, sticky=(N, S, E, W))
         separator6 = ttk.Separator(self.left, orient="horizontal")
-        separator6.grid(column=0, columnspan=3, row=17, sticky=(W, E))
+        separator6.grid(column=0, columnspan=2, row=17, sticky=(E, W))
 
         # Plot EMG
-        plots = ttk.Button(self.left, text="Plot EMG", command=self.plot_emg)
-        plots.grid(column=0, row=16, sticky=W)
+        plots = ctk.CTkButton(
+            self.left,
+            text="Plot EMG",
+            command=lambda: (PlotEmg(parent=self)),
+        )
+        plots.grid(column=0, row=16, sticky=(N, S, E, W))
         separator7 = ttk.Separator(self.left, orient="horizontal")
-        separator7.grid(column=0, columnspan=3, row=19, sticky=(W, E))
+        separator7.grid(column=0, columnspan=2, row=19, sticky=(E, W))
 
         # Reset Analysis
-        reset = ttk.Button(
-            self.left, text="Reset Analysis", command=self.reset_analysis
+        reset = ctk.CTkButton(
+            self.left,
+            text="Reset Analysis",
+            command=self.reset_analysis,
         )
-        reset.grid(column=1, row=18, sticky=(W, E))
+        reset.grid(column=1, row=18, sticky=(N, S, E, W))
 
         # Advanced tools
-        # Create seperate style for this button
-        advanced_button_style = ttk.Style()
-        advanced_button_style.theme_use("clam")
-        advanced_button_style.configure(
-            "B.TButton",
-            foreground="white",
-            background="black",
-            font=("Lucida Sans", 11),
-        )
-
-        advanced = ttk.Button(
+        advanced = ctk.CTkButton(
             self.left,
-            command=self.open_advanced_tools,
             text="Advanced Tools",
-            style="B.TButton",
+            command=lambda: (AdvancedAnalysis(self)),
+            hover_color="#FFBF00",
+            fg_color="#000000",
+            text_color="#FFFFFF",
+            border_color="#FFFFFF",
         )
-        advanced.grid(row=20, column=0, columnspan=2, sticky=(W, E))
+        advanced.grid(row=20, column=0, columnspan=2, sticky=(N, S, E, W))
 
         # Create right side framing for functionalities
-        self.right = ttk.Frame(self.master, padding="10 10 12 12")
+        self.right = ctk.CTkFrame(
+            self,
+        )
         self.right.grid(column=1, row=0, sticky=(N, S, E, W))
-        self.right.columnconfigure(0, weight=1)
-        self.right.columnconfigure(1, weight=1)
-        self.right.rowconfigure(0, weight=1)
-        self.right.rowconfigure(1, weight=1)
-        self.right.rowconfigure(2, weight=1)
-        self.right.rowconfigure(3, weight=1)
-        self.right.rowconfigure(4, weight=1)
-
-        # Create empty figure
-        self.first_fig = Figure(figsize=(20 / 2.54, 15 / 2.54))
-        self.canvas = FigureCanvasTkAgg(self.first_fig, master=self.right)
-        self.canvas.get_tk_widget().grid(row=0, column=0, rowspan=5)
-
-        # Create logo figure
-        self.logo_canvas = Canvas(self.right, height=590, width=800, bg="white")
-        self.logo_canvas.grid(row=0, column=0, rowspan=5)
-
-        logo_path = master_path + "/gui_files/logo.png"  # Get logo path
-        self.logo = tk.PhotoImage(file=logo_path)
-
-        # self.matrix = tk.PhotoImage(file="Matrix_illustration.png")
-        self.logo_canvas.create_image(400, 300, anchor="center", image=self.logo)
-
-        # Create info button
+
+        # Configure columns, plot is weighted more icons are not configured
+        self.right.columnconfigure(0, weight=10)
+        self.right.columnconfigure(1, weight=0)
+        # Configure rows with a loop
+        for row in range(5):
+            self.right.rowconfigure(row, weight=1)
+
+        # Create logo canvas figure
+        self.logo_canvas = Canvas(
+            self.right,
+            width=800,
+            height=600,
+            bg="white",
+        )
+        self.logo_canvas.grid(
+            row=0, column=0, rowspan=6, sticky=(N, S, E, W), pady=(5, 0),
+        )
+
+        # Load the logo as a resizable matplotlib figure
+        logo_path = master_path + "/gui_files/Logo_high_res.png"
+        logo = plt.imread(logo_path)
+        logo_fig, ax = plt.subplots()
+        ax.imshow(logo)
+        ax.axis('off')  # Turn off axis
+        logo_fig.tight_layout()  # Adjust layout padding
+
+        # Plot the figure in the in_gui_plotting canvas
+        self.canvas = FigureCanvasTkAgg(logo_fig, master=self.logo_canvas)
+        self.canvas.get_tk_widget().pack(
+            expand=True, fill="both", padx=5, pady=5,
+        )
+        plt.close()
+        # This solution is more flexible and memory efficient than previously.
+
+        # Create info buttons
+        # Settings button
+        gear_path = master_path + "/gui_files/Gear.png"
+        self.gear = ctk.CTkImage(
+            light_image=Image.open(gear_path),
+            size=(30, 30),
+        )
+
+        settings_b = ctk.CTkButton(
+            self.right,
+            text="",
+            image=self.gear,
+            command=self.open_settings,
+            width=30,
+            height=30,
+            bg_color="LightBlue4",
+            fg_color="LightBlue4",
+            border_width=0,
+        )
+        settings_b.grid(column=1, row=0, sticky=W, pady=(0, 20))
+
         # Information Button
-        info_path = master_path + "/gui_files/Info.png"  # Get infor button path
-        self.info = customtkinter.CTkImage(light_image=Image.open(info_path),
-                                             size=(30,30))
-        info_button = customtkinter.CTkButton(
+        info_path = master_path + "/gui_files/Info.png"  # Get info button path
+        self.info = ctk.CTkImage(
+            light_image=Image.open(info_path),
+            size=(30, 30),
+        )
+        info_button = ctk.CTkButton(
             self.right,
             image=self.info,
             text="",
@@ -545,19 +434,20 @@ def __init__(self, master):
             height=30,
             bg_color="LightBlue4",
             fg_color="LightBlue4",
+            border_width=0,
             command=lambda: (
-                (
-                    webbrowser.open("https://www.giacomovalli.com/openhdemg/gui_intro/")
-                ),
+                (webbrowser.open("https://www.giacomovalli.com/openhdemg/gui_intro/"))
             ),
         )
-        info_button.grid(row=0, column=1, sticky=E)
+        info_button.grid(row=1, column=1, sticky=W, pady=(0, 20))
 
         # Button for online tutorials
         online_path = master_path + "/gui_files/Online.png"
-        self.online = customtkinter.CTkImage(light_image=Image.open(online_path),
-                                             size=(30,30))
-        online_button = customtkinter.CTkButton(
+        self.online = ctk.CTkImage(
+            light_image=Image.open(online_path),
+            size=(30, 30),
+        )
+        online_button = ctk.CTkButton(
             self.right,
             image=self.online,
             text="",
@@ -565,19 +455,24 @@ def __init__(self, master):
             height=30,
             bg_color="LightBlue4",
             fg_color="LightBlue4",
+            border_width=0,
             command=lambda: (
                 (
-                    webbrowser.open("https://www.giacomovalli.com/openhdemg/tutorials/setup_working_env/")
-                ),
+                    webbrowser.open(
+                        "https://www.giacomovalli.com/openhdemg/tutorials/setup_working_env/"
+                    )
+                )
             ),
         )
-        online_button.grid(row=1, column=1, sticky=E)
+        online_button.grid(row=2, column=1, sticky=W, pady=(0, 20))
 
         # Button for dev information
         redirect_path = master_path + "/gui_files/Redirect.png"
-        self.redirect = customtkinter.CTkImage(light_image=Image.open(redirect_path),
-                                             size=(30,30))
-        redirect_button = customtkinter.CTkButton(
+        self.redirect = ctk.CTkImage(
+            light_image=Image.open(redirect_path),
+            size=(30, 30),
+        )
+        redirect_button = ctk.CTkButton(
             self.right,
             image=self.redirect,
             text="",
@@ -585,19 +480,24 @@ def __init__(self, master):
             height=30,
             bg_color="LightBlue4",
             fg_color="LightBlue4",
+            border_width=0,
             command=lambda: (
                 (
-                    webbrowser.open("https://www.giacomovalli.com/openhdemg/about-us/#meet-the-developers")
-                ),
+                    webbrowser.open(
+                        "https://www.giacomovalli.com/openhdemg/about-us/#meet-the-developers"
+                    )
+                )
             ),
         )
-        redirect_button.grid(row=2, column=1, sticky=E)
+        redirect_button.grid(row=3, column=1, sticky=W, pady=(0, 20))
 
         # Button for contact information
         contact_path = master_path + "/gui_files/Contact.png"
-        self.contact = customtkinter.CTkImage(light_image=Image.open(contact_path),
-                                             size=(30,30))
-        contact_button = customtkinter.CTkButton(
+        self.contact = ctk.CTkImage(
+            light_image=Image.open(contact_path),
+            size=(30, 30),
+        )
+        contact_button = ctk.CTkButton(
             self.right,
             image=self.contact,
             text="",
@@ -605,19 +505,20 @@ def __init__(self, master):
             height=30,
             bg_color="LightBlue4",
             fg_color="LightBlue4",
+            border_width=0,
             command=lambda: (
-                (
-                    webbrowser.open("https://www.giacomovalli.com/openhdemg/contacts/")
-                ),
+                (webbrowser.open("https://www.giacomovalli.com/openhdemg/contacts/"))
             ),
         )
-        contact_button.grid(row=3, column=1, sticky=E)
+        contact_button.grid(row=4, column=1, sticky=W, pady=(0, 20))
 
         # Button for citatoin information
         cite_path = master_path + "/gui_files/Cite.png"
-        self.cite = customtkinter.CTkImage(light_image=Image.open(cite_path),
-                                             size=(30,30))
-        cite_button = customtkinter.CTkButton(
+        self.cite = ctk.CTkImage(
+            light_image=Image.open(cite_path),
+            size=(30, 30),
+        )
+        cite_button = ctk.CTkButton(
             self.right,
             image=self.cite,
             text="",
@@ -625,79 +526,151 @@ def __init__(self, master):
             height=30,
             bg_color="LightBlue4",
             fg_color="LightBlue4",
+            border_width=0,
             command=lambda: (
-                # Check user OS for pdf opening
-                (
-                    webbrowser.open("https://www.giacomovalli.com/openhdemg/cite-us/")
-                ),
+                (webbrowser.open("https://www.giacomovalli.com/openhdemg/cite-us/"))
             ),
         )
-        cite_button.grid(row=4, column=1, sticky=E)
+        cite_button.grid(row=5, column=1, sticky=W, pady=(0, 20))
+
+        # Create frame for output
+        self.terminal = ttk.LabelFrame(
+            self, text="Result Output", height=150, relief="ridge",
+        )
+        self.terminal.grid(
+            column=0,
+            row=1,
+            columnspan=2,
+            pady=5,
+            padx=5,
+            sticky=(N, S, W, E),
+        )
 
         for child in self.left.winfo_children():
             child.grid_configure(padx=5, pady=5)
 
-    ## Define functionalities for buttons used in GUI master window
+    # Define functionalities for buttons used in GUI master window
+    def load_settings(self):
+        """
+        Instance Method to load the setting file for.
+
+        Executed each time when the GUI or a toplevel is openened.
+        The settings specified by the user will then be transferred
+        to the code and used.
+        """
+
+        # If not previously imported, just import it
+        global settings
+        self.settings = importlib.reload(settings)
+
+    def open_settings(self):
+        """
+        Instance Method to open the setting file for.
+
+        Executed when the button "Settings" in master GUI window is pressed.
+        A python file is openend containing a dictionary with relevant
+        variables that users should be able to customize.
+        """
+
+        # Determine relative filepath
+        file_path = os.path.dirname(os.path.abspath(__file__)) + "/settings.py"
+
+        # Check for operating system and open in default editor
+        if sys.platform.startswith("darwin"):  # macOS
+            subprocess.run(["open", file_path])
+        elif sys.platform.startswith("win32"):  # Windows
+            os.startfile(file_path)
+        else:  # Linux or other
+            subprocess.run(["xdg-open", file_path])
 
     def get_file_input(self):
         """
-        Instance Method to load the file for analysis. The user is asked to select the file.
+        Instance Method to load the file for analysis. The user is asked to
+        select the file.
 
         Executed when the button "Load File" in master GUI window is pressed.
 
+        This creates both an object containing the file to use and an object
+        to reset to the original file.
+
         See Also
         --------
-        emg_from_demuse, emg_from_otb, refsig_from_otb and emg_from_json in library.
+        emg_from_otb, emg_from_demuse, emg_from_delsys, emg_from_customcsv,
+        refsig_from_otb, refsig_from_delsys, refsig_from_customcsv in library.
         """
+
         def load_file():
             try:
-                if self.filetype.get() in ["OTB", "DEMUSE", "OPENHDEMG", "CUSTOMCSV", "DELSYS"]:
+                if self.filetype.get() in [
+                    "OTB",
+                    "DEMUSE",
+                    "OPENHDEMG",
+                    "CUSTOMCSV",
+                    "DELSYS",
+                ]:
                     # Check filetype for processing
                     if self.filetype.get() == "OTB":
                         # Ask user to select the decomposed file
                         file_path = filedialog.askopenfilename(
-                            title="Open OTB file to load", filetypes=[("MATLAB files", "*.mat")]
+                            title="Open decomposed OTB file to load",
+                            filetypes=[("MATLAB files", "*.mat")],
                         )
                         self.file_path = file_path
                         # Load file
                         self.resdict = openhdemg.emg_from_otb(
                             filepath=self.file_path,
-                            ext_factor=int(self.extension_factor.get()),
+                            ext_factor=self.settings.emg_from_otb__ext_factor,
+                            refsig=self.settings.emg_from_otb__refsig,
+                            extras=self.settings.emg_from_otb__extras,
+                            ignore_negative_ipts=self.settings.emg_from_otb__ignore_negative_ipts,
                         )
                         # Add filespecs
-                        ttk.Label(
-                            self.left, text=str(len(self.resdict["RAW_SIGNAL"].columns))
-                        ).grid(column=2, row=2, sticky=(W, E))
-                        ttk.Label(self.left, text=str(self.resdict["NUMBER_OF_MUS"])).grid(
-                            column=2, row=3, sticky=(W, E)
+                        self.n_channels.configure(
+                            text="N Channels: "
+                            + str(len(self.resdict["RAW_SIGNAL"].columns)),
+                            font=("Segoe UI", 15, ("bold")),
+                        )
+                        self.n_of_mus.configure(
+                            text="N of MUs: " + str(self.resdict["NUMBER_OF_MUS"]),
+                            font=("Segoe UI", 15, "bold"),
                         )
-                        ttk.Label(self.left, text=str(self.resdict["EMG_LENGTH"])).grid(
-                            column=2, row=4, sticky=(W, E)
+                        self.file_length.configure(
+                            text="File Length: " + str(self.resdict["EMG_LENGTH"]),
+                            font=("Segoe UI", 15, "bold"),
                         )
 
                     elif self.filetype.get() == "DEMUSE":
                         # Ask user to select the file
                         file_path = filedialog.askopenfilename(
-                            title="Open DEMUSE file to load", filetypes=[("MATLAB files", "*.mat")]
+                            title="Open DEMUSE file to load",
+                            filetypes=[("MATLAB files", "*.mat")],
                         )
                         self.file_path = file_path
                         # load file
-                        self.resdict = openhdemg.emg_from_demuse(filepath=self.file_path)
+                        self.resdict = openhdemg.emg_from_demuse(
+                            filepath=self.file_path,
+                            ignore_negative_ipts=self.settings.emg_from_demuse__ignore_negative_ipts,
+                        )
                         # Add filespecs
-                        ttk.Label(
-                            self.left, text=str(len(self.resdict["RAW_SIGNAL"].columns))
-                        ).grid(column=2, row=2, sticky=(W, E))
-                        ttk.Label(self.left, text=str(self.resdict["NUMBER_OF_MUS"])).grid(
-                            column=2, row=3, sticky=(W, E)
+                        self.n_channels.configure(
+                            text="N Channels: "
+                            + str(len(self.resdict["RAW_SIGNAL"].columns)),
+                            font=("Segoe UI", 15, ("bold")),
                         )
-                        ttk.Label(self.left, text=str(self.resdict["EMG_LENGTH"])).grid(
-                            column=2, row=4, sticky=(W, E)
+                        self.n_of_mus.configure(
+                            text="N of MUs: " + str(self.resdict["NUMBER_OF_MUS"]),
+                            font=("Segoe UI", 15, "bold"),
                         )
+                        self.file_length.configure(
+                            text="File Length: " + str(self.resdict["EMG_LENGTH"]),
+                            font=("Segoe UI", 15, "bold"),
+                        )
+
                     elif self.filetype.get() == "DELSYS":
                         # Ask user to select the file
                         file_path = filedialog.askopenfilename(
                             title="Select a DELSYS file with raw EMG to load",
-                            filetypes=[("MATLAB files", "*.mat")]
+                            filetypes=[("MATLAB files", "*.mat")],
                         )
                         # Ask user to open the Delsys decompostition
                         self.mus_path = filedialog.askdirectory(
@@ -706,41 +679,73 @@ def load_file():
                         self.file_path = file_path
 
                         # load DELSYS
-                        self.resdict = openhdemg.emg_from_delsys(rawemg_filepath=self.file_path,
-                                                                 mus_directory=self.mus_path)
+                        self.resdict = openhdemg.emg_from_delsys(
+                            rawemg_filepath=self.file_path,
+                            mus_directory=self.mus_path,
+                            emg_sensor_name=self.settings.emg_from_delsys__emg_sensor_name,
+                            refsig_sensor_name=self.settings.emg_from_delsys__refsig_sensor_name,
+                            filename_from=self.settings.emg_from_delsys__filename_from,
+                        )
                         # Add filespecs
-                        ttk.Label(self.left, text=str(len(self.resdict["RAW_SIGNAL"].columns))).grid(column=2, row=2, sticky=(W, E))
-                        ttk.Label(self.left, text=str(self.resdict["NUMBER_OF_MUS"])).grid(
-                            column=2, row=3, sticky=(W, E)
+                        self.n_channels.configure(
+                            text="N Channels: "
+                            + str(len(self.resdict["RAW_SIGNAL"].columns)),
+                            font=("Segoe UI", 15, ("bold")),
                         )
-                        ttk.Label(self.left, text=str(self.resdict["EMG_LENGTH"])).grid(
-                            column=2, row=4, sticky=(W, E)
+                        self.n_of_mus.configure(
+                            text="N of MUs: " + str(self.resdict["NUMBER_OF_MUS"]),
+                            font=("Segoe UI", 15, "bold"),
+                        )
+                        self.file_length.configure(
+                            text="File Length: " + str(self.resdict["EMG_LENGTH"]),
+                            font=("Segoe UI", 15, "bold"),
                         )
 
                     elif self.filetype.get() == "OPENHDEMG":
                         # Ask user to select the file
                         file_path = filedialog.askopenfilename(
-                            title="Open JSON file to load", filetypes=[("JSON files", "*.json")]
+                            title="Open JSON file to load",
+                            filetypes=[("JSON files", "*.json")],
                         )
                         self.file_path = file_path
                         # load OPENHDEMG (.json)
                         self.resdict = openhdemg.emg_from_json(filepath=self.file_path)
                         # Add filespecs
-                        # NOTE this is not correct because when the user asks to load OPENHDEMG (.json)
-                        # files, these could contain also the reference signal only. Therefore line 2
-                        # and 3 will crash. I temporarily fixed it, please review it for next release.
-                        if self.resdict["SOURCE"] in ["DEMUSE", "OTB", "CUSTOMCSV", "DELSYS"]:
-                            ttk.Label(self.left, text=str(len(self.resdict["RAW_SIGNAL"].columns))).grid(column=2, row=2, sticky=(W, E))
-                            ttk.Label(self.left, text=str(self.resdict["NUMBER_OF_MUS"])).grid(column=2, row=3, sticky=(W, E))
-                            ttk.Label(self.left, text=str(self.resdict["EMG_LENGTH"])).grid(column=2, row=4, sticky=(W, E))
+                        if self.resdict["SOURCE"] in [
+                            "DEMUSE",
+                            "OTB",
+                            "CUSTOMCSV",
+                            "DELSYS",
+                        ]:
+                            # Add filespecs
+                            self.n_channels.configure(
+                                text="N Channels: "
+                                + str(len(self.resdict["RAW_SIGNAL"].columns)),
+                                font=("Segoe UI", 15, ("bold")),
+                            )
+                            self.n_of_mus.configure(
+                                text="N of MUs: " + str(self.resdict["NUMBER_OF_MUS"]),
+                                font=("Segoe UI", 15, "bold"),
+                            )
+                            self.file_length.configure(
+                                text="File Length: " + str(self.resdict["EMG_LENGTH"]),
+                                font=("Segoe UI", 15, "bold"),
+                            )
                         else:
-                            # Reconfigure labels for refsig
-                            ttk.Label(
-                                self.left, text=str(len(self.resdict["REF_SIGNAL"].columns))
-                            ).grid(column=2, row=2, sticky=(W, E))
-                            ttk.Label(self.left, text="NA").grid(column=2, row=3, sticky=(W, E))
-                            ttk.Label(self.left, text="        ").grid(
-                                column=2, row=4, sticky=(W, E)
+                            # Add filespecs
+                            self.n_channels.configure(
+                                text="N Channels: "
+                                + str(len(self.resdict["REF_SIGNAL"].columns)),
+                                font=("Segoe UI", 15, ("bold")),
+                            )
+                            self.n_of_mus.configure(
+                                text="N of MUs: N/A",
+                                font=("Segoe UI", 15, "bold"),
+                            )
+                            self.file_length.configure(
+                                text="File Length: "
+                                + str(len(self.resdict["REF_SIGNAL"].iloc[:, 0])),
+                                font=("Segoe UI", 15, "bold"),
                             )
                     else:
                         # Ask user to select the file
@@ -752,22 +757,44 @@ def load_file():
                         # load file
                         self.resdict = openhdemg.emg_from_customcsv(
                             filepath=self.file_path,
-                            fsamp=float(self.fsamp.get()),
+                            ref_signal=self.settings.emg_from_customcsv__ref_signal,
+                            raw_signal=self.settings.emg_from_customcsv__raw_signal,
+                            ipts=self.settings.emg_from_customcsv__ipts,
+                            mupulses=self.settings.emg_from_customcsv__mupulses,
+                            binary_mus_firing=self.settings.emg_from_customcsv__binary_mus_firing,
+                            accuracy=self.settings.emg_from_customcsv__accuracy,
+                            extras=self.settings.emg_from_customcsv__extras,
+                            fsamp=self.settings.emg_from_customcsv__fsamp,
+                            ied=self.settings.emg_from_customcsv__ied,
                         )
                         # Add filespecs
-                        ttk.Label(self.left, text="Custom CSV").grid(column=2, row=2, sticky=(W, E))
-                        ttk.Label(self.left, text="").grid(column=2, row=3, sticky=(W, E))
-                        ttk.Label(self.left, text="").grid(column=2, row=4, sticky=(W, E))
+                        self.n_channels.configure(
+                            text="N Channels: "
+                            + str(len(self.resdict["RAW_SIGNAL"].columns)),
+                            font=("Segoe UI", 15, ("bold")),
+                        )
+                        self.n_of_mus.configure(
+                            text="N of MUs: " + str(self.resdict["NUMBER_OF_MUS"]),
+                            font=("Segoe UI", 15, "bold"),
+                        )
+                        self.file_length.configure(
+                            text="File Length: " + str(self.resdict["EMG_LENGTH"]),
+                            font=("Segoe UI", 15, "bold"),
+                        )
 
                     # Get filename
                     filename = os.path.splitext(os.path.basename(file_path))[0]
                     self.filename = filename
 
                     # Add filename to label
-                    self.master.title(self.filename)
+                    self.title(self.filename)
+
+                    # Lower processing_indicator
+                    if hasattr(self, "processing_indicator"):
+                        self.processing_indicator.lower()
 
-                # This sections is used for refsig loading as they required not the 
-                # the filespecs to be loaded.
+                # This sections is used for refsig loading as they do not
+                # require the filespecs to be loaded.
                 else:
                     if self.filetype.get() == "OTB_REFSIG":
                         file_path = filedialog.askopenfilename(
@@ -776,18 +803,25 @@ def load_file():
                         )
                         self.file_path = file_path
                         # load refsig
-                        self.resdict = openhdemg.refsig_from_otb(filepath=self.file_path)
+                        self.resdict = openhdemg.refsig_from_otb(
+                            filepath=self.file_path,
+                            refsig=self.settings.refsig_from_otb__refsig,
+                            extras=self.settings.refsig_from_otb__extras,
+                        )
 
                     elif self.filetype.get() == "DELSYS_REFSIG":
 
                         # Ask user to select the file
                         file_path = filedialog.askopenfilename(
                             title="Select a DELSYS_REFSIG file with raw EMG to load",
-                            filetypes=[("MATLAB files", "*.mat")]
+                            filetypes=[("MATLAB files", "*.mat")],
                         )
                         self.file_path = file_path
                         # load DELSYS
-                        self.resdict = openhdemg.refsig_from_delsys(filepath=self.file_path)
+                        self.resdict = openhdemg.refsig_from_delsys(
+                            filepath=self.file_path,
+                            refsig_sensor_name=self.settings.refsig_from_delsys__refsig_sensor_name,
+                        )
 
                     elif self.filetype.get() == "CUSTOMCSV_REFSIG":
                         file_path = filedialog.askopenfilename(
@@ -798,145 +832,156 @@ def load_file():
                         # load refsig
                         self.resdict = openhdemg.refsig_from_customcsv(
                             filepath=self.file_path,
-                            fsamp=float(self.fsamp.get()),
-                        )  # NOTE please check that I used correctly self.fsamp.get() here and above.
+                            ref_signal=self.settings.refsig_from_customcsv__ref_signal,
+                            extras=self.settings.refsig_from_customcsv__extras,
+                            fsamp=self.settings.refsig_from_customcsv__fsamp,
+                        )
 
                     # Get filename
                     filename = os.path.splitext(os.path.basename(file_path))[0]
                     self.filename = filename
 
                     # Add filename to label
-                    self.master.title(self.filename)
-
-                    # Reconfigure labels for refsig
-                    ttk.Label(
-                        self.left, text=str(len(self.resdict["REF_SIGNAL"].columns))
-                    ).grid(column=2, row=2, sticky=(W, E))
-                    ttk.Label(self.left, text="NA").grid(column=2, row=3, sticky=(W, E))
-                    ttk.Label(self.left, text="        ").grid(
-                        column=2, row=4, sticky=(W, E)
+                    self.title(self.filename)
+
+                    # Add filespecs
+                    self.n_channels.configure(
+                        text="N Channels: "
+                        + str(len(self.resdict["REF_SIGNAL"].columns)),
+                        font=("Segoe UI", 15, ("bold")),
+                    )
+                    self.n_of_mus.configure(
+                        text="N of MUs: N/A",
+                        font=("Segoe UI", 15, "bold"),
                     )
-                    
-                # End progress
-                progress.stop()
-                progress.grid_remove()
-
-            except ValueError:
-                tk.messagebox.showerror(
-                    "Information",
-                    "When an OTB file is loaded, make sure to "
-                    + "\nspecify an extension factor (number) first."
-                    + "\n"
-                    + "When a DELSYS file is loaded, make sure to "
-                    + "\nspecify the correct folder."
+                    self.file_length.configure(
+                        text="File Length: "
+                        + str(len(self.resdict["REF_SIGNAL"].iloc[:, 0])),
+                        font=("Segoe UI", 15, "bold"),
+                    )
+
+                # Lower processing_indicator
+                if hasattr(self, "processing_indicator"):
+                    self.processing_indicator.lower()
+
+                # If file succesfully loaded, delete previous analyses results
+                self.delete_previous_analyses_results()
+
+                # Make a copy of the loaded file
+                self.resdict_copy_of_original = copy.deepcopy(self.resdict)
+
+                # Display the loaded file
+                if self.resdict["SOURCE"] in ["DEMUSE", "OTB", "CUSTOMCSV", "DELSYS"]:
+                    self.in_gui_plotting(self.resdict)
+                else:
+                    self.in_gui_plotting(self.resdict, plot="refsig_off")
+
+            except ValueError as e:
+                show_error_dialog(
+                    parent=self,
+                    error=e,
+                    solution=str(
+                        "When an OTB file is loaded, make sure to "
+                        + "specify an extension factor (number) first."
+                        + "\nWhen a DELSYS file is loaded, make sure to "
+                        + "specify the correct folder."
+                    ),
+                )
+                # Lower processing_indicator
+                if hasattr(self, "processing_indicator"):
+                    self.processing_indicator.lower()
+
+            except FileNotFoundError as e:
+                show_error_dialog(
+                    parent=self,
+                    error=e,
+                    solution=str(
+                        "Make sure to load correct file"
+                        + "according to your specification."
+                    ),
                 )
-                # End progress
-                progress.stop()
-                progress.grid_remove()
-
-            except FileNotFoundError:
-                # End progress
-                progress.stop()
-                progress.grid_remove()
-
-            except TypeError:
-                tk.messagebox.showerror(
-                    "Information",
-                    "Make sure to load correct file"
-                    + "\naccording to your specification.",
+                # Lower processing_indicator
+                if hasattr(self, "processing_indicator"):
+                    self.processing_indicator.lower()
+
+            except TypeError as e:
+                show_error_dialog(
+                    parent=self,
+                    error=e,
+                    solution=str(
+                        "Make sure to load correct file"
+                        + "according to your specification."
+                    ),
                 )
-                # End progress
-                progress.stop()
-                progress.grid_remove()
-
-            except KeyError:
-                tk.messagebox.showerror(
-                    "Information",
-                    "Make sure to load correct file"
-                    + "\naccording to your specification.",
+                # Lower processing_indicator
+                if hasattr(self, "processing_indicator"):
+                    self.processing_indicator.lower()
+
+            except KeyError as e:
+                show_error_dialog(
+                    parent=self,
+                    error=e,
+                    solution=str(
+                        "Make sure to load correct file"
+                        + "according to your specification."
+                    ),
                 )
-                # End progress
-                progress.stop()
-                progress.grid_remove()
-            except: 
-                # End progress
-                progress.stop()
-                progress.grid_remove()
-
-        # Indicate Progress
-        progress = ttk.Progressbar(self.left, mode="indeterminate")
-        progress.grid(row=4, column=0)
-        progress.start(1)
-
-        # Create a thread to run the load_file function
-        save_thread = threading.Thread(target=load_file)
-        save_thread.start()
+                # Lower processing_indicator
+                if hasattr(self, "processing_indicator"):
+                    self.processing_indicator.lower()
+
+            except:
+                # Lower processing_indicator
+                if hasattr(self, "processing_indicator"):
+                    self.processing_indicator.lower()
+
+        # Re-Load settings
+        self.load_settings()
+
+        # Display the processing indicator
+        self.processing_indicator.lift()
+
+        # Call the function to load the file
+        load_file()
+
+        # Remove file loading indicator
+        if hasattr(self, "processing_indicator"):
+            self.processing_indicator.lower()
 
     def on_filetype_change(self, *args):
         """
-        This function is called when the value of the filetype variable is changed.
-        When the filetype is set to "OTB", "CUSTOMCSV", "CUSTOMCSV_REFSIG" it will
-        create a second combobox on the grid at column 0 and row 2 and when the filetype
-        is set to something else it will remove the second combobox from the grid.
+        This function is called when the value of the filetype variable is
+        changed. If filetype is set to != "OPENHDEMG", it will create a second
+        combobox on the grid at column 0 and row 2 and when the filetype is
+        set to "OPENHDEMG" it will remove the second combobox from the grid.
         """
-        # Forget previous widget when filetype is changes
-        # NOTE I had to separate them and put them on top of the function to
-        # ensure that changing file type consecutively would not miss the
-        # previous entry or combobox.
-        if self.filetype.get() not in ["OTB"]:
-            if hasattr(self, "otb_combobox"):
-                self.otb_combobox.grid_forget()
-        if self.filetype.get() not in ["CUSTOMCSV"]:
-            if hasattr(self, "csv_entry"):
-                self.csv_entry.grid_forget()
-        if self.filetype.get() not in ["CUSTOMCSV_REFSIG"]:
-            if hasattr(self, "csv_entry"):
-                self.csv_entry.grid_forget()
-
-        # Add a combobox containing the OTB extension factors
-        # in case an OTB file is loaded
-        if self.filetype.get() == "OTB":
-            self.extension_factor = StringVar()
-            self.otb_combobox = ttk.Combobox(
+
+        # Make sure to forget all the previous labels
+        if hasattr(self, "verify_settings_text"):
+            self.verify_settings_text.grid_forget()
+
+        if self.filetype.get() != "OPENHDEMG":
+            # Display the text: Verify openfiles settings!
+            self.verify_settings_text = ctk.CTkLabel(
                 self.left,
-                values=[
-                    "8",
-                    "9",
-                    "10",
-                    "11",
-                    "12",
-                    "13",
-                    "14",
-                    "15",
-                    "16",
-                ],
-                width=8,
-                textvariable=self.extension_factor,
-                state="readonly",
+                text="Verify openfiles settings!",
+                font=("Segoe UI", 12, "bold"),
+                text_color="black",
             )
-            self.otb_combobox.grid(column=0, row=2, sticky=(W, E), padx=5)
-            self.otb_combobox.set("Extension Factor")
-
-        # NOTE I forgot to mention, but people should be able to select fsamp for .csv files.
-        # Please check if this can be done better.
-        elif self.filetype.get() in ["CUSTOMCSV", "CUSTOMCSV_REFSIG"]:
-            self.fsamp = StringVar(value="Fsamp")
-            self.csv_entry = ttk.Entry(
-                self.left,
-                width=8,
-                textvariable=self.fsamp,
+            self.verify_settings_text.grid(
+                column=0,
+                row=2,
+                sticky=(W, E),
+                padx=5,
             )
-            self.csv_entry.grid(column=0, row=2, sticky=(W, E), padx=5)
-
-    # TODO remove this and any reference to it
-    def decompose_file(self):
-        pass
 
     def save_emgfile(self):
         """
-        Instance method to save the edited emgfile. Results are saved in a .json file.
+        Instance method to save the edited emgfile. Results are saved in a
+        .json file.
 
-        Executed when the "Save File" button in the master GUI window is pressed.
+        Executed when the "Save File" button in the master GUI window is
+        pressed.
 
         Raises
         ------
@@ -947,103 +992,45 @@ def save_emgfile(self):
         --------
         save_json_emgfile in library.
         """
-        def save_file():
-            try:
-                # Ask user to select the directory and file name
-                save_filepath = filedialog.asksaveasfilename(
-                    defaultextension=".json",
-                    filetypes=(("JSON files", "*.json"), ("all files", "*.*")),
-                )
-
-                if not save_filepath:
-                    # End progress
-                    progress.stop()
-                    progress.grid_remove()
-                    return  # User canceled the file dialog
-
-                # Get emgfile
-                save_emg = self.resdict
 
-                # Save json file
-                openhdemg.save_json_emgfile(emgfile=save_emg, filepath=save_filepath)
+        # Re-Load settings
+        self.load_settings()
 
-                # End progress
-                progress.stop()
-                progress.grid_remove()
+        # Display the processing indicator
+        self.processing_indicator.lift()
 
-            except AttributeError:
-                tk.messagebox.showerror("Information", "Make sure a file is loaded.")
-
-        # Indicate Progress
-        progress = ttk.Progressbar(self.left, mode="indeterminate")
-        progress.grid(row=4, column=0)
-        progress.start(1)
-
-        # Create a thread to run the save_file function
-        save_thread = threading.Thread(target=save_file)
-        save_thread.start()
-
-    def export_to_excel(self):
-        """
-        Instnace method to export any prior analysis results. Results are saved in an excel sheet
-        in a directory specified by the user.
-
-        Executed when button "Save Results" in master GUI window is pressed.
-
-        Raises
-        ------
-        IndexError
-            When no analysis has been performed prior to attempted savig.
-        AttributeError
-            When no file was loaded in the GUI.
-        """
+        # Save the file
         try:
-            # Ask user to select the directory
-            path = filedialog.askdirectory()
-
-            # Define excel writer
-            writer = pd.ExcelWriter(path + "/Results_" + self.filename + ".xlsx")
-
-            # Check for attributes and write sheets
-            if hasattr(self, "mvc_df"):
-                self.mvc_df.to_excel(writer, sheet_name="MVC")
-
-            if hasattr(self, "rfd"):
-                self.rfd.to_excel(writer, sheet_name="RFD")
-
-            if hasattr(self, "exportable_df"):
-                self.exportable_df.to_excel(writer, sheet_name="Basic MU Properties")
-
-            if hasattr(self, "mus_dr"):
-                self.mus_dr.to_excel(writer, sheet_name="MU Discharge Rate")
-
-            if hasattr(self, "mu_thresholds"):
-                self.mu_thresholds.to_excel(writer, sheet_name="MU Thresholds")
-
-            writer.close()
-
-        except IndexError:
-            tk.messagebox.showerror(
-                "Information", "Please conduct at least one analysis before saving"
+            openhdemg.asksavefile(
+                emgfile=self.resdict,
+                compresslevel=self.settings.save_json_emgfile__compresslevel,
             )
-
-        except AttributeError:
-            tk.messagebox.showerror("Information", "Make sure a file is loaded.")
-
-        except PermissionError:
-            tk.messagebox.showerror(
-                "Information",
-                "If /Results.xlsx already opened, please close."
-                + "\nOtherwise ignore as you propably canceled the saving progress.",
+        except AttributeError as e:
+            # Remove file saving indicator
+            if hasattr(self, "processing_indicator"):
+                self.processing_indicator.lower()
+            # Show error
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Make sure a file is loaded."),
             )
+        except Exception:
+            # Remove file saving indicator
+            if hasattr(self, "processing_indicator"):
+                self.processing_indicator.lower()
+
+        # Remove file saving indicator
+        if hasattr(self, "processing_indicator"):
+            self.processing_indicator.lower()
 
     def reset_analysis(self):
         """
-        Instance method to restore the GUI to base data. Any analysis progress will be deleted by
-        reloading the original file.
+        Instance method to restore the GUI to base data. Any analysis progress
+        will be deleted by reloading the original file.
 
-        Executed when button "Reset Analysis" in master GUI window is pressed. The emgfile is
-        updated to its original state.
+        Executed when button "Reset Analysis" in master GUI window is pressed.
+        The emgfile is updated to its original state.
 
         Raises
         ------
@@ -1052,206 +1039,124 @@ def reset_analysis(self):
         FileNotFoundError
             When no file was loaded in the GUI.
         """
+
         # Get user input and check whether analysis wants to be truly resetted
-        if tk.messagebox.askokcancel(
-            "Attention", "Do you really want to reset the analysis?"
+        if not messagebox.askokcancel(
+            title="Attention",
+            message="Do you really want to reset the analysis?",
+            icon="warning",
         ):
-            # user decided to rest analysis
-            try:
-                # reload original file
-                if self.filetype.get() in ["OTB", "DEMUSE", "OPENHDEMG", "CUSTOMCSV", "DELSYS"]:
-                    if self.filetype.get() == "OTB":
-                        self.resdict = openhdemg.emg_from_otb(
-                            filepath=self.file_path,
-                            ext_factor=int(self.extension_factor.get()),
-                        )
-
-                    elif self.filetype.get() == "DEMUSE":
-                        self.resdict = openhdemg.emg_from_demuse(
-                            filepath=self.file_path
-                        )
-
-                    elif self.filetype.get() == "OPENHDEMG":
-                        self.resdict = openhdemg.emg_from_json(filepath=self.file_path)
-
-                    elif self.filetype.get() == "CUSTOMCSV":
-                        self.resdict = openhdemg.emg_from_customcsv(
-                            filepath=self.file_path
-                        )
-                    elif self.filetype.get() == "DELSYS":
-                        self.resdict = openhdemg.emg_from_delsys(rawemg_filepath=self.file_path,
-                                                                 mus_directory=self.mus_path)
-                    # Update Filespecs
-                    ttk.Label(
-                        self.left, text=str(len(self.resdict["RAW_SIGNAL"].columns))
-                    ).grid(column=2, row=2, sticky=(W, E))
-                    ttk.Label(self.left, text=str(self.resdict["NUMBER_OF_MUS"])).grid(
-                        column=2, row=3, sticky=(W, E)
-                    )
-                    ttk.Label(self.left, text=str(self.resdict["EMG_LENGTH"])).grid(
-                        column=2, row=4, sticky=(W, E)
-                    )
+            # user decided to not rest analysis
+            return
 
-                else:
-                    # load refsig
-                    if self.filetype.get() == "OTB_REFSIG":
-                        self.resdict = openhdemg.refsig_from_otb(filepath=self.file_path)
-                    else:  # CUSTOMCSV_REFSIG
-                        self.resdict = openhdemg.refsig_from_customcsv(filepath=self.file_path)
-
-                    # Recondifgure labels for refsig
-                    ttk.Label(
-                        self.left, text=str(len(self.resdict["REF_SIGNAL"].columns))
-                    ).grid(column=2, row=2, sticky=(W, E))
-                    ttk.Label(self.left, text="NA").grid(column=2, row=3, sticky=(W, E))
-                    ttk.Label(self.left, text="        ").grid(
-                        column=2, row=4, sticky=(W, E)
-                    )
+        # user decided to rest analysis
+        try:
+            # Revert to original file
+            if self.resdict["SOURCE"] in [
+                "OTB", "DEMUSE", "CUSTOMCSV", "DELSYS",
+            ]:
+                # Use the resdict copy
+                self.resdict = self.resdict_copy_of_original
+
+                # Update Filespecs
+                self.n_channels.configure(
+                    text="N Channels: " + str(len(self.resdict["RAW_SIGNAL"].columns)),
+                    font=("Segoe UI", 15, ("bold")),
+                )
+                self.n_of_mus.configure(
+                    text="N of MUs: " + str(self.resdict["NUMBER_OF_MUS"]),
+                    font=("Segoe UI", 15, "bold"),
+                )
+                self.file_length.configure(
+                    text="File Length: " + str(self.resdict["EMG_LENGTH"]),
+                    font=("Segoe UI", 15, "bold"),
+                )
 
                 # Update Plot
-                if hasattr(self, "fig"):
-                    self.in_gui_plotting()
+                self.in_gui_plotting(resdict=self.resdict)
+
+            elif self.resdict["SOURCE"] in [
+                "OTB_REFSIG", "CUSTOMCSV_REFSIG", "DELSYS_REFSIG"
+            ]:
+                # Use the resdict copy
+                self.resdict = self.resdict_copy_of_original
+
+                # Reconfigure labels for refsig
+                self.n_channels.configure(
+                    text="N Channels: " + str(len(self.resdict["REF_SIGNAL"].columns)),
+                    font=("Segoe UI", 15, ("bold")),
+                )
+                self.n_of_mus.configure(
+                    text="N of MUs: N/A",
+                    font=("Segoe UI", 15, "bold"),
+                )
+                self.file_length.configure(
+                    text="File Length: "
+                    + str(len(self.resdict["REF_SIGNAL"].iloc[:, 0])),
+                    font=("Segoe UI", 15, "bold"),
+                )
 
-                # Clear frame for output
-                if hasattr(self, "terminal"):
-                    self.terminal = ttk.LabelFrame(
-                        self.master, text="Result Output", height=100, relief="ridge"
-                    )
-                    self.terminal.grid(
-                        column=0,
-                        row=21,
-                        columnspan=2,
-                        pady=8,
-                        padx=10,
-                        sticky=(N, S, W, E),
-                    )
+                # Update Plot
+                self.in_gui_plotting(resdict=self.resdict, plot="refsig_off")
 
-            except AttributeError:
-                tk.messagebox.showerror("Information", "Make sure a file is loaded.")
+            # Clear frame for output
+            if hasattr(self, "terminal"):
+                self.terminal = ttk.LabelFrame(
+                    self, text="Result Output", height=150, relief="ridge"
+                )
+                self.terminal.grid(
+                    column=0,
+                    row=1,
+                    columnspan=2,
+                    pady=5,
+                    padx=5,
+                    sticky=(N, S, W, E),
+                )  # Repeat original settings in init
+
+            # Delete previous analyses results
+            self.delete_previous_analyses_results()
+
+        except AttributeError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Make sure a file is loaded."),
+            )
 
-            except FileNotFoundError:
-                tk.messagebox.showerror("Information", "Make sure a file is loaded.")
+        except FileNotFoundError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Make sure a file is loaded."),
+            )
 
-    def open_advanced_tools(self):
+    def delete_previous_analyses_results(self):
         """
-        Open a window for advanced analysis tools.
+        Instance method to delete the objects storing the analyses results.
         """
-        # Disable config for DELSYS files
-        if self.resdict["SOURCE"] == "DELSYS":
-            tk.messagebox.showerror(
-                "Information",
-                "Advanced Tools for Delsys are only accessible from the library.",
-            )
-            # NOTE I would show an error message
-            return
-
-        # Open window
-        self.a_window = tk.Toplevel(bg="LightBlue4", height=200)
-        self.a_window.title("Advanced Tools Window")
-        self.a_window.iconbitmap(
-            os.path.dirname(os.path.abspath(__file__)) + "/gui_files/Icon.ico"
-        )
-        self.a_window.grab_set()
-
-        # Add Label
-        ttk.Label(
-            self.a_window, text="Select tool and matrix:", font=("Verdana", 14, "bold")
-        ).grid(row=0, column=0)
 
-        # Analysis Tool
-        ttk.Label(self.a_window, text="Analysis Tool").grid(
-            row=2, column=0, sticky=(W, E)
-        )
-        # Add Selection Combobox
-        self.advanced_method = StringVar()
-        adv_box = ttk.Combobox(
-            self.a_window, width=15, textvariable=self.advanced_method
-        )
-        adv_box["values"] = (
-            "Motor Unit Tracking",
-            "Duplicate Removal",
-            "Conduction Velocity",
-        )
-        adv_box["state"] = "readonly"
-        adv_box.grid(row=2, column=1, sticky=(W, E))
-        adv_box.set("Motor Unit Tracking")
-
-        # Matrix Orientation
-        ttk.Label(self.a_window, text="Matrix Orientation").grid(
-            row=3, column=0, sticky=(W, E)
-        )
-        self.mat_orientation_adv = StringVar()
-        orientation = ttk.Combobox(
-            self.a_window, width=8, textvariable=self.mat_orientation_adv
-        )
-        orientation["values"] = ("0", "180")
-        orientation["state"] = "readonly"
-        orientation.grid(row=3, column=1, sticky=(W, E))
-        self.mat_orientation_adv.set("180")
-
-        # Matrix code
-        ttk.Label(self.a_window, text="Matrix Code").grid(
-            row=4, column=0, sticky=(W, E)
-        )
-        self.mat_code_adv = StringVar()
-        matrix_code = ttk.Combobox(
-            self.a_window, width=10, textvariable=self.mat_code_adv
-        )
-        matrix_code["values"] = ("GR08MM1305", "GR04MM1305", "GR10MM0808", "Trigno Galileo Sensor", "None")
-        matrix_code["state"] = "readonly"
-        matrix_code.grid(row=4, column=1, sticky=(W, E))
-        self.mat_code_adv.set("GR08MM1305")
-        
-        # Trace variabel for updating window
-        self.mat_code_adv.trace("w", self.on_matrix_none_adv)
-
-        # Analysis Button
-        adv_button = ttk.Button(
-            self.a_window,
-            text="Advanced Analysis",
-            command=self.advanced_analysis,
-            style="B.TButton",
-        )
-        adv_button.grid(column=0, row=7)
-
-        # Add padding to widgets
-        for child in self.a_window.winfo_children():
-            child.grid_configure(padx=5, pady=5)
-
-    def on_matrix_none_adv(self, *args):
-        """
-        This function is called when the value of the mat_code_adv variable is changed.
+        # Check for attributes and delete them if present
+        if hasattr(self, "mvc_df"):
+            del self.mvc_df
+        if hasattr(self, "rfd"):
+            del self.rfd
+        if hasattr(self, "mu_prop_df"):
+            del self.mu_prop_df
+        if hasattr(self, "mus_dr"):
+            del self.mus_dr
+        if hasattr(self, "mu_thresholds"):
+            del self.mu_thresholds
 
-        When the variable is set to "None" it will create an Entrybox on the grid at column 1 and row 6,
-        and when the mat_code_adv is set to something else it will remove the entrybox from the grid.
-        """
-        if self.mat_code_adv.get() == "None":
-        
-            self.mat_label_adv = ttk.Label(self.a_window, text="Rows, Columns:")
-            self.mat_label_adv.grid(row=5, column=1, sticky = W)
-            
-            self.matrix_rc_adv = StringVar()
-            self.row_cols_entry_adv = ttk.Entry(self.a_window, width=8, textvariable= self.matrix_rc_adv)
-            self.row_cols_entry_adv.grid(row=6, column=1, sticky = W, padx=5, pady=2)
-            self.matrix_rc_adv.set("13,5")
-        
-        else:
-            if hasattr(self, "row_cols_entry_adv"):
-                self.row_cols_entry_adv.grid_forget()
-                self.mat_label_adv.grid_forget()
-        
-        self.a_window.update_idletasks()
-
-    # -----------------------------------------------------------------------------------------------
+    # ----------------------------------------------------------------------------------------------
     # Plotting inside of GUI
 
-    def in_gui_plotting(self, plot="idr"):
+    def in_gui_plotting(self, resdict, plot="idr"):
         """
-        Instance method to plot any analysis results in the GUI for inspection. Plots are updated
-        during the analysis process.
+        Instance method to plot any analysis results in the GUI for inspection.
+        Plots are updated during the analysis process.
 
-        Executed when button "View MUs" in master GUI window is pressed or when the original
-        input file is changed.
+        Executed when button "View MUs" in master GUI window is pressed or
+        when the original input file is changed.
 
         Raises
         ------
@@ -1262,2049 +1167,91 @@ def in_gui_plotting(self, plot="idr"):
         --------
         plot_refsig, plot_idr in the library.
         """
-        try:  # NOTE as I previously said, OPENHDEMG (.json) files can contain anything. better check SOURCE.
-            if self.resdict["SOURCE"] in ["OTB_REFSIG", "CUSTOMCSV_REFSIG", "DELSYS_REFSIG"]:
+
+        try:
+            if self.resdict["SOURCE"] in [
+                "OTB_REFSIG",
+                "CUSTOMCSV_REFSIG",
+                "DELSYS_REFSIG",
+            ]:
                 self.fig = openhdemg.plot_refsig(
-                    emgfile=self.resdict, showimmediately=False, tight_layout=True
+                    emgfile=resdict,
+                    showimmediately=False,
                 )
             elif plot == "idr":
                 self.fig = openhdemg.plot_idr(
-                    emgfile=self.resdict, showimmediately=False, tight_layout=True
+                    emgfile=resdict,
+                    showimmediately=False,
                 )
             elif plot == "refsig_fil":
                 self.fig = openhdemg.plot_refsig(
-                    emgfile=self.resdict, showimmediately=False, tight_layout=True
+                    emgfile=resdict,
+                    showimmediately=False,
                 )
             elif plot == "refsig_off":
                 self.fig = openhdemg.plot_refsig(
-                    emgfile=self.resdict, showimmediately=False, tight_layout=True
+                    emgfile=resdict,
+                    showimmediately=False,
                 )
 
-            self.canvas = FigureCanvasTkAgg(self.fig, master=self.right)
-            self.canvas.get_tk_widget().grid(row=0, column=0, rowspan=5)
-            toolbar = NavigationToolbar2Tk(self.canvas, self.right, pack_toolbar=False)
-            toolbar.grid(row=5, column=0)
+            # Remove previous figure
+            if hasattr(self, 'canvas'):
+                self.canvas.get_tk_widget().destroy()
+
+            # Pack figure inside logo_canvas. This is more reliable and should
+            # be more efficient.
+            self.canvas = FigureCanvasTkAgg(self.fig, master=self.logo_canvas)
+            self.canvas.get_tk_widget().pack(
+                expand=True, fill="both", padx=5, pady=5,
+            )
+
+            # Add toolbar
+            toolbar = NavigationToolbar2Tk(
+                self.canvas,
+                self.right,
+                pack_toolbar=False,
+            )
+            toolbar.grid(row=5, column=0, sticky=(S, E), padx=5, pady=5)
             plt.close()
 
-        except AttributeError:
-            tk.messagebox.showerror("Information", "Make sure a file is loaded.")
+        except AttributeError as e:
+            show_error_dialog(
+                parent=self,
+                error=e,
+                solution=str("Make sure a file is loaded."),
+            )
 
-    # -----------------------------------------------------------------------------------------------
-    # Sorting of motor units
+    # ----------------------------------------------------------------------------------------------
+    # Analysis results display
 
-    def sort_mus(self):
+    def display_results(self, input_df):
         """
-        Instance method to sort motor units ascending according to recruitement order.
-
-        Executed when button "Sort MUs" in master GUI window is pressed. The plot of the MUs
-        and the emgfile are subsequently updated.
+        Instance method that displays all analysis results in the
+        output terminal using Pandastable. Input must be a Pandas dataframe.
 
-        Raises
-        ------
-        AttributeError
-            When no file was loaded in the GUI.
-
-        See Also
-        --------
-        sort_mus in library.
-        """
-        try:
-            # Sort emgfile
-            self.resdict = openhdemg.sort_mus(emgfile=self.resdict)
-
-            # Update plot
-            if hasattr(self, "fig"):
-                self.in_gui_plotting()
-
-        except AttributeError:
-            tk.messagebox.showerror("Information", "Make sure a file is loaded.")
-
-        except KeyError:
-            tk.messagebox.showerror("Information", "Sorting not possible when ≤ 1"
-                                    + "\nMU is present in the File (i.e. Refsigs)")
-    # -----------------------------------------------------------------------------------------------
-    # Removal of single motor units
-
-    def remove_mus(self):
-        """
-        Instance method to open "Motor Unit Removal Window". Further option to select and
-        remove MUs are displayed.
-
-        Executed when button "Remove MUs" in master GUI window is pressed.
-
-        Raises
-        ------
-        AttributeError
-            When no file is loaded prior to analysis.
-        """
-        if hasattr(self, "resdict"):
-            # Create new window
-            self.head = tk.Toplevel(bg="LightBlue4")
-            self.head.title("Motor Unit Removal Window")
-            self.head.iconbitmap(
-                os.path.dirname(os.path.abspath(__file__)) + "/gui_files/Icon.ico"
-            )
-            self.head.grab_set()
-
-            # Select Motor Unit
-            ttk.Label(self.head, text="Select MU:").grid(
-                column=1, row=0, padx=5, pady=5, sticky=W
-            )
-
-            self.mu_to_remove = StringVar()
-            removed_mu_value = [*range(0, self.resdict["NUMBER_OF_MUS"])]
-            removed_mu = ttk.Combobox(
-                self.head, width=10, textvariable=self.mu_to_remove
-            )
-            removed_mu["values"] = removed_mu_value
-            removed_mu["state"] = "readonly"
-            removed_mu.grid(column=1, row=1, columnspan=2, sticky=(W, E), padx=5, pady=5)
-
-            # Remove Motor unit
-            remove = ttk.Button(self.head, text="Remove MU", command=self.remove)
-            remove.grid(column=1, row=2, sticky=(W, E), padx=5, pady=5)
-
-            # Remove empty MUs
-            remove_empty = ttk.Button(self.head, text="Remove empty MUs", command=self.remove_empty)
-            remove_empty.grid(column=2, row=2, padx=5, pady=5)
-        else:
-            tk.messagebox.showerror("Information", "Make sure a file is loaded.")
-
-    def remove(self):
-        """
-        Instance method that actually removes a selected motor unit based on user specification.
-
-        Executed when button "Remove MU" in Motor Unit Removal Window is pressed.
-        The emgfile and the plot are subsequently updated.
-
-        See Also
-        --------
-        delete_mus in library.
-        """
-        try:
-            # Get resdict with MU removed
-            self.resdict = openhdemg.delete_mus(
-                emgfile=self.resdict, munumber=int(self.mu_to_remove.get())
-            )
-            # Upate MU number
-            ttk.Label(self.left, text=str(self.resdict["NUMBER_OF_MUS"])).grid(
-                column=2, row=3, sticky=(W, E)
-            )
-
-            # Update selection field
-            self.mu_to_remove = StringVar()
-            removed_mu_value = [*range(0, self.resdict["NUMBER_OF_MUS"])]
-            removed_mu = ttk.Combobox(
-                self.head, width=10, textvariable=self.mu_to_remove
-            )
-            removed_mu["values"] = removed_mu_value
-            removed_mu["state"] = "readonly"
-            removed_mu.grid(column=1, row=1, columnspan=2, sticky=(W, E), padx=5, pady=5)
-
-            # Update plot
-            if hasattr(self, "fig"):
-                self.in_gui_plotting()
-
-        except AttributeError:
-            tk.messagebox.showerror("Information", "Make sure a file is loaded.")
-
-    def remove_empty(self):
-        """
-        Instance method that removes all empty MUs.
-
-        Executed when button "Remove empty MUs" in Motor Unit Removal Window is pressed.
-        The emgfile and the plot are subsequently updated.
-
-        See Also
-        --------
-        delete_empty_mus in library.
-        """
-        try:
-            # Get resdict with MU removed
-            self.resdict = openhdemg.delete_empty_mus(self.resdict)
-
-            # Upate MU number
-            ttk.Label(self.left, text=str(self.resdict["NUMBER_OF_MUS"])).grid(
-                column=2, row=3, sticky=(W, E)
-            )
-
-            # Update selection field
-            self.mu_to_remove = StringVar()
-            removed_mu_value = [*range(0, self.resdict["NUMBER_OF_MUS"])]
-            removed_mu = ttk.Combobox(
-                self.head, width=10, textvariable=self.mu_to_remove
-            )
-            removed_mu["values"] = removed_mu_value
-            removed_mu["state"] = "readonly"
-            removed_mu.grid(column=1, row=1, columnspan=2, sticky=(W, E), padx=5, pady=5)
-
-            # Update plot
-            if hasattr(self, "fig"):
-                self.in_gui_plotting()
-
-        except AttributeError:
-            tk.messagebox.showerror("Information", "Make sure a file is loaded.")
-
-    # -----------------------------------------------------------------------------------------------
-    # Editing of single motor Units
-
-    # def editing_mus(self):
-    #     """
-    #     Instance method to edit sindle motor units. For now, this contains only plotting single MUs.
-    #     More options will be added.
-
-    #     THIS PART IS NOT YET INTEGRATED IN THE GUI.
-    #     """
-
-    #     # Create new window
-    #     self.head = tk.Toplevel(bg='LightBlue4')
-    #     self.head.title("Motor Unit Eiditing Window")
-    #     self.head.grab_set()
-
-    #     # Select Motor Unit
-    #     ttk.Label(self.head, text="Select MU:").grid(column=0, row=0, sticky=W, padx=5, pady=5)
-
-    #     self.mu_to_edit = StringVar()
-    #     edit_mu_value = [*range(0, self.resdict["NUMBER_OF_MUS"])]
-    #     edit_mu = ttk.Combobox(self.head, width=10, textvariable=self.mu_to_edit)
-    #     edit_mu["values"] = edit_mu_value
-    #     edit_mu["state"] = "readonly"
-    #     edit_mu.grid(column=1, row=0, sticky=(W,E), padx=5, pady=5)
-
-    #     # Button to plot MU
-    #     single_mu = ttk.Button(self.head,
-    #                           text="View single MU",
-    #                           command=self.view_single_mu)
-    #     single_mu.grid(column=1, row=1, sticky=(W,E), padx=5, pady=5)
-
-    # def view_single_mu(self):
-    #     """
-    #     Instance method that plots single selected MU.
-
-    #     THIS PART IS NOT YET INTEGRATED IN THE GUI.
-    #     """
-    #     # Make figure
-    #     fig = openhdemg.plot_idr(emgfile=self.resdict,
-    #                              munumber=int(self.mu_to_edit.get()),
-    #                              showimmediately=False)
-    #     # Create canvas and plot
-    #     canvas = FigureCanvasTkAgg(fig, master=self.head)
-    #     canvas_plot = canvas.get_tk_widget()
-    #     canvas_plot.grid(column=1, row=2, sticky=(W,E))
-    #     # Place matplotlib toolbar
-    #     toolbar = NavigationToolbar2Tk(canvas, self.head, pack_toolbar=False)
-    #     toolbar.grid(row=3, column=1)
-    #     # terminate matplotlib to ensure GUI shutdown when closed
-    #     plt.close()
-
-    # -----------------------------------------------------------------------------------------------
-    # Editing of Reference EMG Signal
-
-    def edit_refsig(self):
-        """
-        Instance method to open "Reference Signal Editing Window". Options for
-        refsig filtering and offset removal are displayed.
-
-        Executed when button "RefSig Editing" in master GUI window is pressed.
-        """
-        # Create new window
-        self.head = tk.Toplevel(bg="LightBlue4")
-        self.head.title("Reference Signal Editing Window")
-        self.head.iconbitmap(
-            os.path.dirname(os.path.abspath(__file__)) + "/gui_files/Icon.ico"
-        )
-        self.head.grab_set()
-
-        # Filter Refsig
-        # Define Labels
-        ttk.Label(self.head, text="Filter Order").grid(column=1, row=0, sticky=(W, E))
-        ttk.Label(self.head, text="Cutoff Freq").grid(column=2, row=0, sticky=(W, E))
-        # Fiter button
-        basic = ttk.Button(self.head, text="Filter Refsig", command=self.filter_refsig)
-        basic.grid(column=0, row=1, sticky=W)
-
-        self.filter_order = StringVar()
-        order = ttk.Entry(self.head, width=10, textvariable=self.filter_order)
-        order.grid(column=1, row=1)
-        self.filter_order.set(4)
-
-        self.cutoff_freq = StringVar()
-        cutoff = ttk.Entry(self.head, width=10, textvariable=self.cutoff_freq)
-        cutoff.grid(column=2, row=1)
-        self.cutoff_freq.set(15)
-
-        # Remove offset of reference signal
-        separator2 = ttk.Separator(self.head, orient="horizontal")
-        separator2.grid(column=0, columnspan=3, row=2, sticky=(W, E), padx=5, pady=5)
-
-        ttk.Label(self.head, text="Offset Value").grid(column=1, row=3, sticky=(W, E))
-        ttk.Label(self.head, text="Automatic Offset").grid(
-            column=2, row=3, sticky=(W, E)
-        )
-
-        # Offset removal button
-        basic2 = ttk.Button(self.head, text="Remove Offset", command=self.remove_offset)
-        basic2.grid(column=0, row=4, sticky=W)
-
-        self.offsetval = StringVar()
-        offset = ttk.Entry(self.head, width=10, textvariable=self.offsetval)
-        offset.grid(column=1, row=4)
-        self.offsetval.set(4)
-
-        self.auto_eval = StringVar()
-        auto = ttk.Entry(self.head, width=10, textvariable=self.auto_eval)
-        auto.grid(column=2, row=4)
-        self.auto_eval.set(0)
-
-        separator3 = ttk.Separator(self.head, orient="horizontal")
-        separator3.grid(column=0, columnspan=3, row=5, sticky=(W, E), padx=5, pady=5)
-
-        # Convert Reference signal
-        ttk.Label(self.head, text="Operator").grid(column=1, row=6, sticky=(W, E))
-        ttk.Label(self.head, text="Factor").grid(
-            column=2, row=6, sticky=(W, E)
-        )
-
-        self.convert = StringVar()
-        convert = ttk.Combobox(self.head, width=10, textvariable=self.convert)
-        convert["values"] = ("Multiply", "Divide")
-        convert["state"] = "readonly"
-        convert.grid(column=1, row=7)
-        self.convert.set("Multiply")
-
-        self.convert_factor = DoubleVar()
-        factor = ttk.Entry(self.head, width=10, textvariable=self.convert_factor)
-        factor.grid(column=2, row=7)
-        self.convert_factor.set(2.5)
-        
-        convert_button = ttk.Button(self.head, text="Convert", command=self.convert_refsig)
-        convert_button.grid(column=0, row=7, sticky=W)
-
-        separator3 = ttk.Separator(self.head, orient="horizontal")
-        separator3.grid(column=0, columnspan=3, row=8, sticky=(W, E), padx=5, pady=5)
-
-        # Convert to percentage
-        ttk.Label(self.head, text="MVC Value").grid(column=1, row=9, sticky=(W, E))
-        
-        percent_button = ttk.Button(self.head, text="To Percent*", command=self.to_percent)
-        percent_button.grid(column=0, row=10, sticky=W)
-
-        self.mvc_value = DoubleVar()
-        mvc = ttk.Entry(self.head, width=10, textvariable=self.mvc_value)
-        mvc.grid(column=1, row=10)
-
-
-        ttk.Label(self.head,
-                  text= "*Use this button \nonly if your Refsig \nis in absolute values!",
-                  font=("Arial", 8)).grid(
-            column=2, row=9, rowspan=2
-        )
-
-        # Add padding to all children widgets of head
-        for child in self.head.winfo_children():
-            child.grid_configure(padx=5, pady=5)
-
-    ### Define functions for Refsig editing
-
-    def filter_refsig(self):
-        """
-        Instance method that filters the refig based on user selected specs.
-
-        Executed when button "Filter Refsig" in Reference Signal Editing Window is pressed.
-        The emgfile and the GUI plot are updated.
-
-        Raises
-        ------
-        AttributeError
-            When no reference signal file is available.
-
-        See Also
-        --------
-        filter_refsig in library.
-        """
-        try:
-            # Filter refsig
-            self.resdict = openhdemg.filter_refsig(
-                emgfile=self.resdict,
-                order=int(self.filter_order.get()),
-                cutoff=int(self.cutoff_freq.get()),
-            )
-            # Plot filtered Refsig
-            self.in_gui_plotting(plot="refsig_fil")
-
-        except AttributeError:
-            tk.messagebox.showerror("Information", "Make sure a Refsig file is loaded.")
-
-    def remove_offset(self):
-        """
-        Instance Method that removes user specified/selected Refsig offset.
-
-        Executed when button "Remove offset" in Reference Signal Editing Window is pressed.
-        The emgfile and the GUI plot are updated.
-
-        Raises
-        ------
-        AttributeError
-            When no reference signal file is available
-
-        See Also
-        --------
-        remove_offset in library.
-        """
-        try:
-            # Remove offset
-            self.resdict = openhdemg.remove_offset(
-                emgfile=self.resdict,
-                offsetval=int(self.offsetval.get()),
-                auto=int(self.auto_eval.get()),
-            )
-            # Update Plot
-            self.in_gui_plotting(plot="refsig_off")
-
-        except AttributeError:
-            tk.messagebox.showerror("Information", "Make sure a Refsig file is loaded.")
-
-        except ValueError:
-            tk.messagebox.showerror("Information", "Make sure to specify valid filtering or offset values.")
-
-    def convert_refsig(self):
-        """
-        Instance Method that converts Refsig by multiplication or division.
-
-        Executed when button "Convert" in Reference Signal Editing Window is pressed.
-        The emgfile and the GUI plot are updated.
-
-        Raises
-        ------
-        AttributeError
-            When no reference signal file is available
-        ValueError
-            When invalid conversion factor is specified
-        
-        """
-        try:
-            if self.convert.get() == "Multiply":
-                self.resdict["REF_SIGNAL"] = self.resdict["REF_SIGNAL"] * self.convert_factor.get()
-            elif self.convert.get() == "Divide":
-                self.resdict["REF_SIGNAL"] = self.resdict["REF_SIGNAL"] / self.convert_factor.get()
-        
-            # Update Plot
-            self.in_gui_plotting(plot="refsig_off")
-
-        except AttributeError:
-            tk.messagebox.showerror("Information", "Make sure a Refsig file is loaded.")
-
-        except ValueError:
-            tk.messagebox.showerror("Information", "Make sure to specify a valid conversion factor.")
-
-    def to_percent(self):
-        """
-        Instance Method that converts Refsig to a percentag value. Should only be used when the Refsig is in absolute values.
-
-        Executed when button "To Percen" in Reference Signal Editing Window is pressed.
-        The emgfile and the GUI plot are updated.
-
-        Raises
-        ------
-        AttributeError
-            When no reference signal file is available
-        ValueError
-            When invalid conversion factor is specified
-        """
-        try:
-            self.resdict["REF_SIGNAL"] = (self.resdict["REF_SIGNAL"] * 100) / self.mvc_value.get()
-        
-            # Update Plot
-            self.in_gui_plotting()
-
-        except AttributeError:
-            tk.messagebox.showerror("Information", "Make sure a Refsig file is loaded.")
-
-        except ValueError:
-            tk.messagebox.showerror("Information", "Make sure to specify a valid conversion factor.")
-    # -----------------------------------------------------------------------------------------------
-    # Resize EMG File
-
-    def resize_file(self):
-        """
-        Instance method to get resize area from user specification on plot and
-        resize emgfile.
-
-        Executed when button "Select Resize" is pressed in Resize file window.
-        The emgfile and the GUI plot are updated.
-
-        Raises
-        ------
-        AttributeError
-            When no file is loaded prior to analysis.
-
-        See Also
-        --------
-        showselect, resize_emgfile in library.
-        """
-        try:
-            # Open selection window for user
-            points = openhdemg.showselect(
-                emgfile=self.resdict,
-                title="Select the start/end area to resize by hovering the mouse\nand pressing the 'a'-key. Wrong points can be removed with right click.\nWhen ready, press enter.",
-                titlesize=10,
-            )
-            start, end = points[0], points[1]
-
-            # Delsys requires different handling for resize
-            if self.resdict["SOURCE"] == "DELSYS":
-                self.resdict, _, _ = openhdemg.resize_emgfile(
-                emgfile=self.resdict, area=[start, end], accuracy="maintain"
-                )
-            else:
-                self.resdict, _, _ = openhdemg.resize_emgfile(
-                    emgfile=self.resdict, area=[start, end]
-                )
-            # Update Plot
-            self.in_gui_plotting()
-
-            # Update filelength
-            ttk.Label(self.left, text=str(self.resdict["EMG_LENGTH"])).grid(
-                    column=2, row=4, sticky=(W, E)
-            ) 
-
-        except AttributeError:
-            tk.messagebox.showerror("Information", "Make sure a file is loaded.")
-
-    # -----------------------------------------------------------------------------------------------
-    # Analysis of Force
-
-    def analyze_force(self):
-        """
-        Instance method to open "Force analysis Window".
-        Options to analyse force singal are displayed.
-
-        Executed when "Analyse Force" button in master GUI window is pressed.
-        """
-        # Create new window
-        self.head = tk.Toplevel(bg="LightBlue4")
-        self.head.title("Force Analysis Window")
-        self.head.iconbitmap(
-            os.path.dirname(os.path.abspath(__file__)) + "/gui_files/Icon.ico"
-        )
-        self.head.grab_set()
-
-        # Get MVC
-        get_mvf = ttk.Button(self.head, text="Get MVC", command=self.get_mvc)
-        get_mvf.grid(column=0, row=1, sticky=(W, E), padx=5, pady=5)
-
-        # Get RFD
-        separator1 = ttk.Separator(self.head, orient="horizontal")
-        separator1.grid(column=0, columnspan=3, row=2, sticky=(W, E), padx=5, pady=5)
-
-        ttk.Label(self.head, text="RFD miliseconds").grid(
-            column=1, row=3, sticky=(W, E), padx=5, pady=5
-        )
-
-        get_rfd = ttk.Button(self.head, text="Get RFD", command=self.get_rfd)
-        get_rfd.grid(column=0, row=4, sticky=(W, E), padx=5, pady=5)
-
-        self.rfdms = StringVar()
-        milisecond = ttk.Entry(self.head, width=10, textvariable=self.rfdms)
-        milisecond.grid(column=1, row=4, sticky=(W, E), padx=5, pady=5)
-        self.rfdms.set("50,100,150,200")
-
-    ### Define functions for force analysis
-
-    def get_mvc(self):
-        """
-        Instance methof to retrieve calculated MVC based on user selection.
-
-        Executed when button "Get MVC" in Analyze Force window is pressed.
-        The Results of the analysis are displayed in the results terminal.
-
-        Raises
-        ------
-        AttributeError
-            When no file is loaded prior to analysis.
-
-        See Also
-        --------
-        get_mvc in the library
-        """
-        try:
-            # get MVC
-            self.mvc = openhdemg.get_mvc(emgfile=self.resdict)
-            # Define dictionary for pandas
-            mvc_dic = {"MVC": [self.mvc]}
-            self.mvc_df = pd.DataFrame(data=mvc_dic)
-            # Display results
-            self.display_results(self.mvc_df)
-
-        except AttributeError:
-            tk.messagebox.showerror("Information", "Make sure a file is loaded.")
-
-    def get_rfd(self):
-        """
-        Instance method to calculate RFD at specified timepoints based on user selection.
-
-        Executed when button "Get RFD" in Analyze Force window is pressed.
-        The Results of the analysis are displayed in the results terminal.
-
-        Raises
-        ------
-        AttributeError
-            When no file is loaded prior to analysis.
-
-        See Also
-        --------
-        get_rfd in library
-        """
-        try:
-            # Define list for RFD computation
-            ms = str(self.rfdms.get())
-            # Split the string at ,
-            ms_list = ms.split(",")
-            # Use comprehension to iterate through
-            ms_list = [int(i) for i in ms_list]
-            # Calculate rfd
-            self.rfd = openhdemg.compute_rfd(emgfile=self.resdict, ms=ms_list)
-            # Display results
-            self.display_results(self.rfd)
-
-        except AttributeError:
-            tk.messagebox.showerror("Information", "Make sure a file is loaded.")
-
-    # -----------------------------------------------------------------------------------------------
-    # Analysis of motor unit properties
-
-    def mu_analysis(self):
-        """
-        Instance method to open "Motor Unit Properties Window". Options to analyse motor
-        unit properties such as recruitement threshold, discharge rate or
-        basic properties computing are displayed.
-
-        Executed when button "MU Properties" button in master GUI window is pressed.
-        """
-        # Create new window
-        self.head = tk.Toplevel(bg="LightBlue4")
-        self.head.title("Motor Unit Properties Window")
-        self.head.iconbitmap(
-            os.path.dirname(os.path.abspath(__file__)) + "/gui_files/Icon.ico"
-        )
-        self.head.grab_set()
-
-        # MVC Entry
-        ttk.Label(self.head, text="Enter MVC[n]:").grid(column=0, row=0, sticky=(W))
-        self.mvc_value = StringVar()
-        enter_mvc = ttk.Entry(self.head, width=20, textvariable=self.mvc_value)
-        enter_mvc.grid(column=1, row=0, sticky=(W, E))
-
-        # Compute MU re-/derecruitement threshold
-        separator = ttk.Separator(self.head, orient="horizontal")
-        separator.grid(column=0, columnspan=4, row=2, sticky=(W, E), padx=5, pady=5)
-
-        thresh = ttk.Button(
-            self.head, text="Compute threshold", command=self.compute_mu_threshold
-        )
-        thresh.grid(column=0, row=3, sticky=W)
-
-        self.ct_event = StringVar()
-        ct_events_entry = ttk.Combobox(self.head, width=10, textvariable=self.ct_event)
-        ct_events_entry["values"] = ("rt", "dert", "rt_dert")
-        ct_events_entry["state"] = "readonly"
-        ct_events_entry.grid(column=1, row=3, sticky=(W, E))
-        self.ct_event.set("Event")
-
-        self.ct_type = StringVar()
-        ct_types_entry = ttk.Combobox(self.head, width=10, textvariable=self.ct_type)
-        ct_types_entry["values"] = ("abs", "rel", "abs_rel")
-        ct_types_entry["state"] = "readonly"
-        ct_types_entry.grid(column=2, row=3, sticky=(W, E))
-        self.ct_type.set("Type")
-
-        # Compute motor unit discharge rate
-        separator1 = ttk.Separator(self.head, orient="horizontal")
-        separator1.grid(column=0, columnspan=4, row=4, sticky=(W, E), padx=5, pady=5)
-
-        ttk.Label(self.head, text="Firings at Rec").grid(column=1, row=5, sticky=(W, E))
-        ttk.Label(self.head, text="Firings Start/End Steady").grid(
-            column=2, row=5, sticky=(W, E)
-        )
-
-        dr_rate = ttk.Button(
-            self.head, text="Compute discharge rate", command=self.compute_mu_dr
-        )
-        dr_rate.grid(column=0, row=6, sticky=W)
-
-        self.firings_rec = StringVar()
-        firings_1 = ttk.Entry(self.head, width=20, textvariable=self.firings_rec)
-        firings_1.grid(column=1, row=6)
-        self.firings_rec.set(4)
-
-        self.firings_ste = StringVar()
-        firings_2 = ttk.Entry(self.head, width=20, textvariable=self.firings_ste)
-        firings_2.grid(column=2, row=6)
-        self.firings_ste.set(10)
-
-        self.dr_event = StringVar()
-        dr_events_entry = ttk.Combobox(self.head, width=10, textvariable=self.dr_event)
-        dr_events_entry["values"] = (
-            "rec",
-            "derec",
-            "rec_derec",
-            "steady",
-            "rec_derec_steady",
-        )
-        dr_events_entry["state"] = "readonly"
-        dr_events_entry.grid(column=3, row=6, sticky=E)
-        self.dr_event.set("Event")
-
-        # Compute basic motor unit properties
-        separator2 = ttk.Separator(self.head, orient="horizontal")
-        separator2.grid(column=0, columnspan=4, row=7, sticky=(W, E), padx=5, pady=5)
-
-        ttk.Label(self.head, text="Firings at Rec").grid(column=1, row=8, sticky=(W, E))
-        ttk.Label(self.head, text="Firings Start/End Steady").grid(
-            column=2, row=8, sticky=(W, E)
-        )
-
-        basic = ttk.Button(
-            self.head, text="Basic MU properties", command=self.basic_mus_properties
-        )
-        basic.grid(column=0, row=9, sticky=W)
-
-        self.b_firings_rec = StringVar()
-        b_firings_1 = ttk.Entry(self.head, width=20, textvariable=self.b_firings_rec)
-        b_firings_1.grid(column=1, row=9)
-        self.b_firings_rec.set(4)
-
-        self.b_firings_ste = StringVar()
-        b_firings_2 = ttk.Entry(self.head, width=20, textvariable=self.b_firings_ste)
-        b_firings_2.grid(column=2, row=9)
-        self.b_firings_ste.set(10)
-
-        for child in self.head.winfo_children():
-            child.grid_configure(padx=5, pady=5)
-
-    ### Define functions for motor unit property calculation
-
-    def compute_mu_threshold(self):
-        """
-        Instance method to compute the motor unit recruitement thresholds
-        based on user selection of events and types.
-
-        Executed when button "Compute threshold" in Motor Unit Properties Window
-        is pressed. The analysis results are displayed in the result terminal.
-
-        Raises
-        ------
-        AttributeError
-            When no file is loaded prior to calculation.
-        ValueError
-            When entered MVC is not valid (inexistent).
-        AssertionError
-            When types/events are not specified.
-
-        See Also
-        --------
-        compute_thresholds in library.
-        """
-        try:
-            # Compute thresholds
-            self.mu_thresholds = openhdemg.compute_thresholds(
-                emgfile=self.resdict,
-                event_=self.ct_event.get(),
-                type_=self.ct_type.get(),
-                mvc=float(self.mvc_value.get()),
-            )
-            # Display results
-            self.display_results(self.mu_thresholds)
-
-        except AttributeError:
-            tk.messagebox.showerror("Information", "Load file prior to computation.")
-
-        except ValueError:
-            tk.messagebox.showerror("Information", "Enter valid MVC.")
-
-        except AssertionError:
-            tk.messagebox.showerror("Information", "Specify Event and/or Type.")
-
-    def compute_mu_dr(self):
-        """
-        Instance method to compute the motor unit discharge rate
-        based on user selection of Firings and Events.
-
-        Executed when button "Compute discharge rate" in Motor Unit Properties Window
-        is pressed. The analysis results are displayed in the result terminal.
-
-        Raises
-        ------
-        AttributeError
-            When no file is loaded prior to calculation.
-        ValueError
-            When entered Firings values are not valid (inexistent).
-        AssertionError
-            When types/events are not specified.
-
-        See Also
-        --------
-        compute_dr in library.
-        """
-        try:
-            # Compute discharge rates
-            self.mus_dr = openhdemg.compute_dr(
-                emgfile=self.resdict,
-                n_firings_RecDerec=int(self.firings_rec.get()),
-                n_firings_steady=int(self.firings_ste.get()),
-                event_=self.dr_event.get(),
-            )
-            # Display results
-            self.display_results(self.mus_dr)
-
-        except AttributeError:
-            tk.messagebox.showerror("Information", "Load file prior to computation.")
-
-        except ValueError:
-            tk.messagebox.showerror(
-                "Information",
-                "Enter valid Firings value or select a correct number of points."
-            )
-
-        except AssertionError:
-            tk.messagebox.showerror("Information", "Specify Event and/or Type.")
-
-    def basic_mus_properties(self):
-        """
-        Instance method to compute basic motor unit properties based in user
-        selection in plot.
-
-        Executed when button "Basic MU properties" in Motor Unit Properties Window
-        is pressed. The analysis results are displayed in the result terminal.
-
-        Raises
-        ------
-        AttributeError
-            When no file is loaded prior to calculation.
-        ValueError
-            When entered Firings values are not valid (inexistent).
-        AssertionError
-            When types/events are not specified.
-        UnboundLocalError
-            When start/end area for calculations are specified wrongly.
-
-        See Also
-        --------
-        basic_mus_properties in library.
-        """
-        try:
-            # Calculate properties
-            self.exportable_df = openhdemg.basic_mus_properties(
-                emgfile=self.resdict,
-                n_firings_RecDerec=int(self.b_firings_rec.get()),
-                n_firings_steady=int(self.b_firings_ste.get()),
-                mvc=float(self.mvc_value.get()),
-            )
-            # Display results
-            self.display_results(self.exportable_df)
-
-        except AttributeError:
-            tk.messagebox.showerror("Information", "Load file prior to computation.")
-
-        except ValueError:
-            tk.messagebox.showerror(
-                "Information",
-                "Enter valid MVC or select a correct number of points."
-            )
-
-        except AssertionError:
-            tk.messagebox.showerror("Information", "Specify Event and/or Type.")
-
-        except UnboundLocalError:
-            tk.messagebox.showerror("Information", "Select start/end area again.")
-
-    # -----------------------------------------------------------------------------------------------
-    # Plot EMG
-
-    def plot_emg(self):
-        """
-        Instance method to open "Plot Window". Options to create
-        several plots from the emgfile are displayed.
-
-        Executed when button "Plot EMG" in master GUI window is pressed.
-        The plots are displayed in seperate windows.
-        """
-        try:
-    
-            # Create new window
-            self.head = tk.Toplevel(bg="LightBlue4")
-            self.head.title("Plot Window")
-            self.head.iconbitmap(
-                os.path.dirname(os.path.abspath(__file__)) + "/gui_files/Icon.ico"
-            )
-            self.head.grab_set()
-
-            # Reference signal
-            ttk.Label(self.head, text="Reference signal").grid(
-                column=0, row=0, sticky=W
-            )
-            self.ref_but = StringVar()
-            ref_button = tk.Checkbutton(
-                self.head,
-                variable=self.ref_but,
-                bg="LightBlue4",
-                onvalue="True",
-                offvalue="False",
-            )
-            ref_button.grid(column=1, row=0, sticky=(W))
-            self.ref_but.set(False)
-
-            # Time
-            ttk.Label(self.head, text="Time in seconds").grid(column=0, row=1, sticky=W)
-            self.time_sec = StringVar()
-            time_button = tk.Checkbutton(
-                self.head,
-                variable=self.time_sec,
-                bg="LightBlue4",
-                onvalue="True",
-                offvalue="False",
-            )
-            time_button.grid(column=1, row=1, sticky=W)
-            self.time_sec.set(False)
-
-            # Figure Size
-            ttk.Label(self.head, text="Figure size in cm (h,w)").grid(column=0, row=2)
-            self.size_fig = StringVar()
-            fig_entry = ttk.Entry(self.head, width=7, textvariable=self.size_fig)
-            self.size_fig.set("20,15")
-            fig_entry.grid(column=1, row=2, sticky=W)
-
-            # Plot emgsig
-            plt_emgsig = ttk.Button(
-                self.head, text="Plot EMGsig", command=self.plt_emgsignal
-            )
-            plt_emgsig.grid(column=0, row=3, sticky=W)
-
-            self.channels = StringVar()
-            channel_entry = ttk.Combobox(
-                self.head, width=15, textvariable=self.channels
-            )
-            channel_entry["values"] = ("0", "0,1,2", "0,1,2,3")
-            channel_entry.grid(column=1, row=3, sticky=(W, E))
-            self.channels.set("Channel Numbers")
-
-            # Plot refsig
-            plt_refsig = ttk.Button(
-                self.head, text="Plot RefSig", command=self.plt_refsignal
-            )
-            plt_refsig.grid(column=0, row=4, sticky=W)
-
-            # Plot motor unit pulses
-            plt_pulses = ttk.Button(
-                self.head, text="Plot MUpulses", command=self.plt_mupulses
-            )
-            plt_pulses.grid(column=0, row=5, sticky=W)
-
-            self.linewidth = StringVar()
-            linewidth_entry = ttk.Combobox(
-                self.head, width=15, textvariable=self.linewidth
-            )
-            linewidth_entry["values"] = ("0.25", "0.5", "0.75", "1")
-            linewidth_entry.grid(column=1, row=5, sticky=(W, E))
-            self.linewidth.set("Linewidth")
-
-            # Plot impulse train
-            plt_ipts = ttk.Button(self.head, text="Plot Source", command=self.plt_ipts)
-            plt_ipts.grid(column=0, row=6, sticky=W)
-
-            self.mu_numb = StringVar()
-            munumb_entry = ttk.Combobox(self.head, width=15, textvariable=self.mu_numb)
-            munumb_entry["values"] = ("0", "0,1,2", "0,1,2,3", "all")
-            munumb_entry.grid(column=1, row=6, sticky=(W, E))
-            self.mu_numb.set("MU Number")
-
-            # Plot instantaneous discharge rate
-            plt_idr = ttk.Button(self.head, text="Plot IDR", command=self.plt_idr)
-            plt_idr.grid(column=0, row=7, sticky=W)
-
-            self.mu_numb_idr = StringVar()
-            munumb_entry_idr = ttk.Combobox(
-                self.head, width=15, textvariable=self.mu_numb_idr
-            )
-            munumb_entry_idr["values"] = ("0", "0,1,2", "0,1,2,3", "all")
-            munumb_entry_idr.grid(column=1, row=7, sticky=(W, E))
-            self.mu_numb_idr.set("MU Number")
-
-            # This section containes the code for column 3++
-
-            # Separator
-            ttk.Separator(self.head, orient="vertical").grid(
-                row=3, column=2, rowspan=6, ipady=120
-            )
-
-            # Matrix code
-            ttk.Label(self.head, text="Matrix Code").grid(row=0, column=3, sticky=(W))
-            self.mat_code = StringVar()
-            matrix_code = ttk.Combobox(self.head, width=15, textvariable=self.mat_code)
-            matrix_code["values"] = ("GR08MM1305", "GR04MM1305", "GR10MM0808", "Trigno Galileo Sensor", "None")
-            matrix_code["state"] = "readonly"
-            matrix_code.grid(row=0, column=4, sticky=(W, E))
-            self.mat_code.set("GR08MM1305")
-
-            # Trace matrix code value
-            self.mat_code.trace("w", self.on_matrix_none)
-
-            # Matrix Orientation
-            ttk.Label(self.head, text="Orientation").grid(row=1, column=3, sticky=(W))
-            self.mat_orientation = StringVar()
-            orientation = ttk.Combobox(
-                self.head, width=15, textvariable=self.mat_orientation
-            )
-            orientation["values"] = ("0", "180")
-            orientation["state"] = "readonly"
-            orientation.grid(row=1, column=4, sticky=(W, E))
-            self.mat_orientation.set("180")
-            if self.resdict["SOURCE"] == "DELSYS":
-                orientation.config(state="disabled")
-
-            # Plot derivation
-            # Button
-            deriv_button = ttk.Button(
-                self.head, text="Plot Derivation", command=self.plot_derivation
-            )
-            deriv_button.grid(row=3, column=3)
-
-            # Combobox Config
-            self.deriv_config = StringVar()
-            configuration = ttk.Combobox(
-                self.head, width=15, textvariable=self.deriv_config
-            )
-            configuration["values"] = ("Single differential", "Double differential")
-            configuration["state"] = "readonly"
-            configuration.grid(row=3, column=4, sticky=(W, E))
-            self.deriv_config.set("Configuration")
-
-            # Combobox Matrix
-            self.deriv_matrix = StringVar()
-            mat_column = ttk.Combobox(
-                self.head, width=15, textvariable=self.deriv_matrix
-            )
-            mat_column["values"] = ("col0", "col1", "col2", "col3", "col4")
-            mat_column["state"] = "readonly"
-            mat_column.grid(row=3, column=5, sticky=(W, E))
-            self.deriv_matrix.set("Matrix column")
-
-            # Motor unit action potential
-            # Button
-            muap_button = ttk.Button(
-                self.head, text="Plot MUAPs", command=self.plot_muaps
-            )
-            muap_button.grid(row=4, column=3)
-
-            # Combobox Config
-            self.muap_config = StringVar()
-            config_muap = ttk.Combobox(
-                self.head, width=15, textvariable=self.muap_config
-            )
-            config_muap["values"] = (
-                "Monopolar",
-                "Single differential",
-                "Double differential",
-            )
-            config_muap["state"] = "readonly"
-            config_muap.grid(row=4, column=4, sticky=(W, E))
-            self.muap_config.set("Configuration")
-            # Disable config for DELSYS files
-            if self.resdict["SOURCE"] == "DELSYS":
-                config_muap.config(state="disabled")
-
-            # Combobox MU Number
-            self.muap_munum = StringVar()
-            muap_munum = ttk.Combobox(self.head, width=15, textvariable=self.muap_munum)
-            mu_numbers = [*range(0, self.resdict["NUMBER_OF_MUS"])]
-            muap_munum["values"] = mu_numbers
-            muap_munum["state"] = "readonly"
-            muap_munum.grid(row=4, column=5, sticky=(W, E))
-            self.muap_munum.set("MU Number")
-
-            # Combobox Timewindow
-            self.muap_time = StringVar()
-            timewindow = ttk.Combobox(self.head, width=15, textvariable=self.muap_time)
-            timewindow["values"] = ("25", "50", "100", "200")
-            timewindow.grid(row=4, column=6, sticky=(W, E))
-            self.muap_time.set("Timewindow (ms)")
-            if self.resdict["SOURCE"] == "DELSYS":
-                timewindow.config(state="disabled")
-
-            # Matrix Illustration Graphic
-            matrix_canvas = Canvas(self.head, height=150, width=600, bg="white")
-            matrix_canvas.grid(row=5, column=3, rowspan=5, columnspan=5)
-            self.matrix = tk.PhotoImage(
-                file=os.path.dirname(os.path.abspath(__file__))
-                + "/gui_files/Matrix.png"
-            )
-            matrix_canvas.create_image(0, 0, anchor="nw", image=self.matrix)
-
-            # Information Button
-            self.info = customtkinter.CTkImage(
-                light_image=Image.open(os.path.dirname(os.path.abspath(__file__)) + "/gui_files/Info.png"),
-                size = (30, 30)
-            )
-            info_button = customtkinter.CTkButton(
-                self.head,
-                image=self.info,
-                text="",
-                width=30,
-                height=30,
-                bg_color="LightBlue4",
-                fg_color="LightBlue4",
-                command=lambda: (
-                    (
-                        webbrowser.open("https://www.giacomovalli.com/openhdemg/gui_basics/#plot-motor-units")
-                    ),
-                ),
-            )
-            info_button.grid(row=0, column=6, sticky=E)
-
-            for child in self.head.winfo_children():
-                child.grid_configure(padx=5, pady=5)
-
-        except AttributeError:
-            tk.messagebox.showerror("Information", "Load file prior to computation.")
-            self.head.destroy()
-
-    ### Define functions for motor unit plotting
-
-    def on_matrix_none(self, *args):
-        """
-        This function is called when the value of the mat_code variable is changed.
-
-        When the variable is set to "None" it will create an Entrybox on the grid at column 1 and row 6,
-        and when the mat_code is set to something else it will remove the entrybox from the grid.
-        """
-        if self.mat_code.get() == "None":
-        
-            self.mat_label = ttk.Label(self.head, text="Rows, Columns:")
-            self.mat_label.grid(row=0, column=5, sticky=E)
-            
-            self.matrix_rc = StringVar()
-            self.row_cols_entry = ttk.Entry(self.head, width=8, textvariable= self.matrix_rc)
-            self.row_cols_entry.grid(row=0, column=6, sticky = W, padx=5)
-            self.matrix_rc.set("13,5")
-        
-        else:
-            if hasattr(self, "row_cols_entry"):
-                self.row_cols_entry.grid_forget()
-                self.mat_label.grid_forget()
-                
-        
-        self.head.update_idletasks()
-
-
-    def plt_emgsignal(self):
-        """
-        Instance method to plot the raw emg signal in an seperate plot window.
-        The channels selected by the user are plotted. The plot can be saved and
-        partly edited using the matplotlib options.
-
-        Executed when button "Plot EMGsig" in Plot Window is pressed.
-
-        Raises
-        ------
-        AttributeError
-            When no file is loaded prior to calculation.
-        ValueError
-            When entered channel number is not valid (inexistent).
-        KeyError
-            When entered channel number is out of bounds.
-
-        See Also
-        --------
-        plot_emgsignal in library.
-        """
-        try:
-            # Create list of channels to be plotted
-            channels = self.channels.get()
-            # Create list of figsize
-            figsize = [int(i) for i in self.size_fig.get().split(",")]
-
-            if len(channels) > 1:
-                chan_list = channels.split(",")
-                chan_list = [int(i) for i in chan_list]
-
-                # Plot raw emg signal
-                openhdemg.plot_emgsig(
-                    emgfile=self.resdict,
-                    channels=chan_list,
-                    addrefsig=eval(self.ref_but.get()),
-                    timeinseconds=eval(self.time_sec.get()),
-                    figsize=figsize,
-                )
-
-            else:
-                # Plot raw emg signal
-                openhdemg.plot_emgsig(
-                    emgfile=self.resdict,
-                    channels=int(channels),
-                    addrefsig=eval(self.ref_but.get()),
-                    timeinseconds=eval(self.time_sec.get()),
-                    figsize=figsize,
-                )
-
-        except ValueError:
-            tk.messagebox.showerror("Information", "Enter valid channel number.")
-
-        except KeyError:
-            tk.messagebox.showerror("Information", "Enter valid channel number.")
-
-    def plt_refsignal(self):
-        """
-        Instance method to plot the reference signal in an seperate plot window.
-        The plot can be saved and partly edited using the matplotlib options.
-
-        Executed when button "Plot REFsig" in Plot Window is pressed.
-
-        Raises
-        ------
-        AttributeError
-            When no file is loaded prior to calculation.
-
-        See Also
-        --------
-        plot_refsig in library.
-        """
-        # Create list of figsize
-        figsize = [int(i) for i in self.size_fig.get().split(",")]
-
-        # Plot reference signal
-        openhdemg.plot_refsig(
-            emgfile=self.resdict,
-            timeinseconds=eval(self.time_sec.get()),
-            figsize=figsize,
-        )
-
-    def plt_mupulses(self):
-        """
-        Instance method to plot the mu pulses in an seperate plot window.
-        The linewidth selected by the user is used. The plot can be saved and
-        partly edited using the matplotlib options.
-
-        Executed when button "Plot MUpulses" in Plot Window is pressed.
-
-        Raises
-        ------
-        AttributeError
-            When no file is loaded prior to calculation.
-        ValueError
-            When entered channel number is not valid (inexistent).
-
-        See Also
-        --------
-        plot_mupulses in library.
-        """
-        try:
-            # Create list of figsize
-            figsize = [int(i) for i in self.size_fig.get().split(",")]
-
-            # Plot motor unig pulses
-            openhdemg.plot_mupulses(
-                emgfile=self.resdict,
-                linewidths=float(self.linewidth.get()),
-                addrefsig=eval(self.ref_but.get()),
-                timeinseconds=eval(self.time_sec.get()),
-                figsize=figsize,
-            )
-
-        except ValueError:
-            tk.messagebox.showerror("Information", "Enter valid linewidth number.")
-
-    def plt_ipts(self):
-        """
-        Instance method to plot the motor unit pulse train in an seperate plot window.
-        The motor units selected by the user are plotted. The plot can be saved and
-        partly edited using the matplotlib options.
-
-        Executed when button "Plot Source" in Plot Window is pressed.
-
-        Raises
-        ------
-        AttributeError
-            When no file is loaded prior to calculation.
-        ValueError
-            When entered motor unit number is not valid (inexistent).
-        KeyError
-            When entered motor number is out of bounds.
-
-        See Also
-        --------
-        plot_ipts in library.
-        """
-        try:
-            # Create list contaning motor units to be plotted
-            mu_numb = self.mu_numb.get()
-            # Create list of figsize
-            figsize = [int(i) for i in self.size_fig.get().split(",")]
-
-            if mu_numb == "all":
-                # Plot motor unit puls train in default
-                openhdemg.plot_ipts(
-                    emgfile=self.resdict,
-                    addrefsig=eval(self.ref_but.get()),
-                    timeinseconds=eval(self.time_sec.get()),
-                    figsize=figsize,
-                )
-
-            elif len(mu_numb) > 2:
-                # Split at ,
-                mu_list = mu_numb.split(",")
-                # Use comprehension to loop troug mu_list
-                mu_list = [int(i) for i in mu_list]
-                # Plot motor unit puls train in default
-                openhdemg.plot_ipts(
-                    emgfile=self.resdict,
-                    munumber=mu_list,
-                    addrefsig=eval(self.ref_but.get()),
-                    timeinseconds=eval(self.time_sec.get()),
-                    figsize=figsize,
-                )
-
-            else:
-                # Plot motor unit puls train in default
-                openhdemg.plot_ipts(
-                    emgfile=self.resdict,
-                    munumber=int(mu_numb),
-                    addrefsig=eval(self.ref_but.get()),
-                    timeinseconds=eval(self.time_sec.get()),
-                    figsize=figsize,
-                )
-
-        except ValueError:
-            tk.messagebox.showerror("Information", "Enter valid motor unit number.")
-
-        except KeyError:
-            tk.messagebox.showerror("Information", "Enter valid motor unit number.")
-
-    def plt_idr(self):
-        """
-        Instance method to plot the instanteous discharge rate in an seperate plot window.
-        The motor units selected by the user are plotted. The plot can be saved and
-        partly edited using the matplotlib options.
-
-        Executed when button "Plot IDR" in Plot Window is pressed.
-
-        Raises
-        ------
-        AttributeError
-            When no file is loaded prior to calculation.
-        ValueError
-            When entered channel number is not valid (inexistent).
-        KeyError
-            When entered channel number is out of bounds.
-
-        See Also
-        --------
-        plot_idr in library.
-        """
-        try:
-            mu_idr = self.mu_numb_idr.get()
-            # Create list of figsize
-            figsize = [int(i) for i in self.size_fig.get().split(",")]
-
-            if mu_idr == "all":
-                # Plot instanteous discharge rate
-                openhdemg.plot_idr(
-                    emgfile=self.resdict,
-                    addrefsig=eval(self.ref_but.get()),
-                    timeinseconds=eval(self.time_sec.get()),
-                    figsize=figsize,
-                )
-
-            elif len(mu_idr) > 2:
-                mu_list_idr = mu_idr.split(",")
-                mu_list_idr = [int(mu) for mu in mu_list_idr]
-                # Plot instanteous discharge rate
-                openhdemg.plot_idr(
-                    emgfile=self.resdict,
-                    munumber=mu_list_idr,
-                    addrefsig=eval(self.ref_but.get()),
-                    timeinseconds=eval(self.time_sec.get()),
-                    figsize=figsize,
-                )
-
-            else:
-                # Plot instanteous discharge rate
-                openhdemg.plot_idr(
-                    emgfile=self.resdict,
-                    munumber=int(mu_idr),
-                    addrefsig=eval(self.ref_but.get()),
-                    timeinseconds=eval(self.time_sec.get()),
-                    figsize=figsize,
-                )
-
-        except ValueError:
-            tk.messagebox.showerror("Information", "Enter valid motor unit number.")
-
-        except KeyError:
-            tk.messagebox.showerror("Information", "Enter valid motor unit number.")
-
-    def plot_derivation(self):
-        """
-        Instance method to plot the differential derivation of the RAW_SIGNAL by matrix column.
-
-        Both the single and the double differencials can be plotted.
-        This function is used to plot also the sorted RAW_SIGNAL.
-        """
-        try:
-            if self.mat_code.get() == "None":
-                # Get rows and columns and turn into list
-                list_rcs = [int(i) for i in self.matrix_rc.get().split(",")]
-
-                try:
-                    # Sort emg file
-                    sorted_file = openhdemg.sort_rawemg(
-                        emgfile=self.resdict,
-                        code=self.mat_code.get(),
-                        orientation=int(self.mat_orientation.get()),
-                        n_rows=list_rcs[0],
-                        n_cols=list_rcs[1]
-                    )
-
-                except ValueError:
-                    tk.messagebox.showerror(
-                        "Information",
-                        "Number of specified rows and columns must match" +
-                        "\nnumber of channels."
-                    )
-                    return
-
-            else:
-                # Sort emg file
-                sorted_file = openhdemg.sort_rawemg(
-                    emgfile=self.resdict,
-                    code=self.mat_code.get(),
-                    orientation=int(self.mat_orientation.get()),
-                )
-
-            # calcualte derivation
-            if self.deriv_config.get() == "Single differential":
-                diff_file = openhdemg.diff(sorted_rawemg=sorted_file)
-
-            elif self.deriv_config.get() == "Double differential":
-                diff_file = openhdemg.double_diff(sorted_rawemg=sorted_file)
-
-            # Create list of figsize
-            figsize = [int(i) for i in self.size_fig.get().split(",")]
-
-            # Plot derivation
-            openhdemg.plot_differentials(
-                emgfile=self.resdict,
-                differential=diff_file,
-                column=self.deriv_matrix.get(),
-                addrefsig=eval(self.ref_but.get()),
-                timeinseconds=eval(self.time_sec.get()),
-                figsize=figsize,
-            )
-        except ValueError:
-            tk.messagebox.showerror(
-                "Information",
-                "Enter valid input parameters."
-                + "\nPotenital error sources:"
-                + "\n - Matrix Code"
-                + "\n - Matrix Orientation"
-                + "\n - Figure size arguments"
-                + "\n - Rows, Columns arguments",
-            )
-        except UnboundLocalError:
-            tk.messagebox.showerror(
-                "Information", "Enter valid Configuration and Matrix Column."
-            )
-
-        except KeyError:
-            tk.messagebox.showerror("Information", "Enter valid Matrix Column.")
-
-    def plot_muaps(self):
-        """
-        Instance methos to plot motor unit action potenital obtained from STA from one or
-        multiple MUs. Except for DELSYS files, where the STA is not comupted.
-
-        There is no limit to the number of MUs and STA files that can be overplotted.
-        ``Remember: the different STAs should be matched`` with same number of electrode,
-        processing (i.e., differential) and computed on the same timewindow.
-        """
-        try:
-            # DELSYS requires different MUAPS plot
-            if self.resdict["SOURCE"] == "DELSYS":
-                figsize = [int(i) for i in self.size_fig.get().split(",")]
-                muaps_dict = openhdemg.extract_delsys_muaps(self.resdict)
-                openhdemg.plot_muaps(muaps_dict[int(self.muap_munum.get())], figsize=figsize)
-            
-            else:
-                if self.mat_code.get() == "None":
-                    # Get rows and columns and turn into list
-                    list_rcs = [int(i) for i in self.matrix_rc.get().split(",")]
-
-                    try:
-                        # Sort emg file
-                        sorted_file = openhdemg.sort_rawemg(
-                            emgfile=self.resdict,
-                            code=self.mat_code.get(),
-                            orientation=int(self.mat_orientation.get()),
-                            n_rows=list_rcs[0],
-                            n_cols=list_rcs[1]
-                        )
-
-                    except ValueError:
-                        tk.messagebox.showerror(
-                                "Information",
-                                "Number of specified rows and columns must match"
-                                + "\nnumber of channels."
-                        )
-                        return
-                    
-                else:
-                    # Sort emg file
-                    sorted_file = openhdemg.sort_rawemg(
-                        emgfile=self.resdict,
-                        code=self.mat_code.get(),
-                        orientation=int(self.mat_orientation.get()),
-                    )
-
-                # calcualte derivation
-                if self.muap_config.get() == "Single differential":
-                    diff_file = openhdemg.diff(sorted_rawemg=sorted_file)
-
-                elif self.muap_config.get() == "Double differential":
-                    diff_file = openhdemg.double_diff(sorted_rawemg=sorted_file)
-
-                elif self.muap_config.get() == "Monopolar":
-                    diff_file = sorted_file
-
-                # Calculate STA dictionary
-                # Plot deviation
-                sta_dict = openhdemg.sta(
-                    emgfile=self.resdict,
-                    sorted_rawemg=diff_file,
-                    firings="all",
-                    timewindow=int(self.muap_time.get()),
-                )
-
-                # Create list of figsize
-                figsize = [int(i) for i in self.size_fig.get().split(",")]
-
-                # Plot MUAPS
-                openhdemg.plot_muaps(sta_dict[int(self.muap_munum.get())], figsize=figsize)
-
-        except ValueError:
-            tk.messagebox.showerror(
-                "Information",
-                "Enter valid input parameters."
-                + "\nPotenital error sources:"
-                + "\n - Matrix Code"
-                + "\n - Matrix Orientation"
-                + "\n - Figure size arguments"
-                + "\n - Timewindow"
-                + "\n - MU Number"
-                + "\n - Rows, Columns arguments",
-            )
-
-        except UnboundLocalError:
-            tk.messagebox.showerror("Information", "Enter valid Configuration.")
-
-        except KeyError:
-            tk.messagebox.showerror("Information", "Enter valid Matrix Column.")
-
-    # -----------------------------------------------------------------------------------------------
-    # Advanced Analysis
-
-    def advanced_analysis(self):
-        """
-        Open top-level windows based on the selected advanced method.
-        """
-        
-
-        if self.advanced_method.get() == "Motor Unit Tracking":
-            head_title = "MUs Tracking Window"
-        elif self.advanced_method.get() == "Conduction Velocity": 
-            head_title = "Conduction Velocity Window"
-        else:
-            head_title = "Duplicate Removal Window"
-        
-
-        self.head = tk.Toplevel(bg="LightBlue4")
-        self.head.title(head_title)
-        self.head.iconbitmap(
-            os.path.dirname(os.path.abspath(__file__)) + "/gui_files/Icon.ico"
-        )
-        self.head.grab_set()
-
-        # Specify Signal
-        self.filetype_adv = StringVar()
-        signal_value = ("OTB", "DEMUSE", "OPENHDEMG", "CUSTOMCSV")
-        signal_entry = ttk.Combobox(
-            self.head, text="Signal", width=8, textvariable=self.filetype_adv
-        )
-        signal_entry["values"] = signal_value
-        signal_entry["state"] = "readonly"
-        signal_entry.grid(column=0, row=1, sticky=(W, E))
-        self.filetype_adv.set("Type of file")
-        self.filetype_adv.trace("w", self.on_filetype_change_adv)
-
-        # Load file
-        load1 = ttk.Button(self.head, text="Load File 1", command=self.open_emgfile1)
-        load1.grid(column=0, row=2, sticky=(W, E))
-
-        # Load file
-        load2 = ttk.Button(self.head, text="Load File 2", command=self.open_emgfile2)
-        load2.grid(column=0, row=3, sticky=(W, E))
-
-        # Threshold label
-        threshold_label = ttk.Label(self.head, text="Threshold:")
-        threshold_label.grid(column=0, row=9)
-
-        # Combobox for threshold
-        self.threshold_adv = StringVar()
-        threshold_combobox = ttk.Combobox(
-            self.head,
-            values=[0.6, 0.7, 0.8, 0.9],
-            textvariable=self.threshold_adv,
-            state="readonly",
-            width=8,
-        )
-        threshold_combobox.grid(column=1, row=9)
-        self.threshold_adv.set(0.8)
-
-        # Time Label
-        time_window_label = ttk.Label(self.head, text="Time window:")
-        time_window_label.grid(column=0, row=10)
-
-        # Time Combobox
-        self.time_window = StringVar()
-        time_combobox = ttk.Combobox(
-            self.head,
-            values=[25, 50],
-            textvariable=self.time_window,
-            state="readonly",
-            width=8,
-        )
-        time_combobox.grid(column=1, row=10)
-        self.time_window.set(25)
-
-        # Exclude below threshold
-        exclude_label = ttk.Label(self.head, text="Exclude below threshold")
-        exclude_label.grid(column=0, row=11)
-
-        # Add exclude checkbox
-        self.exclude_thres = tk.BooleanVar()
-        exclude_checkbox = tk.Checkbutton(
-            self.head, variable=self.exclude_thres, bg="LightBlue4"
-        )
-        exclude_checkbox.grid(column=1, row=11)
-        self.exclude_thres.set(True)
-
-        # Filter
-        filter_label = ttk.Label(self.head, text="Filter")
-        filter_label.grid(column=0, row=12)
-
-        # Add filter checkbox
-        self.filter_adv = tk.BooleanVar()
-        filter_checkbox = tk.Checkbutton(
-            self.head, variable=self.filter_adv, bg="LightBlue4"
-        )
-        filter_checkbox.grid(column=1, row=12)
-        self.filter_adv.set(True)
-
-        # Exclude below threshold
-        show_label = ttk.Label(self.head, text="Show")
-        show_label.grid(column=0, row=13)
-
-        # Add exclude checkbox
-        self.show_adv = tk.BooleanVar()
-        show_checkbox = tk.Checkbutton(
-            self.head, variable=self.show_adv, bg="LightBlue4"
-        )
-        show_checkbox.grid(column=1, row=13)
-
-        # Add button to execute MU tracking
-        track_button = ttk.Button(self.head, text="Track", command=self.track_mus)
-        track_button.grid(column=0, row=15, columnspan=2, sticky=(W, E))
-
-        # Add padding
-        for child in self.head.winfo_children():
-            child.grid_configure(padx=5, pady=5)
-
-        # Add Which widget and update the track button
-        # to match functionalities required for duplicate removal
-        if self.advanced_method.get() == "Duplicate Removal":
-
-            # Add Which label
-            ttk.Label(self.head, text="Which").grid(column=0, row=14)
-            # Combobox for Which option
-            self.which_adv = StringVar()
-            which_combobox = ttk.Combobox(
-                self.head,
-                values=["munumber", "accuracy"],
-                textvariable=self.which_adv,
-                state="readonly",
-                width=8,
-            )
-            which_combobox.grid(row=14, column=1, padx=5, pady=5)
-            self.which_adv.set("munumber")
-
-            # Add button to execute MU tracking
-            track_button.config(
-                text="Remove Duplicates", command=self.remove_duplicates_between
-            )
-
-        if self.advanced_method.get() == "Conduction Velocity":
-            try:
-                # Destroy unnecessary pop-ups
-                self.head.destroy()
-                self.a_window.destroy()
-
-                if self.mat_code_adv.get() == "None":
-
-                    # Get rows and columns and turn into list
-                    list_rcs = [int(i) for i in self.matrix_rc_adv.get().split(",")]
-
-                    try:
-                        # Sort emg file
-                        sorted_rawemg = openhdemg.sort_rawemg(
-                            self.resdict,
-                            code=self.mat_code_adv.get(),
-                            orientation=int(self.mat_orientation_adv.get()),
-                            n_rows=list_rcs[0],
-                            n_cols=list_rcs[1]
-                        )
-                    except ValueError:
-                        tk.messagebox.showerror(
-                            "Information",
-                            "Number of specified rows and columns must match"
-                            + "\nnumber of channels."
-                        )
-                        return
-                # # DELSYS conduction velocity not available
-                # elif self.mat_code_adv.get() == "Trigno Galileo Sensor":
-                #     tk.messagebox.showerror(
-                #         "Information",
-                #         "MUs conduction velocity estimation is not available for this matrix."
-                #         )
-                #     return
-                
-                else:
-                    # Sort emg file
-                    sorted_rawemg = openhdemg.sort_rawemg(
-                        self.resdict,
-                        code=self.mat_code_adv.get(),
-                        orientation=int(self.mat_orientation_adv.get()),
-                    )
-
-                openhdemg.MUcv_gui(
-                    emgfile=self.resdict,
-                    sorted_rawemg=sorted_rawemg,
-                )
-
-            except AttributeError:
-                tk.messagebox.showerror(
-                    "Information",
-                    "Please make sure to load a file"
-                    + "prior to Conduction velocity calculation.",
-                )
-                self.head.destroy()
-            
-            except ValueError:
-                tk.messagebox.showerror(
-                    "Information",
-                    "Please make sure to enter valid Rows, Columns arguments."
-                    + "\nArguments must be non-negative and seperated by `,`.",
-                )
-                self.head.destroy()
-
-
-        # Destroy first window to avoid too many pop-ups
-        self.a_window.destroy()
-
-    ### Define function for advanced analysis tools
-    def open_emgfile1(self):
-        """
-        Open EMG file based on the selected file type and extension factor.
-
-        This function is used to open and store the first emgfile that is
-        required for the MU tracking. As both files required are loaded by
-        different buttons, two functions storing the two files were created.
-
-        See Also
-        --------
-        open_emgfile1(), openhdemg.askopenfile()
-        """
-        try: 
-            # Open OTB file
-            if self.filetype_adv.get() == "OTB":
-                self.emgfile1 = openhdemg.askopenfile(
-                    filesource=self.filetype_adv.get(),
-                    otb_ext_factor=int(self.extension_factor_adv.get()),
-                )
-            # Open all other filetypes
-            else:
-                self.emgfile1 = openhdemg.askopenfile(
-                    filesource=self.filetype_adv.get(),
-                )
-
-            # Add filename to GUI
-            ttk.Label(self.head, text="File 1 loaded").grid(column=1, row=2)
-
-        except ValueError:
-            tk.messagebox.showerror(
-                "Information",
-                "Make sure to specify a valid extension factor.",
-            )
-            
-    def open_emgfile2(self):
-        """
-        Open EMG file based on the selected file type and extension factor.
-
-        This function is used to open and store the first emgfile that is
-        required for the MU tracking. As both files required are loaded by
-        different buttons, two functions storing the two files were created.
-
-        See Also
-        --------
-        open_emgfile1(), openhdemg.askopenfile()
-        """
-        # Open OTB file
-        if self.filetype_adv.get() == "OTB":
-            self.emgfile2 = openhdemg.askopenfile(
-                filesource=self.filetype_adv.get(),
-                otb_ext_factor=int(self.extension_factor_adv.get()),
-            )
-        # Open all other filetypes
-        else:
-            self.emgfile2 = openhdemg.askopenfile(
-                filesource=self.filetype_adv.get(),
-            )
-
-        # Add filename to GUI
-        ttk.Label(self.head, text="File 2 loaded").grid(column=1, row=3)
-
-    def on_filetype_change_adv(self, *args):
-        """
-        This function is called when the value of the filetype variable is changed.
-        When the filetype is set to "OTB" it will create a second combobox on the grid at column 0 and row 2,
-        and when the filetype is set to something else it will remove the second combobox from the grid.
-        """
-        # Add a combobox containing the OTB extension factors
-        # in case an OTB file is loaded
-        if self.filetype_adv.get() == "OTB":
-            self.extension_factor_adv = StringVar()
-            self.otb_combobox = ttk.Combobox(
-                self.head,
-                values=["8", "9", "10", "11", "12", "13", "14", "15", "16"],
-                width=8,
-                textvariable=self.extension_factor_adv,
-                state="readonly",
-            )
-            self.otb_combobox.grid(column=1, row=1, sticky=(W, E), padx=5)
-            self.otb_combobox.set("Extension Factor")
-
-        # Forget the widget in case the filetype is changed
-        else:
-            if hasattr(self, "otb_combobox"):
-                self.otb_combobox.grid_forget()
-
-    def track_mus(self):
-        """
-        Perform MUs tracking on the loaded EMG files.
-
-        Notes
-        -----
-        The function uses the openhdemg.tracking
-        function to perform the tracking of MUs.
-
-        Raises
-        ------
-        AttributeError
-            If the required EMG files have not been loaded.
-        ValueError
-            If the input parameters are not valid.
-
-        See Also
-        --------
-        openhdemg.tracking()
-        """
-        try:
-            if self.mat_code_adv.get() == "None":
-                # Get rows and columns and turn into list
-                list_rcs = [int(i) for i in self.matrix_rc_adv.get().split(",")]
-                n_rows = list_rcs[0]
-                n_cols = list_rcs[1]
-            else:
-                n_rows = None
-                n_cols = None
-        except ValueError:
-            tk.messagebox.showerror(
-                "Information",
-                "Verify that Rows and Columns are separated by ','",
-            )
-
-        try:
-            # Track motor units
-            tracking_res = openhdemg.tracking(
-                emgfile1=self.emgfile1,
-                emgfile2=self.emgfile2,
-                threshold=float(self.threshold_adv.get()),
-                timewindow=int(self.time_window.get()),
-                matrixcode=self.mat_code_adv.get(),
-                orientation=int(self.mat_orientation_adv.get()),
-                n_rows=n_rows,
-                n_cols=n_cols,
-                exclude_belowthreshold=self.exclude_thres.get(),
-                filter=self.filter_adv.get(),
-                show=self.show_adv.get(),
-            )
-
-            # Add result terminal
-            track_terminal = ttk.LabelFrame(
-                self.head, text="MUs Tracking Result", height=100, relief="ridge"
-            )
-            track_terminal.grid(
-                column=2,
-                row=0,
-                columnspan=2,
-                rowspan=12,
-                pady=8,
-                padx=10,
-                sticky=(N, S, W, E),
-            )
-
-            # Add table containing results to the label frame
-            track_table = Table(track_terminal, dataframe=tracking_res)
-            track_table.show()
-
-        except AttributeError:
-            tk.messagebox.showerror(
-                "Information",
-                "Make sure to load all required EMG files prior to tracking.",
-            )
-
-        except ValueError:
-            tk.messagebox.showerror(
-                "Information",
-                "Enter valid input parameters."
-                + "\nPotenital error sources:"
-                + "\n - Extension Factor (in case of OTB file)"
-                + "\n - Matrix Code"
-                + "\n - Matrix Orientation"
-                + "\n - Threshold"
-                + "\n - Rows, Columns",
-            )
-
-    def remove_duplicates_between(self):
-        """
-        Perform duplicate removal between two EMG files.
-
-        Notes
-        -----
-        The function uses the openhdemg.remove_duplicates_between function to remove duplicates between two EMG files.
-        If the required parameters are not provided, the function will raise an AttributeError or ValueError.
-
-        Raises
-        ------
-        AttributeError
-            If the required EMG files have not been loaded.
-        ValueError
-            If the input parameters are not valid.
-
-        See Also
-        --------
-        openhdemg.remove_duplicates_between(), openhdemg.asksavefile()
-        """
-        try:
-            if self.mat_code_adv.get() == "None":
-                # Get rows and columns and turn into list
-                list_rcs = [int(i) for i in self.matrix_rc_adv.get().split(",")]
-                n_rows = list_rcs[0]
-                n_cols = list_rcs[1]
-            else:
-                n_rows = None
-                n_cols = None
-        except ValueError:
-            tk.messagebox.showerror(
-                "Information",
-                "Verify that Rows and Columns are separated by ','",
-            )
-
-        try:
-            # Remove motor unit duplicates
-            emg_file1, emg_file2, _ = openhdemg.remove_duplicates_between(
-                emgfile1=self.emgfile1,
-                emgfile2=self.emgfile2,
-                threshold=float(self.threshold_adv.get()),
-                timewindow=int(self.time_window.get()),
-                matrixcode=self.mat_code_adv.get(),
-                orientation=int(self.mat_orientation_adv.get()),
-                n_rows=n_rows,
-                n_cols=n_cols,
-                filter=self.filter_adv.get(),
-                show=self.show_adv.get(),
-                which=self.which_adv.get(),
-            )
-
-            # Save files
-            openhdemg.asksavefile(emg_file1)
-            openhdemg.asksavefile(emg_file2)
-
-        except AttributeError:
-            tk.messagebox.showerror(
-                "Information",
-                "Make sure to load all required EMG files prior to tracking.",
-            )
-
-        except ValueError:
-            tk.messagebox.showerror(
-                "Information",
-                "Enter valid input parameters."
-                + "\nPotenital error sources:"
-                + "\n - Extension Factor (in case of OTB file)"
-                + "\n - Matrix Code"
-                + "\n - Matrix Orientation"
-                + "\n - Threshold"
-                + "\n - Which"
-                + "\n - Rows, Columns",
-            )
-
-    def calculate_conduct_vel(self):
-        # Add result terminal
-        track_terminal = ttk.LabelFrame(
-            self.head, text="Conduction Velocity", height=100, relief="ridge"
-        )
-        track_terminal.grid(
-            column=2,
-            row=0,
-            columnspan=2,
-            rowspan=12,
-            pady=8,
-            padx=10,
-            sticky=(N, S, W, E),
-        )
-
-    # -----------------------------------------------------------------------------------------------
-    # Analysis results display
-
-    def display_results(self, input_df):
-        """
-        Instance method that displays all analysis results in the
-        output terminal using Pandastable. Input must be a Pandas dataframe.
-
-        Executed trough functions with calculated anylsis results.
+        Executed trough functions with calculated anylsis results.
 
         Parameters
         ----------
         input_df : pd.DataFrame
             Dataftame containing the analysis results.
         """
-        # Create frame for output
-        self.terminal = ttk.LabelFrame(
-            root, text="Result Output", height=100, relief="ridge"
-        )
-        self.terminal.grid(
-            column=0, row=21, columnspan=2, pady=8, padx=10, sticky=(N, S, W, E)
-        )
 
         # Display results
+        # Clear/recreate frame for output
+        if hasattr(self, "terminal"):
+            self.terminal = ttk.LabelFrame(
+                self, text="Result Output", height=150, relief="ridge",
+            )
+            self.terminal.grid(
+                column=0,
+                row=1,
+                columnspan=2,
+                pady=5,
+                padx=5,
+                sticky=(N, S, W, E),
+            )  # Repeat original settings in init
+
         table = Table(
             self.terminal,
             dataframe=input_df,
@@ -3315,23 +1262,23 @@ def display_results(self, input_df):
         )
 
         # Resize column width
-        options = {"cellwidth": 10}
-        config.apply_options(options, table)
+        """ options = {"cellwidth": 10}
+        config.apply_options(options, table) """
 
         # Show results
         table.show()
 
 
-# -----------------------------------------------------------------------------------------------
+# ----------------------------------------------------------------------------------------------
 def run_main():
     # Run GUI upon calling
     if __name__ == "__main__":
-        root = Tk()
-        emgGUI(root)
-        root.mainloop()
+        app = emgGUI()
+        app._state_before_windows_set_titlebar_color = "zoomed"
+        app.mainloop()
 
 
 if __name__ == "__main__":
-    root = Tk()
-    emgGUI(root)
-    root.mainloop()
+    app = emgGUI()
+    app._state_before_windows_set_titlebar_color = "zoomed"
+    app.mainloop()
diff --git a/openhdemg/gui/settings.py b/openhdemg/gui/settings.py
new file mode 100644
index 0000000..fb335ea
--- /dev/null
+++ b/openhdemg/gui/settings.py
@@ -0,0 +1,102 @@
+"""
+Module docstring explaining how to change the GUI settings.
+
+The settings can be related to both the GUI appearence and the analyses
+functions. Parameters for the analyses functions are clustered based on the
+openhdemg library's modules (here described as #----- MODULE NAME -----) and
+can be better known from the API section of the openhdemg website.
+
+Each parameter for the analyses functions is named FunctionName__Parameter.
+An extensive explanation of each "Parameter" can be found in the specific
+API module and in the specific "FunctionName".
+
+A tutorial on how to use this settings file is available at:
+https://www.giacomovalli.com/openhdemg/gui_settings/
+
+If you mess up with the settings, you can restore the default ones from
+backup_settings.py
+"""
+
+import numpy as np
+
+# --------------------------------- openfiles ---------------------------------
+
+# in emg_from_demuse()
+emg_from_demuse__ignore_negative_ipts = False
+
+# in emg_from_otb()
+emg_from_otb__ext_factor = 8
+emg_from_otb__refsig = [True, "fullsampled"]
+emg_from_otb__extras = None
+emg_from_otb__ignore_negative_ipts = False
+
+# in refsig_from_otb()
+refsig_from_otb__refsig = "fullsampled"
+refsig_from_otb__extras = None
+
+# in emg_from_delsys()
+emg_from_delsys__emg_sensor_name = "Galileo sensor"
+emg_from_delsys__refsig_sensor_name = "Trigno Load Cell"
+emg_from_delsys__filename_from = "mus_directory"
+
+# in refsig_from_delsys()
+refsig_from_delsys__refsig_sensor_name = "Trigno Load Cell"
+
+# in emg_from_customcsv()
+emg_from_customcsv__ref_signal = "REF_SIGNAL"
+emg_from_customcsv__raw_signal = "RAW_SIGNAL"
+emg_from_customcsv__ipts = "IPTS"
+emg_from_customcsv__mupulses = "MUPULSES"
+emg_from_customcsv__binary_mus_firing = "BINARY_MUS_FIRING"
+emg_from_customcsv__accuracy = "ACCURACY"
+emg_from_customcsv__extras = "EXTRAS"
+emg_from_customcsv__fsamp = 2048
+emg_from_customcsv__ied = 8
+
+# in refsig_from_customcsv()
+refsig_from_customcsv__ref_signal = "REF_SIGNAL"
+refsig_from_customcsv__extras = "EXTRAS"
+refsig_from_customcsv__fsamp = 2048
+
+# in save_json_emgfile()
+save_json_emgfile__compresslevel = 4
+
+
+# ---------------------------------- analysis ---------------------------------
+
+# in compute_thresholds()
+compute_thresholds__n_firings = 1
+
+# in basic_mus_properties()
+basic_mus_properties__n_firings_rt_dert = 1
+basic_mus_properties__accuracy = "default"
+basic_mus_properties__ignore_negative_ipts = False
+basic_mus_properties__constrain_pulses = [True, 3]
+
+
+# ----------------------------------- tools -----------------------------------
+
+# in resize_emgfile()
+resize_emgfile__how = "ref_signal"
+resize_emgfile__accuracy = "recalculate"
+resize_emgfile__ignore_negative_ipts = False
+
+
+# ------------------------------------ muap -----------------------------------
+# in tracking()
+tracking__firings = "all"
+tracking__derivation = "sd"
+
+# in remove_duplicates_between()
+remove_duplicates_between__firings = "all"
+remove_duplicates_between__derivation = "sd"
+
+# in MUcv_gui()
+MUcv_gui__n_firings = [0, 50]
+MUcv_gui__muaps_timewindow = 50
+MUcv_gui__figsize = [25, 20]
+
+# --------------------------------- electrodes --------------------------------
+# This custom sorting order is valid for all the GUI windows, although the
+# documentation is accessible in the api of the electrodes module.
+custom_sorting_order = None
diff --git a/openhdemg/library/analysis.py b/openhdemg/library/analysis.py
index dd6edd9..d8f297f 100644
--- a/openhdemg/library/analysis.py
+++ b/openhdemg/library/analysis.py
@@ -32,8 +32,10 @@ def compute_thresholds(
 
         ``rt_dert``
             Both recruitment and derecruitment tresholds will be calculated.
+
         ``rt``
             Only recruitment tresholds will be calculated.
+
         ``dert``
             Only derecruitment tresholds will be calculated.
     type_ : str {"abs_rel", "rel", "abs"}, default "abs_rel"
@@ -41,8 +43,10 @@ def compute_thresholds(
 
         ``abs_rel``
             Both absolute and relative tresholds will be calculated.
+
         ``rel``
             Only relative tresholds will be calculated.
+
         ``abs``
             Only absolute tresholds will be calculated.
     n_firings : int, default 1
@@ -214,17 +218,21 @@ def compute_dr(
     event_ : str {"rec_derec_steady", "rec", "derec", "rec_derec", "steady"}, default "rec_derec_steady"
         When to calculate the DR.
 
-            ``rec_derec_steady``
-                DR is calculated at recruitment, derecruitment and during the
-                steady-state phase.
-            ``rec``
-                DR is calculated at recruitment.
-            ``derec``
-                DR is calculated at derecruitment.
-            ``rec_derec``
-                DR is calculated at recruitment and derecruitment.
-            ``steady``
-                DR is calculated during the steady-state phase.
+        ``rec_derec_steady``
+            DR is calculated at recruitment, derecruitment and during the
+            steady-state phase.
+
+        ``rec``
+            DR is calculated at recruitment.
+
+        ``derec``
+            DR is calculated at derecruitment.
+
+        ``rec_derec``
+            DR is calculated at recruitment and derecruitment.
+
+        ``steady``
+            DR is calculated during the steady-state phase.
 
     Returns
     -------
@@ -317,11 +325,16 @@ def compute_dr(
     idr = compute_idr(emgfile=emgfile)
 
     # Check if we need to manually select the area for the steady-state phase
+    title = (
+        "Select the start/end area of the steady-state by hovering the mouse" +
+        "\nand pressing the 'a'-key. Wrong points can be removed with right " +
+        "\nclick or canc/delete key. When ready, press enter."
+    )
     if event_ == "rec_derec_steady" or event_ == "steady":
         if (start_steady < 0 and end_steady < 0) or (start_steady < 0 or end_steady < 0):
             points = showselect(
                 emgfile,
-                title="Select the start/end area of the steady-state by hovering the mouse\nand pressing the 'a'-key. Wrong points can be removed with right click.\nWhen ready, press enter.",
+                title=title,
                 titlesize=10,
             )
             start_steady, end_steady = points[0], points[1]
@@ -445,6 +458,8 @@ def basic_mus_properties(
     start_steady=-1,
     end_steady=-1,
     accuracy="default",
+    ignore_negative_ipts=False,
+    constrain_pulses=[True, 3],
     mvc=0,
 ):
     """
@@ -487,13 +502,30 @@ def basic_mus_properties(
         ``default``
             The original accuracy measure already contained in the emgfile is
             returned without any computation.
+
         ``SIL``
             The Silhouette score is computed.
+
         ``PNR``
             The pulse to noise ratio is computed.
+
         ``SIL_PNR``
             Both the Silhouette score and the pulse to noise ratio are
             computed.
+    ignore_negative_ipts : bool, default False
+        This parameter determines the silhouette score estimation. If True,
+        only positive ipts values are used during peak and noise clustering.
+        This is particularly important for compensating sources with large
+        negative components. This parameter is considered only when
+        accuracy=="SIL" or accuracy=="SIL_PNR".
+    constrain_pulses : list, default [True, 3]
+        This parameter determines the PNR estimation. If
+        constrain_pulses[0]==True, the times of firing are considered those in
+        mupulses +- the number of samples specified in constrain_pulses[1].
+        If constrain_pulses[0]==False, the times of firing are estimated via
+        a heuristic penalty funtion (see Notes in compute_pnr()).
+        constrain_pulses[1] must be an integer (see Notes in compute_pnr() for
+        instructions on how to set the appropriate value).
     mvc : float, default 0
         The maximum voluntary contraction (MVC). It is suggest to report
         MVC in Newton (N). If 0 (default), the user will be asked to imput it
@@ -548,10 +580,15 @@ def basic_mus_properties(
     # TODO make new examples, also with accuracy
 
     # Check if we need to select the steady-state phase
+    title = (
+        "Select the start/end area of the steady-state by hovering the mouse" +
+        "\nand pressing the 'a'-key. Wrong points can be removed with right " +
+        "\nclick or canc/delete key. When ready, press enter."
+    )
     if (start_steady < 0 and end_steady < 0) or (start_steady < 0 or end_steady < 0):
         points = showselect(
             emgfile,
-            title="Select the start/end area of the steady-state by hovering the mouse\nand pressing the 'a'-key. Wrong points can be removed with right click.\nWhen ready, press enter.",
+            title=title,
             titlesize=10,
         )
         start_steady, end_steady = points[0], points[1]
@@ -604,6 +641,7 @@ def basic_mus_properties(
             sil = compute_sil(
                 ipts=emgfile["IPTS"][mu],
                 mupulses=emgfile["MUPULSES"][mu],
+                ignore_negative_ipts=ignore_negative_ipts,
             )
             toappend.append({"SIL": sil})
         toappend = pd.DataFrame(toappend)
@@ -623,6 +661,7 @@ def basic_mus_properties(
                 ipts=emgfile["IPTS"][mu],
                 mupulses=emgfile["MUPULSES"][mu],
                 fsamp=emgfile["FSAMP"],
+                constrain_pulses=constrain_pulses,
             )
             toappend.append({"PNR": pnr})
         toappend = pd.DataFrame(toappend)
@@ -641,6 +680,7 @@ def basic_mus_properties(
             sil = compute_sil(
                 ipts=emgfile["IPTS"][mu],
                 mupulses=emgfile["MUPULSES"][mu],
+                ignore_negative_ipts=ignore_negative_ipts,
             )
             toappend.append({"SIL": sil})
         toappend = pd.DataFrame(toappend)
@@ -659,6 +699,7 @@ def basic_mus_properties(
                 ipts=emgfile["IPTS"][mu],
                 mupulses=emgfile["MUPULSES"][mu],
                 fsamp=emgfile["FSAMP"],
+                constrain_pulses=constrain_pulses,
             )
             toappend.append({"PNR": pnr})
         toappend = pd.DataFrame(toappend)
@@ -744,17 +785,21 @@ def compute_covisi(
     event_ : str {"rec_derec_steady", "rec", "derec", "rec_derec", "steady"}, default "rec_derec_steady"
         When to calculate the COVisi.
 
-            ``rec_derec_steady``
-                covisi is calculated at recruitment, derecruitment and during
-                the steady-state phase.
-            ``rec``
-                covisi is calculated at recruitment.
-            ``derec``
-                covisi is calculated at derecruitment.
-            ``rec_derec``
-                covisi is calculated at recruitment and derecruitment.
-            ``steady``
-                covisi is calculated during the steady-state phase.
+        ``rec_derec_steady``
+            covisi is calculated at recruitment, derecruitment and during
+            the steady-state phase.
+
+        ``rec``
+            covisi is calculated at recruitment.
+
+        ``derec``
+            covisi is calculated at derecruitment.
+
+        ``rec_derec``
+            covisi is calculated at recruitment and derecruitment.
+
+        ``steady``
+            covisi is calculated during the steady-state phase.
     single_mu_number : int, default -1
         Number of the specific MU to compute the COVisi.
         If single_mu_number >= 0, only the COVisi of the entire contraction
@@ -844,10 +889,15 @@ def compute_covisi(
         # Check if we need to manually select the area for the steady-state
         # phase.
         if event_ == "rec_derec_steady" or event_ == "steady":
+            title = (
+                "Select the start/end area of the steady-state by hovering the mouse" +
+                "\nand pressing the 'a'-key. Wrong points can be removed with right " +
+                "\nclick or canc/delete key. When ready, press enter."
+            )
             if (start_steady < 0 and end_steady < 0) or (start_steady < 0 or end_steady < 0):
                 points = showselect(
                     emgfile,
-                    title="Select the start/end area of the steady-state by hovering the mouse\nand pressing the 'a'-key. Wrong points can be removed with right click.\nWhen ready, press enter.",
+                    title=title,
                     titlesize=10,
                 )
                 start_steady, end_steady = points[0], points[1]
@@ -953,17 +1003,21 @@ def compute_drvariability(
     event_ : str {"rec_derec_steady", "rec", "derec", "rec_derec", "steady"}, default "rec_derec_steady"
         When to calculate the DR variability.
 
-            ``rec_derec_steady``
-                DR variability is calculated at recruitment, derecruitment and
-                during the steady-state phase.
-            ``rec``
-                DR variability is calculated at recruitment.
-            ``derec``
-                DR variability is calculated at derecruitment.
-            ``rec_derec``
-                DR variability is calculated at recruitment and derecruitment.
-            ``steady``
-                DR variability is calculated during the steady-state phase.
+        ``rec_derec_steady``
+            DR variability is calculated at recruitment, derecruitment and
+            during the steady-state phase.
+
+        ``rec``
+            DR variability is calculated at recruitment.
+
+        ``derec``
+            DR variability is calculated at derecruitment.
+
+        ``rec_derec``
+            DR variability is calculated at recruitment and derecruitment.
+
+        ``steady``
+            DR variability is calculated during the steady-state phase.
 
     Returns
     -------
@@ -1038,10 +1092,15 @@ def compute_drvariability(
 
     # Check if we need to manually select the area for the steady-state phase
     if event_ == "rec_derec_steady" or event_ == "steady":
+        title = (
+            "Select the start/end area of the steady-state by hovering the mouse" +
+            "\nand pressing the 'a'-key. Wrong points can be removed with right " +
+            "\nclick or canc/delete key. When ready, press enter."
+        )
         if (start_steady < 0 and end_steady < 0) or (start_steady < 0 or end_steady < 0):
             points = showselect(
                 emgfile,
-                title="Select the start/end area of the steady-state by hovering the mouse\nand pressing the 'a'-key. Wrong points can be removed with right click.\nWhen ready, press enter.",
+                title=title,
                 titlesize=10,
             )
             start_steady, end_steady = points[0], points[1]
diff --git a/openhdemg/library/electrodes.py b/openhdemg/library/electrodes.py
index 899b8c6..f565ca1 100644
--- a/openhdemg/library/electrodes.py
+++ b/openhdemg/library/electrodes.py
@@ -6,6 +6,7 @@
 
 import numpy as np
 import copy
+import itertools
 
 OTBelectrodes_tuple = (
     "GR04MM1305",
@@ -143,6 +144,7 @@ def sort_rawemg(
     dividebycolumn=True,
     n_rows=None,
     n_cols=None,
+    custom_sorting_order=None,
 ):
     """
     Sort RAW_SIGNAL based on matrix type and orientation.
@@ -150,10 +152,11 @@ def sort_rawemg(
     To date, built-in sorting functions have been implemented for the matrices:
 
         Code                    (Orientation)
-        GR08MM1305              (0, 180),
-        GR04MM1305              (0, 180),
-        GR10MM0808              (0, 180),
-        Trigno Galileo Sensor   (na).
+        GR08MM1305              (0, 180)
+        GR04MM1305              (0, 180)
+        GR10MM0808              (0, 180)
+        Trigno Galileo Sensor   (na)
+        Custom order            (any)
 
     Parameters
     ----------
@@ -163,32 +166,52 @@ def sort_rawemg(
         The code of the matrix used. It can be one of:
 
         ``GR08MM1305``
+
         ``GR04MM1305``
+
         ``GR10MM0808``
+
         ``Trigno Galileo Sensor``
+
+        ``Custom order``
+
+        ``None``
+
         If "None", the electodes are not sorted but n_rows and n_cols must be
         specified when dividebycolumn == True.
+        If "Custom order", the electrodes are sorted based on
+        custom_sorting_order.
     orientation : int {0, 180}, default 180
         Orientation in degree of the matrix.
         E.g. 180 corresponds to the matrix connection toward the researcher or
         the ground (depending on the limb).
         Ignore if using the "Trigno Galileo Sensor". In this case, channels
         will be oriented as in the Delsys Neuromap Explorer software.
+        This Parameter is ignored if code=="Custom order" or code=="None".
     dividebycolumn = bool, default True
         Whether to return the sorted channels classified by matrix column.
     n_rows : None or int, default None
         The number of rows of the matrix. This parameter is used to divide the
-        channels based on the matrix shape. These are normally inferred by the
-        matrix code and must be specified only if code == None.
+        channels based on the matrix shape. These are inferred by the matrix
+        code and must be specified only if code==None.
     n_cols : None or int, default None
         The number of columns of the matrix. This parameter is used to divide
-        the channels based on the matrix shape. These are normally inferred by
-        the matrix code and must be specified only if code == None.
+        the channels based on the matrix shape. These are inferred by the
+        matrix code and must be specified only if code==None.
+    custom_sorting_order : None or list, default None
+        If code=="Custom order", custom_sorting_order will be used for
+        channels sorting. In this case, custom_sorting_order must be a list of
+        lists containing the order of the matrix channels.
+        Specifically, the number of columns are defined by
+        len(custom_sorting_order) while the number of rows by
+        len(custom_sorting_order[0]). np.nan can be used to specify empty
+        channels. Please refer to the Notes and Examples section for the
+        structure of the custom sorting order.
 
     Returns
     -------
     sorted_rawemg : dict or pd.DataFrame
-        If dividebycolumn == True a dict containing the sorted electrodes is
+        If dividebycolumn == True, a dict containing the sorted electrodes is
         returned. Every key of the dictionary represents a different column of
         the matrix. Rows are stored in the dict as a pd.DataFrame.
         If dividebycolumn == False a pd.DataFrame containing the sorted
@@ -199,9 +222,13 @@ def sort_rawemg(
     -----
     The returned file is called ``sorted_rawemg`` for convention.
 
+    Additional info on how to create the custom sorting order is available at:
+    https://www.giacomovalli.com/openhdemg/gui_settings/#electrodes
+
     Examples
     --------
-    Sort emgfile RAW_SIGNAL and divide it by columns.
+    Sort emgfile RAW_SIGNAL and divide it by columns with built-in sorting
+    orders.
 
     >>> import openhdemg.library as emg
     >>> emgfile = emg.askopenfile(filesource="OTB", otb_ext_factor=8)
@@ -251,7 +278,9 @@ def sort_rawemg(
     Avoid RAW_SIGNAL sorting but divide it by columns.
 
     >>> emgfile = emg.askopenfile(filesource="CUSTOM")
-    >>> sorted_rawemg = emg.sort_rawemg(emgfile, code="None", n_cols=5, n_rows=13)
+    >>> sorted_rawemg = emg.sort_rawemg(
+    ...     emgfile, code="None", n_cols=5, n_rows=13,
+    ...     )
     >>> sorted_rawemg["col0"]
                  0         1         2  ...       10        11  12
     0      0.008138  0.001017  0.002035 ... 0.005595  0.008647 NaN
@@ -265,6 +294,38 @@ def sort_rawemg(
     62461  0.001526  0.009664  0.000000 ... 0.001526  0.025940 NaN
     62462  0.033061  0.037130  0.027974 ... 0.022380  0.049845 NaN
     62463  0.020854  0.028992  0.017802 ... 0.013733  0.037638 NaN
+
+    Sort RAW_SIGNAL based on a custom order and divide it by columns.
+    The custom_sorting_order refers to a grid of 13 rows and 5 columns with the
+    empty channel in last position.
+
+    Additional info on how to create the custom sorting order is available at:
+    https://www.giacomovalli.com/openhdemg/gui_settings/#electrodes
+
+    >>> import openhdemg.library as emg
+    >>> emgfile = emg.askopenfile(filesource="OTB", otb_ext_factor=8)
+    >>> custom_sorting_order = [
+    ...     [63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52,     51,],
+    ...     [38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,     50,],
+    ...     [37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26,     25,],
+    ...     [12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,     24,],
+    ...     [11, 10,  9,  8,  7,  6,  5,  4,  3,  2,  1,  0, np.nan,],
+    ... ]  # 13 rows and 5 columns
+    >>> sorted_rawemg = emg.sort_rawemg(
+    ...     emgfile=emgfile,
+    ...     code="Custom order",
+    ...     dividebycolumn=True,
+    ...     custom_sorting_order=custom_sorting_order,
+    ... )
+    >>> sorted_rawemg["col0"]
+                  0          1          2  ...         10         11         12
+    0       2.034505  -9.663899  -5.086263 ... -26.957193  -8.138021  -2.034505
+    1       1.017253  -8.646647  -3.560384 ... -26.957193  -8.138021  -9.663899
+    2      17.293295   6.612142  11.189778 ... -13.224284   9.663899   6.612142
+    3      21.362305  14.750163  22.888184 ...  -9.155273  17.293295  12.715657
+    ...          ...        ...        ... ...        ...        ...        ...
+    63483 -15.258789 -20.345053  -8.646647 ... -20.853678 -15.767415 -10.681152
+    63484 -13.732910 -19.327799  -7.120768 ... -21.362305 -17.801920 -14.241536
     """
 
     valid_codes = [
@@ -273,6 +334,7 @@ def sort_rawemg(
         "GR10MM0808",
         "Trigno Galileo Sensor",
         "None",
+        "Custom order",
     ]
     if code not in valid_codes:
         return ValueError("Unsupported code in sort_rawemg()")
@@ -280,8 +342,19 @@ def sort_rawemg(
     # Work on a copy of the RAW_SIGNAL
     rawemg = copy.deepcopy(emgfile["RAW_SIGNAL"])
 
-    # Get sorting order by matrix
-    if code in ["GR08MM1305", "GR04MM1305"]:
+    # Get sorting order by matrix code
+    if code == "Custom order":
+        # Theck that custom_sorting_order has been specified
+        if not isinstance(custom_sorting_order, list):
+            raise ValueError(
+                "In sort_rawemg(), custom_sorting_order must be a list of " +
+                "lists when code=='Custom order'"
+            )
+
+        # Get custom sorting order
+        base0_sorting_order = custom_sorting_order
+
+    elif code in ["GR08MM1305", "GR04MM1305"]:
         # Get sorting order by matrix orientation
         if orientation == 0:
             """
@@ -295,7 +368,7 @@ def sort_rawemg(
             channel.
 
             Channel Order GR08MM1305
-                    0   1   2   3   4
+                   0   1   2   3   4
             0     64  39  38  13  12
             1     63  40  37  14  11
             2     62  41  36  15  10
@@ -311,17 +384,17 @@ def sort_rawemg(
             12    52  51  26  25 NaN
             """
             base0_sorting_order = [
-                63,62,61,60,59,58,57,56,55,54,53,52,    51,
-                38,39,40,41,42,43,44,45,46,47,48,49,    50,
-                37,36,35,34,33,32,31,30,29,28,27,26,    25,
-                12,13,14,15,16,17,18,19,20,21,22,23,    24,
-                11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,np.nan
+                [63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52,     51],
+                [38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,     50],
+                [37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26,     25],
+                [12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,     24],
+                [11, 10,  9,  8,  7,  6,  5,  4,  3,  2,  1,  0, np.nan],
             ]
 
         elif orientation == 180:
             """
             Channel Order GR08MM1305
-                    0   1   2   3   4
+                   0   1   2   3   4
             0    NaN  25  26  51  52
             1      1  24  27  50  53
             2      2  23  28  49  54
@@ -337,11 +410,11 @@ def sort_rawemg(
             12    12  13  38  39  64
             """
             base0_sorting_order = [
-                np.nan,0, 1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10,11,
-                24    ,23,22,21,20,19,18,17,16,15,14,13,12,
-                25    ,26,27,28,29,30,31,32,33,34,35,36,37,
-                50    ,49,48,47,46,45,44,43,42,41,40,39,38,
-                51    ,52,53,54,55,56,57,58,59,60,61,62,63
+                [np.nan,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11],
+                [24,     23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12],
+                [25,     26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37],
+                [50,     49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38],
+                [51,     52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63],
             ]
 
     elif code == "GR10MM0808":
@@ -359,14 +432,14 @@ def sort_rawemg(
             7  64  56  48  40  32  24  16   8
             """
             base0_sorting_order = [
-                56, 57, 58, 59, 60, 61, 62, 63,
-                48, 49, 50, 51, 52, 53, 54, 55,
-                40, 41, 42, 43, 44, 45, 46, 47,
-                33, 33, 34, 35, 36, 37, 38, 39,
-                24, 25, 26, 27, 28, 29, 30, 31,
-                16, 17, 18, 19, 20, 21, 22, 23,
-                8,  9, 10, 11, 12, 13, 14, 15,
-                0,  1,  2,  3,  4,  5,  6,  7,
+                [56, 57, 58, 59, 60, 61, 62, 63],
+                [48, 49, 50, 51, 52, 53, 54, 55],
+                [40, 41, 42, 43, 44, 45, 46, 47],
+                [33, 33, 34, 35, 36, 37, 38, 39],
+                [24, 25, 26, 27, 28, 29, 30, 31],
+                [16, 17, 18, 19, 20, 21, 22, 23],
+                [8,  9, 10, 11, 12, 13, 14, 15],
+                [0,  1,  2,  3,  4,  5,  6,  7],
             ]
 
         elif orientation == 180:
@@ -383,14 +456,14 @@ def sort_rawemg(
             7   1   9  17  25  33  41  49  57
             """
             base0_sorting_order = [
-                7,   6,  5,  4,  3,  2,  1,  0,
-                15, 14, 13, 12, 11, 10,  9,  8,
-                23, 22, 21, 20, 19, 18, 17, 16,
-                31, 30, 29, 28, 27, 26, 25, 24,
-                39, 38, 37, 36, 35, 34, 33, 32,
-                47, 46, 45, 44, 43, 42, 41, 40,
-                55, 54, 53, 52, 51, 50, 49, 48,
-                63, 62, 61, 60, 59, 58, 57, 56,
+                [7,   6,  5,  4,  3,  2,  1,  0],
+                [15, 14, 13, 12, 11, 10,  9,  8],
+                [23, 22, 21, 20, 19, 18, 17, 16],
+                [31, 30, 29, 28, 27, 26, 25, 24],
+                [39, 38, 37, 36, 35, 34, 33, 32],
+                [47, 46, 45, 44, 43, 42, 41, 40],
+                [55, 54, 53, 52, 51, 50, 49, 48],
+                [63, 62, 61, 60, 59, 58, 57, 56],
             ]
 
     elif code == "Trigno Galileo Sensor":
@@ -408,53 +481,48 @@ def sort_rawemg(
         2   3
         3   4
         """
-        base0_sorting_order = [0, 1, 2, 3]
+        base0_sorting_order = [[0, 1, 2, 3]]
 
-    else:
+    else:  # elif code == "None":
         pass
 
     # Once the order to sort channels has been retrieved,
     # Sort the channels based on pre-specified order and reset columns
-    if code != "None":
-        sorted_rawemg = rawemg.reindex(columns=base0_sorting_order)
+    if code not in [None, "None"]:
+        flattened_base0_sorting_order = list(
+            itertools.chain(*base0_sorting_order),
+        )
+        sorted_rawemg = rawemg.reindex(columns=flattened_base0_sorting_order)
         sorted_rawemg.columns = range(sorted_rawemg.columns.size)
     else:
         # Always allow a way to avoid electrodes sorting.
         # Return a copy of the RAW_SIGNAL
-        sorted_rawemg = copy.deepcopy(emgfile["RAW_SIGNAL"])
+        sorted_rawemg = rawemg
 
     # Check if we need the sorted RAW_SIGNAL divided by column
     if dividebycolumn:
-        if code in ["GR08MM1305", "GR04MM1305"]:
-            if orientation in [0, 180]:
-                n_rows = 13
-                n_cols = 5
-
-        elif code == "GR10MM0808":
-            if orientation in [0, 180]:
-                n_rows = 8
-                n_cols = 8
-
-        elif code == "Trigno Galileo Sensor":
-            n_rows = 4
-            n_cols = 1
+        if code not in [None, "None"]:
+            n_cols = len(base0_sorting_order)
+            n_rows = len(base0_sorting_order[0])
 
         else:
             # Check if n_rows and n_cols have been passed
             if not isinstance(n_rows, int):
                 raise ValueError(
-                    "In sort_rawemg(), n_rows and n_cols must be integers when code == 'None'"
+                    "In sort_rawemg(), n_rows and n_cols must be integers " +
+                    "when code == 'None'"
                 )
             if not isinstance(n_cols, int):
                 raise ValueError(
-                    "In sort_rawemg(), n_rows and n_cols must be integers when code == 'None'"
+                    "In sort_rawemg(), n_rows and n_cols must be integers " +
+                    "when code == 'None'"
                 )
 
         # Create the empty dict to fill with the sorted_rawemg divided by
         # columns. But first check for missing empty channel.
         if n_rows * n_cols != sorted_rawemg.shape[1]:
             raise ValueError(
-                "Number of specified rows and columns must match the number of channels."
+                "Number of rows * columns must match the number of channels."
             )
 
         empty_dict = {f"col{n}": None for n in range(n_cols)}
diff --git a/openhdemg/library/info.py b/openhdemg/library/info.py
index f7afc01..a7dc713 100644
--- a/openhdemg/library/info.py
+++ b/openhdemg/library/info.py
@@ -295,13 +295,17 @@ def citeus(self):
         >>> emg.info().citeus()
         """
 
-        cite = {
-            "Journal": "Waiting for publication...",
-            "Title": "Thinking about it...",
-        }
+        cite = (
+            "Valli G, Ritsche P, Casolo A, Negro F, De Vito G. " +
+            "Tutorial: Analysis of central and peripheral motor unit " +
+            "properties from decomposed High-Density surface EMG signals " +
+            "with openhdemg. J Electromyogr Kinesiol. 2024 Feb;74:102850. " +
+            "doi: 10.1016/j.jelekin.2023.102850. Epub 2023 Nov 30."
+        )
 
         # Pretty dict printing
         print("\nCite Us:\n")
-        print(json.dumps(cite, indent=4))
+        print(cite)
+        print("\n")
 
         return cite
diff --git a/openhdemg/library/mathtools.py b/openhdemg/library/mathtools.py
index 5360887..f08b7aa 100644
--- a/openhdemg/library/mathtools.py
+++ b/openhdemg/library/mathtools.py
@@ -74,6 +74,7 @@ def norm_xcorr(sig1, sig2, out="both"):
         ``both``
            The output is the greatest positive or negative cross-correlation
            value.
+
         ``max``
            The output is the maximum cross-correlation value.
 
@@ -137,10 +138,12 @@ def norm_twod_xcorr(df1, df2, mode="full"):
         ``full``
            The output is the full discrete linear cross-correlation
            of the inputs. (Default)
+
         ``valid``
            The output consists only of those elements that do not
            rely on the zero-padding. In 'valid' mode, either `sta_mu1` or
            `sta_mu2` must be at least as large as the other in every dimension.
+
         ``same``
            The output is the same size as `in1`, centered
            with respect to the 'full' output.
@@ -235,7 +238,7 @@ def norm_twod_xcorr(df1, df2, mode="full"):
     return normxcorr_df, normxcorr_max
 
 
-def compute_sil(ipts, mupulses):
+def compute_sil(ipts, mupulses, ignore_negative_ipts=False):
     """
     Calculate the Silhouette score for a single MU.
 
@@ -250,6 +253,9 @@ def compute_sil(ipts, mupulses):
         interest.
     mupulses : ndarray
         The time of firing (MUPULSES[mu]) of the MU of interest.
+    ignore_negative_ipts : bool, default False
+        If True, only use positive ipts during peak and noise clustering. This
+        is particularly important for sources with large negative components.
 
     Returns
     -------
@@ -259,14 +265,34 @@ def compute_sil(ipts, mupulses):
     See also
     --------
     - compute_pnr : to calculate the Pulse to Noise ratio of a single MU.
+
+    Examples
+    --------
+    Calculate the SIL score for the third MU (MU number 2) ignoring the
+    negative component of the decomposed source.
+
+    >>> import openhdemg.library as emg
+    >>> emgfile = emg.emg_from_samplefile()
+    >>> mu_of_interest = 2
+    >>> sil_value = emg.compute_sil(
+    ...     ipts=emgfile["IPTS"][mu_of_interest],
+    ...     mupulses=emgfile["MUPULSES"][mu_of_interest],
+    ...     ignore_negative_ipts=True,
+    ... )
     """
 
-    # Manage exception of no firings (e.g., as can happen in files from DEMUSE)
+    # Manage exception of no firings
     if len(mupulses) == 0:
         return np.nan
 
     # Extract source and peaks and align source and peaks based on IPTS
     source = ipts.to_numpy()
+
+    if ignore_negative_ipts:
+        # Ignore negative values, this is particularly needed for negative
+        # unbalanced sources.
+        source = source * np.abs(source)
+
     peaks_idxs = mupulses - ipts.index[0]
 
     # Create clusters
@@ -300,7 +326,13 @@ def compute_sil(ipts, mupulses):
     return sil
 
 
-def compute_pnr(ipts, mupulses, fsamp, separate_paired_firings=True):
+def compute_pnr(
+    ipts,
+    mupulses,
+    fsamp,
+    constrain_pulses=[True, 3],
+    separate_paired_firings=True,
+):
     """
     Calculate the pulse to noise ratio for a single MU.
 
@@ -311,12 +343,19 @@ def compute_pnr(ipts, mupulses, fsamp, separate_paired_firings=True):
         interest.
     mupulses : ndarray
         The time of firing (MUPULSES[mu]) of the MU of interest.
+    constrain_pulses : list, default [True, 3]
+        If constrain_pulses[0] == True, the times of firing are considered
+        those in mupulses +- the number of samples specified in
+        constrain_pulses[1].
+        If constrain_pulses[0] == False, the times of firing are estimated via
+        a heuristic penalty funtion (see Notes).
+        constrain_pulses[1] must be an integer (see Notes for instructions on
+        how to set the appropriate value).
     separate_paired_firings : bool, default False
         Whether to treat differently paired and non-paired firings during
-        the estimation of the signal/noise threshold. According to Holobar
-        2012, this is common in pathological tremor. The user is encouraged to
-        use the default value (True) to increase the robustness of the
-        estimation.
+        the estimation of the signal/noise threshold (heuristic penalty
+        funtion, see Notes). This is relevant only if
+        constrain_pulses[0] == False. Otherwise, this argument is ignored.
 
     Returns
     -------
@@ -326,105 +365,168 @@ def compute_pnr(ipts, mupulses, fsamp, separate_paired_firings=True):
     See also
     --------
     - compute_sil : to calculate the Silhouette score for a single MU.
-    """
 
-    # According to Holobar 2014, the PNR is calculated as:
-    # 10 * log10((mean of firings) / (mean of noise))
-    # Where instants in the decomposed source are classified as firings or
-    # noise based on a threshold value named "Pi" or "r".
-    #
-    # Pi is calculated via a heuristic penalty funtion described in Holobar
-    # 2012 as:
-    # Pi = D · χ[3,50](D) + CoVIDI + CoVpIDI
-    # Where:
-    # D is the median of the low-pass filtered instantaneous motor unit
-    # discharge rate (first-order Butterworth filter, cut-off frequency 3 Hz)
-    # χ[3,50](D) stands for an indicator function that penalizes motor units
-    # with filtered discharge rate D below 3 pulses per second (pps) or above
-    # 50 pps:
-    # χ[3,50](D) = 0 if D is between 3 and 50 or D if D is not between 3 and 50.
-    # Two separate coefficients of variation for inter-discharge interval (IDI)
-    # calculated as standard deviation (SD) of IDI divided by the mean IDI,
-    # are used. CoVIDI is the coefficient of variation for IDI of non-paired
-    # MUs discharges only, whereas CoVpIDI is the coefficient of variation for
-    # IDI of paired MUs discharges.
-    # Holobar 2012 considered MUs discharges paired whenever the second
-    # discharge was within 50 ms of the first.
-    # Paired discharges are typical in pathological tremor and the use of both
-    # CoVIDI and CoVpIDI accounts for this condition.
-    #
-    # However, this heuristic penalty function penalizes MUs firing during
-    # specific types of contractions like explosive contractions
-    # (MUs discharge up to 200 pps).
-    # Therefore, in this implementation of the PNR estimation we did not use a
-    # penality based on MUs discharge.
-    # Additionally, the user can decide whether to adopt the two coefficients
-    # of variations to estimate Pi or not.
-    # If both are used, Pi would be calculated as:
-    # Pi = CoVIDI + CoVpIDI
-    # Otherwise, Pi would be calculated as:
-    # Pi = CoV_all_IDI
-
-    # Calculate IDI
-    idi = np.diff(mupulses)
-
-    # In order to increase robustness to outlier values, remove values outside
-    # mean +- 3 * STD in the idi array and the firings happening with more
-    # than 500ms of difference between each others.
-    idi = idi[idi <= (fsamp * 0.5)]
-
-    mean, std = np.mean(idi), np.std(idi)
-    upper_bound = mean + 3*std
-    lower_bound = mean - 3*std
-
-    idi = idi[idi <= upper_bound]
-    idi = idi[idi >= lower_bound]
-
-    # Calculate Pi
-    if separate_paired_firings is False:
-        # Calculate Pi on all IDI
-        CoV_all_IDI = np.std(idi) / np.mean(idi)
-
-        if math.isnan(CoV_all_IDI):
-            CoV_all_IDI = 0
-
-        Pi = CoV_all_IDI
+    Notes
+    -----
+    The behaviour of the compute_pnr() function is determined by the argument
+    constrain_pulses.
+
+    If constrain_pulses[0] == True, the times of firing are considered those
+    in mupulses +- a number of samples specified in constrain_pulses[1].
+    The inclusion of the samples around the mupulses values allows to capture
+    the full ipts corresponding to the time of firing (e.g., including also
+    the raising and falling wedges). The appropriate value of
+    constrain_pulses[1] must be determined by the user and depends on the
+    sampling frequency. It is suggested to use 3 when the sampling frequency is
+    2000 or 2048 Hz and increase it if the sampling frequency is higher (e.g.
+    use 6 at 4000 or 4096 Hz). With this approach, the PNR estimation is not
+    related to the variability of the firings.
+
+    If constrain_pulses[0] == False, the ipts values are classified as firings
+    or noise based on a threshold value (named "Pi" or "r") estimated from the
+    euristic penalty funtion described in Holobar 2012, as proposed in
+    Holobar 2014. If the variability of the firings is relevant, this
+    apoproach should be preferred. Specifically:
+    Pi = D · χ[3,50](D) + CoVIDI + CoVpIDI
+    Where:
+    D is the median of the low-pass filtered instantaneous motor unit discharge
+    rate (first-order Butterworth filter, cut-off frequency 3 Hz).
+    χ[3,50](D) stands for an indicator function that penalizes motor units
+    with filtered discharge rate D below 3 pulses per second (pps) or above
+    50 pps:
+    χ[3,50](D) = 0 if D is between 3 and 50 or D if D is not between 3 and 50.
+    Two separate coefficients of variation for inter-discharge interval (IDI)
+    calculated as standard deviation (SD) of IDI divided by the mean IDI,
+    are used. CoVIDI is the coefficient of variation for IDI of non-paired
+    MUs discharges only, whereas CoVpIDI is the coefficient of variation for
+    IDI of paired MUs discharges.
+    Holobar 2012 considered MUs discharges paired whenever the second
+    discharge was within 50 ms of the first.
+    Paired discharges are typical in pathological tremor and the use of both
+    CoVIDI and CoVpIDI accounts for this condition.
+    However, this heuristic penalty function penalizes MUs firing during
+    specific types of contractions like explosive contractions
+    (MUs discharge up to 200 pps).
+    Therefore, in this implementation of the PNR, we did ``not`` include the
+    penalty based on MUs discharge.
+    Additionally, the user can decide whether to adopt the two coefficients
+    of variations to estimate Pi or not.
+    If both are used, Pi would be calculated as:
+    Pi = CoVIDI + CoVpIDI
+    Otherwise, Pi would be calculated as:
+    Pi = CoV_all_IDI
 
-    else:
-        # Divide paired and non-paired IDIs before calculating specific CoV.
-        # De Luca 1985 considered dublets < 10 ms, Holobar < 50 ms
-        idinonp = idi[idi >= (fsamp * 0.05)]
-        idip = idi[idi < (fsamp * 0.05)]
+    Examples
+    --------
+    Calculate the PNR value for the third MU (MU number 2) forcing the
+    selction of the times of firing.
 
-        if len(idinonp) > 1:
-            CoVIDI = np.std(idinonp) / np.mean(idinonp)
-        else:
-            CoVIDI = 0
+    >>> import openhdemg.library as emg
+    >>> emgfile = emg.emg_from_samplefile()
+    >>> mu_of_interest = 2
+    >>> pnr_value = emg.compute_pnr(
+    ...     ipts=emgfile["IPTS"][mu_of_interest],
+    ...     mupulses=emgfile["MUPULSES"][mu_of_interest],
+    ...     fsamp=emgfile["FSAMP"],
+    ...     constrain_pulses=[True, 3],
+    ... )
 
-        if len(idip) > 1:
-            CoVpIDI = np.std(idip) / np.mean(idip)
-        else:
-            CoVpIDI = 0
+    Calculate the PNR value for the third MU (MU number 2) selecting the times
+    of firing based on the euristic penalty funtion described in Holobar 2012
+    and considering, separately, the paired and the non-paired firings.
 
-        # Calculate Pi
-        Pi = CoVIDI + CoVpIDI
+    >>> import openhdemg.library as emg
+    >>> emgfile = emg.emg_from_samplefile()
+    >>> mu_of_interest = 2
+    >>> pnr_value = emg.compute_pnr(
+    ...     ipts=emgfile["IPTS"][mu_of_interest],
+    ...     mupulses=emgfile["MUPULSES"][mu_of_interest],
+    ...     fsamp=emgfile["FSAMP"],
+    ...     constrain_pulses=[False],
+    ...     separate_paired_firings=True,
+    ... )
+    """
+
+    # Manage exception of no firings
+    if len(mupulses) == 0:
+        return np.nan
 
     # Extract the source
     source = ipts.to_numpy()
 
-    # Use only absolute values from the source and normalise the source.
-    # This step is fundamental for the OTBiolab+ output.
-    source = np.abs(source)
-    source = (source - np.min(source)) / (np.max(source) - np.min(source))
+    # Normalise source
+    source = source / np.mean(source[mupulses])
 
-    # Create clusters
-    peak_cluster = source[source >= Pi]
-    noise_cluster = source[source < Pi]
+    # Check how to estimate PNR
+    if constrain_pulses[0] is True:
+        # Estimate by mupulses
+        start, stop = -int(constrain_pulses[1]), int(constrain_pulses[1])
+        extended_mupulses = np.concatenate(
+            [mupulses + t for t in range(start, stop+1)]
+        )
 
-    peak_cluster = np.square(peak_cluster)
-    noise_cluster = np.square(noise_cluster)
+        # Consider noise what outside the extenbded mupulses
+        noise_times = np.setdiff1d(
+            np.arange(mupulses[0], mupulses[-1]+1), extended_mupulses,
+        )
+
+        # Create clusters
+        peak_cluster = source[mupulses]
+        noise_cluster = source[noise_times]
+        noise_cluster = noise_cluster[~np.isnan(noise_cluster)]
+        noise_cluster = noise_cluster[noise_cluster >= 0]
+
+    elif constrain_pulses[0] is False:
+        # Estimate by Pi
+        # Calculate IDI
+        idi = np.diff(mupulses)
+
+        # In order to increase robustness to outlier values, remove values
+        # with more than 500ms of difference between each others.
+        idi = idi[idi <= (fsamp * 0.5)]
+
+        # Calculate Pi
+        if separate_paired_firings is False:
+            # Calculate Pi on all IDI
+            CoV_all_IDI = np.std(idi) / np.mean(idi)
+
+            if math.isnan(CoV_all_IDI):
+                CoV_all_IDI = 0
+
+            Pi = CoV_all_IDI
+
+        else:
+            # Divide paired and non-paired IDIs before calculating specific
+            # CoV. De Luca 1985 considered dublets < 10 ms, Holobar < 50 ms.
+            idinonp = idi[idi >= (fsamp * 0.05)]
+            idip = idi[idi < (fsamp * 0.05)]
+
+            if len(idinonp) > 1:
+                CoVIDI = np.std(idinonp) / np.mean(idinonp)
+            else:
+                CoVIDI = 0
+
+            if len(idip) > 1:
+                CoVpIDI = np.std(idip) / np.mean(idip)
+            else:
+                CoVpIDI = 0
+
+            # Calculate Pi
+            Pi = CoVIDI + CoVpIDI
+
+        # Create clusters
+        peak_cluster = source[source >= Pi]
+        noise_cluster = source[source < Pi]
+
+    else:
+        raise ValueError(
+            "constrain_pulses[0] can only be True or False. " +
+            f"{constrain_pulses[0]} was passed instead."
+        )
 
     # Calculate PNR
+    peak_cluster = np.square(peak_cluster)
+    noise_cluster = np.square(noise_cluster)
     pnr = 10 * np.log10(np.mean(peak_cluster) / np.mean(noise_cluster))
 
     return pnr
@@ -439,7 +541,7 @@ def derivatives_beamforming(sig, row, teta):
 
     Parameters
     ----------
-    sig : pd.Dataframe
+    sig : np.ndarray
         The source signal to be used for the calculation.
         Different channels should be organised in different rows.
     row : int
@@ -454,22 +556,21 @@ def derivatives_beamforming(sig, row, teta):
 
     See also
     --------
-    mle_cv_est : Estimate conduction velocity (CV) via maximum likelihood
-        estimation.
+    - estimate_cv_via_mle : Estimate signal conduction velocity via maximum
+        likelihood estimation.
+    - MUcv_gui : Graphical user interface for the estimation of MUs conduction
+        velocity.
     """
 
-    # To implement with nympy arrays instead of pd.Series for performance?
-    # Check also the use of pandas in row-major.
-
     # Define some necessary variables
-    total_rows = len(sig)
+    total_rows = np.shape(sig)[0]
     m = total_rows - 1
-    total_columns = len(sig.columns)
-    half_of_the_columns = pd.Series(np.arange((round(total_columns/2)))) + 1
+    total_columns = np.shape(sig)[1]
+    half_of_the_columns = np.arange((round(total_columns/2))) + 1
 
     # Create a custom position index with negative and mirrored values for
     # index < row.
-    position = np.zeros(len(sig.index))
+    position = np.zeros(np.shape(sig)[0])
     position[0] = row + 1  # + 1 used to overcome base 0 here and following
 
     for i in range(1, row+1):
@@ -481,35 +582,30 @@ def derivatives_beamforming(sig, row, teta):
     position = np.delete(position, 0)
 
     # Shift sig and move the value contained in sig[row] to sig[0]
-    this_row = pd.DataFrame(sig.iloc[row]).transpose()
-    sig = sig.drop(row).reset_index(drop=True)
-    sig = pd.concat([this_row, sig], ignore_index=True)
+    this_row = sig[row, :]
+    sig = np.delete(sig, (row), axis=0)  # axis=0 to delete rows
+    sig = np.insert(sig, 0, this_row, axis=0)
 
     # Calculate fft row-wise (for each signal)
-    sigfft = pd.DataFrame(0, index=np.arange(total_rows), columns=sig.columns)
+    sigfft = np.zeros_like(sig, dtype=np.complex128)  # Specify dtype as complex
     for i in range(total_rows):
-        sigfft.iloc[i] = fft(sig.iloc[i].to_numpy())
+        sigfft[i, :] = fft(sig[i, :])
 
     # Create the series used to store the terms of the derivatives
-    term_de1 = pd.Series(0, np.arange(len(half_of_the_columns)))
-    term_de2 = pd.Series(0, np.arange(len(half_of_the_columns)))
-    term_de12 = pd.Series(0, np.arange(len(half_of_the_columns)))
-    term_de22 = pd.Series(0, np.arange(len(half_of_the_columns)))
+    term_de1 = np.zeros(np.shape(half_of_the_columns)[0])
+    term_de2 = np.zeros(np.shape(half_of_the_columns)[0])
+    term_de12 = np.zeros(np.shape(half_of_the_columns)[0])
+    term_de22 = np.zeros(np.shape(half_of_the_columns)[0])
 
     # Calculate the first term of the first derivative
     for i in range(m):
         for u in range(i+1, m):
 
-            s_fft = sigfft.iloc[i+1, list(half_of_the_columns)]
-            s_conj = np.conj(sigfft.iloc[u+1, list(half_of_the_columns)])
+            s_fft = sigfft[i+1, half_of_the_columns]
+            s_conj = np.conj(sigfft[u+1, half_of_the_columns])
             s_exp = np.exp(1j * 2 * np.pi * half_of_the_columns * (position[i]-position[u]) * teta / total_columns)
             s_last = 2 * np.pi * half_of_the_columns * (position[i]-position[u]) / total_columns
 
-            s_fft = s_fft.reset_index(drop=True)
-            s_conj = s_conj.reset_index(drop=True)
-            s_exp = s_exp.reset_index(drop=True)
-            s_last = s_last.reset_index(drop=True)
-
             image = np.imag(s_fft * s_conj * s_exp * s_last)
 
             term_de1 = term_de1-image
@@ -519,18 +615,13 @@ def derivatives_beamforming(sig, row, teta):
     # Calculate the second term of the first derivative
     for i in range(m):
 
-        s_fft = sigfft.iloc[i+1, list(half_of_the_columns)]
+        s_fft = sigfft[i+1, half_of_the_columns]
         s_exp = np.exp(1j * 2 * np.pi * half_of_the_columns * position[i] * teta / total_columns)
         s_last = 2 * np.pi * half_of_the_columns * position[i] / total_columns
 
-        s_fft = s_fft.reset_index(drop=True)
-        s_exp = s_exp.reset_index(drop=True)
-        s_last = s_last.reset_index(drop=True)
-
         term_de2 = term_de2 + (s_fft * s_exp * s_last)
 
-    s_conj = np.conj(sigfft.iloc[0, list(half_of_the_columns)])
-    s_conj = s_conj.reset_index(drop=True)
+    s_conj = np.conj(sigfft[0, half_of_the_columns])
 
     term_de2 = 2 * np.imag(s_conj * term_de2) / m
 
@@ -541,16 +632,11 @@ def derivatives_beamforming(sig, row, teta):
     for i in range(m):
         for u in range(i+1, m):
 
-            s_fft = sigfft.iloc[i+1, list(half_of_the_columns)]
-            s_conj = np.conj(sigfft.iloc[u+1, list(half_of_the_columns)])
+            s_fft = sigfft[i+1, half_of_the_columns]
+            s_conj = np.conj(sigfft[u+1, half_of_the_columns])
             s_exp = np.exp(1j * 2 * np.pi * half_of_the_columns * (position[i]-position[u]) * teta / total_columns)
             s_last = 2 * np.pi * half_of_the_columns * (position[i]-position[u]) / total_columns
 
-            s_fft = s_fft.reset_index(drop=True)
-            s_conj = s_conj.reset_index(drop=True)
-            s_exp = s_exp.reset_index(drop=True)
-            s_last = s_last.reset_index(drop=True)
-
             term_de12 = term_de12 - np.real(s_fft * s_conj * s_exp * (s_last**2))
 
     term_de12 = (term_de12 * 2) / (m ** 2)
@@ -558,18 +644,13 @@ def derivatives_beamforming(sig, row, teta):
     # Calculate the second term of the second derivative
     for i in range(m):
 
-        s_fft = sigfft.iloc[i+1, list(half_of_the_columns)]
+        s_fft = sigfft[i+1, half_of_the_columns]
         s_exp = np.exp(1j * 2 * np.pi * half_of_the_columns * position[i] * teta / total_columns)
         s_last = 2 * np.pi * half_of_the_columns * position[i] / total_columns
 
-        s_fft = s_fft.reset_index(drop=True)
-        s_exp = s_exp.reset_index(drop=True)
-        s_last = s_last.reset_index(drop=True)
-
         term_de22 = term_de22 + (s_fft * s_exp * (s_last**2))
 
-    s_conj = np.conj(sigfft.iloc[0, list(half_of_the_columns)])
-    s_conj = s_conj.reset_index(drop=True)
+    s_conj = np.conj(sigfft[0, half_of_the_columns])
 
     term_de22 = 2 * np.real(s_conj * term_de22) / m
 
@@ -585,7 +666,7 @@ def mle_cv_est(sig, initial_teta, ied, fsamp):
 
     Parameters
     ----------
-    sig : pd.Dataframe
+    sig : np.ndarray
         The source signal to be used for the calculation.
         Different channels should be organised in different rows.
     initial_teta : int
@@ -604,7 +685,8 @@ def mle_cv_est(sig, initial_teta, ied, fsamp):
 
     See also
     --------
-    - find_teta : Find the starting value for teta.
+    - estimate_cv_via_mle : Estimate signal conduction velocity via maximum
+        likelihood estimation.
     - MUcv_gui : Graphical user interface for the estimation of MUs conduction
         velocity.
 
@@ -613,9 +695,6 @@ def mle_cv_est(sig, initial_teta, ied, fsamp):
     Refer to the examples of find_teta to obtain sig and initial_teta.
     """
 
-    # Set index to 0
-    sig = sig.reset_index(drop=True)
-
     # Calculate ied in meters
     ied = ied / 1000
 
@@ -627,14 +706,14 @@ def mle_cv_est(sig, initial_teta, ied, fsamp):
     eps = sys.float_info.epsilon
 
     while abs(teta - t) >= 5e-5 and trial < 30:
-        trial = trial+1
+        trial = trial + 1
         teta = t
         # Initialize the first and second derivatives
         de1 = 0
         de2 = 0
 
         # Calculate the first and second derivatives
-        for row in range(len(sig)):
+        for row in range(np.shape(sig)[0]):
             de1t, de2t = derivatives_beamforming(sig=sig, row=row, teta=teta)
             de1 = de1 + de1t + eps
             de2 = de2 + de2t + eps
@@ -667,12 +746,12 @@ def find_teta(sig1, sig2, ied, fsamp):
 
     Parameters
     ----------
-    sig1, sig2 : pd.Series
+    sig1, sig2 : np.ndarray
         The two signals based on which to calculate teta.
-        These must be pd.Series, i.e., 1-dimensional data structures.
-    ied : int
+        These must be 1-dimensional arrays where the data is contained in a row.
+    ied : int or float
         Interelectrode distance (mm).
-    fsamp : int
+    fsamp : int or float
         Sampling frequency (Hz).
 
     Returns
@@ -682,23 +761,24 @@ def find_teta(sig1, sig2, ied, fsamp):
 
     See also
     --------
-    - mle_cv_est : Estimate conduction velocity (CV) via maximum likelihood
-        estimation.
+    - estimate_cv_via_mle : Estimate signal conduction velocity via maximum
+        likelihood estimation.
     - MUcv_gui : Graphical user interface for the estimation of MUs conduction
         velocity.
 
     Examples
     --------
     Calculate the starting point for the maximum likelihood estimation.
-    In this example we calculate teta for the first MU on the channels 5,6,7
-    in the third column ("col3") of the double differential representation of
-    the MUAPs.
+    In this example, we calculate teta for the first MU (number 0) on the
+    channels 31, 32, 34, 34 that are contained in the second column ("col2")
+    of the double differential representation of the MUAPs.
     First, obtain the spike-triggered average of the double differential
     derivation.
 
     >>> import openhdemg.library as emg
     >>> emgfile = emg.askopenfile(filesource="OTB", otb_ext_factor=8)
-    ... sorted_rawemg = emg.sort_rawemg(
+    >>> emgfile = emg.filter_rawemg(emgfile)
+    >>> sorted_rawemg = emg.sort_rawemg(
     ...     emgfile,
     ...     code="GR08MM1305",
     ...     orientation=180,
@@ -717,17 +797,17 @@ def find_teta(sig1, sig2, ied, fsamp):
     each row and all the instants are contained in columns. For this reason,
     the original content of the spike-triggered average has to be transposed.
     After that, the 1D signals used to estimate teta are defined based on the
-    number of available channels.
-
-    >>> sig = sta[1]["col3"].transpose()
-    >>> sig = sig.iloc[[5,6,7], :]
-    >>> sig = sig.reset_index(drop=True)
-    >>> if len(sig) > 3:
-    >>>     sig1 = sig.iloc[1]
-    >>>     sig2 = sig.iloc[2]
+    number of available channels. Please note that the original signal
+    contained in a pandas DataFrame has to be convertedn in a numpy array.
+
+    >>> sig = sta[0]["col2"].loc[:, 31:34]
+    >>> sig = sig.to_numpy()
+    >>> if np.shape(sig)[0] > 3:
+    ...     sig1 = sig[1, :]
+    ...     sig2 = sig[2, :]
     >>> else:
-    >>>     sig1 = sig.iloc[0]
-    >>>     sig2 = sig.iloc[1]
+    ...     sig1 = sig[0, :]
+    ...     sig2 = sig[1, :]
 
     Third, estimate teta.
 
@@ -747,10 +827,6 @@ def find_teta(sig1, sig2, ied, fsamp):
     teta_min = math.floor(ied / max_cv * fsamp)
     teta_max = math.ceil(ied / min_cv * fsamp)
 
-    # Work with numpy arrays for better performance
-    sig1 = sig1.to_numpy()
-    sig2 = sig2.to_numpy()
-
     # Verify that the input is a 1D array. If not, it will affect the
     # calculation of corrpos.
     if sig1.ndim != 1:
diff --git a/openhdemg/library/muap.py b/openhdemg/library/muap.py
index 6b29502..b0a2067 100644
--- a/openhdemg/library/muap.py
+++ b/openhdemg/library/muap.py
@@ -371,6 +371,7 @@ def sta(
     # Compute half of the timewindow in samples
     timewindow_samples = round((timewindow / 1000) * emgfile["FSAMP"])
     halftime = round(timewindow_samples / 2)
+    tottime = halftime * 2
 
     # Container of the STA for every MUs
     # {0: {}, 1: {}, 2: {}, 3: {}}
@@ -378,23 +379,35 @@ def sta(
 
     # Calculate STA on sorted_rawemg for every mu and put it into sta_dict[mu]
     for mu in sta_dict.keys():
+        # Check if there are firings in this MU
+        tot_firings = len(emgfile["MUPULSES"][mu])
+        if tot_firings == 0:
+            raise ValueError(
+                "Empty MU in sta(). First use delete_empty_mus()."
+            )
+
         # Set firings if firings="all"
         if firings == "all":
-            firings_ = [0, len(emgfile["MUPULSES"][mu])]
+            firings_ = [0, tot_firings]
         else:
             firings_ = firings
 
+        # Get current mupulses
+        thismups = emgfile["MUPULSES"][mu][firings_[0]: firings_[1]]
+
         # Calculate STA for each column in sorted_rawemg
         sorted_rawemg_sta = {}
         for col in sorted_rawemg.keys():
             row_dict = {}
             for row in sorted_rawemg[col].columns:
-                thismups = emgfile["MUPULSES"][mu][firings_[0]: firings_[1]]
-                df = sorted_rawemg[col][row].to_numpy()
+                emg_array = sorted_rawemg[col][row].to_numpy()
                 # Calculate STA using NumPy vectorized operations
                 sta_values = []
                 for pulse in thismups:
-                    sta_values.append(df[pulse - halftime: pulse + halftime])
+                    ls = emg_array[pulse - halftime: pulse + halftime]
+                    # Avoid incomplete muaps
+                    if len(ls) == tottime:
+                        sta_values.append(ls)
                 row_dict[row] = np.mean(sta_values, axis=0)
             sorted_rawemg_sta[col] = pd.DataFrame(row_dict)
         sta_dict[mu] = sorted_rawemg_sta
@@ -470,6 +483,7 @@ def st_muap(emgfile, sorted_rawemg, timewindow=50):
     # Compute half of the timewindow in samples
     timewindow_samples = round((timewindow / 1000) * emgfile["FSAMP"])
     halftime = round(timewindow_samples / 2)
+    tottime = halftime * 2
 
     # Container of the ST for every MUs
     # {0: {}, 1: {}, 2: {}, 3: {} ...}
@@ -477,6 +491,13 @@ def st_muap(emgfile, sorted_rawemg, timewindow=50):
 
     # Calculate ST on sorted_rawemg for every mu and put it into sta_dict[mu]
     for mu in sta_dict.keys():
+        # Check if there are firings in this MU
+        tot_firings = len(emgfile["MUPULSES"][mu])
+        if tot_firings == 0:
+            raise ValueError(
+                "Empty MU in sta(). First use delete_empty_mus()."
+            )
+
         # Container for the st of each MUs' matrix column.
         sta_dict_cols = {}
         # Get MUPULSES for this MU
@@ -492,7 +513,9 @@ def st_muap(emgfile, sorted_rawemg, timewindow=50):
                 # Calculate ST using NumPy vectorized operations
                 for pos, pulse in enumerate(thismups):
                     muap = this_emgsig[pulse - halftime: pulse + halftime]
-                    crow_muaps[pos] = muap
+                    # Avoid incomplete muaps
+                    if len(muap) == tottime:
+                        crow_muaps[pos] = muap
                 sta_dict_crows[row] = pd.DataFrame(crow_muaps)
             sta_dict_cols[col] = sta_dict_crows
         sta_dict[mu] = sta_dict_cols
@@ -729,6 +752,7 @@ def tracking(
     orientation=180,
     n_rows=None,
     n_cols=None,
+    custom_sorting_order=None,
     custom_muaps=None,
     exclude_belowthreshold=True,
     filter=True,
@@ -747,8 +771,10 @@ def tracking(
         The range of firings to be used for the STA.
         If a MU has less firings than the range, the upper limit
         is adjusted accordingly.
+
         ``all``
             The STA is calculated over all the firings.
+
         A list can be passed as [start, stop] e.g., [0, 25]
         to compute the STA on the first 25 firings.
     derivation : str {mono, sd, dd}, default sd
@@ -763,14 +789,24 @@ def tracking(
         The code of the matrix used. It can be one of:
 
         ``GR08MM1305``
+
         ``GR04MM1305``
+
         ``GR10MM0808``
-        This is necessary to sort the channels in
-        the correct order. If matrixcode="None", the electrodes are not sorted.
+
+        ``Custom order``
+
+        ``None``
+
+        This is necessary to sort the channels in the correct order.
+        If matrixcode="None", the electrodes are not sorted.
         In this case, n_rows and n_cols must be specified.
+        If "Custom order", the electrodes are sorted based on
+        custom_sorting_order.
     orientation : int {0, 180}, default 180
         Orientation in degree of the matrix (same as in OTBiolab).
         E.g. 180 corresponds to the matrix connection toward the user.
+        This Parameter is ignored if code=="Custom order" or code=="None".
     n_rows : None or int, default None
         The number of rows of the matrix. This parameter is used to divide the
         channels based on the matrix shape. These are normally inferred by the
@@ -779,6 +815,15 @@ def tracking(
         The number of columns of the matrix. This parameter is used to divide
         the channels based on the matrix shape. These are normally inferred by
         the matrix code and must be specified only if code == None.
+    custom_sorting_order : None or list, default None
+        If code=="Custom order", custom_sorting_order will be used for
+        channels sorting. In this case, custom_sorting_order must be a list of
+        lists containing the order of the matrix channels.
+        Specifically, the number of columns are defined by
+        len(custom_sorting_order) while the number of rows by
+        len(custom_sorting_order[0]). np.nan can be used to specify empty
+        channels. Please refer to the Examples section for the structure of
+        the custom sorting order.
     custom_muaps : None or list, default None
         With this parameter, it is possible to perform MUs tracking on MUAPs
         computed with custom techniques. If this parameter is None (default),
@@ -859,6 +904,35 @@ def tracking(
     8         19        10  0.802635
     9         21        14  0.896419
     10        22        16  0.836356
+
+    Track MUs between two files where channels are sorted with a custom order.
+
+    >>> import openhdemg.library as emg
+    >>> emgfile1 = emg.askopenfile(filesource="CUSTOMCSV")
+    >>> emgfile2 = emg.askopenfile(filesource="CUSTOMCSV")
+    >>> custom_sorting_order = [
+    ...     [63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52,     51,],
+    ...     [38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,     50,],
+    ...     [37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26,     25,],
+    ...     [12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,     24,],
+    ...     [11, 10,  9,  8,  7,  6,  5,  4,  3,  2,  1,  0, np.nan,],
+    ... ]  # 13 rows and 5 columns
+    >>> tracking_res = emg.tracking(
+    ...     emgfile1=emgfile1,
+    ...     emgfile2=emgfile2,
+    ...     firings="all",
+    ...     derivation="sd",
+    ...     timewindow=50,
+    ...     threshold=0.8,
+    ...     matrixcode="Custom order",
+    ...     orientation=180,
+    ...     n_rows=None,
+    ...     n_cols=None,
+    ...     custom_sorting_order=custom_sorting_order,
+    ...     exclude_belowthreshold=True,
+    ...     filter=True,
+    ...     show=False,
+    ... )
     """
 
     # Obtain STAs
@@ -870,6 +944,7 @@ def tracking(
             orientation=orientation,
             n_rows=n_rows,
             n_cols=n_cols,
+            custom_sorting_order=custom_sorting_order,
         )
         emgfile2_sorted = sort_rawemg(
             emgfile2,
@@ -877,6 +952,7 @@ def tracking(
             orientation=orientation,
             n_rows=n_rows,
             n_cols=n_cols,
+            custom_sorting_order=custom_sorting_order,
         )
 
         # Calculate the derivation if needed
@@ -1070,6 +1146,7 @@ def remove_duplicates_between(
     orientation=180,
     n_rows=None,
     n_cols=None,
+    custom_sorting_order=None,
     custom_muaps=None,
     filter=True,
     show=False,
@@ -1088,8 +1165,10 @@ def remove_duplicates_between(
         The range of firings to be used for the STA.
         If a MU has less firings than the range, the upper limit
         is adjusted accordingly.
+
         ``all``
             The STA is calculated over all the firings.
+
         A list can be passed as [start, stop] e.g., [0, 25]
         to compute the STA on the first 25 firings.
     derivation : str {mono, sd, dd}, default sd
@@ -1100,13 +1179,28 @@ def remove_duplicates_between(
     threshold : float, default 0.9
         The 2-dimensional cross-correlation minimum value
         to consider two MUs to be the same. Ranges 0-1.
-    matrixcode : str {"GR08MM1305", "GR04MM1305", "GR10MM0808", "None"}, default "GR08MM1305"
-        The code of the matrix used. This is necessary to sort the channels in
-        the correct order. If matrixcode="None", the electrodes are not sorted.
+    matrixcode : str, default "GR08MM1305"
+        The code of the matrix used. It can be one of:
+
+        ``GR08MM1305``
+
+        ``GR04MM1305``
+
+        ``GR10MM0808``
+
+        ``Custom order``
+
+        ``None``
+
+        This is necessary to sort the channels in the correct order.
+        If matrixcode="None", the electrodes are not sorted.
         In this case, n_rows and n_cols must be specified.
+        If "Custom order", the electrodes are sorted based on
+        custom_sorting_order.
     orientation : int {0, 180}, default 180
         Orientation in degree of the matrix (same as in OTBiolab).
         E.g. 180 corresponds to the matrix connection toward the user.
+        This Parameter is ignored if code=="Custom order" or code=="None".
     n_rows : None or int, default None
         The number of rows of the matrix. This parameter is used to divide the
         channels based on the matrix shape. These are normally inferred by the
@@ -1115,6 +1209,15 @@ def remove_duplicates_between(
         The number of columns of the matrix. This parameter is used to divide
         the channels based on the matrix shape. These are normally inferred by
         the matrix code and must be specified only if code == None.
+    custom_sorting_order : None or list, default None
+        If code=="Custom order", custom_sorting_order will be used for
+        channels sorting. In this case, custom_sorting_order must be a list of
+        lists containing the order of the matrix channels.
+        Specifically, the number of columns are defined by
+        len(custom_sorting_order) while the number of rows by
+        len(custom_sorting_order[0]). np.nan can be used to specify empty
+        channels. Please refer to the Examples section for the structure of
+        the custom sorting order.
     custom_muaps : None or list, default None
         With this parameter, it is possible to perform MUs tracking on MUAPs
         computed with custom techniques. If this parameter is None (default),
@@ -1137,6 +1240,7 @@ def remove_duplicates_between(
 
         ``munumber``
             Duplicated MUs are removed from the file with more MUs.
+
         ``accuracy``
             The MU with the lowest accuracy is removed.
 
@@ -1181,6 +1285,39 @@ def remove_duplicates_between(
     ... )
     >>> emg.asksavefile(emgfile1)
     >>> emg.asksavefile(emgfile2)
+
+    Remove duplicated MUs between two files where channels are sorted with a
+    custom order and save the emgfiles without duplicates. Of the 2 duplicated
+    MUs, the one with the lowest accuracy is removed.
+
+    >>> import openhdemg.library as emg
+    >>> emgfile1 = emg.askopenfile(filesource="CUSTOMCSV")
+    >>> emgfile2 = emg.askopenfile(filesource="CUSTOMCSV")
+    >>> custom_sorting_order = [
+    ...     [63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52,     51,],
+    ...     [38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,     50,],
+    ...     [37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26,     25,],
+    ...     [12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,     24,],
+    ...     [11, 10,  9,  8,  7,  6,  5,  4,  3,  2,  1,  0, np.nan,],
+    ... ]  # 13 rows and 5 columns
+    >>> emgfile1, emgfile2, tracking_res = emg.remove_duplicates_between(
+    ...     emgfile1,
+    ...     emgfile2,
+    ...     firings="all",
+    ...     derivation="sd",
+    ...     timewindow=50,
+    ...     threshold=0.9,
+    ...     matrixcode="Custom order",
+    ...     orientation=180,
+    ...     n_rows=None,
+    ...     n_cols=None,
+    ...     custom_sorting_order=custom_sorting_order,
+    ...     filter=True,
+    ...     show=False,
+    ...     which="accuracy",
+    ... )
+    >>> emg.asksavefile(emgfile1)
+    >>> emg.asksavefile(emgfile2)
     """
 
     # Work on deepcopies
@@ -1199,6 +1336,7 @@ def remove_duplicates_between(
         orientation=orientation,
         n_rows=n_rows,
         n_cols=n_cols,
+        custom_sorting_order=custom_sorting_order,
         custom_muaps=custom_muaps,
         exclude_belowthreshold=True,
         filter=filter,
@@ -1329,6 +1467,101 @@ def xcc_sta(sta):
     return xcc_sta
 
 
+def estimate_cv_via_mle(emgfile, signal):
+    """
+    Estimate signal conduction velocity via maximum likelihood estimation.
+
+    This function can be used for the estimation of conduction velocity for 2
+    or more signals. These can be either MUAPs or global EMG signals.
+
+    Parameters
+    ----------
+    emgfile : dict
+        The dictionary containing the emgfile from whic "signal" has been
+        extracted. This is used to know IED and FSAMP.
+    signal : pd.DataFrame
+        A dataframe containing the signals on which to estimate CV. The signals
+        should be organised in colums.
+
+    Returns
+    -------
+    cv : float
+        The conduction velocity value in M/s.
+
+    See also
+    --------
+    - MUcv_gui : Graphical user interface for the estimation of MUs conduction
+        velocity.
+
+    Examples
+    --------
+    Calculate the CV for the first MU (number 0) on the channels 31, 32, 34,
+    34 that are contained in the second column ("col2") of the double
+    differential representation of the MUAPs. First, obtain the spike-
+    triggered average of the double differential derivation.
+
+    >>> import openhdemg.library as emg
+    >>> emgfile = emg.askopenfile(filesource="OTB", otb_ext_factor=8)
+    >>> emgfile = emg.filter_rawemg(emgfile)
+    >>> sorted_rawemg = emg.sort_rawemg(
+    ...     emgfile,
+    ...     code="GR08MM1305",
+    ...     orientation=180,
+    ...     dividebycolumn=True
+    ... )
+    >>> dd = emg.double_diff(sorted_rawemg=sorted_rawemg)
+    >>> sta = emg.sta(
+    ...     emgfile=emgfile,
+    ...     sorted_rawemg=sorted_rawemg,
+    ...     firings=[0,50],
+    ...     timewindow=50,
+    ... )
+
+    Second, extract the channels of interest and estimate CV.
+
+    >>> signal = sta[0]["col2"].loc[:, 31:34]
+    >>> cv = estimate_cv_via_mle(emgfile=emgfile, signal=signal)
+    """
+
+    """
+    sta_mu is a pandas dataframe containing the signals where to estimate CV
+    sta = emg.sta(emgfile, dd)
+    sta_mu = sta[0]["col2"].loc[:, 31:34]
+    """
+    ied = emgfile["IED"]
+    fsamp = emgfile["FSAMP"]
+
+    # Work with numpy vectorised operations for better performance
+    sig = signal.to_numpy()
+    sig = sig.T
+
+    # Prepare the input 1D signals for find_teta
+    if np.shape(sig)[0] > 3:
+        sig1 = sig[1, :]
+        sig2 = sig[2, :]
+    else:
+        sig1 = sig[0, :]
+        sig2 = sig[1, :]
+
+    teta = find_teta(
+        sig1=sig1,
+        sig2=sig2,
+        ied=ied,
+        fsamp=fsamp,
+    )
+
+    cv, teta = mle_cv_est(
+        sig=sig,
+        initial_teta=teta,
+        ied=ied,
+        fsamp=fsamp,
+    )
+
+    cv = abs(cv)
+
+    return cv
+
+
 class MUcv_gui():
     """
     Graphical user interface for the estimation of MUs conduction velocity.
@@ -1339,7 +1572,7 @@ class MUcv_gui():
     Parameters
     ----------
     emgfile : dict
-        The dictionary containing the first emgfile.
+        The dictionary containing the emgfile.
     sorted_rawemg : dict
         A dict containing the sorted electrodes.
         Every key of the dictionary represents a different column of the
@@ -1349,16 +1582,24 @@ class MUcv_gui():
         The range of firings to be used for the STA.
         If a MU has less firings than the range, the upper limit
         is adjusted accordingly.
+
         ``all``
             The STA is calculated over all the firings.
     muaps_timewindow : int, default 50
         Timewindow to compute ST MUAPs in milliseconds.
 
+    See also
+    --------
+    - estimate_cv_via_mle : Estimate signal conduction velocity via maximum
+        likelihood estimation.
+
     Examples
     --------
     Call the GUI.
 
+    >>> import openhdemg.library as emg
     >>> emgfile = emg.askopenfile(filesource="OTB", otb_ext_factor=8)
+    >>> emgfile = emg.filter_rawemg(emgfile)
     >>> sorted_rawemg = emg.sort_rawemg(
     ...     emgfile,
     ...     code="GR08MM1305",
@@ -1379,7 +1620,7 @@ def __init__(
         sorted_rawemg,
         n_firings=[0, 50],
         muaps_timewindow=50,
-        figsize=[20, 15],
+        figsize=[25, 20],
     ):
         # On start, compute the necessary information
         self.emgfile = emgfile
@@ -1402,7 +1643,7 @@ def __init__(
             "..",
             "gui",
             "gui_files",
-            "Icon.ico"
+            "Icon_transp.ico"
         )
         self.root.iconbitmap(iconpath)
 
@@ -1534,8 +1775,7 @@ def __init__(
     # Define functions necessary for the GUI
     # Use empty docstrings to hide the functions from the documentation.
     def gui_plot(self):
-        """
-        """  # Plot the MUAPs used to estimate CV.
+        # Plot the MUAPs used to estimate CV.
 
         # Get MU number
         mu = int(self.selectmu_cb.get())
@@ -1555,58 +1795,32 @@ def gui_plot(self):
         plt.close()
 
     def copy_to_clipboard(self):
-        """
-        """  # Copy the dataframe to clipboard in csv format.
+        # Copy the dataframe to clipboard in csv format.
 
         pyperclip.copy(self.res_df.to_csv(index=False, sep='\t'))
 
     # Define functions for cv estimation
     def compute_cv(self):
-        """
-        """  # Compute conduction velocity.
+        # Compute conduction velocity.
 
-        # Get the muaps of the selected columns and represent them in
-        # different rows (as requested by the functions find_teta and
-        # mle_cv_est).
-        sig = self.st[int(self.selectmu_cb.get())][self.col_cb.get()].transpose()
+        # Get the muaps of the selected columns.
+        sig = self.st[int(self.selectmu_cb.get())][self.col_cb.get()]
         col_list = list(range(int(self.start_cb.get()), int(self.stop_cb.get())+1))
 
-        sig = sig.iloc[col_list, :]
-        sig = sig.reset_index(drop=True)
+        sig = sig.iloc[:, col_list]
 
         # Verify that the signal is correcly oriented
-        if len(sig) > len(sig.columns):
+        if len(sig) < len(sig.columns):
             raise ValueError(
                 "The number of signals exceeds the number of samples. Verify that each row represents a signal"
             )
 
-        # Prepare the input 1D signals for find_teta
-        if len(sig) > 3:
-            sig1 = sig.iloc[1]
-            sig2 = sig.iloc[2]
-        else:
-            sig1 = sig.iloc[0]
-            sig2 = sig.iloc[1]
-
-        initial_teta = find_teta(
-            sig1=sig1,
-            sig2=sig2,
-            ied=self.ied,
-            fsamp=self.fsamp,
-        )
-
-        # Calculate CV (and return only positive values)
-        cv, teta = mle_cv_est(
-            sig=sig,
-            initial_teta=initial_teta,
-            ied=self.ied,
-            fsamp=self.fsamp,
-        )
-        cv = abs(cv)
+        # Estimate CV
+        cv = estimate_cv_via_mle(emgfile=self.emgfile, signal=sig)
 
         # Calculate RMS
         sig = sig.to_numpy()
-        rms = np.mean(np.sqrt((np.mean(sig**2, axis=1))))
+        rms = np.mean(np.sqrt((np.mean(sig**2, axis=0))))
 
         # Update the self.res_df and the self.textbox
         mu = int(self.selectmu_cb.get())
diff --git a/openhdemg/library/openfiles.py b/openhdemg/library/openfiles.py
index 36089b3..639d3cf 100644
--- a/openhdemg/library/openfiles.py
+++ b/openhdemg/library/openfiles.py
@@ -102,7 +102,7 @@
 # --------------------------------------------------------------------- #
 # Function to open decomposed files coming from DEMUSE.
 
-def emg_from_demuse(filepath):
+def emg_from_demuse(filepath, ignore_negative_ipts=False):
     """
     Import the .mat file decomposed in DEMUSE.
 
@@ -112,6 +112,11 @@ def emg_from_demuse(filepath):
         The directory and the name of the file to load
         (including file extension .mat).
         This can be a simple string, the use of Path is not necessary.
+    ignore_negative_ipts : bool, default False
+        This parameter determines the silhouette score estimation. If True,
+        only positive ipts values are used during peak and noise clustering.
+        This is particularly important for compensating sources with large
+        negative components.
 
     Returns
     -------
@@ -261,7 +266,11 @@ def emg_from_demuse(filepath):
     if NUMBER_OF_MUS > 0:
         to_append = []
         for mu in range(NUMBER_OF_MUS):
-            sil = compute_sil(ipts=IPTS[mu], mupulses=MUPULSES[mu])
+            sil = compute_sil(
+                ipts=IPTS[mu],
+                mupulses=MUPULSES[mu],
+                ignore_negative_ipts=ignore_negative_ipts,
+            )
             to_append.append(sil)
         ACCURACY = pd.DataFrame(to_append)
 
@@ -538,6 +547,7 @@ def emg_from_otb(
     refsig=[True, "fullsampled"],
     version="1.5.9.3",
     extras=None,
+    ignore_negative_ipts=False,
 ):
     """
     Import the .mat file exportable from OTBiolab+.
@@ -575,6 +585,11 @@ def emg_from_otb(
         will be stored in a pd.DataFrame with columns named as in the .mat
         file. If not None, pass a regex pattern unequivocally identifying the
         variable in the .mat file to load as extras.
+    ignore_negative_ipts : bool, default False
+        This parameter determines the silhouette score estimation. If True,
+        only positive ipts values are used during peak and noise clustering.
+        This is particularly important for compensating sources with large
+        negative components.
 
     Returns
     -------
@@ -717,7 +732,11 @@ def emg_from_otb(
         if NUMBER_OF_MUS > 0:
             to_append = []
             for mu in range(NUMBER_OF_MUS):
-                sil = compute_sil(ipts=IPTS[mu], mupulses=MUPULSES[mu])
+                sil = compute_sil(
+                    ipts=IPTS[mu],
+                    mupulses=MUPULSES[mu],
+                    ignore_negative_ipts=ignore_negative_ipts,
+                )
                 to_append.append(sil)
             ACCURACY = pd.DataFrame(to_append)
 
@@ -1152,7 +1171,7 @@ def emg_from_delsys(
 
 # --------------------------------------------------------------------- #
 # Function to open the reference signal from Delsys.
-def refsig_from_delsys(filepath, refsig_sensor_name="Trigno Load Cell",):
+def refsig_from_delsys(filepath, refsig_sensor_name="Trigno Load Cell"):
     """
     Import the reference signal in the .mat file exportable by Delsys Neuromap.
 
@@ -1825,7 +1844,15 @@ def emg_from_json(filepath):
         ref_signal.sort_index(inplace=True)
         # ACCURACY
         accuracy = pd.read_json(jsonemgfile["ACCURACY"], orient='split')
-        accuracy.columns = accuracy.columns.astype(int)
+        try:
+            accuracy.columns = accuracy.columns.astype(int)
+        except Exception:
+            accuracy.columns = [*range(len(accuracy.columns))]
+            warnings.warn(
+                "Error while loading accuracy, check or recalculate accuracy"
+            )
+            # TODO error occurring when accuracy was recalculated on empty MUs.
+            # Check if the error is present also for other params.
         accuracy.index = accuracy.index.astype(int)
         accuracy.sort_index(inplace=True)
         # IPTS
@@ -1919,23 +1946,36 @@ def askopenfile(initialdir="/", filesource="OPENHDEMG", **kwargs):
 
         ``OPENHDEMG``
             File saved from openhdemg (.json).
+
         ``DEMUSE``
             File saved from DEMUSE (.mat).
+
         ``OTB``
             File exported from OTB with decomposition and EMG signal.
             (.mat).
+
         ``DELSYS``
             Files exported from Delsys Neuromap and Neuromap explorer with
             decomposition and EMG signal (.mat + .txt).
+
         ``CUSTOMCSV``
             Custom file format (.csv) with decomposition and EMG signal.
+
         ``OTB_REFSIG``
             File exported from OTB with only the reference signal (.mat).
+
         ``DELSYS_REFSIG``
             File exported from DELSYS Neuromap with the reference signal
             (.mat).
+
         ``CUSTOMCSV_REFSIG``
             Custom file format (.csv) containing only the reference signal.
+    ignore_negative_ipts : bool, default False
+        This parameter determines the silhouette score estimation. If True,
+        only positive ipts values are used during peak and noise clustering.
+        This is particularly important for compensating sources with large
+        negative components. Currently, this parameter is used when loading
+        files decomposed in DEMUSE or OTB.
     otb_ext_factor : int, default 8
         The extension factor used for the decomposition in the OTbiolab+
         software.
@@ -1959,6 +1999,11 @@ def askopenfile(initialdir="/", filesource="OPENHDEMG", **kwargs):
         If your specific version is not available in the tested versions,
         trying with the closer one usually works, but please double check the
         results. Ignore if loading other files.
+    otb_extras : None or str, default None
+        Extras is used to store additional custom values. These information
+        will be stored in a pd.DataFrame with columns named as in the .mat
+        file. If not None, pass a regex pattern unequivocally identifying the
+        variable in the .mat file to load as extras.
     delsys_emg_sensor_name : str, default "Galileo sensor"
         The name of the EMG sensor used to collect the data. We currently
         support only the "Galileo sensor".
@@ -2154,13 +2199,18 @@ def askopenfile(initialdir="/", filesource="OPENHDEMG", **kwargs):
 
     # Open file depending on file origin
     if filesource == "DEMUSE":
-        emgfile = emg_from_demuse(filepath=file_toOpen)
+        emgfile = emg_from_demuse(
+            filepath=file_toOpen,
+            ignore_negative_ipts=kwargs.get("ignore_negative_ipts", False),
+        )
     elif filesource == "OTB":
         emgfile = emg_from_otb(
             filepath=file_toOpen,
             ext_factor=kwargs.get("otb_ext_factor", 8),
             refsig=kwargs.get("otb_refsig_type", [True, "fullsampled"]),
-            version=kwargs.get("otb_version", "1.5.9.3")
+            version=kwargs.get("otb_version", "1.5.9.3"),
+            extras=kwargs.get("otb_extras", None),
+            ignore_negative_ipts=kwargs.get("ignore_negative_ipts", False),
         )
     elif filesource == "OTB_REFSIG":
         ref = kwargs.get("otb_refsig_type", [True, "fullsampled"])
@@ -2168,6 +2218,7 @@ def askopenfile(initialdir="/", filesource="OPENHDEMG", **kwargs):
             filepath=file_toOpen,
             refsig=ref[1],
             version=kwargs.get("otb_version", "1.5.9.3"),
+            extras=kwargs.get("otb_extras", None),
         )
     elif filesource == "DELSYS":
         emgfile = emg_from_delsys(
diff --git a/openhdemg/library/plotemg.py b/openhdemg/library/plotemg.py
index 1c88a8d..b8d1219 100644
--- a/openhdemg/library/plotemg.py
+++ b/openhdemg/library/plotemg.py
@@ -23,8 +23,11 @@ def showgoodlayout(tight_layout=True, despined=False):
     tight_layout : bool, default True
         If true (default), plt.tight_layout() is applied to the figure.
     despined : bool or str {"2yaxes"}, default False
+
         False: left and bottom is not despined (standard plotting).
+
         True: all the sides are despined.
+
         ``2yaxes``
             Only the top is despined.
             This is used to show y axes both on the right and left side at the
@@ -158,7 +161,8 @@ def plot_emgsig(
 
     else:
         raise TypeError(
-            "While calling the plot_emgsig function, you should pass an integer, a list or 'all' to channels"
+            "While calling the plot_emgsig function, you should pass an "
+            + "integer or a list to channels"
         )
 
     if addrefsig:
@@ -427,6 +431,7 @@ def plot_mupulses(
 
         ``all``
             IPTS of all the MUs is plotted.
+
         Otherwise, a single MU (int) or multiple MUs (list of int) can be
         specified.
         The list can be passed as a manually-written list or with:
@@ -591,8 +596,10 @@ def plot_ipts(
     emgfile : dict
         The dictionary containing the emgfile.
     munumber : str, int or list, default "all"
+
         ``all``
             IPTS of all the MUs is plotted.
+
         Otherwise, a single MU (int) or multiple MUs (list of int) can be
         specified.
         The list can be passed as a manually-written list or with:
@@ -750,7 +757,8 @@ def plot_idr(
     emgfile : dict
         The dictionary containing the emgfile.
     munumber : str, int or list, default "all"
-        ``all"
+
+        ``all``
             IDR of all the MUs is plotted.
 
         Otherwise, a single MU (int) or multiple MUs (list of int) can be
diff --git a/openhdemg/library/tools.py b/openhdemg/library/tools.py
index 712b5af..541dc43 100644
--- a/openhdemg/library/tools.py
+++ b/openhdemg/library/tools.py
@@ -14,19 +14,28 @@
 from openhdemg.library.mathtools import compute_sil
 
 
-def showselect(emgfile, title="", titlesize=12, nclic=2):
+def showselect(emgfile, how="ref_signal", title="", titlesize=12, nclic=2):
     """
-    Select a part of the recording.
+    Visually select a part of the recording.
 
-    The area can be selected (based on the REF_SIGNAL) with any letter or
-    number in the keyboard, wrong points can be removed by pressing the
-    right mouse button. Once finished, press enter to continue.
+    The area can be selected based on the reference signal or based on the
+    mean EMG signal. The selection can be performed with any letter or number
+    in the keyboard, wrong points can be removed by pressing the right mouse
+    button. Once finished, press enter to continue.
 
     Parameters
     ----------
     emgfile : dict
-        The dictionary containing the emgfile and in particular the REF_SIGNAL
-        (which is used for the selection).
+        The dictionary containing the emgfile.
+    how : str {"ref_signal", "mean_emg"}, default "ref_signal"
+        What to display in the figure used to visually select the area to
+        resize.
+
+        ``ref_signal``
+            Visualise the reference signal to select the area to resize.
+
+        ``mean_emg``
+            Visualise the mean EMG signal to select the area to resize.
     title : str
         The title of the plot. It is optional but strongly recommended.
         It should describe the task to do.
@@ -48,23 +57,51 @@ def showselect(emgfile, title="", titlesize=12, nclic=2):
 
     Examples
     --------
-    Load the EMG file and select the points.
+    Load the EMG file and select the points based on the reference signal.
 
     >>> import openhdemg.library as emg
     >>> emgfile = emg.askopenfile(filesource="OTB_REFSIG")
     >>> points = emg.showselect(
     ...     emgfile,
+    ...     how="ref_signal",
     ...     title="Select 2 points",
     ...     nclic=2,
     ... )
     >>> points
     [16115, 40473]
+
+    Load the EMG file and select the points based on the mean EMG signal.
+
+    >>> import openhdemg.library as emg
+    >>> emgfile = emg.askopenfile(filesource="OPENHDEMG")
+    >>> points = emg.showselect(
+    ...     emgfile,
+    ...     how="mean_emg",
+    ...     title="Select 2 points",
+    ...     nclic=2,
+    ... )
+    >>> points
+    [135, 26598]
     """
+
+    # Get the data to plot
+    if how == "ref_signal":
+        data_to_plot = emgfile["REF_SIGNAL"][0]
+        y_label = "Reference signal"
+    elif how == "mean_emg":
+        data_to_plot = emgfile["RAW_SIGNAL"].mean(axis=1)
+        y_label = "Mean EMG signal"
+    else:
+        raise ValueError(
+            "Wrong argument in showselect(). how can only be 'ref_signal' or "
+            + f"'mean_emg'. {how} was passed instead."
+        )
+
     # Show the signal for the selection
     plt.figure(num="Fig_ginput")
-    plt.plot(emgfile["REF_SIGNAL"][0])
+    plt.plot(data_to_plot)
     plt.xlabel("Samples")
-    plt.ylabel("MVC")
+    plt.ylabel(y_label)
     plt.title(title, fontweight="bold", fontsize=titlesize)
 
     ginput_res = plt.ginput(n=-1, timeout=0, mouse_add=False, show_clicks=True)
@@ -152,9 +189,15 @@ def mupulses_from_binary(binarymusfiring):
     return MUPULSES
 
 
-def resize_emgfile(emgfile, area=None, accuracy="recalculate"):
+def resize_emgfile(
+    emgfile,
+    area=None,
+    how="ref_signal",
+    accuracy="recalculate",
+    ignore_negative_ipts=False,
+):
     """
-    Resize all the emgfile.
+    Resize all the components in the emgfile.
 
     This function can be useful to compute the various parameters only in the
     area of interest.
@@ -167,13 +210,30 @@ def resize_emgfile(emgfile, area=None, accuracy="recalculate"):
         The resizing area. If already known, it can be passed in samples, as a
         list (e.g., [120,2560]).
         If None, the user can select the area of interest manually.
+    how : str {"ref_signal", "mean_emg"}, default "ref_signal"
+        If area==None, allow the user to visually select the area to resize
+        based on how.
+
+        ``ref_signal``
+            Visualise the reference signal to select the area to resize.
+
+        ``mean_emg``
+            Visualise the mean EMG signal to select the area to resize.
     accuracy : str {"recalculate", "maintain"}, default "recalculate"
+
         ``recalculate``
             The Silhouette score is computed in the new resized file. This can
             be done only if IPTS is present.
+
         ``maintain``
             The original accuracy measure already contained in the emgfile is
             returned without any computation.
+    ignore_negative_ipts : bool, default False
+        This parameter determines the silhouette score estimation. If True,
+        only positive ipts values are used during peak and noise clustering.
+        This is particularly important for compensating sources with large
+        negative components. This parameter is considered only if
+        accuracy=="recalculate".
 
     Returns
     -------
@@ -185,6 +245,28 @@ def resize_emgfile(emgfile, area=None, accuracy="recalculate"):
     Notes
     -----
     Suggested names for the returned objects: rs_emgfile, start_, end_.
+
+    Examples
+    --------
+    Manually select the area to resize the emgfile based on mean EMG signal
+    and recalculate the silhouette score in the new portion of the signal.
+
+    >>> emgfile = emg.askopenfile(filesource="DEMUSE", ignore_negative_ipts=True)
+    >>> rs_emgfile, start_, end_ = emg.resize_emgfile(
+    ...     emgfile,
+    ...     how="mean_emg",
+    ...     accuracy="recalculate",
+    ... )
+
+    Automatically resize the emgfile in the pre-specified area. Do not
+    recalculate the silhouette score in the new portion of the signal.
+
+    >>> emgfile = emg.askopenfile(filesource="CUSTOMCSV")
+    >>> rs_emgfile, start_, end_ = emg.resize_emgfile(
+    ...     emgfile,
+    ...     area=[120, 25680],
+    ...     accuracy="maintain",
+    ... )
     """
 
     # Identify the area of interest
@@ -194,9 +276,15 @@ def resize_emgfile(emgfile, area=None, accuracy="recalculate"):
 
     else:
         # Visualise and select the area to resize
+        title = (
+            "Select the start/end area to resize by hovering the mouse" +
+            "\nand pressing the 'a'-key. Wrong points can be removed with " +
+            "right \nclick or canc/delete key. When ready, press enter."
+        )
         points = showselect(
             emgfile,
-            title="Select the start/end area to resize by hovering the mouse\nand pressing the 'a'-key. Wrong points can be removed with right click.\nWhen ready, press enter.",
+            how=how,
+            title=title,
             titlesize=10,
         )
         start_, end_ = points[0], points[1]
@@ -232,11 +320,10 @@ def resize_emgfile(emgfile, area=None, accuracy="recalculate"):
         # Resize the reference signal and identify the first value of the
         # index to resize the mupulses. Then, reset the index.
         rs_emgfile["REF_SIGNAL"] = rs_emgfile["REF_SIGNAL"].loc[start_:end_]
-        first_idx = rs_emgfile["REF_SIGNAL"].index[0]
         rs_emgfile["REF_SIGNAL"] = rs_emgfile["REF_SIGNAL"].reset_index(drop=True)
-        rs_emgfile["RAW_SIGNAL"] = (
-            rs_emgfile["RAW_SIGNAL"].loc[start_:end_].reset_index(drop=True)
-        )
+        rs_emgfile["RAW_SIGNAL"] = rs_emgfile["RAW_SIGNAL"].loc[start_:end_]
+        first_idx = rs_emgfile["RAW_SIGNAL"].index[0]
+        rs_emgfile["RAW_SIGNAL"] = rs_emgfile["RAW_SIGNAL"].reset_index(drop=True)
         rs_emgfile["IPTS"] = rs_emgfile["IPTS"].loc[start_:end_].reset_index(drop=True)
         rs_emgfile["EMG_LENGTH"] = int(len(rs_emgfile["RAW_SIGNAL"].index))
         rs_emgfile["BINARY_MUS_FIRING"] = (
@@ -258,19 +345,19 @@ def resize_emgfile(emgfile, area=None, accuracy="recalculate"):
             if rs_emgfile["NUMBER_OF_MUS"] > 0:
                 if not rs_emgfile["IPTS"].empty:
                     # Calculate SIL
-                    to_append = []
                     for mu in range(rs_emgfile["NUMBER_OF_MUS"]):
                         res = compute_sil(
                             ipts=rs_emgfile["IPTS"][mu],
                             mupulses=rs_emgfile["MUPULSES"][mu],
+                            ignore_negative_ipts=ignore_negative_ipts,
                         )
-                        to_append.append(res)
-                    rs_emgfile["ACCURACY"] = pd.DataFrame(to_append)
+                        rs_emgfile["ACCURACY"].iloc[mu] = res
 
                 else:
                     raise ValueError(
-                        "Impossible to calculate ACCURACY (SIL). IPTS not found." +
-                        " If IPTS is not present or empty, set accuracy='maintain'"
+                        "Impossible to calculate ACCURACY (SIL). IPTS not " +
+                        "found. If IPTS is not present or empty, set " +
+                        "accuracy='maintain'"
                     )
 
         elif accuracy == "maintain":
@@ -286,7 +373,6 @@ def resize_emgfile(emgfile, area=None, accuracy="recalculate"):
 
     elif emgfile["SOURCE"] in ["OTB_REFSIG", "CUSTOMCSV_REFSIG", "DELSYS_REFSIG"]:
         rs_emgfile["REF_SIGNAL"] = rs_emgfile["REF_SIGNAL"].loc[start_:end_]
-        first_idx = rs_emgfile["REF_SIGNAL"].index[0]
         rs_emgfile["REF_SIGNAL"] = rs_emgfile["REF_SIGNAL"].reset_index(drop=True)
 
         return rs_emgfile, start_, end_
@@ -314,10 +400,10 @@ def compute_idr(emgfile):
         A dict containing a pd.DataFrame for each MU (keys are integers).
         Accessing the key, we have a pd.DataFrame containing:
 
-            - mupulses: firing sample.
-            - diff_mupulses: delta between consecutive firing samples.
-            - timesec: delta between consecutive firing samples in seconds.
-            - idr: instantaneous discharge rate.
+        - mupulses: firing sample.
+        - diff_mupulses: delta between consecutive firing samples.
+        - timesec: delta between consecutive firing samples in seconds.
+        - idr: instantaneous discharge rate.
 
     Examples
     --------
@@ -383,7 +469,9 @@ def compute_idr(emgfile):
         )
 
 
-def delete_mus(emgfile, munumber, if_single_mu="ignore"):
+def delete_mus(
+    emgfile, munumber, if_single_mu="ignore", delete_delsys_muaps=True,
+):
     """
     Delete unwanted MUs.
 
@@ -401,12 +489,16 @@ def delete_mus(emgfile, munumber, if_single_mu="ignore"):
     if_single_mu : str {"ignore", "remove"}, default "ignore"
         A string indicating how to behave in case of a file with a single MU.
 
-            ``ignore``
-            Ignore the process and return the original emgfile. (Default)
-            ``remove``
-            Remove the MU and return the emgfile without the MU. (Default)
-            This should allow full compatibility with the use of this file
-            in following processing (i.e., save/load and analyse).
+        ``ignore``
+        Ignore the process and return the original emgfile. (Default)
+
+        ``remove``
+        Remove the MU and return the emgfile without the MU. (Default)
+        This should allow full compatibility with the use of this file
+        in following processing (i.e., save/load and analyse).
+    delete_delsys_muaps : Bool, default True
+        If true, deletes also the associated MUAPs computed by the Delsys
+        software stored in emgfile["EXTRAS"].
 
     Returns
     -------
@@ -459,6 +551,7 @@ def delete_mus(emgfile, munumber, if_single_mu="ignore"):
                 "EMG_LENGTH" : EMG_LENGTH,
                 ==> "NUMBER_OF_MUS" : NUMBER_OF_MUS,
                 ==> "BINARY_MUS_FIRING" : BINARY_MUS_FIRING,
+                ==> "EXTRAS" : EXTRAS but only for DELSYS file
                 }
     """
 
@@ -513,6 +606,37 @@ def delete_mus(emgfile, munumber, if_single_mu="ignore"):
         # list of ndarray
         del_emgfile["MUPULSES"] = [np.array([])]
 
+    if emgfile["SOURCE"] == "DELSYS" and delete_delsys_muaps:
+        # Remove also DELSYS MUAPs
+        if isinstance(munumber, int):
+            munumber = [munumber]
+
+        data = del_emgfile["EXTRAS"]
+
+        for mu in munumber:
+            # Get MU ID
+            mu_id = f"MU_{mu}_"
+            # Remove all columns with MU ID
+            data = data[[col for col in data.columns if not col.startswith(mu_id)]]
+
+        # Rescale the numbers in the remaining column names
+        col_list = list(data.columns)
+        if len(col_list) % 4 != 0:
+            raise ValueError("Unexpected number of channels in Delsys MUAPS")
+        new_col_list = []
+        for mu in range(del_emgfile["NUMBER_OF_MUS"]):
+            new_col_list.extend(
+                [
+                    f"MU_{mu}_CH_0",
+                    f"MU_{mu}_CH_1",
+                    f"MU_{mu}_CH_2",
+                    f"MU_{mu}_CH_3",
+                ]
+            )
+        data.columns = new_col_list
+
+        del_emgfile["EXTRAS"] = data
+
     return del_emgfile
 
 
@@ -668,9 +792,14 @@ def compute_covsteady(emgfile, start_steady=-1, end_steady=-1):
     """
 
     if (start_steady < 0 and end_steady < 0) or (start_steady < 0 or end_steady < 0):
+        title = (
+            "Select the start/end area of the steady-state by hovering the " +
+            "mouse \nand pressing the 'a'-key. Wrong points can be removed " +
+            "with right \nclick or canc/delete key. When ready, press enter."
+        )
         points = showselect(
             emgfile=emgfile,
-            title="Select the start/end area of the steady-state by hovering the mouse\nand pressing the 'a'-key. Wrong points can be removed with right click.\nWhen ready, press enter.",
+            title=title,
             titlesize=10,
         )
         start_steady, end_steady = points[0], points[1]
@@ -831,9 +960,15 @@ def remove_offset(emgfile, offsetval=0, auto=0):
         else:
             # Select the area to calculate the offset
             # (average value of the selected area)
+            title = (
+                "Select the start/end area to calculate the offset by " +
+                "hovering the mouse \nand pressing the 'a'-key. Wrong " +
+                " points can be removed with right \nclick or canc/delete " +
+                "key. When ready, press enter."
+            )
             points = showselect(
                 emgfile=offs_emgfile,
-                title="Select the start/end of area to calculate the offset by hovering the mouse\nand pressing the 'a'-key. Wrong points can be removed with right click.\nWhen ready, press enter.",
+                title=title,
                 titlesize=10,
             )
             start_, end_ = points[0], points[1]
@@ -843,7 +978,6 @@ def remove_offset(emgfile, offsetval=0, auto=0):
             offs_emgfile["REF_SIGNAL"][0] = (
                 offs_emgfile["REF_SIGNAL"][0] - float(offsetval[0])
             )
-            print(offsetval)
 
     else:
         # Compute and subtract the offset value.
@@ -869,6 +1003,7 @@ def get_mvc(emgfile, how="showselect", conversion_val=0):
         ``showselect``
             Ask the user to select the area where to calculate the MVC
             with a GUI.
+
         ``all``
             Calculate the MVC on the entire file.
     conversion_val : float or int, default 0
@@ -915,9 +1050,14 @@ def get_mvc(emgfile, how="showselect", conversion_val=0):
 
     elif how == "showselect":
         # Select the area to measure the MVC (maximum value)
+        title = (
+            "Select the start/end area to compute MVC by hovering the " +
+            "mouse \nand pressing the 'a'-key. Wrong points can be removed " +
+            "with right \nclick or canc/delete key. When ready, press enter."
+        )
         points = showselect(
             emgfile=emgfile,
-            title="Select the start/end area to compute MVC by hovering the mouse\nand pressing the 'a'-key. Wrong points can be removed with right click.\nWhen ready, press enter.",
+            title=title,
             titlesize=10,
         )
         start_, end_ = points[0], points[1]
@@ -1022,9 +1162,14 @@ def compute_rfd(
         start_ = startpoint
     else:
         # Otherwise select the starting point for the RFD
+        title = (
+            "Select the start/end area to compute the RFD by hovering the " +
+            "mouse \nand pressing the 'a'-key. Wrong points can be removed " +
+            "with right \nclick or canc/delete key. When ready, press enter."
+        )
         points = showselect(
             emgfile,
-            title="Select the start area to compute the RFD by hovering the mouse\nand pressing the 'a'-key. Wrong points can be removed with right click.\nWhen ready, press enter.",
+            title=title,
             titlesize=10,
             nclic=1,
         )
diff --git a/pyproject.toml b/pyproject.toml
index f54900d..8b13789 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,2 +1,1 @@
-[build-system]
-requires = ["setuptools>=68.0"]
+
diff --git a/reqs_for_devs.txt b/reqs_for_devs.txt
deleted file mode 100644
index 2caca46..0000000
Binary files a/reqs_for_devs.txt and /dev/null differ
diff --git a/requirements.txt b/requirements.txt
index 970783b..9a01b8d 100644
Binary files a/requirements.txt and b/requirements.txt differ
diff --git a/setup.py b/setup.py
index 352d436..853a955 100644
--- a/setup.py
+++ b/setup.py
@@ -1,20 +1,24 @@
 # Copyright (C) 2023 Giacomo Valli, Paul Ritsche
 
 # For the openhdemg version
-import openhdemg as emg
 # To read the content of the README or description file
 from pathlib import Path
+
 # To install required dependencies
 from setuptools import setup
 
+import openhdemg as emg
+
 INSTALL_REQUIRES = [
     "customtkinter==5.2.1",
+    "CTkMessagebox==2.5",
     "matplotlib==3.8.1",
     "numpy==1.26.1",
     "openpyxl==3.1.2",
     "pandas==2.0.3",
     "pandastable==0.13.1",
     "pyperclip==1.8.2",
+    # "pre-commit==3.7.0",
     "scipy==1.11.3",
     "seaborn==0.13.0",
     "joblib==1.3.2",
@@ -27,6 +31,11 @@
     "openhdemg.compatibility",
     "openhdemg.gui",
     "openhdemg.gui.gui_files",
+    "openhdemg.gui.gui_modules",
+    "openhdemg.tests",
+    "openhdemg.tests.fixtures",
+    "openhdemg.tests.integration",
+    "openhdemg.tests.unit",
 ]
 
 CLASSIFIERS = [
@@ -52,7 +61,7 @@
         maintainer_email="giacomo.valli@unibs.it",
         description="Open-source analysis of High-Density EMG data",
         long_description=long_descr,
-        long_description_content_type='text/markdown',
+        long_description_content_type="text/markdown",
         license="GPL-3.0",
         project_urls={
             "Documentation": "https://giacomovalli.com/openhdemg",


Test patch:
diff --git a/openhdemg/gui/gui_files/test.pdf b/openhdemg/gui/gui_files/test.pdf
deleted file mode 100644
index 882cbe1..0000000
Binary files a/openhdemg/gui/gui_files/test.pdf and /dev/null differ
diff --git a/openhdemg/tests/fixtures/__init__.py b/openhdemg/tests/fixtures/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/openhdemg/tests/integration/__init__.py b/openhdemg/tests/integration/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/openhdemg/tests/unit/__init__.py b/openhdemg/tests/unit/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/openhdemg/tests/unit/functions_for_unit_test.py b/openhdemg/tests/unit/functions_for_unit_test.py
new file mode 100644
index 0000000..97e9dc2
--- /dev/null
+++ b/openhdemg/tests/unit/functions_for_unit_test.py
@@ -0,0 +1,151 @@
+import os
+import pandas as pd
+import numpy as np
+
+
+def get_directories(folder, subfolder, filename):
+    """
+    Get the directory of the specified file.
+
+    Parameters:
+    -----------
+    folder : str
+        The name of the folder containing the folder with the specified file
+        (e.g., library).
+        The folder should be inside "tests/fixtures/..."
+    subfolder : str or list
+        A sting or list of the subfolder/s containing the specified file
+        (e.g., "demuse" or ["delsys", "4pin"]).
+        The folder/s should be inside "tests/fixtures/...folder..."
+    filename : str
+        The name of the file to open including the file extension. The file to
+        open should be inside "tests/fixtures/...folder.../...subfolder".
+
+    Returns
+    -------
+    filepath : str
+        The full path to the file.
+    """
+
+    # Get the absolute path of the current Python file
+    current_file_path = os.path.abspath(__file__)
+
+    # Get the directory containing the current Python file
+    current_directory = os.path.dirname(os.path.dirname(current_file_path))
+
+    if isinstance(subfolder, str):
+        subfolder = [subfolder]
+
+    filepath = os.path.join(
+        current_directory, "fixtures", folder, *subfolder, filename,
+    )
+
+    return filepath
+
+
+def validate_emgfile_content(tc, emgfile):
+    """
+    Verify the emgfile content (instances and shapes).
+
+    Parameters:
+    -----------
+    tc : class
+        The unittest.TestCase instance (this should be "self").
+    emgfile : dict
+        The loaded emgfile (with decomposition outcome).
+    """
+
+    # Verify instances
+    tc.assertIsInstance(emgfile, dict)
+    tc.assertIsInstance(emgfile["SOURCE"], str)
+    tc.assertIsInstance(emgfile["FILENAME"], str)
+    tc.assertIsInstance(emgfile["RAW_SIGNAL"], pd.DataFrame)
+    tc.assertIsInstance(emgfile["REF_SIGNAL"], pd.DataFrame)
+    tc.assertIsInstance(emgfile["ACCURACY"], pd.DataFrame)
+    tc.assertIsInstance(emgfile["IPTS"], pd.DataFrame)
+    tc.assertIsInstance(emgfile["MUPULSES"], list)
+    tc.assertIsInstance(emgfile["MUPULSES"][0], np.ndarray)
+    tc.assertIsInstance(emgfile["FSAMP"], float)
+    tc.assertIsInstance(emgfile["IED"], float)
+    tc.assertIsInstance(emgfile["EMG_LENGTH"], int)
+    tc.assertIsInstance(emgfile["NUMBER_OF_MUS"], int)
+    tc.assertIsInstance(emgfile["BINARY_MUS_FIRING"], pd.DataFrame)
+    tc.assertIsInstance(emgfile["EXTRAS"], pd.DataFrame)
+
+    # Verify shapes
+    tc.assertEqual(len(emgfile.keys()), 13)
+    tc.assertTrue(
+        emgfile["RAW_SIGNAL"].shape[0] > emgfile["RAW_SIGNAL"].shape[1]
+    )
+    try:  # Manage excpetion of no reference signal
+        tc.assertTrue(
+            emgfile["REF_SIGNAL"].shape[0] > emgfile["REF_SIGNAL"].shape[1]
+        )
+    except AssertionError:
+        tc.assertTrue(emgfile["REF_SIGNAL"].shape[0] == 0)
+    tc.assertTrue(
+        emgfile["ACCURACY"].shape[0] >= emgfile["ACCURACY"].shape[1]
+    )  # >= to manage the exception of a single MU
+    if emgfile["SOURCE"] != "DELSYS":
+        tc.assertTrue(
+            emgfile["IPTS"].shape[0] > emgfile["IPTS"].shape[1]
+        )
+    else:
+        tc.assertTrue(
+            emgfile["IPTS"].shape[0] == 0
+        )
+    tc.assertTrue(
+        emgfile["BINARY_MUS_FIRING"].shape[0] > emgfile["BINARY_MUS_FIRING"].shape[1]
+    )
+
+    # Verify congruent sizes
+    if emgfile["SOURCE"] != "DELSYS":
+        tc.assertTrue(
+            emgfile["RAW_SIGNAL"].shape[0] == emgfile["IPTS"].shape[0]
+        )
+        tc.assertTrue(
+            emgfile["IPTS"].shape[0] == emgfile["BINARY_MUS_FIRING"].shape[0]
+        )
+        tc.assertTrue(emgfile["IPTS"].shape[1] == emgfile["NUMBER_OF_MUS"])
+        tc.assertTrue(
+            emgfile["BINARY_MUS_FIRING"].shape[1] == emgfile["NUMBER_OF_MUS"]
+        )
+        tc.assertTrue(len(emgfile["MUPULSES"]) == emgfile["NUMBER_OF_MUS"])
+    else:
+        tc.assertTrue(
+            emgfile["RAW_SIGNAL"].shape[0] == emgfile["BINARY_MUS_FIRING"].shape[0]
+        )
+        tc.assertTrue(
+            emgfile["BINARY_MUS_FIRING"].shape[1] == emgfile["NUMBER_OF_MUS"]
+        )
+        tc.assertTrue(len(emgfile["MUPULSES"]) == emgfile["NUMBER_OF_MUS"])
+        tc.assertTrue(
+            emgfile["EXTRAS"].shape[1] == emgfile["NUMBER_OF_MUS"] * 4
+        )
+
+
+def validate_emg_refsig_content(tc, emg_refsig):
+    """
+    Verify the emg_refsig file content (instances and shapes).
+
+    Parameters:
+    -----------
+    tc : class
+        The unittest.TestCase instance (this should be "self").
+    emg_refsig : dict
+        The loaded emg_refsig.
+    """
+
+    # Verify instances
+    tc.assertIsInstance(emg_refsig, dict)
+    tc.assertIsInstance(emg_refsig["SOURCE"], str)
+    tc.assertIsInstance(emg_refsig["FILENAME"], str)
+    tc.assertIsInstance(emg_refsig["REF_SIGNAL"], pd.DataFrame)
+    tc.assertIsInstance(emg_refsig["FSAMP"], float)
+    tc.assertIsInstance(emg_refsig["EXTRAS"], pd.DataFrame)
+
+    # Verify shapes
+    tc.assertEqual(len(emg_refsig.keys()), 5)
+    tc.assertTrue(
+        emg_refsig["REF_SIGNAL"].shape[0] > emg_refsig["REF_SIGNAL"].shape[1]
+    )
diff --git a/openhdemg/tests/unit/test_openfiles.py b/openhdemg/tests/unit/test_openfiles.py
new file mode 100644
index 0000000..8151afb
--- /dev/null
+++ b/openhdemg/tests/unit/test_openfiles.py
@@ -0,0 +1,425 @@
+"""
+To run the tests using unittest, execute from the openhdemg/tests directory:
+    python -m unittest discover
+
+First, you should dowload all the files necessary for the testing and store
+them inside openhdemg/tests/fixtures. The files are available at:
+https://drive.google.com/drive/folders/1suCZSils8rSCs2E3_K25vRCbN3AFDI7F?usp=sharing
+
+IMPORTANT: Do not alter the content of the dowloaded folder!
+
+WARNING!!! Since the library's functions perform complex tasks and return
+complex data structures, these tests can verify that no critical errors occur,
+but the accuracy of each function must be assessed independently upon creation,
+or at each revision of the code.
+
+WARNING!!! - UNTESTED FUNCTIONS: askopenfile, asksavefile
+"""
+
+
+import unittest
+from openhdemg.tests.unit.functions_for_unit_test import get_directories as getd
+from openhdemg.tests.unit.functions_for_unit_test import (
+    validate_emgfile_content, validate_emg_refsig_content,
+)
+from openhdemg.library.openfiles import (
+    emg_from_demuse, emg_from_otb, refsig_from_otb, emg_from_delsys,
+    refsig_from_delsys, emg_from_customcsv, refsig_from_customcsv,
+    save_json_emgfile, emg_from_json, askopenfile, asksavefile,
+    emg_from_samplefile,
+)
+
+
+class TestOpenFiles(unittest.TestCase):
+    """
+    Test the functions/classes in the openfiles module.
+    """
+
+    def test_from_demuse(self):
+        """
+        Test loading various decomposed files saved from the DEMUSE software.
+        """
+
+        # Load decomposed file with multiple MUs and reference signal
+        demuse_D_R_mMU = emg_from_demuse(
+            filepath=getd("library", "demuse", "DEMUSE_D_R_mMU.mat"),
+            )
+        validate_emgfile_content(self, demuse_D_R_mMU)
+
+        # Load decomposed file with only 1 MU and reference signal
+        demuse_D_R_1MU = emg_from_demuse(
+            filepath=getd("library", "demuse", "DEMUSE_D_R_1MU.mat"),
+            )
+        validate_emgfile_content(self, demuse_D_R_1MU)
+
+        # Load decomposed file with multiple MUs (some empty) and reference
+        # signal
+        demuse_D_R_E_mMU = emg_from_demuse(
+            filepath=getd("library", "demuse", "demuse_D_R_E_mMU.mat"),
+            )
+        validate_emgfile_content(self, demuse_D_R_E_mMU)
+
+    def test_from_otb(self):
+        """
+        Test loading various files saved from the OTBiolab+ software.
+        """
+
+        # Load decomposed file with multiple MUs and reference signal
+        otb_D_R_mMU = emg_from_otb(
+            filepath=getd("library", "otb", "OTB_D_R_mMU.mat"),
+            )
+        validate_emgfile_content(self, otb_D_R_mMU)
+
+        # Load decomposed file with multiple MUs, reference signal and EXTRAS
+        otb_D_R_EX_mMU = emg_from_otb(
+            filepath=getd("library", "otb", "OTB_D_R_EX_mMU.mat"),
+            extras="requested|AUX  Force"
+            )
+        validate_emgfile_content(self, otb_D_R_EX_mMU)
+
+        # Load decomposed file with only 1 MU and reference signal
+        otb_D_R_1_MU = emg_from_otb(
+            filepath=getd("library", "otb", "OTB_D_R_1MU.mat"),
+            )
+        validate_emgfile_content(self, otb_D_R_1_MU)
+
+        # Load file with only the reference signal
+        otb_R = refsig_from_otb(
+            filepath=getd("library", "otb", "OTB_R.mat"),
+            )
+        validate_emg_refsig_content(self, otb_R)
+
+        # Load file with only the reference signal and EXTRAS
+        otb_R_EX = refsig_from_otb(
+            filepath=getd("library", "otb", "OTB_R_EX.mat"),
+            extras="requested path",
+            )
+        validate_emg_refsig_content(self, otb_R_EX)
+
+    def test_from_delsys(self):
+        """
+        Test loading various files saved from the Delsys software.
+        """
+
+        # Load decomposed file with multiple MUs, reference signal and MUAPs
+        delsys_D_R_MUAPs_mMU = emg_from_delsys(
+            rawemg_filepath=getd(
+                "library",
+                ["delsys", "4pin", "DELSYS_D_R_MUAPs_mMU"],
+                "Raw_EMG_signal_withFakeRef.mat"
+            ),
+            mus_directory=getd(
+                "library",
+                ["delsys", "4pin", "DELSYS_D_R_MUAPs_mMU"],
+                "Bicep_Brachii_Motor_Units (Sensor 1)"
+            ),
+        )
+        validate_emgfile_content(self, delsys_D_R_MUAPs_mMU)
+
+        # Load decomposed file with multiple MUs and MUAPs
+        delsys_D_MUAPs_mMU = emg_from_delsys(
+            rawemg_filepath=getd(
+                "library",
+                ["delsys", "4pin", "DELSYS_D_MUAPs_mMU"],
+                "Raw_EMG_signal.mat"
+            ),
+            mus_directory=getd(
+                "library",
+                ["delsys", "4pin", "DELSYS_D_MUAPs_mMU"],
+                "Bicep_Brachii_Motor_Units (Sensor 1)"
+            ),
+        )
+        validate_emgfile_content(self, delsys_D_MUAPs_mMU)
+
+        # Load file with only the reference signal
+        delsys_R = refsig_from_delsys(
+            filepath=getd(
+                "library",
+                ["delsys", "4pin", "DELSYS_D_R_MUAPs_mMU"],
+                "Raw_EMG_signal_withFakeRef.mat",
+            )
+        )
+        validate_emg_refsig_content(self, delsys_R)
+
+    def test_from_customcsv(self):
+        """
+        Test loading various files saved in a custom .csv file.
+        """
+
+        # Load decomposed file with multiple MUs, reference signal and EXTRAS
+        custom_csv_D_R_EX_mMU = emg_from_customcsv(
+            filepath=getd("library", "custom_csv", "C_CSV_D_R_EX_mMU.csv"),
+            )
+        validate_emgfile_content(self, custom_csv_D_R_EX_mMU)
+
+        # Load decomposed file with multiple MUs, reference signal and EXTRAS
+        custom_csv_D_R_EX_1MU = emg_from_customcsv(
+            filepath=getd("library", "custom_csv", "C_CSV_D_R_EX_1MU.csv"),
+            )
+        validate_emgfile_content(self, custom_csv_D_R_EX_1MU)
+
+        # Load file with only the reference signal EXTRAS
+        custom_csv_R_EX = refsig_from_customcsv(
+            filepath=getd("library", "custom_csv", "C_CSV_R_EX.csv"),
+            )
+        validate_emg_refsig_content(self, custom_csv_R_EX)
+
+    def test_from_samplefile(self):
+        """
+        Test loading the decomposed sample file.
+        """
+
+        # Load decomposed file with multiple MUs and reference signal
+        emgfile = emg_from_samplefile()
+        validate_emgfile_content(self, emgfile)
+
+    def test_from_json(self):
+        """
+        Test loading various files saved in .json.
+        """
+
+        # Load decomposed file with multiple MUs and reference signal
+        openhdemg_D_R_mMU = emg_from_json(
+            filepath=getd("library", "openhdemg", "OPENHD_D_R_mMU.json"),
+            )
+        validate_emgfile_content(self, openhdemg_D_R_mMU)
+
+        # Load decomposed file with multiple MUs, reference signal and EXTRAS
+        openhdemg_D_R_EX_mMU = emg_from_json(
+            filepath=getd("library", "openhdemg", "OPENHD_D_R_EX_mMU.json"),
+            )
+        validate_emgfile_content(self, openhdemg_D_R_EX_mMU)
+
+        # Load file with only reference signal
+        openhdemg_R = emg_from_json(
+            filepath=getd("library", "openhdemg", "OPENHD_R.json"),
+            )
+        validate_emg_refsig_content(self, openhdemg_R)
+
+        # Load file with only reference signal and EXTRAS
+        openhdemg_R_EX = emg_from_json(
+            filepath=getd("library", "openhdemg", "OPENHD_R_EX.json"),
+            )
+        validate_emg_refsig_content(self, openhdemg_R_EX)
+
+    def test_saving_and_reloading(self):
+        """
+        Test saving (various) and reloading (.json) files.
+        """
+
+        # Test with sample file
+        emgfile = emg_from_samplefile()
+        save_json_emgfile(
+            emgfile, filepath=getd("library", "saved", "temp.json")
+        )
+        emgfile = emg_from_json(filepath=getd("library", "saved", "temp.json"))
+        validate_emgfile_content(self, emgfile)
+
+        # Test with DEMUSE files
+        # Load decomposed file with multiple MUs and reference signal
+        emgfile = emg_from_demuse(
+            filepath=getd("library", "demuse", "DEMUSE_D_R_mMU.mat"),
+            )
+        save_json_emgfile(
+            emgfile, filepath=getd("library", "saved", "temp.json")
+        )
+        emgfile = emg_from_json(filepath=getd("library", "saved", "temp.json"))
+        validate_emgfile_content(self, emgfile)
+
+        # Load decomposed file with only 1 MU and reference signal
+        emgfile = emg_from_demuse(
+            filepath=getd("library", "demuse", "DEMUSE_D_R_1MU.mat"),
+            )
+        save_json_emgfile(
+            emgfile, filepath=getd("library", "saved", "temp.json")
+        )
+        emgfile = emg_from_json(filepath=getd("library", "saved", "temp.json"))
+        validate_emgfile_content(self, emgfile)
+
+        # Load decomposed file with multiple MUs (some empty) and reference
+        # signal
+        emgfile = emg_from_demuse(
+            filepath=getd("library", "demuse", "demuse_D_R_E_mMU.mat"),
+            )
+        save_json_emgfile(
+            emgfile, filepath=getd("library", "saved", "temp.json")
+        )
+        emgfile = emg_from_json(filepath=getd("library", "saved", "temp.json"))
+        validate_emgfile_content(self, emgfile)
+
+        # Test with OTB files
+        # Load decomposed file with multiple MUs and reference signal
+        emgfile = emg_from_otb(
+            filepath=getd("library", "otb", "OTB_D_R_mMU.mat"),
+            )
+        save_json_emgfile(
+            emgfile, filepath=getd("library", "saved", "temp.json")
+        )
+        emgfile = emg_from_json(filepath=getd("library", "saved", "temp.json"))
+        validate_emgfile_content(self, emgfile)
+
+        # Load decomposed file with multiple MUs, reference signal and EXTRAS
+        emgfile = emg_from_otb(
+            filepath=getd("library", "otb", "OTB_D_R_EX_mMU.mat"),
+            extras="requested|AUX  Force"
+            )
+        save_json_emgfile(
+            emgfile, filepath=getd("library", "saved", "temp.json")
+        )
+        emgfile = emg_from_json(filepath=getd("library", "saved", "temp.json"))
+        validate_emgfile_content(self, emgfile)
+
+        # Load decomposed file with only 1 MU and reference signal
+        emgfile = emg_from_otb(
+            filepath=getd("library", "otb", "OTB_D_R_1MU.mat"),
+            )
+        save_json_emgfile(
+            emgfile, filepath=getd("library", "saved", "temp.json")
+        )
+        emgfile = emg_from_json(filepath=getd("library", "saved", "temp.json"))
+        validate_emgfile_content(self, emgfile)
+
+        # Load file with only the reference signal
+        emg_refsig = refsig_from_otb(
+            filepath=getd("library", "otb", "OTB_R.mat"),
+            )
+        save_json_emgfile(
+            emg_refsig, filepath=getd("library", "saved", "temp.json")
+        )
+        emg_refsig = emg_from_json(filepath=getd("library", "saved", "temp.json"))
+        validate_emg_refsig_content(self, emg_refsig)
+
+        # Load file with only the reference signal and EXTRAS
+        emg_refsig = refsig_from_otb(
+            filepath=getd("library", "otb", "OTB_R_EX.mat"),
+            extras="requested path",
+            )
+        save_json_emgfile(
+            emg_refsig, filepath=getd("library", "saved", "temp.json")
+        )
+        emg_refsig = emg_from_json(filepath=getd("library", "saved", "temp.json"))
+        validate_emg_refsig_content(self, emg_refsig)
+
+        # Test with Delsys files
+        # Load decomposed file with multiple MUs, reference signal and MUAPs
+        emgfile = emg_from_delsys(
+            rawemg_filepath=getd(
+                "library",
+                ["delsys", "4pin", "DELSYS_D_R_MUAPs_mMU"],
+                "Raw_EMG_signal_withFakeRef.mat"
+            ),
+            mus_directory=getd(
+                "library",
+                ["delsys", "4pin", "DELSYS_D_R_MUAPs_mMU"],
+                "Bicep_Brachii_Motor_Units (Sensor 1)"
+            ),
+        )
+        save_json_emgfile(
+            emgfile, filepath=getd("library", "saved", "temp.json")
+        )
+        emgfile = emg_from_json(filepath=getd("library", "saved", "temp.json"))
+        validate_emgfile_content(self, emgfile=emgfile)
+
+        # Load decomposed file with multiple MUs and MUAPs
+        emgfile = emg_from_delsys(
+            rawemg_filepath=getd(
+                "library",
+                ["delsys", "4pin", "DELSYS_D_MUAPs_mMU"],
+                "Raw_EMG_signal.mat"
+            ),
+            mus_directory=getd(
+                "library",
+                ["delsys", "4pin", "DELSYS_D_MUAPs_mMU"],
+                "Bicep_Brachii_Motor_Units (Sensor 1)"
+            ),
+        )
+        save_json_emgfile(
+            emgfile, filepath=getd("library", "saved", "temp.json")
+        )
+        emgfile = emg_from_json(filepath=getd("library", "saved", "temp.json"))
+        validate_emgfile_content(self, emgfile=emgfile)
+
+        # Load file with only the reference signal
+        emg_refsig = refsig_from_delsys(
+            filepath=getd(
+                "library",
+                ["delsys", "4pin", "DELSYS_D_R_MUAPs_mMU"],
+                "Raw_EMG_signal_withFakeRef.mat",
+            )
+        )
+        save_json_emgfile(
+            emg_refsig, filepath=getd("library", "saved", "temp.json")
+        )
+        emg_refsig = emg_from_json(
+            filepath=getd("library", "saved", "temp.json"),
+        )
+        validate_emg_refsig_content(self, emg_refsig)
+
+        # Test with CUSTOMCSV files
+        # Load decomposed file with multiple MUs, reference signal and EXTRAS
+        emgfile = emg_from_customcsv(
+            filepath=getd("library", "custom_csv", "C_CSV_D_R_EX_mMU.csv"),
+            )
+        save_json_emgfile(
+            emgfile, filepath=getd("library", "saved", "temp.json")
+        )
+        emgfile = emg_from_json(filepath=getd("library", "saved", "temp.json"))
+        validate_emgfile_content(self, emgfile)
+
+        # Load decomposed file with multiple MUs, reference signal and EXTRAS
+        emgfile = emg_from_customcsv(
+            filepath=getd("library", "custom_csv", "C_CSV_D_R_EX_1MU.csv"),
+            )
+        save_json_emgfile(
+            emgfile, filepath=getd("library", "saved", "temp.json")
+        )
+        emgfile = emg_from_json(filepath=getd("library", "saved", "temp.json"))
+        validate_emgfile_content(self, emgfile)
+
+        # Load file with only the reference signal, with EXTRAS
+        emg_refsig = refsig_from_customcsv(
+            filepath=getd("library", "custom_csv", "C_CSV_R_EX.csv"),
+            )
+        save_json_emgfile(
+            emg_refsig, filepath=getd("library", "saved", "temp.json")
+        )
+        emg_refsig = emg_from_json(
+            filepath=getd("library", "saved", "temp.json"),
+        )
+        validate_emg_refsig_content(self, emg_refsig)
+
+    def test_compression_level(self):
+        """
+        Test saving (various) and reloading (.json) files.
+        """
+
+        # Test with sample file, no compression
+        emgfile = emg_from_samplefile()
+        save_json_emgfile(
+            emgfile, filepath=getd("library", "saved", "temp.json"),
+            compresslevel=0,
+        )
+        emgfile = emg_from_json(filepath=getd("library", "saved", "temp.json"))
+        validate_emgfile_content(self, emgfile)
+
+        # Normal compression
+        emgfile = emg_from_samplefile()
+        save_json_emgfile(
+            emgfile, filepath=getd("library", "saved", "temp.json"),
+            compresslevel=4,
+        )
+        emgfile = emg_from_json(filepath=getd("library", "saved", "temp.json"))
+        validate_emgfile_content(self, emgfile)
+
+        # Maximum compression
+        emgfile = emg_from_samplefile()
+        save_json_emgfile(
+            emgfile, filepath=getd("library", "saved", "temp.json"),
+            compresslevel=9,
+        )
+        emgfile = emg_from_json(filepath=getd("library", "saved", "temp.json"))
+        validate_emgfile_content(self, emgfile)
+
+
+if __name__ == '__main__':
+    unittest.main()