#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Statistics PSD View
"""
import numpy as np
from PyQt5.QtGui import QDoubleValidator
from PyQt5.QtWidgets import QWidget, QGridLayout, QLineEdit, QPushButton, QLabel, QVBoxLayout, QHBoxLayout, \
QButtonGroup, QScrollArea, QCheckBox
from matplotlib import pyplot as plt
from mne.stats import permutation_t_test, ttest_1samp_no_p, ttest_ind_no_p
from scipy.stats import ttest_ind, ttest_1samp
from utils.elements_selector.elements_selector_controller import multipleSelectorController
from utils.view.error_window import errorWindow
from utils.view.separator import create_layout_separator
__author__ = "Lemahieu Antoine"
__copyright__ = "Copyright 2022"
__credits__ = ["Lemahieu Antoine"]
__license__ = "GNU General Public License v3.0"
__maintainer__ = "Lemahieu Antoine"
__email__ = "Antoine.Lemahieu@ulb.be"
__status__ = "Dev"
[docs]class statisticsPsdView(QWidget):
def __init__(self, minimum_time, maximum_time, event_ids, all_channels_names):
"""
Window displaying the parameters for computing the power spectral density on the dataset.
:param minimum_time: Minimum time of the epochs from which the power spectral density can be computed.
:type minimum_time: float
:param maximum_time: Maximum time of the epochs from which the power spectral density can be computed.
:type maximum_time: float
:param event_ids: The events' ids
:type event_ids: dict
:param all_channels_names: All the channels' names
:type all_channels_names: list of str
"""
super().__init__()
self.power_spectral_density_listener = None
self.channels_selector_controller = None
self.channels_selection_opened = False
self.channels_selected = None
self.all_channels_names = all_channels_names
self.event_ids = event_ids
self.setWindowTitle("Statistics PSD")
self.global_layout = QVBoxLayout()
self.setLayout(self.global_layout)
# Statistics and on what to compute
self.statistics_widget = QWidget()
self.statistics_global_layout = QVBoxLayout()
self.statistics_title_label = QLabel("Select the two independent variables to compute the statistics on :")
# Independent variables
self.statistics_independent_variables_widget = QWidget()
self.statistics_independent_variables_layout = QHBoxLayout()
# First independent variable
self.first_independent_variable_widget = QWidget()
self.first_independent_variable_layout = QVBoxLayout()
self.first_independent_variable_label = QLabel("First independent variable :")
self.first_independent_variable_layout.addWidget(self.first_independent_variable_label)
self.first_independent_variable_button = QButtonGroup()
self.create_first_independent_variable_check_boxes()
self.first_independent_variable_widget.setLayout(self.first_independent_variable_layout)
self.first_independent_variable_scroll_area = QScrollArea()
self.first_independent_variable_scroll_area.setWidgetResizable(True)
self.first_independent_variable_scroll_area.setWidget(self.first_independent_variable_widget)
self.statistics_independent_variables_layout.addWidget(self.first_independent_variable_scroll_area)
# Second independent variable
self.second_independent_variable_widget = QWidget()
self.second_independent_variable_layout = QVBoxLayout()
self.second_independent_variable_label = QLabel("Second independent variable :")
self.second_independent_variable_layout.addWidget(self.second_independent_variable_label)
self.second_independent_variable_button = QButtonGroup()
self.create_second_independent_variable_check_boxes()
self.second_independent_variable_widget.setLayout(self.second_independent_variable_layout)
self.second_independent_variable_scroll_area = QScrollArea()
self.second_independent_variable_scroll_area.setWidgetResizable(True)
self.second_independent_variable_scroll_area.setWidget(self.second_independent_variable_widget)
self.statistics_independent_variables_layout.addWidget(self.second_independent_variable_scroll_area)
self.statistics_independent_variables_widget.setLayout(self.statistics_independent_variables_layout)
self.statistics_global_layout.addWidget(self.statistics_title_label)
self.statistics_global_layout.addWidget(self.statistics_independent_variables_widget)
self.statistics_widget.setLayout(self.statistics_global_layout)
# Buttons
self.channels_selection_widget = QWidget()
self.channels_selection_layout = QHBoxLayout()
self.channels_selection_button = QPushButton("&Channels ...", self)
self.channels_selection_button.clicked.connect(self.channels_selection_trigger)
self.channels_selection_layout.addWidget(QLabel("Channels : "))
self.channels_selection_layout.addWidget(self.channels_selection_button)
self.channels_selection_widget.setLayout(self.channels_selection_layout)
# Lines
self.lines_widget = QWidget()
self.lines_layout = QGridLayout()
self.minimum_frequency_line = QLineEdit("2,0")
self.minimum_frequency_line.setValidator(QDoubleValidator())
self.maximum_frequency_line = QLineEdit("25,0")
self.maximum_frequency_line.setValidator(QDoubleValidator())
self.minimum_time_line = QLineEdit(str(minimum_time))
self.minimum_time_line.setValidator(QDoubleValidator(minimum_time, maximum_time, 3))
self.maximum_time_line = QLineEdit(str(maximum_time))
self.maximum_time_line.setValidator(QDoubleValidator(minimum_time, maximum_time, 3))
self.time_points_line = QLineEdit("6 10 22")
self.lines_layout.addWidget(QLabel("Minimum frequency of interest (Hz) : "), 0, 0)
self.lines_layout.addWidget(self.minimum_frequency_line, 0, 1)
self.lines_layout.addWidget(QLabel("Maximum frequency of interest (Hz) : "), 1, 0)
self.lines_layout.addWidget(self.maximum_frequency_line, 1, 1)
self.lines_layout.addWidget(QLabel("Minimum time of interest (sec) : "), 2, 0)
self.lines_layout.addWidget(self.minimum_time_line, 2, 1)
self.lines_layout.addWidget(QLabel("Maximum time of interest (sec) : "), 3, 0)
self.lines_layout.addWidget(self.maximum_time_line, 3, 1)
self.lines_layout.addWidget(QLabel("Time points for the topographies to plot (sec) : "), 4, 0)
self.lines_layout.addWidget(self.time_points_line, 4, 1)
self.lines_widget.setLayout(self.lines_layout)
# Cancel confirm
self.cancel_confirm_widget = QWidget()
self.cancel_confirm_layout = QHBoxLayout()
self.cancel = QPushButton("&Cancel", self)
self.cancel.clicked.connect(self.cancel_power_spectral_density_trigger)
self.confirm = QPushButton("&Confirm", self)
self.confirm.clicked.connect(self.confirm_power_spectral_density_trigger)
self.cancel_confirm_layout.addWidget(self.cancel)
self.cancel_confirm_layout.addWidget(self.confirm)
self.cancel_confirm_widget.setLayout(self.cancel_confirm_layout)
# Layout
self.global_layout.addWidget(self.statistics_widget)
self.global_layout.addWidget(create_layout_separator())
self.global_layout.addWidget(self.channels_selection_widget)
self.global_layout.addWidget(self.lines_widget)
self.global_layout.addWidget(create_layout_separator())
self.global_layout.addWidget(self.cancel_confirm_widget)
[docs] def create_first_independent_variable_check_boxes(self):
event_ids = self.event_ids
self.first_independent_variable_button.setExclusive(True)
for i, event_id in enumerate(event_ids):
check_box = QCheckBox()
check_box.setText(event_id)
if i == 0:
check_box.setChecked(True)
self.first_independent_variable_layout.addWidget(check_box)
self.first_independent_variable_button.addButton(check_box, i)
[docs] def create_second_independent_variable_check_boxes(self):
event_ids = self.event_ids
self.second_independent_variable_button.setExclusive(True)
for i, event_id in enumerate(event_ids):
check_box = QCheckBox()
check_box.setText(event_id)
if i == 0:
check_box.setChecked(True)
self.second_independent_variable_layout.addWidget(check_box)
self.second_independent_variable_button.addButton(check_box, i)
"""
Triggers
"""
[docs] def cancel_power_spectral_density_trigger(self):
"""
Send the information to the controller that the computation is cancelled.
"""
self.power_spectral_density_listener.cancel_button_clicked()
[docs] def confirm_power_spectral_density_trigger(self):
"""
Retrieve the parameters and send the information to the controller.
"""
try:
if self.channels_selection_opened:
if len(self.channels_selected) >= 1:
minimum_frequency = None
maximum_frequency = None
if self.minimum_frequency_line.hasAcceptableInput():
minimum_frequency = float(self.minimum_frequency_line.text().replace(',', '.'))
if self.maximum_frequency_line.hasAcceptableInput():
maximum_frequency = float(self.maximum_frequency_line.text().replace(',', '.'))
minimum_time = float(self.minimum_time_line.text().replace(',', '.'))
maximum_time = float(self.maximum_time_line.text().replace(',', '.'))
topo_time_points = self.create_array_from_time_points()
stats_first_variable = self.get_first_independent_variable_selected()
stats_second_variable = self.get_second_independent_variable_selected()
self.power_spectral_density_listener.confirm_button_clicked(minimum_frequency, maximum_frequency, minimum_time,
maximum_time, topo_time_points, self.channels_selected,
stats_first_variable, stats_second_variable)
else:
error_message = "Please select at least 1 channel in the 'channel selection' menu before starting the computation."
error_window = errorWindow(error_message)
error_window.show()
else:
error_message = "Please select a channel in the 'channel selection' menu before starting the computation."
error_window = errorWindow(error_message)
error_window.show()
except Exception as e:
print(e)
[docs] def channels_selection_trigger(self):
"""
Open the multiple selector window.
The user can select multiple channels.
"""
title = "Select the channel used for the PSD computation :"
self.channels_selector_controller = multipleSelectorController(self.all_channels_names, title, box_checked=True, unique_box=True)
self.channels_selector_controller.set_listener(self.power_spectral_density_listener)
self.channels_selection_opened = True
"""
Plots
"""
[docs] @staticmethod
def plot_psd(psd_fig_one, topo_fig_one, psd_fig_two, topo_fig_two):
"""
Plot the power spectral density.
:param psd_fig_one: The figure of the actual power spectral density's data computed on the first independent variable
:type psd_fig_one: matplotlib.Figure
:param topo_fig_one: The figure of the topographies of the actual power spectral density's data computed on the first independent variable
:type topo_fig_one: matplotlib.Figure
:param psd_fig_two: The figure of the actual power spectral density's data computed on the second independent variable
:type psd_fig_two: matplotlib.Figure
:param topo_fig_two: The figure of the topographies of the actual power spectral density's data computed on the second independent variable
:type topo_fig_two: matplotlib.Figure
"""
try:
# First Variable
psd_fig_one.axes[0].set_title("Power spectral density - First independent variable")
psd_fig_one.show()
topo_fig_one.suptitle("PSD Topographies - First independent variable")
topo_fig_one.show()
# Second variable
psd_fig_two.axes[0].set_title("Power spectral density - Second independent variable")
psd_fig_two.show()
topo_fig_two.suptitle("PSD Topographies - Second independent variable")
topo_fig_two.show()
# Stats
psd_one_data = psd_fig_one.axes[0].lines[2].get_ydata()
psd_two_data = psd_fig_two.axes[0].lines[2].get_ydata()
# Get the first axis corresponding to the main plot, then the line 2 is the plotted psd data (0 and 1 are the axis).
# t_values = ttest_ind_no_p(psd_one_data, psd_two_data)
# t_values = ttest_1samp_no_p(np.array([psd_one_data, psd_two_data]))
p_values = []
for i in range(len(psd_one_data)):
new_t_values, new_p_values = ttest_1samp(np.array([psd_one_data[i], psd_two_data[i]]), popmean=0.0, nan_policy="omit")
p_values.append(new_p_values)
print(p_values)
x = psd_fig_one.axes[0].lines[2].get_xdata()
# Plot
fig, ax = plt.subplots()
ax.plot(x, p_values)
ax.set_title("P-values for PSD")
ax.set_ylim([0.001, 1.0])
ax.set_yscale("log")
fig.show()
except Exception as error:
error_message = "An error has occurred during the computation of the PSD"
error_window = errorWindow(error_message, detailed_message=str(error))
error_window.show()
"""
Utils
"""
[docs] def create_array_from_time_points(self):
"""
Create an array of time points depending on the time points given.
:return: The time points for the topomaps.
:rtype: list of float
"""
try:
time_points = self.time_points_line.text()
if time_points == "":
return [6.0, 10.0, 22.0]
else:
split_time_points = time_points.split()
float_time_points = []
for time_point in split_time_points:
float_time_points.append(float(time_point.replace(',', '.')))
return float_time_points
except Exception as error:
error_message = "The time points provided are not following the right format, please use integer separated " \
"by spaces."
error_window = errorWindow(error_message)
error_window.show()
"""
Setters
"""
[docs] def set_listener(self, listener):
"""
Set the listener to the controller.
:param listener: Listener to the controller.
:type listener: powerSpectralDensityController
"""
self.power_spectral_density_listener = listener
[docs] def set_channels_selected(self, channels_selected):
"""
Set the channels selected in the multiple selector window.
:param channels_selected: Channels selected.
:type channels_selected: list of str
"""
self.channels_selected = channels_selected
"""
Getters
"""
[docs] def get_first_independent_variable_selected(self):
"""
Get the first independent variable selected by the user.
:return: First independent variable selected
:rtype: str
"""
for i in range(1, self.first_independent_variable_layout.count()): # Being at 1 because of the label
check_box = self.first_independent_variable_layout.itemAt(i).widget()
if check_box.isChecked():
return check_box.text()
[docs] def get_second_independent_variable_selected(self):
"""
Get the second independent variable selected by the user.
:return: Second independent variable selected
:rtype: str
"""
for i in range(1, self.second_independent_variable_layout.count()): # Being at 1 because of the label
check_box = self.second_independent_variable_layout.itemAt(i).widget()
if check_box.isChecked():
return check_box.text()