Compare commits

..

52 Commits

Author SHA1 Message Date
56b817956f falscher name 2024-11-23 12:33:36 +00:00
53f9ca8975 AM tuning von Sarah mit richtigem Namen 2024-11-23 12:33:01 +00:00
ad9ab32130 AM tuning von Sarah 2024-11-23 12:28:41 +00:00
c40bdc46fa movie 2 uploaded 2024-11-11 08:38:44 +00:00
39a7c823a8 reupload of movie 2024-11-11 08:17:47 +00:00
08d31c0cdf deleted the movie 2024-11-11 08:16:36 +00:00
2f4dc268ab Movie in new folder, easier to find 2024-11-11 08:14:23 +00:00
c5e98d966d Dateien nach "results" hochladen
Power spectrum animations for contrast 10% and 20%
2024-11-10 17:10:17 +00:00
Diana
22f533db85 idk 2024-10-28 17:32:19 +01:00
Diana
91f27157dc ? 2024-10-28 15:21:24 +01:00
Diana
861a689f4e Changes dianas plot 2024-10-28 15:19:37 +01:00
d2444240f2 [Animation_ChatGPT] updated 2024-10-28 15:11:32 +01:00
2515472d32 [Animation_ChatGPT] updated 2024-10-28 15:11:01 +01:00
Diana
851857c19d Changes plot function 2024-10-27 13:14:33 +01:00
Diana
4a7e963c03 Changed find_nearest_peak 2024-10-27 12:37:00 +01:00
Diana
09a86f5d6f dont remember 2024-10-27 12:36:32 +01:00
Diana
904fa5cf30 Added find_nearest_peak function 2024-10-26 14:11:04 +02:00
Diana
2e2e79f5fe no idea 2024-10-25 18:10:57 +02:00
Diana
79bb459da9 no idea 2024-10-25 18:10:15 +02:00
Diana
2947782652 Added find_AM 2024-10-25 17:16:15 +02:00
Diana
447e88b212 no idea 2024-10-25 17:10:56 +02:00
Diana
136e8a380c Merge branch 'main' of https://whale.am28.uni-tuebingen.de/git/mbergmann/gpgrewe2024 2024-10-25 17:09:46 +02:00
Diana
423fe451be Changes integral function 2024-10-25 17:09:29 +02:00
f888737aaa [tuning_curve_max] updated to final version 2024-10-25 16:41:45 +02:00
86f702b946 added results for tuning curves 2024-10-25 16:41:01 +02:00
3ea0083f4c new code for am plots 2024-10-25 15:44:08 +02:00
3e155beb19 new code for am plots 2024-10-25 15:43:28 +02:00
f7374bb75c Merge branch 'main' of https://whale.am28.uni-tuebingen.de/git/mbergmann/gpgrewe2024 2024-10-25 15:41:41 +02:00
337b9d8e0e Delete code/GP_Code.py 2024-10-25 13:39:39 +00:00
53358acbde Delete code/analysis_1.py 2024-10-25 13:39:17 +00:00
84d3e3d28e added results for tuning curve 10.16 2024-10-25 15:22:09 +02:00
Diana
3a1d7748ba Changed dianas plot function 2024-10-24 19:38:07 +02:00
Diana
528823f317 Changed dianas plot 2024-10-24 17:02:14 +02:00
Diana
51ab5f668b added calculate_integral_2 without p_power 2024-10-24 15:46:14 +02:00
Diana
a9378771ac Changes dianas plot function 2024-10-24 15:45:41 +02:00
25e51d3eed Merge branch 'main' of https://whale.am28.uni-tuebingen.de/git/mbergmann/gpgrewe2024 2024-10-24 15:29:06 +02:00
edc8d832e1 [useful_functions.py] added true_eodf function 2024-10-24 15:14:57 +02:00
Diana
6faef3c004 Merge branch 'main' of https://whale.am28.uni-tuebingen.de/git/mbergmann/gpgrewe2024 2024-10-24 13:50:13 +02:00
Diana
fadef774d4 changed highlight function 2024-10-24 13:49:57 +02:00
100c36e703 Merge branch 'main' of https://whale.am28.uni-tuebingen.de/git/mbergmann/gpgrewe2024 2024-10-24 11:02:25 +02:00
eea706e1b5 Merge branch 'main' of https://whale.am28.uni-tuebingen.de/git/mbergmann/gpgrewe2024 2024-10-24 10:59:55 +02:00
873be0a698 [useful_functions.py] added contrast_sorting function 2024-10-24 10:59:29 +02:00
Diana
89b1254425 Changed my plot function 2024-10-24 10:25:26 +02:00
Diana
b0d5d5ccfb Changed my plot function 2024-10-24 10:21:15 +02:00
Diana
6e54a8c508 Changed my plot function 2024-10-24 10:20:46 +02:00
e6e252ac1d Merge branch 'main' of https://whale.am28.uni-tuebingen.de/git/mbergmann/gpgrewe2024 2024-10-24 10:13:49 +02:00
2eac3ecde6 idk 2024-10-24 10:13:28 +02:00
Diana
e526026250 changed 2024-10-24 10:13:04 +02:00
732f4d39a9 [useful_functions.py] added sam_spectrum function 2024-10-24 09:51:47 +02:00
8f5c2f65e6 finished basic tuning curve 2024-10-24 09:17:25 +02:00
78af3d05bd Merge branch 'main' of https://whale.am28.uni-tuebingen.de/git/mbergmann/gpgrewe2024 2024-10-24 09:15:36 +02:00
40215bc5b5 updates 2024-10-24 09:15:22 +02:00
40 changed files with 49064 additions and 651 deletions

View File

@@ -9,12 +9,14 @@ import numpy as np
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
from scipy.signal import welch from scipy.signal import welch
from matplotlib.animation import FuncAnimation, PillowWriter from matplotlib.animation import FuncAnimation, PillowWriter
import useful_functions as f
# Generate distances and corresponding frequencies # Generate distances and corresponding frequencies
distances = np.arange(-400, 451, 1) distances = np.arange(-400, 2000, 1)
f1 = 800 f1 = 800
f2 = f1 + distances f2 = f1 + distances
# Time parameters # Time parameters
dt = 0.00001 dt = 0.00001
t = np.arange(0, 2, dt) t = np.arange(0, 2, dt)
@@ -27,37 +29,45 @@ axs[1].set_xlabel('Frequency [Hz]')
axs[1].set_ylabel('Power [1/Hz]') axs[1].set_ylabel('Power [1/Hz]')
axs[1].set_xlim(0, 1500) axs[1].set_xlim(0, 1500)
# Function to compute and plot the power spectrum
def plot_powerspectrum(i):
# Generate the signal as a sum of two sine waves
def plot_powerspectrum_2(i):
# Clear the previous plots # Clear the previous plots
axs[0].cla() axs[0].cla()
axs[1].cla() axs[1].cla()
# Generate the signal # Generate the signal as a sum of two sine waves
x = np.sin(2*np.pi*f1*t) + 0.2 * np.sin(2*np.pi*f2[i]*t) x = np.sin(2 * np.pi * f1 * t) + 0.8 * np.sin(2 * np.pi * f2[i] * t) # Second wave is 20% as strong
x[x < 0] = 0 # Apply half-wave rectification
# Plot the signal (first 20 ms for clarity) # Plot the signal (first 20 ms for clarity)
axs[0].plot(t[t < 0.02], x[t < 0.02]) axs[0].plot(t[t < 0.02], x[t < 0.02])
axs[0].set_title(f"Signal (f2={f2[i]} Hz)") axs[0].set_title(f"Signal (f2={f2[i]} Hz)")
axs[0].set_xlabel('Time [s]') axs[0].set_xlabel('Time [s]')
axs[0].set_ylabel('Amplitude') axs[0].set_ylabel('Amplitude')
axs[0].set_ylim(0, 1.2) axs[0].set_ylim(-2, 2)
x[x < 0] = 0 # Apply half-wave rectification (optional)
# Compute power spectrum # Compute power spectrum
freq, power = welch(x, fs=1/dt, nperseg=2**16) freq, power = welch(x, fs=1/dt, nperseg=2**16)
pref = np.max(power)
decibel_power = 10 * np.log10(power/pref)
AM = f.find_AM(f1, 0.5 * f1, f2[i])
# Plot the power spectrum # Plot the power spectrum
axs[1].plot(freq, power) axs[1].plot(freq, power)
axs[1].set_xlim(0, 1500) axs[1].set_xlim(0, 3000)
axs[1].set_ylim(0, 0.05)
axs[1].set_title(f'Power Spectrum (f2={f2[i]} Hz)') axs[1].set_title(f'Power Spectrum (f2={f2[i]} Hz)')
axs[1].set_xlabel('Frequency [Hz]') axs[1].set_xlabel('Frequency [Hz]')
axs[1].set_ylabel('Power [1/Hz]') axs[1].set_ylabel('Power [1/Hz]')
#axs[1].set_ylim(0, 0.00007)
axs[1].plot(f1, power[np.argmin(np.abs(freq-f1))], 'o')
axs[1].plot(f2[i], power[np.argmin(np.abs(freq-f2[i]))], 'd')
axs[1].plot(AM, power[np.argmin(np.abs(freq-AM))], '*')
axs[1].axvline(AM, alpha = 0.5, color = 'r')
# Create the animation # Create the animation
ani = FuncAnimation(fig, plot_powerspectrum, frames=len(distances), interval=500) ani = FuncAnimation(fig, plot_powerspectrum_2, frames=len(distances), interval=500)
# Display the animation # Save the animation as a GIF file (optional)
ani.save("signal_animation.gif", writer=PillowWriter(fps=30)) ani.save("sum_of_sinewaves.gif", writer=PillowWriter(fps=30))
plt.show()

