Source code for src.statistics.statistics_psd.statistics_psd_view

#!/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()