diff --git a/README.md b/README.md
index cd001bc..dd18d4b 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@ or conda:
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/).
+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
In addition to the rich set of modules and functions presented in the **API documentation**, *openhdemg* offers also a practical graphical user interface (GUI) from which many tasks can be performed without writing a single line of code!
diff --git a/docs/API_openfiles.md b/docs/API_openfiles.md
index 705a504..9611ec4 100644
--- a/docs/API_openfiles.md
+++ b/docs/API_openfiles.md
@@ -35,12 +35,12 @@ Notes
-----
Once opened, the file is returned as a dictionary with keys:
-"SOURCE" : source of the file (i.e., "DEMUSE", "OTB", "custom")
+"SOURCE" : source of the file (e.g., "DEMUSE", "OTB", "custom")
"RAW_SIGNAL" : the raw EMG signal
"REF_SIGNAL" : the reference signal
"PNR" : pulse to noise ratio
"SIL" : silouette score
-"IPTS" : pulse train
+"IPTS" : pulse train (decomposed source)
"MUPULSES" : instants of firing
"FSAMP" : sampling frequency
"IED" : interelectrode distance
diff --git a/docs/GUI_advanced.md b/docs/GUI_advanced.md
index 422fd97..96fb3e5 100644
--- a/docs/GUI_advanced.md
+++ b/docs/GUI_advanced.md
@@ -4,18 +4,28 @@ This is the toturial for the `Advanced Tools` in the *openhdemg* GUI. Great that
![advanced_analysis](md_graphics/GUI/Advanced_analysis_window.png)
-So far, we have included three advanced analyses in the *openhdemg* GUI.
+So far, we have included three advanced analyses in the *openhdemg* GUI.
+
- `Motor Unit Tracking`
- `Duplicate Removal`
- `Conduction Velocity Calculation`
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.
-The `Matrix Code` must be specified according to the one you used during acquisition. So far, the codes
+The `Matrix Code` must be specified according to the one you used during acquisition. So far, the codes
+
- `GR08MM1305`
- `GR04MM1305`
- `GR10MM0808`
+- `None`
+
are implemented. You must choose one from the respective dropdown list.
-Once you specified these parameter, you can click the "Advaned Analysis" button to start your analysis.
+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.
+Once you specified these parameter, you can click the `Advaned Analysis` button to start your analysis.
-----------------------------------------
@@ -25,6 +35,7 @@ When you want to track MUs across two different files, you need to select the `M
![mus_tracking](md_graphics/GUI/MU_Tracking_window.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)
@@ -51,10 +62,11 @@ When you want to remove MUs duplicates across different files, you need to selec
![duplicate_removal](md_graphics/GUI/Duplicate_Removal_window.png)
-1. You should specify How to remove the duplicated MUs in the `Which` dropdown. You can choose between
-- munumber: Duplicated MUs are removed from the file with more MUs.
-- PNR: The MU with the lowest PNR is removed.
-- SIL: The MU with the lowest SIL is removed.
+1. You should specify How to remove the duplicated MUs in the `Which` dropdown. You can choose between
+
+ - munumber: Duplicated MUs are removed from the file with more MUs.
+ - PNR: The MU with the lowest PNR is removed.
+ - SIL: The MU with the lowest SIL is removed.
2. By clicking the `Remove Duplicates` button, you start the removal process.
diff --git a/docs/GUI_basics.md b/docs/GUI_basics.md
index ed59949..c27d9e6 100644
--- a/docs/GUI_basics.md
+++ b/docs/GUI_basics.md
@@ -38,7 +38,7 @@ The *openhdemg* GUI also allows you to edit and filter reference signals corresp
1. View the MUs using the `View MUs` button prior to reference signal editing, so you can see what is happening.
-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.
+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.
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
@@ -66,7 +66,21 @@ The *openhdemg* GUI also allows you to edit and filter reference signals corresp
Offset Value : 0
Automatic: 0
```
- will allow you to manually correct the offset in a new pop-up plot. You just need to follow the instructions on the plot.
+ will allow you to manually correct the offset in a new pop-up plot. You just need to follow the instructions on the plot.
+
+5. When you click the `Convert` button, the reference signal will be multiplied or divided (depending on `Operator`) by the `Factor`. In example, specifying
+
+ ```Python
+ Operator : "Multiply"
+ Factor: 2.5
+ ```
+ will amplify the reference signal 2.5 times.
+
+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
+ MVC Value : 500
+ ```
## Resize EMG File
Sometimes, resizing of your analysis file is unevitable. Luckily, *openhdemg* provides an easy solution. In row five and column two in the left side of the GUI, you can find the `Resize File` button.
@@ -194,7 +208,7 @@ You can choose between the follwing plotting options:
- Plot the raw emg signal. Single or multiple channels. (Plot EMGSig)
- Plot the reference signal. (Plot RefSig)
- Plot all the MUs pulses (binary representation of the firings time). (Plot MUPulses)
-- Plot the source of decomposition. (Plot Source)
+- 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)
@@ -215,6 +229,13 @@ These three setting options are universally used in all plots. There are two mor
- `GR08MM1305`
- `GR04MM1305`
- `GR10MM0808`
+ - `None`
+
+ 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.
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`.
@@ -232,9 +253,9 @@ These three setting options are universally used in all plots. There are two mor
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 Source of decomposition
-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 MUs decomposition in your analysis file.
-2. Enter/select a `MU Number` in/from the dropdown list. For example, if you want to plot the source of `MU Number` one enter *0* in the dropdown. If you want to plot the sources 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 sources of all included MUs in the analysis file.
+### 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
@@ -277,7 +298,7 @@ We all make mistakes! But, most likely, we are also able to correct them. In cas
--------------------------------------------
-We hope you had fun! We are now at the end of describing the basic functions included in the *openhdemg* GUI. In case you need further clarification, don't hesitate to post a question in the Github discussion forum (LINK). Moreover, if you noticed an error that was not properly catched by the GUI, please file a bug report according to our guidelines (LINK).
+We hope you had fun! We are now at the end of describing the basic functions included in the *openhdemg* GUI. In case you need further clarification, don't hesitate to post a question in the [*openhdemg* discussion section](https://github.com/GiacomoValliPhD/openhdemg/discussions){:target="_blank"}. Moreover, if you noticed an error that was not properly catched by the GUI, please file a bug report according to our guidelines (LINK).
If you want to proceed to the advanced stuff now, take a look at the [advanced](GUI_advanced.md) tab on the left side of the webpage.
## More questions?
diff --git a/docs/md_graphics/GUI/Refsig_Filter_window.png b/docs/md_graphics/GUI/Refsig_Filter_window.png
index 9f638ee..370fdef 100644
Binary files a/docs/md_graphics/GUI/Refsig_Filter_window.png and b/docs/md_graphics/GUI/Refsig_Filter_window.png differ
diff --git a/docs/tutorials/Setup_working_env.md b/docs/tutorials/Setup_working_env.md
index 7116034..68e2ece 100644
--- a/docs/tutorials/Setup_working_env.md
+++ b/docs/tutorials/Setup_working_env.md
@@ -87,10 +87,16 @@ The Virtual environments provide an isolated and controlled environment for your
In order to create a virtual environment type in your terminal:
+For Windows users:
```shell
python -m venv myvenv
```
+For Mac users:
+```shell
+python3 -m venv myvenv
+```
+
![venv_command](Setup_working_env/venv_command.png)
This command will create a Virtual environment named `myvenv`.
diff --git a/mkdocs.yml b/mkdocs.yml
index 181226e..c74ed95 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -43,9 +43,9 @@ nav:
- For new users:
- Setup working environment: tutorials/Setup_working_env.md
- Graphical-Interface:
- - Intro: GUI_intro.md
- - Basics: GUI_basics.md
- - Advanced: GUI_advanced.md
+ - intro: GUI_intro.md
+ - basics: GUI_basics.md
+ - advanced: GUI_advanced.md
- What's-New.md
- Contacts.md
- Cite-Us.md
@@ -108,9 +108,9 @@ extra:
generator: false # Remove 'Made with Material for MkDocs' in the footer.
social:
- icon: fontawesome/brands/github
- link: https://github.com/GiacomoValliPhD/openhdemg
+ link: https://github.com/GiacomoValliPhD
- icon: fontawesome/brands/twitter
- link: https://twitter.com/openhdemg
+ link: https://
version:
provider: mike # TODO versioning
diff --git a/openhdemg/gui/openhdemg_gui.py b/openhdemg/gui/openhdemg_gui.py
index 6397b99..8da01f4 100644
--- a/openhdemg/gui/openhdemg_gui.py
+++ b/openhdemg/gui/openhdemg_gui.py
@@ -7,10 +7,10 @@
import customtkinter
import webbrowser
from tkinter import ttk, filedialog, Canvas
-from tkinter import StringVar, Tk, N, S, W, E
+from tkinter import StringVar, Tk, N, S, W, E, DoubleVar
from pandastable import Table, config
-from pathlib import Path
-from sys import platform
+
+from PIL import Image
import matplotlib.pyplot as plt
import matplotlib
@@ -47,6 +47,10 @@ class emgGUI:
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"
@@ -74,7 +78,7 @@ 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 "OTB", "DEMUSE", or "Refsig". # TODO Paul verify this
+ Filetype can be "OENHDEMG", "OTB", "DEMUSE", or "OTB_REFSIG", "CUSTOM".
self.filter_order : int, default 4
The filter order.
self.firings_rec : int, default 4
@@ -127,6 +131,8 @@ class emgGUI:
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.
@@ -178,11 +184,17 @@ class emgGUI:
analysis window when OTB files are loaded. Contains
the extension factor for OTB files.
Stringvariable containing the
- self.extension_factor : tk.Stringvar()
+ 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.
@@ -290,6 +302,10 @@ class emgGUI:
Method to perform MUs tracking on the loaded EMG files.
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
-----
@@ -487,7 +503,8 @@ def __init__(self, master):
# Create info button
# Information Button
info_path = master_path + "/gui_files/Info.png" # Get infor button path
- self.info = tk.PhotoImage(file=info_path)
+ self.info = customtkinter.CTkImage(light_image=Image.open(info_path),
+ size=(30,30))
info_button = customtkinter.CTkButton(
self.right,
image=self.info,
@@ -497,15 +514,8 @@ def __init__(self, master):
bg_color="LightBlue4",
fg_color="LightBlue4",
command=lambda: (
- # Get file path
- path := Path("./openhdemg/gui/gui_files/test.pdf").resolve(),
- # Check user OS for pdf opening
(
- webbrowser.open_new(str(path))
- if platform in ("win32", "linux")
- else os.system(f"open {str(path)}")
- if platform == "darwin"
- else None
+ webbrowser.open("https://www.giacomovalli.com/openhdemg/GUI_intro/")
),
),
)
@@ -513,7 +523,8 @@ def __init__(self, master):
# Button for online tutorials
online_path = master_path + "/gui_files/Online.png"
- self.online = tk.PhotoImage(file=online_path)
+ self.online = customtkinter.CTkImage(light_image=Image.open(online_path),
+ size=(30,30))
online_button = customtkinter.CTkButton(
self.right,
image=self.online,
@@ -522,12 +533,18 @@ def __init__(self, master):
height=30,
bg_color="LightBlue4",
fg_color="LightBlue4",
+ command=lambda: (
+ (
+ webbrowser.open("https://www.giacomovalli.com/openhdemg/tutorials/Setup_working_env/")
+ ),
+ ),
)
online_button.grid(row=1, column=1, sticky=E)
# Button for dev information
redirect_path = master_path + "/gui_files/Redirect.png"
- self.redirect = tk.PhotoImage(file=redirect_path)
+ self.redirect = customtkinter.CTkImage(light_image=Image.open(redirect_path),
+ size=(30,30))
redirect_button = customtkinter.CTkButton(
self.right,
image=self.redirect,
@@ -536,12 +553,18 @@ def __init__(self, master):
height=30,
bg_color="LightBlue4",
fg_color="LightBlue4",
+ command=lambda: (
+ (
+ webbrowser.open("https://www.giacomovalli.com/openhdemg/Contacts/#meet-the-developers")
+ ),
+ ),
)
redirect_button.grid(row=2, column=1, sticky=E)
# Button for contact information
contact_path = master_path + "/gui_files/Contact.png"
- self.contact = tk.PhotoImage(file=contact_path)
+ self.contact = customtkinter.CTkImage(light_image=Image.open(contact_path),
+ size=(30,30))
contact_button = customtkinter.CTkButton(
self.right,
image=self.contact,
@@ -550,12 +573,18 @@ def __init__(self, master):
height=30,
bg_color="LightBlue4",
fg_color="LightBlue4",
+ command=lambda: (
+ (
+ webbrowser.open("https://www.giacomovalli.com/openhdemg/Contacts/")
+ ),
+ ),
)
contact_button.grid(row=3, column=1, sticky=E)
# Button for citatoin information
cite_path = master_path + "/gui_files/Cite.png"
- self.cite = tk.PhotoImage(file=cite_path)
+ self.cite = customtkinter.CTkImage(light_image=Image.open(cite_path),
+ size=(30,30))
cite_button = customtkinter.CTkButton(
self.right,
image=self.cite,
@@ -564,6 +593,12 @@ def __init__(self, master):
height=30,
bg_color="LightBlue4",
fg_color="LightBlue4",
+ command=lambda: (
+ # Check user OS for pdf opening
+ (
+ webbrowser.open("https://www.giacomovalli.com/openhdemg/Cite-Us/")
+ ),
+ ),
)
cite_button.grid(row=4, column=1, sticky=E)
@@ -928,7 +963,7 @@ def open_advanced_tools(self):
adv_box.set("Motor Unit Tracking")
# Matrix Orientation
- ttk.Label(self.a_window, text="Matrix Orientation*").grid(
+ ttk.Label(self.a_window, text="Matrix Orientation").grid(
row=3, column=0, sticky=(W, E)
)
self.mat_orientation_adv = StringVar()
@@ -941,7 +976,7 @@ def open_advanced_tools(self):
self.mat_orientation_adv.set("180")
# Matrix code
- ttk.Label(self.a_window, text="Matrix Code*").grid(
+ ttk.Label(self.a_window, text="Matrix Code").grid(
row=4, column=0, sticky=(W, E)
)
self.mat_code_adv = StringVar()
@@ -952,13 +987,9 @@ def open_advanced_tools(self):
matrix_code["state"] = "readonly"
matrix_code.grid(row=4, column=1, sticky=(W, E))
self.mat_code_adv.set("GR08MM1305")
-
- # Instruction
- ttk.Label(
- self.a_window,
- text="*Ignored for DEMUSE files, \ninsert random values",
- font=("Arial", 8),
- ).grid(row=5, column=1, sticky=W)
+
+ # Trace variabel for updating window
+ self.mat_code_adv.trace("w", self.on_matrix_none_adv)
# Analysis Button
adv_button = ttk.Button(
@@ -967,12 +998,36 @@ def open_advanced_tools(self):
command=self.advanced_analysis,
style="B.TButton",
)
- adv_button.grid(column=0, row=6)
+ 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.
+
+ 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
@@ -1229,6 +1284,7 @@ def edit_refsig(self):
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)
@@ -1243,6 +1299,50 @@ def edit_refsig(self):
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)
@@ -1258,7 +1358,7 @@ def filter_refsig(self):
Raises
------
- TypeError
+ AttributeError
When no reference signal file is available.
See Also
@@ -1275,19 +1375,19 @@ def filter_refsig(self):
# Plot filtered Refsig
self.in_gui_plotting(plot="refsig_fil")
- except TypeError:
+ except AttributeError:
tk.messagebox.showerror("Information", "Make sure a Refsig file is loaded.")
def remove_offset(self):
"""
- Instance Method that remves user specified/selected Refsig offset.
+ 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
------
- TypeError
+ AttributeError
When no reference signal file is available
See Also
@@ -1304,13 +1404,67 @@ def remove_offset(self):
# Update Plot
self.in_gui_plotting(plot="refsig_off")
- except TypeError:
+ 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 the start & end-point on the plot")
+ 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
@@ -1809,7 +1963,7 @@ def plot_emg(self):
)
# Matrix code
- ttk.Label(self.head, text="Matrix Code*").grid(row=0, column=3, sticky=(W))
+ 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", "None")
@@ -1817,8 +1971,11 @@ def plot_emg(self):
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))
+ 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
@@ -1828,13 +1985,6 @@ def plot_emg(self):
orientation.grid(row=1, column=4, sticky=(W, E))
self.mat_orientation.set("180")
- # Instruction
- ttk.Label(
- self.head,
- text="*Ignored for DEMUSE files, insert random values",
- font=("Arial", 8),
- ).grid(row=2, column=3, sticky=W)
-
# Plot derivation
# Button
deriv_button = ttk.Button(
@@ -1909,8 +2059,9 @@ def plot_emg(self):
matrix_canvas.create_image(0, 0, anchor="nw", image=self.matrix)
# Information Button
- self.info = tk.PhotoImage(
- file=os.path.dirname(os.path.abspath(__file__)) + "/gui_files/Info.png"
+ 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,
@@ -1921,15 +2072,8 @@ def plot_emg(self):
bg_color="LightBlue4",
fg_color="LightBlue4",
command=lambda: (
- # Get file path
- path := Path("./openhdemg/gui/gui_files/test.pdf").resolve(),
- # Check user OS for pdf opening
(
- webbrowser.open_new(str(path))
- if platform in ("win32", "linux")
- else os.system(f"open {str(path)}")
- if platform == "darwin"
- else None
+ webbrowser.open("https://www.giacomovalli.com/openhdemg/GUI_basics/#plot-motor-units")
),
),
)
@@ -1937,13 +2081,39 @@ def plot_emg(self):
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.
@@ -2196,12 +2366,35 @@ def plot_derivation(self):
This function is used to plot also the sorted RAW_SIGNAL.
"""
try:
- # Sort emg file
- sorted_file = openhdemg.sort_rawemg(
- emgfile=self.resdict,
- code=self.mat_code.get(),
- orientation=int(self.mat_orientation.get()),
- )
+ 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":
@@ -2229,7 +2422,8 @@ def plot_derivation(self):
+ "\nPotenital error sources:"
+ "\n - Matrix Code"
+ "\n - Matrix Orientation"
- + "\n - Figure size",
+ + "\n - Figure size arguments"
+ + "\n - Rows, Columns arguments",
)
except UnboundLocalError:
tk.messagebox.showerror(
@@ -2249,12 +2443,36 @@ def plot_muaps(self):
processing (i.e., differential) and computed on the same timewindow.
"""
try:
- # Sort emg file
- sorted_file = openhdemg.sort_rawemg(
- emgfile=self.resdict,
- code=self.mat_code.get(),
- orientation=int(self.mat_orientation.get()),
- )
+ 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)
@@ -2287,9 +2505,10 @@ def plot_muaps(self):
+ "\nPotenital error sources:"
+ "\n - Matrix Code"
+ "\n - Matrix Orientation"
- + "\n - Figure size"
+ + "\n - Figure size arguments"
+ "\n - Timewindow"
- + "\n - MU Number",
+ + "\n - MU Number"
+ + "\n - Rows, Columns arguments",
)
except UnboundLocalError:
@@ -2446,12 +2665,35 @@ def advanced_analysis(self):
self.head.destroy()
self.a_window.destroy()
- # Sort emg file
- sorted_rawemg = openhdemg.sort_rawemg(
- self.resdict,
- code=self.mat_code_adv.get(),
- orientation=int(self.mat_orientation_adv.get()),
- )
+ 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
+
+ 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,
@@ -2465,12 +2707,20 @@ def advanced_analysis(self):
+ "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.
@@ -2483,7 +2733,7 @@ def open_emgfile1(self):
--------
open_emgfile1(), openhdemg.askopenfile()
"""
- try:
+ try:
# Open OTB file
if self.filetype_adv.get() == "OTB":
self.emgfile1 = openhdemg.askopenfile(
@@ -2577,6 +2827,21 @@ def track_mus(self):
--------
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(
@@ -2586,6 +2851,8 @@ def track_mus(self):
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(),
@@ -2623,7 +2890,8 @@ def track_mus(self):
+ "\n - Extension Factor (in case of OTB file)"
+ "\n - Matrix Code"
+ "\n - Matrix Orientation"
- + "\n - Threshold",
+ + "\n - Threshold"
+ + "\n - Rows, Columns",
)
def remove_duplicates_between(self):
@@ -2646,15 +2914,32 @@ def remove_duplicates_between(self):
--------
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(
+ 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(),
@@ -2679,7 +2964,8 @@ def remove_duplicates_between(self):
+ "\n - Matrix Code"
+ "\n - Matrix Orientation"
+ "\n - Threshold"
- + "\n - Which",
+ + "\n - Which"
+ + "\n - Rows, Columns",
)
def calculate_conduct_vel(self):
diff --git a/openhdemg/library/electrodes.py b/openhdemg/library/electrodes.py
index 129b56c..fec4bcb 100644
--- a/openhdemg/library/electrodes.py
+++ b/openhdemg/library/electrodes.py
@@ -374,7 +374,7 @@ def sort_rawemg(
# columns. But first check for missing empty channel.
if n_rows * n_cols != sorted_rawemg.shape[1]:
raise ValueError(
- "Wrong number of channels in sorted_rawemg. Check also n_rows and n_cols"
+ "Number of specified rows and 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 c95cd24..3f51a66 100644
--- a/openhdemg/library/info.py
+++ b/openhdemg/library/info.py
@@ -122,7 +122,7 @@ def abbreviations(self):
"FSAMP": "Sampling frequency",
"IDR": "Instantaneous discharge rate",
"IED": "Inter electrode distance",
- "IPTS": "Impulse train (source of decomposition)",
+ "IPTS": "Impulse train (decomposed source)",
"MU": "Motor units",
"MUAP": "MUs action potential",
"PNR": "Pulse to noise ratio",
@@ -141,7 +141,7 @@ def abbreviations(self):
"FSAMP": "Sampling frequency",
"IDR": "Instantaneous discharge rate",
"IED": "Inter electrode distance",
- "IPTS": "Impulse train (source of decomposition)",
+ "IPTS": "Impulse train (decomposed source)",
"MU": "Motor units",
"MUAP": "MUs action potential",
"PNR": "Pulse to noise ratio",
diff --git a/openhdemg/library/mathtools.py b/openhdemg/library/mathtools.py
index c3fdec1..39114de 100644
--- a/openhdemg/library/mathtools.py
+++ b/openhdemg/library/mathtools.py
@@ -246,7 +246,7 @@ def compute_sil(ipts, mupulses):
Parameters
----------
ipts : pd.Series
- The source of decomposition (or pulse train, IPTS[mu]) of the MU of
+ The decomposed source (or pulse train, IPTS[mu]) of the MU of
interest.
mupulses : ndarray
The time of firing (MUPULSES[mu]) of the MU of interest.
@@ -261,6 +261,10 @@ def compute_sil(ipts, mupulses):
- compute_pnr : to calculate the Pulse to Noise ratio of a single MU.
"""
+ # Manage exception of no firings (e.g., as can happen in files from DEMUSE)
+ if len(mupulses) == 0:
+ return np.nan
+
# Extract source and peaks and align source and peaks based on IPTS
source = ipts.to_numpy()
peaks_idxs = mupulses - ipts.index[0]
@@ -296,22 +300,23 @@ def compute_sil(ipts, mupulses):
return sil
-def compute_pnr(ipts, mupulses, fsamp, separate_paired_firings=False):
+def compute_pnr(ipts, mupulses, fsamp, separate_paired_firings=True):
"""
Calculate the pulse to noise ratio for a single MU.
Parameters
----------
ipts : pd.Series
- The source of decomposition (or pulse train, IPTS[mu]) of the MU of
+ The decomposed source (or pulse train, IPTS[mu]) of the MU of
interest.
mupulses : ndarray
The time of firing (MUPULSES[mu]) of the MU of interest.
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. This can be set to
- True when working with pathological tremor.
+ 2012, this is common in pathological tremor. The user is encouraged to
+ use the default value (True) to increase the robustness of the
+ estimation.
Returns
-------
@@ -325,8 +330,8 @@ def compute_pnr(ipts, mupulses, fsamp, separate_paired_firings=False):
# According to Holobar 2014, the PNR is calculated as:
# 10 * log10((mean of firings) / (mean of noise))
- # Where instants in the source of decomposition are classified as firings
- # or noise based on a threshold value named "Pi" or "r".
+ # 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:
@@ -357,7 +362,6 @@ def compute_pnr(ipts, mupulses, fsamp, separate_paired_firings=False):
# of variations to estimate Pi or not.
# If both are used, Pi would be calculated as:
# Pi = CoVIDI + CoVpIDI
- # which remains valid also in tremor.
# Otherwise, Pi would be calculated as:
# Pi = CoV_all_IDI
@@ -392,12 +396,14 @@ def compute_pnr(ipts, mupulses, fsamp, separate_paired_firings=False):
idinonp = idi[idi >= (fsamp * 0.05)]
idip = idi[idi < (fsamp * 0.05)]
- CoVIDI = np.std(idinonp) / np.mean(idinonp)
- if math.isnan(CoVIDI):
+ if len(idinonp) > 1:
+ CoVIDI = np.std(idinonp) / np.mean(idinonp)
+ else:
CoVIDI = 0
- CoVpIDI = np.std(idip) / np.mean(idip)
- if math.isnan(CoVpIDI):
+ if len(idip) > 1:
+ CoVpIDI = np.std(idip) / np.mean(idip)
+ else:
CoVpIDI = 0
# Calculate Pi
diff --git a/openhdemg/library/muap.py b/openhdemg/library/muap.py
index af2a64a..ef9a2a5 100644
--- a/openhdemg/library/muap.py
+++ b/openhdemg/library/muap.py
@@ -743,7 +743,7 @@ def tracking(
emgfile1,
emgfile2,
firings="all",
- derivation="mono",
+ derivation="sd",
timewindow=50,
threshold=0.8,
matrixcode="GR08MM1305",
@@ -771,7 +771,7 @@ def tracking(
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 mono
+ derivation : str {mono, sd, dd}, default sd
Whether to compute the sta on the monopolar signal, or on the single or
double differential derivation.
timewindow : int, default 50
@@ -898,6 +898,8 @@ def tracking(
emgfile2, emgfile2_sorted, firings=firings, timewindow=timewindow * 2,
)
+ print("\nTracking started")
+
# Tracking function to run in parallel
def parallel(mu_file1): # Loop all the MUs of file 1
# Dict to fill with the 2d cross-correlation results
@@ -911,7 +913,7 @@ def parallel(mu_file1): # Loop all the MUs of file 1
sta_emgfile2[mu_file2],
finalduration=0.5
)
- aligned_sta1, aligned_sta2 = sta_emgfile1[mu_file1], sta_emgfile2[mu_file2]
+ #aligned_sta1, aligned_sta2 = sta_emgfile1[mu_file1], sta_emgfile2[mu_file2]
# Second, compute 2d cross-correlation
df1, _ = unpack_sta(aligned_sta1)
@@ -936,10 +938,10 @@ def parallel(mu_file1): # Loop all the MUs of file 1
return res
# Start parallel execution
- # Meausere running time
+ # Measure running time
t0 = time.time()
- res = Parallel(n_jobs=-1)(
+ res = Parallel(n_jobs=8)(
delayed(parallel)(mu_file1) for mu_file1 in range(emgfile1["NUMBER_OF_MUS"])
)
@@ -1032,7 +1034,7 @@ def remove_duplicates_between(
emgfile1,
emgfile2,
firings="all",
- derivation="mono",
+ derivation="sd",
timewindow=50,
threshold=0.9,
matrixcode="GR08MM1305",
@@ -1060,7 +1062,7 @@ def remove_duplicates_between(
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 mono
+ derivation : str {mono, sd, dd}, default sd
Whether to compute the sta on the monopolar signal, or on the single or
double differential derivation.
timewindow : int, default 50
@@ -1098,6 +1100,9 @@ def remove_duplicates_between(
-------
emgfile1, emgfile2 : dict
The original emgfiles without the duplicated MUs.
+ tracking_res : pd.DataFrame
+ The results of the tracking including the MU from file 1,
+ MU from file 2 and the normalised cross-correlation value (XCC).
See also
--------
@@ -1114,7 +1119,7 @@ def remove_duplicates_between(
>>> emgfile1 = emg.askopenfile(filesource="OTB", otb_ext_factor=8)
>>> emgfile2 = emg.askopenfile(filesource="OTB", otb_ext_factor=8)
- >>> emgfile1, emgfile2 = emg.remove_duplicates_between(
+ >>> emgfile1, emgfile2, tracking_res = emg.remove_duplicates_between(
... emgfile1,
... emgfile2,
... firings="all",
@@ -1163,7 +1168,7 @@ def remove_duplicates_between(
emgfile=emgfile1, munumber=mus_to_remove, if_single_mu="remove"
)
- return emgfile1, emgfile2
+ return emgfile1, emgfile2, tracking_res
else:
# Remove MUs from emgfile2
@@ -1173,7 +1178,7 @@ def remove_duplicates_between(
emgfile=emgfile2, munumber=mus_to_remove, if_single_mu="remove"
)
- return emgfile1, emgfile2
+ return emgfile1, emgfile2, tracking_res
elif which == "PNR":
# Create a list containing which MU to remove in which file based
@@ -1199,7 +1204,7 @@ def remove_duplicates_between(
emgfile=emgfile2, munumber=to_remove2, if_single_mu="remove"
)
- return emgfile1, emgfile2
+ return emgfile1, emgfile2, tracking_res
elif which == "SIL":
# Create a list containing which MU to remove in which file based
@@ -1225,7 +1230,7 @@ def remove_duplicates_between(
emgfile=emgfile2, munumber=to_remove2, if_single_mu="remove"
)
- return emgfile1, emgfile2
+ return emgfile1, emgfile2, tracking_res
else:
raise ValueError(
diff --git a/openhdemg/library/openfiles.py b/openhdemg/library/openfiles.py
index f9c7919..0524985 100644
--- a/openhdemg/library/openfiles.py
+++ b/openhdemg/library/openfiles.py
@@ -40,7 +40,7 @@
"REF_SIGNAL" : the reference signal
"PNR" : pulse to noise ratio
"SIL" : silouette score
- "IPTS" : pulse train
+ "IPTS" : pulse train (decomposed source)
"MUPULSES" : instants of firing
"FSAMP" : sampling frequency
"IED" : interelectrode distance
@@ -433,7 +433,7 @@ def get_otb_mupulses(binarymusfiring):
for i in binarymusfiring: # Loop all the MUs
my_ndarray = []
- for idx, x in binarymusfiring[i].items(): # Loop the MU firing times #This was iteritems() in older versions
+ for idx, x in binarymusfiring[i].items(): # Loop the MU firing times
if x > 0:
my_ndarray.append(idx) # Take the firing time and add it to the ndarray
diff --git a/openhdemg/library/plotemg.py b/openhdemg/library/plotemg.py
index 90320ea..eb63e3a 100644
--- a/openhdemg/library/plotemg.py
+++ b/openhdemg/library/plotemg.py
@@ -582,7 +582,7 @@ def plot_ipts(
tight_layout=True,
):
"""
- Plot the IPTS (source of decomposition).
+ Plot the IPTS (decomposed source).
IPTS is the non-binary representation of the MUs firing times.
diff --git a/setup.py b/setup.py
index 35607b4..424f559 100644
--- a/setup.py
+++ b/setup.py
@@ -15,6 +15,7 @@
"pyperclip>=1.8.2",
"scipy>=1.10.1",
"seaborn>=0.12.2",
+ "joblib>=1.3.1",
]
PACKAGES = [
@@ -23,7 +24,6 @@
"openhdemg.gui.gui_files",
"openhdemg.library",
"openhdemg.library.decomposed_test_files",
- "docs.md_graphics.Index",
]
CLASSIFIERS = [
@@ -45,24 +45,24 @@
from distutils.core import setup
this_directory = Path(__file__).parent
-LONG_DESCRIPTION = (this_directory / "README.md").read_text()
+long_descr = (this_directory / "README.md").read_text()
if __name__ == "__main__":
setup(
- name="testgiacomovalli",
+ name="openhdemg",
maintainer="Giacomo Valli",
maintainer_email="giacomo.valli@phd.unipd.it",
description="Open-source analysis of High-Density EMG data",
- long_description=LONG_DESCRIPTION,
+ long_description=long_descr,
long_description_content_type='text/markdown',
license="GPL-3.0",
project_urls={
"Documentation": "https://giacomovalli.com/openhdemg",
- "release notes": "https://giacomovalli.com/openhdemg/What%27s-New",
+ "Release Notes": "https://giacomovalli.com/openhdemg/What%27s-New",
"Source Code": "https://github.com/GiacomoValliPhD/openhdemg",
"Bug Tracker": "https://github.com/GiacomoValliPhD/openhdemg/issues",
},
- version=emg.__version__, # "0.1.0-beta.30",
+ version=emg.__version__,
install_requires=INSTALL_REQUIRES,
include_package_data=True,
packages=PACKAGES,