View File

@@ -1,329 +0,0 @@
# -*- coding: utf-8 -*-
"""
Created on Tue Oct 22 15:21:41 2024
@author: diana
"""
import glob
import os
import rlxnix as rlx
import numpy as np
import matplotlib.pyplot as plt
import scipy.signal as sig
from scipy.integrate import quad
### FUNCTIONS ###
def binary_spikes(spike_times, duration, dt):
"""Converts the spike times to a binary representation.
Zeros when there is no spike, one when there is.
Parameters
----------
spike_times : np.array
The spike times.
duration : float
The trial duration.
dt : float
The temporal resolution.
Returns
-------
binary : np.array
The binary representation of the spike times.
"""
binary = np.zeros(int(np.round(duration / dt))) #Vektor, der genauso lang ist wie die stim time
spike_indices = np.asarray(np.round(spike_times / dt), dtype=int)
binary[spike_indices] = 1
return binary
def firing_rate(binary_spikes, box_width, dt=0.000025):
"""Calculate the firing rate from binary spike data.
Parameters
----------
binary_spikes : np.array
A binary array representing spike occurrences.
box_width : float
The width of the box filter in seconds.
dt : float, optional
The temporal resolution (time step) in seconds. Default is 0.000025 seconds.
Returns
-------
rate : np.array
An array representing the firing rate at each time step.
"""
box = np.ones(int(box_width // dt))
box /= np.sum(box) * dt # Normalization of box kernel to an integral of 1
rate = np.convolve(binary_spikes, box, mode="same")
return rate
def powerspectrum(rate, dt):
"""Compute the power spectrum of a given firing rate.
This function calculates the power spectrum using the Welch method.
Parameters
----------
rate : np.array
An array of firing rates.
dt : float
The temporal resolution (time step) in seconds.
Returns
-------
frequency : np.array
An array of frequencies corresponding to the power values.
power : np.array
An array of power spectral density values.
"""
frequency, power = sig.welch(rate, fs=1/dt, nperseg=2**15, noverlap=2**14)
return frequency, power
def calculate_integral(frequency, power, point, delta):
"""
Calculate the integral around a single specified point.
Parameters
----------
frequency : np.array
An array of frequencies corresponding to the power values.
power : np.array
An array of power spectral density values.
point : float
The harmonic frequency at which to calculate the integral.
delta : float
Half-width of the range for integration around the point.
Returns
-------
integral : float
The calculated integral around the point.
local_mean : float
The local mean value (adjacent integrals).
"""
indices = (frequency >= point - delta) & (frequency <= point + delta)
integral = np.trapz(power[indices], frequency[indices])
left_indices = (frequency >= point - 5 * delta) & (frequency < point - delta)
right_indices = (frequency > point + delta) & (frequency <= point + 5 * delta)
l_integral = np.trapz(power[left_indices], frequency[left_indices])
r_integral = np.trapz(power[right_indices], frequency[right_indices])
local_mean = np.mean([l_integral, r_integral])
return integral, local_mean
def valid_integrals(integral, local_mean, threshold, point):
"""
Check if the integral exceeds the threshold compared to the local mean and
provide feedback on whether the given point is valid or not.
Parameters
----------
integral : float
The calculated integral around the point.
local_mean : float
The local mean value (adjacent integrals).
threshold : float
Threshold value to compare integrals with local mean.
point : float
The harmonic frequency point being evaluated.
Returns
-------
valid : bool
True if the integral exceeds the local mean by the threshold, otherwise False.
message : str
A message stating whether the point is valid or not.
"""
valid = integral > (local_mean * threshold)
if valid:
message = f"The point {point} is valid, as its integral exceeds the threshold."
else:
message = f"The point {point} is not valid, as its integral does not exceed the threshold."
return valid, message
def prepare_harmonics(frequencies, categories, num_harmonics, colors):
"""
Prepare harmonic frequencies and assign colors based on categories.
Parameters
----------
frequencies : list
Base frequencies to generate harmonics.
categories : list
Corresponding categories for the base frequencies.
num_harmonics : list
Number of harmonics for each base frequency.
colors : list
List of colors corresponding to the categories.
Returns
-------
points : list
A flat list of harmonic frequencies.
color_mapping : dict
A dictionary mapping each category to its corresponding color.
points_categories : dict
A mapping of categories to their harmonic frequencies.
"""
points_categories = {}
for idx, (freq, category) in enumerate(zip(frequencies, categories)):
points_categories[category] = [freq * (i + 1) for i in range(num_harmonics[idx])]
points = [p for harmonics in points_categories.values() for p in harmonics]
color_mapping = {category: colors[idx] for idx, category in enumerate(categories)}
return points, color_mapping, points_categories
def find_exceeding_points(frequency, power, points, delta, threshold):
"""
Find the points where the integral exceeds the local mean by a given threshold.
Parameters
----------
frequency : np.array
An array of frequencies corresponding to the power values.
power : np.array
An array of power spectral density values.
points : list
A list of harmonic frequencies to evaluate.
delta : float
Half-width of the range for integration around the point.
threshold : float
Threshold value to compare integrals with local mean.
Returns
-------
exceeding_points : list
A list of points where the integral exceeds the local mean by the threshold.
"""
exceeding_points = []
for point in points:
# Calculate the integral and local mean for the current point
integral, local_mean = calculate_integral(frequency, power, point, delta)
# Check if the integral exceeds the threshold
valid, message = valid_integrals(integral, local_mean, threshold, point)
if valid:
exceeding_points.append(point)
return exceeding_points
def plot_highlighted_integrals(frequency, power, exceeding_points, delta, threshold, color_mapping, points_categories):
"""
Plot the power spectrum and highlight integrals that exceed the threshold.
Parameters
----------
frequency : np.array
An array of frequencies corresponding to the power values.
power : np.array
An array of power spectral density values.
exceeding_points : list
A list of harmonic frequencies that exceed the threshold.
delta : float
Half-width of the range for integration around each point.
threshold : float
Threshold value to compare integrals with local mean.
color_mapping : dict
A dictionary mapping each category to its color.
points_categories : dict
A mapping of categories to lists of points.
Returns
-------
fig : matplotlib.figure.Figure
The created figure object with highlighted integrals.
"""
fig, ax = plt.subplots()
ax.plot(frequency, power) # Plot power spectrum
for point in exceeding_points:
integral, local_mean = calculate_integral(frequency, power, point, delta)
valid, _ = valid_integrals(integral, local_mean, threshold, point)
if valid:
# Define color based on the category of the point
color = next((c for cat, c in color_mapping.items() if point in points_categories[cat]), 'gray')
# Shade the region around the point where the integral was calculated
ax.axvspan(point - delta, point + delta, color=color, alpha=0.3, label=f'{point:.2f} Hz')
print(f"Integral around {point:.2f} Hz: {integral:.5e}")
# Define left and right boundaries of adjacent regions
left_boundary = frequency[np.where((frequency >= point - 5 * delta) & (frequency < point - delta))[0][0]]
right_boundary = frequency[np.where((frequency > point + delta) & (frequency <= point + 5 * delta))[0][-1]]
# Add vertical dashed lines at the boundaries of the adjacent regions
ax.axvline(x=left_boundary, color="k", linestyle="--")
ax.axvline(x=right_boundary, color="k", linestyle="--")
ax.set_xlim([0, 1200])
ax.set_xlabel('Frequency (Hz)')
ax.set_ylabel('Power')
ax.set_title('Power Spectrum with Highlighted Integrals')
ax.legend()
return fig
### Data retrieval ###
datafolder = "../data"
example_file = os.path.join("..", "data", "2024-10-16-ad-invivo-1.nix")
dataset = rlx.Dataset(example_file)
sams = dataset.repro_runs("SAM")
sam = sams[2]
## Data for functions
df = sam.metadata["RePro-Info"]["settings"]["deltaf"][0][0]
stim = sam.stimuli[1]
potential, time = stim.trace_data("V-1")
spikes, _ = stim.trace_data("Spikes-1")
duration = stim.duration
dt = stim.trace_info("V-1").sampling_interval
### Apply Functions to calculate data ###
b = binary_spikes(spikes, duration, dt)
rate = firing_rate(b, box_width=0.05, dt=dt)
frequency, power = powerspectrum(b, dt)
### Important stuff ###
## Frequencies
eodf = stim.metadata[stim.name]["EODf"][0][0]
stimulus_frequency = eodf + df
AM = 50 # Hz
frequencies = [AM, eodf, stimulus_frequency]
categories = ["AM", "EODf", "Stimulus frequency"]
num_harmonics = [4, 2, 2]
colors = ["green", "orange", "red"]
delta = 2.5
threshold = 10
### Apply functions to make powerspectrum ###
integral, local = calculate_integral(frequency, power, eodf, delta)
valid = valid_integrals(integral, local, threshold, eodf)
points, color, categories = prepare_harmonics(frequencies, categories, num_harmonics, colors)
print(len(points))
exceeding = find_exceeding_points(frequency, power, points, delta, threshold)
print(len(exceeding))
## Plot power spectrum and highlight integrals
fig = plot_highlighted_integrals(frequency, power, points, delta, threshold, color, categories)
plt.show()

View File

@@ -0,0 +1,162 @@
import matplotlib.pyplot as plt
import numpy as np
import os
import glob
import rlxnix as rlx
from useful_functions import sam_data, sam_spectrum, calculate_integral, contrast_sorting, remove_poor
from tqdm import tqdm # Import tqdm for the progress bar
def load_files(file_path_pattern):
"""Load all files matching the pattern and remove poor quality files."""
all_files = glob.glob(file_path_pattern)
good_files = remove_poor(all_files)
return good_files
def process_sam_data(sam):
"""Process data for a single SAM and return necessary frequencies and powers."""
_, _, _, _, eodf, nyquist, stim_freq = sam_data(sam)
# Skip if stim_freq is NaN
if np.isnan(stim_freq):
return None
# Get power spectrum and frequency index for 1/2 EODf
freq, power = sam_spectrum(sam)
nyquist_idx = np.searchsorted(freq, nyquist)
# Get frequencies and powers before 1/2 EODf
freqs_before_half_eodf = freq[:nyquist_idx]
powers_before_half_eodf = power[:nyquist_idx]
# Get peak frequency and power
am_peak_f = freqs_before_half_eodf[np.argmax(powers_before_half_eodf)]
_, _, peak_power = calculate_integral(freq, power, am_peak_f)
return stim_freq, am_peak_f, peak_power
def plot_contrast_data(contrast_dict, file_tag, axs1, axs2):
"""Loop over all contrasts and plot AM Frequency and AM Power."""
for idx, contrast in enumerate(contrast_dict): # contrasts = keys of dict
ax1 = axs1[idx] # First figure (AM Frequency vs Stimulus Frequency)
ax2 = axs2[idx] # Second figure (AM Power vs Stimulus Frequency)
contrast_sams = contrast_dict[contrast]
# store all stim_freq and peak_power/nyquist_freq for this contrast
stim_freqs = []
am_freqs = []
peak_powers = []
# loop over all sams of one contrast
for sam in contrast_sams:
processed_data = process_sam_data(sam)
if processed_data is None:
continue
stim_freq, am_peak_f, peak_power = processed_data
stim_freqs.append(stim_freq)
am_freqs.append(am_peak_f)
peak_powers.append(peak_power)
# Plot in the first figure (AM Frequency vs Stimulus Frequency)
ax1.plot(stim_freqs, am_freqs, '-', label=file_tag)
ax1.set_title(f'Contrast {contrast}%')
ax1.grid(True)
ax1.legend(loc='upper right')
# Plot in the second figure (AM Power vs Stimulus Frequency)
ax2.plot(stim_freqs, peak_powers, '-', label=file_tag)
ax2.set_title(f'Contrast {contrast}%')
ax2.grid(True)
ax2.legend(loc='upper right')
def process_file(file, axs1, axs2):
"""Process a single file: extract SAMs and plot data for each contrast."""
dataset = rlx.Dataset(file)
sam_list = dataset.repro_runs('SAM')
# Extract the file tag (first part of the filename) for the legend
file_tag = '-'.join(os.path.basename(file).split('-')[0:4])
# Sort SAMs by contrast
contrast_dict = contrast_sorting(sam_list)
# Plot the data for each contrast
plot_contrast_data(contrast_dict, file_tag, axs1, axs2)
def loop_over_files(files, axs1, axs2):
"""Loop over all good files, process each file, and plot the data."""
for file in tqdm(files, desc="Processing files"):
process_file(file, axs1, axs2)
def main():
# Load files
file_path_pattern = '../data/16-10-24/*.nix'
good_files = load_files(file_path_pattern)
# Initialize figures
fig1, axs1 = plt.subplots(3, 1, constrained_layout=True, sharex=True) # For AM Frequency vs Stimulus Frequency
fig2, axs2 = plt.subplots(3, 1, constrained_layout=True, sharex=True) # For AM Power vs Stimulus Frequency
# Loop over files and process data
loop_over_files(good_files, axs1, axs2)
# Add labels to figures
fig1.supxlabel('Stimulus Frequency (df + EODf) [Hz]')
fig1.supylabel('AM Frequency [Hz]')
fig2.supxlabel('Stimulus Frequency (df + EODf) [Hz]')
fig2.supylabel('AM Power')
# Show plots
plt.show()
# Run the main function
if __name__ == '__main__':
main()
'''
Function that gets eodf and 1/2 eodf per contrast:
def calculate_mean_eodf(sams):
"""
Calculate mean EODf and mean 1/2 EODf for the given SAM data.
Args:
sams (list): List of SAM objects.
Returns:
mean_eodf (float): Mean EODf across all SAMs.
mean_half_eodf (float): Mean 1/2 EODf (Nyquist frequency) across all SAMs.
"""
eodfs = []
nyquists = []
for sam in sams:
_, _, _, _, eodf, nyquist, _ = sam_data(sam)
# Add to list only if valid
if not np.isnan(eodf):
eodfs.append(eodf)
nyquists.append(nyquist)
# Calculate mean EODf and 1/2 EODf
mean_eodf = np.mean(eodfs)
mean_half_eodf = np.mean(nyquists)
return mean_eodf, mean_half_eodf
'''
# TODO:
# display eodf values in plot for one cell, one intensity - integrate function for this
# lowpass with gaussian kernel for amplitude plot(0.5 sigma in frequency spectrum (dont filter too narrowly))
# fix legends (only for the cells that are being displayed)
# save figures
# plot remaining 3 plots, make 1 function for every option and put that in main code
# push files to git

View File

@@ -0,0 +1,96 @@
import matplotlib.pyplot as plt
import numpy as np
import os
import rlxnix as rlx
from useful_functions import sam_data, sam_spectrum, calculate_integral, contrast_sorting
# close all open plots
plt.close('all')
def plot_am_vs_frequency_single_intensity(file, contrast=20):
"""
Plots AM Power vs Stimulus Frequency and Nyquist Frequency vs Stimulus Frequency for
one intensity and one cell (file).
Parameters:
file (str): Path to the file (one cell).
intensity (int): The intensity level (contrast) to filter by.
"""
# Load the dataset for the given file
dataset = rlx.Dataset(file)
# Get SAMs for the whole recording
sam_list = dataset.repro_runs('SAM')
# Extract the file tag (first part of the filename) for the legend
file_tag = '-'.join(os.path.basename(file).split('-')[0:4])
# Sort SAMs by contrast
contrast_dict = contrast_sorting(sam_list)
# Get the SAMs for 20% contrast
sams = contrast_dict[contrast]
# Create a figure with 1 row and 2 columns
fig, axs = plt.subplots(2, 1, layout='constrained')
# Store all stim_freq, peak_power, and am_freq for the given contrast
stim_freqs = []
peak_powers = []
am_freqs = []
# Loop over all SAMs of the specified contrast
for sam in sams:
# Get stim_freq for each SAM
_, _, _, _, eodf, nyquist, stim_freq = sam_data(sam)
# Skip over empty SAMs
if np.isnan(stim_freq):
continue
# Get power spectrum from one SAM
freq, power = sam_spectrum(sam)
# get index of 1/2 eodf frequency
nyquist_idx = np.searchsorted(freq, nyquist)
# get frequencies until 1/2 eodf and powers for those frequencies
freqs_before_half_eodf = freq[:nyquist_idx]
powers_before_half_eodf = power[:nyquist_idx]
# Get the frequency of the highest peak before 1/2 EODf
am_peak_f = freqs_before_half_eodf[np.argmax(powers_before_half_eodf)]
# Get the power of the highest peak before 1/2 EODf
_, _, peak_power = calculate_integral(freq, power, am_peak_f)
# Collect data for plotting
stim_freqs.append(stim_freq)
peak_powers.append(peak_power)
am_freqs.append(am_peak_f)
# Plot AM Power vs Stimulus Frequency (first column)
ax = axs[0]
ax.plot(stim_freqs, am_freqs, '-')
ax.set_ylabel('AM Frequency [Hz]')
ax.grid(True)
# Plot AM Frequency vs Stimulus Frequency (second column)
ax = axs[1]
ax.plot(stim_freqs, peak_powers, '-')
ax.set_ylabel('AM Power')
ax.grid(True)
# Figure settings
fig.suptitle(f"Cell: {file_tag}, Contrast: {contrast}%")
fig.supxlabel("Stimulus Frequency (df + EODf) [Hz]")
plt.show()
# Call function
file = '../data/16-10-24/2024-10-16-ad-invivo-1.nix'
# Call the function to plot the data for one intensity and one cell
plot_am_vs_frequency_single_intensity(file)

View File

@@ -1,154 +0,0 @@
import rlxnix as rlx
import numpy as np
import matplotlib.pyplot as plt
import os
from scipy.signal import welch
# close all currently open figures
plt.close('all')
'''FUNCTIONS'''
def plot_vt_spikes(t, v, spike_t):
fig = plt.figure(figsize=(5, 2.5))
# alternative to ax = axs[0]
ax = fig.add_subplot()
# plot vt diagram
ax.plot(t[t<0.1], v[t<0.1])
# plot spikes into vt diagram, at max V
ax.scatter(spike_t[spike_t<0.1], np.ones_like(spike_t[spike_t<0.1]) * np.max(v))
plt.show()
def scatter_plot(colormap, stimuli_list, stimulus_count):
'''plot scatter plot for one sam with all 3 stims'''
fig = plt.figure()
ax = fig.add_subplot()
ax.eventplot(stimuli_list, colors=colormap)
ax.set_xlabel('Spike Times [ms]')
ax.set_ylabel('Loop #')
ax.set_yticks(range(stimulus_count))
ax.set_title('Spikes of SAM 3')
plt.show()
# create binary array with ones for spike times
def binary_spikes(spike_times, duration , dt):
'''Converts spike times to binary representation
Params
------
spike_times: np.array
spike times
duration: float
trial duration
dt: float
temporal resolution
Returns
--------
binary: np.array
The binary representation of the spike times
'''
binary = np.zeros(int(duration//dt)) # // is truncated division, returns number w/o decimals, same as np.round
spike_indices = np.asarray(np.round(spike_times//dt), dtype=int)
binary[spike_indices] = 1
return binary
# function to plot psth
def firing_rates(binary_spikes, box_width=0.01, dt=0.000025):
box = np.ones(int(box_width // dt))
box /= np.sum(box * dt) # normalize box kernel w interal of 1
rate = np.convolve(binary_spikes, box, mode='same')
return rate
def power_spectrum(rate, dt):
f, p = welch(rate, fs = 1./dt, nperseg=2**16, noverlap=2**15)
# algorithm makes rounding mistakes, we want to calc many spectra and take mean of those
# nperseg: length of segments in # datapoints
# noverlap: # datapoints that overlap in segments
return f, p
def power_spectrum_plot(f, p):
# plot power spectrum
fig = plt.figure()
ax = fig.add_subplot()
ax.plot(freq, power)
ax.set_xlabel('Frequency [Hz]')
ax.set_ylabel('Power [1/Hz]')
ax.set_xlim(0, 1000)
plt.show()
'''IMPORT DATA'''
datafolder = '../data' #./ wo ich gerade bin; ../ eine ebene höher; ../../ zwei ebenen höher
example_file = os.path.join('..', 'data', '2024-10-16-ac-invivo-1.nix')
'''EXTRACT DATA'''
dataset = rlx.Dataset(example_file)
# get sams
sams = dataset.repro_runs('SAM')
sam = sams[2]
# get potetial over time (vt curve)
potential, time = sam.trace_data('V-1')
# get spike times
spike_times, _ = sam.trace_data('Spikes-1')
# get stim count
stim_count = sam.stimulus_count
# extract spike times of all 3 loops of current sam
stimuli = []
for i in range(stim_count):
# get stim i from sam
stim = sam.stimuli[i]
potential_stim, time_stim = stim.trace_data('V-1')
# get spike_times
spike_times_stim, _ = stim.trace_data('Spikes-1')
stimuli.append(spike_times_stim)
eodf = stim.metadata[stim.name]['EODF'][0][0]
df = stim.metadata['RePro-Info']['settings']['deltaf'][0][0]
stimulus_freq = df + eodf
'''PLOT'''
# create colormap
colors = plt.cm.prism(np.linspace(0, 1, stim_count))
# timeline of whole rec
dataset.plot_timeline()
# voltage and spikes of current sam
plot_vt_spikes(time, potential, spike_times)
# spike times of all loops
scatter_plot(colors, stimuli, stim_count)
'''POWER SPECTRUM'''
# define variables for binary spikes function
spikes, _ = stim.trace_data('Spikes-1')
ti = stim.trace_info('V-1')
dt = ti.sampling_interval
duration = stim.duration
### spectrum
# vector with binary values for wholes length of stim
binary = binary_spikes(spikes, duration, dt)
# calculate firing rate
rate = firing_rates(binary, 0.01, dt) # box width of 10 ms
# plot psth or whatever
# plt.plot(time_stim, rate)
# plt.show()
freq, power = power_spectrum(binary, dt)
power_spectrum_plot(freq, power)
### TODO:
# then loop over sams/dfs, all stims, intensities
# when does stim start in eodf/ at which phase and how does that influence our signal --> alignment problem: egal wenn wir spectren haben
# we want to see peaks at phase locking to own and stim frequency, and at amp modulation frequency

View File

@@ -71,13 +71,22 @@ def power_spectrum_plot(f, p):
functions_path = r"C:\Users\diana\OneDrive - UT Cloud\Master\GPs\GP1_Grewe\Projekt\gpgrewe2024\code" functions_path = r"C:\Users\diana\OneDrive - UT Cloud\Master\GPs\GP1_Grewe\Projekt\gpgrewe2024\code"
sys.path.append(functions_path) sys.path.append(functions_path)
import useful_functions as u import useful_functions as u
import matplotlib.ticker as ticker
import matplotlib.patches as mpatches
import matplotlib.cm as cm
def plot_highlighted_integrals(frequency, power, points, color_mapping, points_categories, delta=2.5): def float_formatter(x, _):
"""Format the y-axis values as floats with a specified precision."""
return f'{x:.5f}'
def plot_highlighted_integrals(ax, frequency, power, points, nyquist, true_eodf, color_mapping, points_categories, delta=2.5):
""" """
Plot the power spectrum and highlight integrals that exceed the threshold. Highlights integrals on the existing axes of the power spectrum for a given dataset.
Parameters Parameters
---------- ----------
ax : matplotlib.axes.Axes
The axes on which to plot the highlighted integrals.
frequency : np.array frequency : np.array
An array of frequencies corresponding to the power values. An array of frequencies corresponding to the power values.
power : np.array power : np.array
@@ -93,47 +102,53 @@ def plot_highlighted_integrals(frequency, power, points, color_mapping, points_c
Returns Returns
------- -------
fig : matplotlib.figure.Figure None
The created figure object with highlighted integrals.
""" """
fig, ax = plt.subplots() # Define color mappings for specific categories
ax.plot(frequency, power) # Plot power spectrum category_colors = {
"AM": "#ff7f0e",
"Nyquist": "#2ca02c",
"EODf": "#d62728",
"Stimulus": "#9467bd",
"EODf (awake fish)": "#8c564b"
}
# Plot the power spectrum on the provided axes
for point in points: for point in points:
# Use the imported function to calculate the integral and local mean # Identify the category for the current point
integral, local_mean, _ = u.calculate_integral(frequency, power, point) point_category = next((cat for cat, pts in points_categories.items() if point in pts), "Unknown")
# Use the imported function to check if the point is valid # Assign color based on category, or default to grey if unknown
color = color_mapping.get(point_category, 'gray')
# Calculate the integral and check validity
integral, local_mean = u.calculate_integral_2(frequency, power, point)
valid = u.valid_integrals(integral, local_mean, point) valid = u.valid_integrals(integral, local_mean, point)
if valid: if valid:
# Define color based on the category of the point # Highlight valid points with a shaded region
color = next((c for cat, c in color_mapping.items() if point in points_categories[cat]), 'gray') ax.axvspan(point - delta, point + delta, color=color, alpha=0.35, label=f'{point_category}')
# Shade the region around the point where the integral was calculated
ax.axvspan(point - delta, point + delta, color=color, alpha=0.3, label=f'{point:.2f} Hz')
# Print out point and color
print(f"Integral around {point:.2f} Hz: {integral:.5e}, Color: {color}")
# Annotate the plot with the point and its color
ax.text(point, max(power) * 0.9, f'{point:.2f}', color=color, fontsize=10, ha='center')
# Define left and right boundaries of adjacent regions
left_boundary = frequency[np.where((frequency >= point - 5 * delta) & (frequency < point - delta))[0][0]]
right_boundary = frequency[np.where((frequency > point + delta) & (frequency <= point + 5 * delta))[0][-1]]
# Add vertical dashed lines at the boundaries of the adjacent regions
ax.axvline(x=left_boundary, color="k", linestyle="--")
ax.axvline(x=right_boundary, color="k", linestyle="--")
ax.plot(frequency, power, color="#1f77b4", linewidth=1.5)
# Use the category colors for 'Nyquist' and 'EODf' lines
ax.axvline(nyquist, color=category_colors.get("Nyquist", "#2ca02c"), linestyle="--")
ax.axvline(true_eodf, color=category_colors.get("EODf (awake fish)", "#8c564b"), linestyle="--")
# Set plot limits and labels
ax.set_xlim([0, 1200]) ax.set_xlim([0, 1200])
ax.set_xlabel('Frequency (Hz)') ax.set_ylim([0, 6e-5])
ax.set_ylabel('Power') ax.set_xlabel('Frequency (Hz)', fontsize=12)
ax.set_title('Power Spectrum with Highlighted Integrals') ax.set_ylabel(r'Power [$\frac{\mathrm{Hz^2}}{\mathrm{Hz}}$]', fontsize=12)
ax.legend() #ax.set_title('Power Spectrum with highlighted Integrals', fontsize=14)
# Apply float formatting to the y-axis
ax.yaxis.set_major_formatter(ticker.FuncFormatter(float_formatter))
return fig

View File

@@ -1,27 +1,47 @@
import glob import glob
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import numpy as np import numpy as np
import os
import rlxnix as rlx import rlxnix as rlx
import scipy as sp
import time
import useful_functions as f import useful_functions as f
from matplotlib.lines import Line2D
from tqdm import tqdm
# tatsächliche Power der peaks benutzen # plot the tuning curves for all cells y/n
single_plots = True
# variables
delta = 2.5 # radius for peak detection
# all files we want to use # all files we want to use
files = glob.glob("../data/2024-10-16-af*.nix") files = glob.glob("../data/2024-10-*.nix")
#EODf file for either day
eodf_file_w = glob.glob('../data/EOD_only/*-16*.nix')[0]
eodf_file_m = glob.glob('../data/EOD_only/*-21*.nix')[0]
# get only the good and fair filepaths # get only the good and fair filepaths
new_files = f.remove_poor(files) new_files = f.remove_poor(files)
#get the filenames as labels for plotting
labels = [os.path.splitext(os.path.basename(file))[0] for file in new_files]
# dict for all the different contrasts
contrast_files = {20 : {'power' :[], 'freq' : []},
10 : {'power' :[], 'freq' : []},
5 : {'power' :[], 'freq' : []}}
norm_contrast_files = {20 : {'power' :[], 'freq' : []},
10 : {'power' :[], 'freq' : []},
5 : {'power' :[], 'freq' : []}}
# loop over all the good files # loop over all the good files
for file in new_files: for u, file in tqdm(enumerate(new_files), total = len(new_files)):
#use correct eodf file
if "-16" in file:
orig_eodf = f.true_eodf(eodf_file_w)
else:
orig_eodf = f.true_eodf(eodf_file_m)
#define lists
contrast_frequencies = []
contrast_powers = []
# load a file # load a file
dataset = rlx.Dataset(file) dataset = rlx.Dataset(file)
# extract sams # extract sams
@@ -29,46 +49,146 @@ for file in new_files:
# get arrays for frequnecies and power # get arrays for frequnecies and power
stim_frequencies = np.zeros(len(sams)) stim_frequencies = np.zeros(len(sams))
peak_powers = np.zeros_like(stim_frequencies) peak_powers = np.zeros_like(stim_frequencies)
# loop over all sams contrast_sams = f.contrast_sorting(sams)
for i, sam in enumerate(sams):
# get sam frequency and stimuli eodfs = []
avg_dur, _, _, _, _, _, stim_frequency = f.sam_data(sam) # loop over the contrasts
print(avg_dur) for key in contrast_sams:
if np.isnan(avg_dur): stim_frequencies = np.zeros(len(contrast_sams[key]))
continue norm_stim_frequencies = np.zeros_like(stim_frequencies)
# use this to change lists basically and add the contrast somewhere peak_powers = np.zeros_like(stim_frequencies)
else:
stimuli = sam.stimuli for i, sam in enumerate(contrast_sams[key]):
# lists for the power spectra # get stimulus frequency and stimuli
frequencies = [] _, _, _, _, eodf, _, stim_frequency = f.sam_data(sam)
powers = [] sam_frequency, sam_power = f.sam_spectrum(sam)
# loop over the stimuli # detect peaks
for stimulus in stimuli: _, _, peak_powers[i] = f.calculate_integral(sam_frequency,
# get the powerspectrum for each stimuli
frequency, power = f.power_spectrum(stimulus)
# append the power spectrum data
frequencies.append(frequency)
powers.append(power)
#average over the stimuli
sam_frequency = np.mean(frequencies, axis = 0)
sam_power = np.mean(powers, axis = 0)
# detect and validate peaks
integral, surroundings, peak_power = f.calculate_integral(sam_frequency,
sam_power, stim_frequency) sam_power, stim_frequency)
valid = f.valid_integrals(integral, surroundings, stim_frequency)
#if there is a peak get the power in the peak powers
if valid == True:
peak_powers[i] = peak_power
# add the current stimulus frequency # add the current stimulus frequency
stim_frequencies[i] = stim_frequency stim_frequencies[i] = stim_frequency
norm_stim_frequencies[i] = stim_frequency - orig_eodf
# replae zeros with NaN eodfs.append(eodf)
peak_powers = np.where(peak_powers == 0, np.nan, peak_powers) # replae zeros with NaN
peak_powers = np.where(peak_powers == 0, np.nan, peak_powers)
plt.plot(stim_frequencies, peak_powers) contrast_frequencies.append(stim_frequencies)
contrast_powers.append(peak_powers)
if key == 20:
contrast_files[20]['freq'].append(stim_frequencies)
contrast_files[20]['power'].append(peak_powers)
norm_contrast_files[20]['freq'].append(norm_stim_frequencies)
norm_contrast_files[20]['power'].append(peak_powers)
elif key == 10:
contrast_files[10]['freq'].append(stim_frequencies)
contrast_files[10]['power'].append(peak_powers)
norm_contrast_files[10]['freq'].append(norm_stim_frequencies)
norm_contrast_files[10]['power'].append(peak_powers)
else:
contrast_files[5]['freq'].append(stim_frequencies)
contrast_files[5]['power'].append(peak_powers)
norm_contrast_files[5]['freq'].append(norm_stim_frequencies)
norm_contrast_files[5]['power'].append(peak_powers)
curr_eodf = np.mean(eodfs)
if single_plots == True:
# one cell with all contrasts in one subplot
fig, ax = plt.subplots()
ax.plot(contrast_frequencies[0], contrast_powers[0])
ax.plot(contrast_frequencies[1], contrast_powers[1])
if contrast_frequencies and contrast_frequencies[-1].size == 0:
if contrast_frequencies and contrast_frequencies[-2].size == 0:
ax.set_xlim(0,2000)
else:
ax.set_xlim(0,np.max(contrast_frequencies[-2]))
else:
ax.plot(contrast_frequencies[2], contrast_powers[2])
ax.set_xlim(0,np.max(contrast_frequencies[-1]))
ax.axvline(orig_eodf, color = 'black',linestyle = 'dashed', alpha = 0.8)
ax.axvline(2*curr_eodf, color = 'black', linestyle = 'dotted', alpha = 0.8)
ax.set_ylim(0, 0.00014)
ax.set_xlabel('stimulus frequency [Hz]')
ax.set_ylabel(r' power [$\frac{\mathrm{mV^2}}{\mathrm{Hz}}$]')
ax.set_title(f"{file}")
fig.legend(labels = ['20 % contrast', '10 % contrast','5 % contrast','EODf of awake fish', '1st harmonic of current EODf' ], loc = 'lower center', ncol = 3)
plt.tight_layout(rect=[0, 0.06, 1, 1])
plt.savefig(f'../results/tuning_curve{labels[u]}.svg')
#one cell with the contrasts in different subplots
fig, axs = plt.subplots(1, 3, figsize = [10,6], sharex = True, sharey = True)
for p, key in enumerate(contrast_files):
ax = axs[p]
ax.plot(contrast_files[key]['freq'][-1],contrast_files[key]['power'][-1])
ax.set_title(f"{key}")
ax.axvline(orig_eodf, color = 'black',linestyle = 'dashed')
ax.axvline(2*curr_eodf, color = 'darkblue', linestyle = 'dotted', alpha = 0.8)
if p == 0:
ax.set_ylabel(r'power [$\frac{\mathrm{mV^2}}{\mathrm{Hz}}$]', fontsize=12)
fig.supxlabel('stimulus frequency [Hz]', fontsize=12)
fig.suptitle(f'{labels[u]}')
fig.legend(labels = ['power of stimulus peak', 'EODf of awake fish','1st harmonic of current EODf'], loc = 'lower center', bbox_to_anchor=(0.5, 0.05), ncol = 3)
plt.tight_layout(rect=[0, 0.06, 1, 1])
plt.savefig(f'../results/contrast_tuning{labels[u]}.svg')
cmap = plt.get_cmap('viridis')
colors = cmap(np.linspace(0, 1, len(new_files)))
plt.close('all')
if len(new_files) < 10:
lines = []
labels_legend = []
fig, axs = plt.subplots(1, 3, figsize = [10,6], sharex = True, sharey = True)
for p, key in enumerate(contrast_files):
ax = axs[p]
for i in range(len(contrast_files[key]['power'])):
line, = ax.plot(contrast_files[key]['freq'][i],contrast_files[key]['power'][i], label = labels[i], color = colors[i])
ax.set_title(f"{key}")
ax.axvline(orig_eodf, color = 'black',linestyle = 'dashed')
if p == 0:
lines.append(line)
labels_legend.append(labels[i])
fig.supxlabel('stimulus frequency [Hz]', fontsize=12)
fig.supylabel(r'power [$\frac{\mathrm{mV^2}}{\mathrm{Hz}}$]', fontsize=12)
# Create a single legend beneath the plots with 3 columns
lines.append(Line2D([0], [0], color='black', linestyle='--')) # Custom line for the legend
labels_legend.append("Awake fish EODf") # Custom label
fig.legend(lines, labels_legend, loc='upper center', ncol=3, fontsize=10)
plt.tight_layout(rect=[0, 0, 1, 0.85]) # Adjust layout to make space for the legend
if "-16" in new_files[-1]:
plt.savefig('../results/tuning_curves_10_16.svg')
elif "-21" in new_files[0]:
plt.savefig('../results/tuning_curves_10_21.svg')
else:
for o in range(2):
lines = []
labels_legend = []
fig, axs = plt.subplots(1, 3, figsize = [10,6], sharex = True, sharey = True)
for p, key in enumerate(norm_contrast_files):
ax = axs[p]
for i in range(len(norm_contrast_files[key]['power'])):
line, = ax.plot(norm_contrast_files[key]['freq'][i],norm_contrast_files[key]['power'][i], label = labels[i], color = colors[i])
ax.set_title(f"{key}")
ax.axvline(0, color = 'black',linestyle = 'dashed')
if p == 0:
lines.append(line)
labels_legend.append(labels[i])
fig.supylabel(r'power [$\frac{\mathrm{mV^2}}{\mathrm{Hz}}$]', fontsize=12)
# Create a single legend beneath the plots with 3 columns
lines.append(Line2D([0], [0], color='black', linestyle='--')) # Custom line for the legend
labels_legend.append("Awake fish EODf") # Custom label
fig.legend(lines, labels_legend, loc='upper center', ncol=3, fontsize=10)
plt.tight_layout(rect=[0, 0, 1, 0.82]) # Adjust layout to make space for the legend
if o == 0:
ax.set_xlim(-600, 2100)
fig.supxlabel('stimulus frequency [Hz]', fontsize=12)
plt.savefig('../results/tuning_curves_norm.svg')
else:
ax.set_xlim(-600, 600)
fig.supxlabel(' relative stimulus frequency [Hz]', fontsize=12)
plt.savefig('../results/tuning_curves_norm_zoom.svg')
#plt.close('all')

View File

@@ -1,46 +1,13 @@
import glob
import pathlib
import numpy as np import numpy as np
import matplotlib.pyplot as plt
import rlxnix as rlx import rlxnix as rlx
from IPython import embed
from scipy.signal import welch from scipy.signal import welch
from scipy import signal
import matplotlib.pyplot as plt
from scipy.signal import find_peaks
def all_coming_together(freq_array, power_array, points_list, categories, num_harmonics_list, colors, delta=2.5, threshold=0.5): def all_coming_together(freq_array, power_array, points_list, categories, num_harmonics_list, colors, delta=2.5, threshold=0.5):
""" # Initialize dictionaries and lists
Process a list of points, calculating integrals, checking validity, and preparing harmonics for valid points. valid_points = []
Parameters
----------
freq_array : np.array
Array of frequencies corresponding to the power values.
power_array : np.array
Array of power spectral density values.
points_list : list
List of harmonic frequency points to process.
categories : list
List of corresponding categories for each point.
num_harmonics_list : list
List of the number of harmonics for each point.
colors : list
List of colors corresponding to each point's category.
delta : float, optional
Radius of the range for integration around each point (default is 2.5).
threshold : float, optional
Threshold value to compare integrals with local mean (default is 0.5).
Returns
-------
valid_points : list
A continuous list of harmonics for all valid points.
color_mapping : dict
A dictionary mapping categories to corresponding colors.
category_harmonics : dict
A mapping of categories to their harmonic frequencies.
messages : list
A list of messages for each point, stating whether it was valid or not.
"""
valid_points = [] # A continuous list of harmonics for valid points
color_mapping = {} color_mapping = {}
category_harmonics = {} category_harmonics = {}
messages = [] messages = []
@@ -50,21 +17,25 @@ def all_coming_together(freq_array, power_array, points_list, categories, num_ha
num_harmonics = num_harmonics_list[i] num_harmonics = num_harmonics_list[i]
color = colors[i] color = colors[i]
# Step 1: Calculate the integral for the point # Calculate the integral for the point
integral, local_mean, _ = calculate_integral(freq_array, power_array, point, delta) integral, local_mean = calculate_integral_2(freq_array, power_array, point)
# Step 2: Check if the point is valid # Check if the point is valid
valid = valid_integrals(integral, local_mean, point, threshold) valid = valid_integrals(integral, local_mean, point)
if valid: if valid:
# Step 3: Prepare harmonics if the point is valid # Prepare harmonics if the point is valid
harmonics, color_map, category_harm = prepare_harmonic(point, category, num_harmonics, color) harmonics, color_map, category_harm = prepare_harmonic(point, category, num_harmonics, color)
valid_points.extend(harmonics) # Use extend() to append harmonics in a continuous manner valid_points.extend(harmonics)
color_mapping.update(color_map) color_mapping[category] = color # Store color for category
category_harmonics.update(category_harm) category_harmonics[category] = harmonics
messages.append(f"The point {point} is valid.") messages.append(f"The point {point} is valid.")
else: else:
messages.append(f"The point {point} is not valid.") messages.append(f"The point {point} is not valid.")
# Debugging print statements
print("Color Mapping:", color_mapping)
print("Category Harmonics:", category_harmonics)
return valid_points, color_mapping, category_harmonics, messages return valid_points, color_mapping, category_harmonics, messages
@@ -154,6 +125,97 @@ def calculate_integral(freq, power, point, delta = 2.5):
local_mean = np.mean([l_integral, r_integral]) local_mean = np.mean([l_integral, r_integral])
return integral, local_mean, p_power return integral, local_mean, p_power
def calculate_integral_2(freq, power, peak_freq, delta=2.5):
"""
Calculate the integral around a specified peak frequency and the local mean.
Parameters
----------
freq : np.array
An array of frequencies corresponding to the power values.
power : np.array
An array of power spectral density values.
peak_freq : float
The frequency of the peak around which to calculate the integral.
delta : float, optional
Radius of the range for integration around the peak. The default is 2.5.
Returns
-------
integral : float
The calculated integral around the peak frequency.
local_mean : float
The local mean value (adjacent integrals).
"""
# Calculate integral around the peak frequency
indices = (freq >= peak_freq - delta) & (freq <= peak_freq + delta)
integral = np.trapz(power[indices], freq[indices])
# Calculate local mean from adjacent ranges
left_indices = (freq >= peak_freq - 5 * delta) & (freq < peak_freq - delta)
right_indices = (freq > peak_freq + delta) & (freq <= peak_freq + 5 * delta)
l_integral = np.trapz(power[left_indices], freq[left_indices]) if np.any(left_indices) else 0
r_integral = np.trapz(power[right_indices], freq[right_indices]) if np.any(right_indices) else 0
local_mean = np.mean([l_integral, r_integral])
return integral, local_mean
def contrast_sorting(sams, con_1 = 20, con_2 = 10, con_3 = 5, stim_count = 3, stim_dur = 2):
'''
sorts the sams into three contrasts
Parameters
----------
sams : ReproRuns
The sams to be sorted.
con_1 : int, optional
the first contrast. The default is 20.
con_2 : int, optional
the second contrast. The default is 10.
con_3 : int, optional
the third contrast. The default is 5.
stim_count : int, optional
the amount of stimuli per sam in a good sam. The default is 3.
stim_dur : int, optional
The stimulus duration. The default is 2.
Returns
-------
contrast_sams : dictionary
A dictionary containing all sams sorted to the contrasts.
'''
# dictionary for the contrasts
contrast_sams = {con_1 : [],
con_2 : [],
con_3 : []}
# loop over all sams
for sam in sams:
# get the contrast
avg_dur, contrast, _, _, _, _, _ = sam_data(sam)
# check for valid trails
if np.isnan(contrast):
continue
elif sam.stimulus_count < stim_count: #aborted trials
continue
elif avg_dur < (stim_dur * 0.8):
continue
else:
contrast = int(contrast) # get integer of contrast
# sort them accordingly
if contrast == con_1:
contrast_sams[con_1].append(sam)
elif contrast == con_2:
contrast_sams[con_2].append(sam)
elif contrast == con_3:
contrast_sams[con_3].append(sam)
else:
continue
return contrast_sams
def extract_stim_data(stimulus): def extract_stim_data(stimulus):
''' '''
extracts all necessary metadata for each stimulus extracts all necessary metadata for each stimulus
@@ -189,44 +251,66 @@ def extract_stim_data(stimulus):
stim_freq = round(stimulus.metadata[stimulus.name]['Frequency'][0][0]) stim_freq = round(stimulus.metadata[stimulus.name]['Frequency'][0][0])
stim_dur = stimulus.duration stim_dur = stimulus.duration
# calculates the amplitude modulation # calculates the amplitude modulation
amp_mod, ny_freq = AM(eodf, stim_freq) _, ny_freq = AM(eodf, stim_freq)
return amplitude, df, eodf, stim_freq,stim_dur, amp_mod, ny_freq amp_mod = find_AM(eodf, ny_freq, stim_freq)
return amplitude, df, eodf, stim_freq, stim_dur, amp_mod, ny_freq
def find_exceeding_points(frequency, power, points, delta, threshold): def find_AM(eodf, nyquist, stimulus_frequency):
t = signal.windows.triang(eodf) * nyquist
length_t2 = int(eodf*10)
t2 = np.tile(t, length_t2)
x_values = np.arange(len(t2))
#fig, ax = plt.subplots()
#ax.plot(t2)
#ax.scatter(stimulus_frequency, t2[np.argmin(np.abs(x_values - stimulus_frequency))])
#plt.grid()
AM = t2[np.argmin(np.abs(x_values - stimulus_frequency))]
return AM
def find_nearest_peak(freq, power, point, peak_search_range=30, threshold=None):
""" """
Find the points where the integral exceeds the local mean by a given threshold. Find the nearest peak within a specified range around a given point.
Parameters Parameters
---------- ----------
frequency : np.array freq : np.array
An array of frequencies corresponding to the power values. An array of frequencies corresponding to the power values.
power : np.array power : np.array
An array of power spectral density values. An array of power spectral density values.
points : list point : float
A list of harmonic frequencies to evaluate. The harmonic frequency for which to find the nearest peak.
delta : float peak_search_range : float, optional
Half-width of the range for integration around the point. Range in Hz to search for peaks around the specified point. The default is 30.
threshold : float threshold : float, optional
Threshold value to compare integrals with local mean. Minimum height of peaks to consider. If None, no threshold is applied.
Returns Returns
------- -------
exceeding_points : list peak_freq : float
A list of points where the integral exceeds the local mean by the threshold. The frequency of the nearest peak within the specified range, or the input point if no peak is found.
""" """
exceeding_points = [] # Define the range for peak searching
search_indices = (freq >= point - peak_search_range) & (freq <= point + peak_search_range)
for point in points: # Find peaks in the specified range
# Calculate the integral and local mean for the current point peaks, properties = find_peaks(power[search_indices], height=threshold)
integral, local_mean = calculate_integral(frequency, power, point, delta)
# Adjust peak indices to match the original frequency array
# Check if the integral exceeds the threshold peaks_freq = freq[search_indices][peaks]
valid, message = valid_integrals(integral, local_mean, threshold, point)
if valid:
exceeding_points.append(point)
return exceeding_points if peaks_freq.size == 0:
# No peaks detected, return the input point
return point
# Find the nearest peak to the specified point
nearest_peak_index = np.argmin(np.abs(peaks_freq - point))
peak_freq = peaks_freq[nearest_peak_index]
return peak_freq
def firing_rate(binary_spikes, dt = 0.000025, box_width = 0.01): def firing_rate(binary_spikes, dt = 0.000025, box_width = 0.01):
''' '''
@@ -399,6 +483,39 @@ def sam_data(sam):
avg_dur = np.mean(durations) avg_dur = np.mean(durations)
return avg_dur, sam_amp, sam_am, sam_df, sam_eodf, sam_nyquist, sam_stim return avg_dur, sam_amp, sam_am, sam_df, sam_eodf, sam_nyquist, sam_stim
def sam_spectrum(sam):
"""
Creates a power spectrum for a ReproRun of a SAM.
Parameters
----------
sam : ReproRun Object
The Reprorun the powerspectrum should be generated from.
Returns
-------
sam_frequency : np.array
The frequencies of the powerspectrum.
sam_power : np.array
The powers of the frequencies.
"""
stimuli = sam.stimuli
# lists for the power spectra
frequencies = []
powers = []
# loop over the stimuli
for stimulus in stimuli:
# get the powerspectrum for each stimuli
frequency, power = power_spectrum(stimulus)
# append the power spectrum data
frequencies.append(frequency)
powers.append(power)
#average over the stimuli
sam_frequency = np.mean(frequencies, axis = 0)
sam_power = np.mean(powers, axis = 0)
return sam_frequency, sam_power
def spike_times(stim): def spike_times(stim):
""" """
Reads out the spike times and other necessary parameters Reads out the spike times and other necessary parameters
@@ -427,6 +544,29 @@ def spike_times(stim):
dt = ti.sampling_interval dt = ti.sampling_interval
return spikes, stim_dur, dt # se changed spike_times to spikes so its not the same as name of function return spikes, stim_dur, dt # se changed spike_times to spikes so its not the same as name of function
def true_eodf(eodf_file):
'''
Calculates the Eodf of the fish when it was awake from a nix file.
Parameters
----------
eodf_file : str
path to the file with nix-file for the eodf.
Returns
-------
orig_eodf : int
The original eodf.
'''
eod_data = rlx.Dataset(eodf_file)#load eodf file
baseline = eod_data.repro_runs('baseline')[0]
eod, time = baseline.trace_data('EOD') # get time and eod
dt = baseline.trace_info('EOD').sampling_interval
eod_freq, eod_power = welch(eod, fs = 1/dt, nperseg = 2**16, noverlap = 2**15)
orig_eodf = round(eod_freq[np.argmax(eod_power)])
return orig_eodf
def valid_integrals(integral, local_mean, point, threshold = 0.1): def valid_integrals(integral, local_mean, point, threshold = 0.1):
""" """
Check if the integral exceeds the threshold compared to the local mean and Check if the integral exceeds the threshold compared to the local mean and
@@ -450,9 +590,9 @@ def valid_integrals(integral, local_mean, point, threshold = 0.1):
""" """
valid = integral > (local_mean * (1 + threshold)) valid = integral > (local_mean * (1 + threshold))
if valid: if valid:
print(f"The point {point} is valid, as its integral exceeds the threshold.") print(f"The point {point} is valid.")
else: else:
print(f"The point {point} is not valid, as its integral does not exceed the threshold.") print(f"The point {point} is not valid.")
return valid return valid
'''TODO Sarah: AM-freq plot: '''TODO Sarah: AM-freq plot:

BIN
protocol_movies/movie_1.mp4 Normal file

Binary file not shown.

BIN
protocol_movies/movie_2.mp4 Normal file

Binary file not shown.

BIN
protocol_movies/movie_3.mp4 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 42 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 43 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 41 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 46 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 47 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 41 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 41 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 48 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 50 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 48 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 48 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 52 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 38 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 39 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 37 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 42 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 43 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 38 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 38 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 42 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 44 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 38 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 40 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 44 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 71 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 99 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 99 KiB