import os
import re
from typing import List, Dict
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import pickle as pkl
from neurokin.utils.experiments.neural_correlates import (get_events_dict, get_neural_correlates_dict, compute_psd_for_row)
from neurokin.utils.experiments.neural_states_helper import (
get_per_animal_psds_df, save_data,
compute_events_percentage,
condense_distribution_event_types,
get_group_split, get_state_graph_stats,
get_runs_list)
from neurokin.utils.helper.load_config import read_config
from neurokin.utils.experiments.neural_correlates_plot import plot_psd_single_state
[docs]
class NeuralCorrelatesStates():
"""
This class allows to deal with unimodal and multimodal retrieval of sata based on states timestamps.
"""
def __init__(self,
timeslice: float,
experiment_structure_filepath: str,
skip_subjects: List[str] = [],
skip_conditions: List[str] = [],
skiprows: int = 2,
framerate: float = 200):
self.timeslice = timeslice
self.experiment_structure = read_config(experiment_structure_filepath, converts_keys_to_string=True)
self.skip_subjects = skip_subjects
self.skip_conditions = skip_conditions
self.skiprows = skiprows
self.framerate = framerate
self.stream_names: List[str] = []
self.ch_of_interest: Dict[str, int] = {}
self.freqs: np.array = None
self.fs: float = None
self.events_dataset: pd.DataFrame = None
self.raw_neural_correlates_dataset: pd.DataFrame = None
self.psds_correlates_dataset: pd.DataFrame = None
[docs]
def save_dataset(self, dataset, filename):
"""
Saves dataset dictionary to pickle file
:param dataset: str name of which dataset to save
:param filename: filename without extension
:return:
"""
accepted_datasets = ["events_dataset", "raw_neural_correlates_dataset", "psd_neural_correlates_dataset"]
if dataset not in accepted_datasets:
raise ValueError(f"Dataset not found. Please select one of the following : {accepted_datasets}")
if dataset == "events_dataset":
save_data(self.events_dataset, filename)
if dataset == "raw_neural_correlates_dataset":
save_data(self.fs, "fs")
save_data(self.raw_neural_correlates_dataset, filename)
if dataset == "psd_neural_correlates_dataset":
save_data(self.psds_correlates_dataset, filename)
save_data(self.freqs, "freqs")
[docs]
def create_events_dataset(self, experiment_path, verbose=False, file_starts_with=None):
"""
Takes the experiment structure and based on the runs listed there it looks for .csv files to import.
Then based on the labels, creates a dataframe containing condition, date, animal, run and event.
Each event contains the list of timestamps [start, end] for that specific run
:param experiment_path: folder where to find the dataset
:param verbose: if True it will print the currently processed run
:param file_starts_with: set if the files to fetch begin with a specific pattern
:return: dataframe containing data of each trial and timestamps of events
:param experiment_path:
"""
trial_list = get_runs_list(self.experiment_structure, self.skip_subjects, self.skip_conditions)
trial_data = []
for trial in trial_list:
date, subject, condition, run = trial
run_path = "/".join([experiment_path, date, subject, run]) + "/"
if verbose:
print(f"Currently processing: {date} - {subject} - {condition} - {run}")
try:
if file_starts_with:
event_path = [run_path + fname for fname in os.listdir(run_path)
if fname.startswith(file_starts_with) and fname.endswith(".csv")][0]
else:
event_path = [run_path + fname for fname in os.listdir(run_path) if
re.match(r"(?i)[a-z_-]+[0-9]{1,3}.csv", fname)][0]
events_dict = get_events_dict(event_path=event_path,
skiprows=self.skiprows,
framerate=self.framerate)
events = [events_dict["gait"],
events_dict["nlm_rest"],
events_dict["nlm_active"],
events_dict["fog_rest"],
events_dict["fog_active"]]
trial_data.append(trial + events)
except FileNotFoundError as error:
print(f"{error} No .csv events File Found For {date}, {subject}, {run}")
df = pd.DataFrame(trial_data, columns=["date",
"subject",
"condition",
"run",
"event_gait",
"event_nlm_rest",
"event_nlm_active",
"event_fog_rest",
"event_fog_active"])
self.events_dataset = df
return
[docs]
def create_raw_neural_dataset(self, experiment_path, stream_names: List[str], ch_of_interest: Dict[str, int],
verbose=False):
"""
Creates a pandas dataframe containing neural chunks corresponding to locomotor events in each run.
:param experiment_path: path to the dataset
:param stream_names: names of streams where the data is saved (TDT specific)
:param ch_of_interest: channel of interest. Neural chunks are for only one channel
:param verbose: whether to print info on the run currently processed
:return:
"""
if self.events_dataset is None:
print("Please create or load an events dictionary first, "
"using either the method create_events_dataset or load_dataset"
"If the dataset is loaded it should be assigned to the attribute events_dataset_dict")
return
self.stream_names = stream_names
self.ch_of_interest = ch_of_interest
trial_list = get_runs_list(self.experiment_structure, self.skip_subjects, self.skip_conditions)
trial_neural_data = []
for trial in trial_list:
date, subject, condition, run = trial
run_path = "/".join([experiment_path, date, subject, run]) + "/"
channel_of_interest = self.ch_of_interest[subject]
neural_event_df = self.events_dataset[(self.events_dataset["date"] == date) &
(self.events_dataset["subject"] == subject) &
(self.events_dataset["condition"] == condition) &
(self.events_dataset["run"] == run)]
if verbose:
print(f"Currently processing: {date} - {subject} - {condition} - {run}")
raw_neural_correlate, fs = get_neural_correlates_dict(neural_path=run_path,
channel_of_interest=channel_of_interest,
stream_names=stream_names,
events_df=neural_event_df,
time_cutoff=self.timeslice)
if fs is not None:
self.fs = fs
events = [raw_neural_correlate["event_gait"],
raw_neural_correlate["event_nlm_rest"],
raw_neural_correlate["event_nlm_active"],
raw_neural_correlate["event_fog_rest"],
raw_neural_correlate["event_fog_active"]]
trial_neural_data.append(trial + events + [fs])
columns = self.events_dataset.columns.tolist()
columns.append("fs")
df = pd.DataFrame(trial_neural_data, columns=columns)
self.raw_neural_correlates_dataset = df
[docs]
def create_psd_dataset(self, nfft, nov, zscore=False):
"""
Computes the Power Spectra Density of the neural correlates.
:param nfft: NFFT parameter to use for the Fourier Transform
:param nov: Overlap parameter to use for the Fourier Transform
:param verbose: if True it will print the currently processed run
:return: dictionary structured as condition, date, animal, run, event containing the PSD neural correlates.
"""
if self.raw_neural_correlates_dataset is None:
print("Please create or load a raw neural dictionary first, "
"using either the method create_events_dataset or load_dataset."
"If the dataset is loaded it should be assigned to the attribute raw_neural_correlates_dict")
return
events_columns = [c for c in self.raw_neural_correlates_dataset.columns if c.startswith("event")]
meta_columns = [c for c in self.raw_neural_correlates_dataset.columns if not c.startswith("event")]
psds_correlates_dataset = self.raw_neural_correlates_dataset.apply(lambda row: compute_psd_for_row(row,
events_columns,
nfft,
nov,
zscore), axis=1)
#psds_correlates_dataset = self.raw_neural_correlates_dataset[events_columns].applymap(
# get_psd_single_event_type,
# fs=self.fs,
# nfft=nfft,
# noverlap=nov,
# zscore=zscore)
self.psds_correlates_dataset = pd.concat((self.raw_neural_correlates_dataset[meta_columns],
psds_correlates_dataset), axis=1)
#self.freqs = np.fft.rfftfreq(n=nfft, d=1 / self.fs)
[docs]
def plot_prep_psds_dataset(self, test_sbj_list, condense=True):
"""
Creates a reduced dataframe of PSD ready to be plotted
:param test_sbj_list: list of subjects that belong in the experimental condition
:param condense: whether to retain the 5 states format or condense to the 3 format.
:return:
"""
per_animal_avg = get_per_animal_psds_df(self.psds_correlates_dataset, condense=condense)
group_split = get_group_split(test_sbj_list=test_sbj_list, df=per_animal_avg)
return group_split
[docs]
def plot_prep_states_distribution(self, test_sbj_list, condense=True, stat="std"):
"""
Fixed shortcut to generate a stats dictionary of the state distribution, ready to be plotted
:param test_sbj_list: list of subject IDs that belong to the test group
:param condense: bool if to condense from 5 categories to 3
:return: stats dictionary dataset structured as group, condition, state, stats {"mean":, "upper_bound", "lower_bound}
"""
events_percentage = compute_events_percentage(self.events_dataset)
if condense:
events_percentage = condense_distribution_event_types(events_percentage)
group_split = get_group_split(test_sbj_list=test_sbj_list, df=events_percentage)
stats = get_state_graph_stats(group_cond_df=group_split, stat=stat)
return stats
if __name__ == "__main__":
NFFT = 2 ** 12
NOV = int(NFFT / 4)
TIME_CUTOFF = 1.5
experiment_structure_path = "../../../analysis/neural_correlates_states_clean/experiment_structure.yaml"
pda = ["NWE00053", "NWE00054", "NWE00130", "NWE00160", "NWE00161", "NWE00162", "NWE00163", "NWE00164"]
CHANNEL_DICT = {"NWE00052": 6,
"NWE00053": 1,
"NWE00054": 1,
"NWE00089": 1,
"NWE00090": 1,
"NWE00092": 1,
"NWE00093": 1,
"NWE00130": 3,
"NWE00131": 2,
"NWE00158": 3,
"NWE00159": 3,
"NWE00166": 3,
"NWE00160": 3,
"NWE00161": 3,
"NWE00162": 3,
"NWE00163": 3,
"NWE00164": 3}
skip_animals = [i for i in CHANNEL_DICT.keys() if not i=="NWE00130"]
ncs = NeuralCorrelatesStates(timeslice=TIME_CUTOFF,
experiment_structure_filepath=experiment_structure_path,
skip_subjects=skip_animals)
ncs.events_dataset_dict = ncs.events_dataset = pd.read_pickle("../../../analysis/neural_correlates_states_clean/raw_states.pkl")
#ncs.fs = 24414.1
#ncs.raw_neural_correlates_dataset = pd.read_pickle(
# "../../../analysis/neural_correlates_states_clean/raw_neural.pkl")
#ncs.create_events_dataset("../../../analysis/neural_correlates_states_clean/data/neural_correlates_cervical_dataset/")
ncs.create_raw_neural_dataset("../../../analysis/neural_correlates_states_clean/data/neural_correlates_cervical_dataset/",
stream_names=["LFP1", "NPr1", "EOG1"],
ch_of_interest=CHANNEL_DICT)
with open('../../../analysis/neural_correlates_states_clean/data/neural_correlates_cervical_dataset/raw_neural_fs.pkl', 'wb') as handle:
pkl.dump(ncs.raw_neural_correlates_dataset, handle, protocol=pkl.HIGHEST_PROTOCOL)
ncs.create_psd_dataset(NFFT, NOV, zscore=False)
df = ncs.plot_prep_psds_dataset(test_sbj_list=pda, condense=True)
fig, ax = plt.subplots()
plot_psd_single_state(ax, df, group=True,
condition="baseline",
state="event_gait",
freqs=ncs.freqs,
color="crimson",
idx_min=2,
idx_max=75)
print("")