import unittest import numpy as np from my_util import helperFunctions as hF import matplotlib.pyplot as plt from warnings import warn class FrequencyFunctionsTester(unittest.TestCase): noise_levels = [0, 0.05, 0.1, 0.2] frequencies = [0, 1, 5, 30, 100, 500, 750, 1000] def setUp(self): pass def tearDown(self): pass def test_calculate_eod_frequency(self): start = 0 end = 5 step = 0.1 / 1000 freqs = [0, 1, 10, 500, 700, 1000] for freq in freqs: time = np.arange(start, end, step) eod = np.sin(freq*(2*np.pi) * time) self.assertEqual(freq, round(hF.calculate_eod_frequency(time, eod), 2)) def test_mean_freq_of_spiketimes_after_time_x(self): simulation_time = 8 for freq in self.frequencies: for n in self.noise_levels: spikes = generate_jittered_spiketimes(freq, n, end=simulation_time) sim_freq = hF.mean_freq_of_spiketimes_after_time_x(spikes, simulation_time / 4, time_in_ms=False) max_diff = round(n*(10+0.7*np.sqrt(freq)), 2) # print("noise: {:.2f}".format(n), "\texpected: {:.2f}".format(freq), "\tgotten: {:.2f}".format(round(sim_freq, 2)), "\tfreq diff: {:.2f}".format(abs(freq-round(sim_freq, 2))), "\tmax_diff:", max_diff) self.assertTrue(abs(freq-round(sim_freq)) <= max_diff, msg="expected freq: {:.2f} vs calculated: {:.2f}. max diff was {:.2f}".format(freq, sim_freq, max_diff)) def test_calculate_isi_frequency(self): simulation_time = 1 sampling_interval = 0.00005 for freq in self.frequencies: for n in self.noise_levels: spikes = generate_jittered_spiketimes(freq, n, end=simulation_time) sim_freq = hF.calculate_isi_frequency_trace(spikes, sampling_interval, time_in_ms=False) isis = np.diff(spikes) step_length = isis / sampling_interval rounded_step_length = np.around(step_length) expected_length = sum(rounded_step_length) length = len(sim_freq) self.assertEqual(expected_length, length) def test_calculate_isi_frequency_trace(self): sampling_intervals = [0.00005, 0.001, 0.01, 0.2, 0.5, 1] test1 = [0, 1, 2, 3, 4] # 1-1-1-1 only 1s in the result test2 = [0, 1, 3, 5, 6] # 1-2-2-1 test3 = [0, 3, 10, 12, 15] # 3-7-2-3 pos_tests = [test1, test2, test3] test4 = generate_jittered_spiketimes(100, 0.2) test5 = generate_jittered_spiketimes(500, 0.2) test6 = generate_jittered_spiketimes(1000, 0) realistic_tests = [test4, test5, test6] test_neg_isi = [0, 3, 4, 2, 5] # should raise error non sorted spiketimes test_too_small_sampling_rate = [0.001, 0.0015, 0.002] neg_tests = [test_neg_isi, test_too_small_sampling_rate] for test in pos_tests: for sampling_interval in sampling_intervals: calculated_trace = hF.calculate_isi_frequency_trace(test, sampling_interval, time_in_ms=False) diffs = np.diff(test) j = 0 count = 0 value = 1/diffs[j] for i in range(len(calculated_trace)): if calculated_trace[i] == value: count += 1 else: expected_length = round(diffs[j] / sampling_interval) # if there are multiple isis of the same length after each other add them together while expected_length < count and value == 1/diffs[j+1]: j += 1 expected_length += round(diffs[j] / sampling_interval, 0) self.assertEqual(count, expected_length, msg="Length of isi frequency part is not right: expected {:.1f} vs {:.1f}".format(float(count), expected_length)) j += 1 value = 1/diffs[j] count = 1 for test in neg_tests: self.assertRaises(ValueError, hF.calculate_isi_frequency_trace, test, 0.2, False) def test_calculate_time_and_frequency_trace(self): # !!! the produced frequency trace is tested in the test function for specifically the freq_Trace function sampling_intervals = [0.0001, 0.1, 0.5, 1] test1 = [0, 1, 2, 5, 7] test2 = [1, 3, 5, 6, 7, 10] test3 = [-1, 2, 4, 5, 11] pos_tests = [test1, test2, test3] for sampling_interval in sampling_intervals: for test in pos_tests: time, freq = hF.calculate_time_and_frequency_trace(test, sampling_interval, time_in_ms=False) self.assertEqual(test[0], time[0]) self.assertEqual(test[-1], round(time[-1]+sampling_interval)) def test_calculate_mean_of_frequency_traces(self): # TODO expand this test to more than this single test case test1_f = [0.5, 0.5, 0.5, 0.5, 1, 1, 1, 1] test1_t = np.arange(0, 8, 0.5) test2_f = [1, 2, 2, 3, 3, 4] test2_t = np.arange(0.5, 7.5, 0.5) time_traces = [test1_t, test2_t] freq_traces = [test1_f, test2_f] time, mean = hF.calculate_mean_of_frequency_traces(time_traces, freq_traces, 0.5) expected_time = np.arange(0.5, 7, 0.5) expected_mean = [0.75, 1.25, 1.25, 2, 2, 2.5] time_equal = np.all([time[i] == expected_time[i] for i in range(len(time))]) mean_equal = np.all([mean[i] == expected_mean[i] for i in range(len(mean))]) self.assertTrue(time_equal) self.assertTrue(mean_equal, msg="expected:\n" + str(expected_mean) + "\n actual: \n" + str(mean)) self.assertEqual(len(expected_mean), len(mean)) self.assertEqual(len(expected_time), len(time), msg="expected:\n" + str(expected_time) + "\n actual: \n" + str(time)) # TODO: # all_calculate_mean_isi_frequency_traces(spiketimes, sampling_interval, time_in_ms=False): #def test_all_calculate_mean_isi_frequency_traces(self): # hF.all_calculate_mean_isi_frequency_traces(, def generate_jittered_spiketimes(frequency, noise_level=0., start=0, end=5, method='normal'): if method is 'normal': return normal_dist_jittered_spikes(frequency, noise_level, start, end) elif method is 'poisson': if noise_level != 0: warn("Poisson jittered spike trains don't support a noise level! ") return poisson_jittered_spikes(frequency, start, end) def poisson_jittered_spikes(frequency, start, end): if frequency == 0: return [] mean_isi = 1 / frequency spikes = [] for part in np.arange(start, end+mean_isi, mean_isi): num_spikes_in_part = np.random.poisson(1) positions = np.sort(np.random.random(num_spikes_in_part)) while not __poisson_min_dist_test__(positions): positions = np.sort(np.random.random(num_spikes_in_part)) for pos in positions: spikes.append(part+pos*mean_isi) while spikes[-1] > end: del spikes[-1] return spikes def __poisson_min_dist_test__(positions): if len(positions) > 1: diffs = np.diff(positions) if len(diffs[diffs < 0.0001]) > 0: return False return True def normal_dist_jittered_spikes(frequency, noise_level, start, end): if frequency == 0: return [] mean_isi = 1 / frequency if noise_level == 0: return np.arange(start, end, mean_isi) isis = np.random.normal(mean_isi, noise_level*mean_isi, int((end-start)*1.05/mean_isi)) spikes = np.cumsum(isis) + start spikes = np.sort(spikes) if spikes[-1] > end: return spikes[spikes < end] else: additional_spikes = [spikes[-1] + np.random.normal(mean_isi, noise_level*mean_isi)] while additional_spikes[-1] < end: next_isi = np.random.normal(mean_isi, noise_level*mean_isi) additional_spikes.append(additional_spikes[-1] + next_isi) additional_spikes = np.sort(np.array(additional_spikes[:-1])) spikes = np.concatenate((spikes, additional_spikes)) return spikes def test_distribution(): simulation_time = 5 freqs = [5, 30, 100, 500, 1000] noise_level = [0.05, 0.1, 0.2, 0.3] repetitions = 1000 for freq in freqs: diffs_per_noise = [] for n in noise_level: diffs = [] print("#### - freq:", freq, "noise level:", n ) for reps in range(repetitions): spikes = generate_jittered_spiketimes(freq, n, end=simulation_time) sim_freq = hF.mean_freq_of_spiketimes_after_time_x(spikes, simulation_time / 4, time_in_ms=False) diffs.append(sim_freq-freq) diffs_per_noise.append(diffs) fig, axs = plt.subplots(1, len(noise_level), figsize=(3.5*len(noise_level), 4), sharex='all') for i in range(len(diffs_per_noise)): max_diff = np.max(np.abs(diffs_per_noise[i])) print("Freq: ", freq, "noise: {:.2f}".format(noise_level[i]), "mean: {:.2f}".format(np.mean(diffs_per_noise[i])), "max_diff: {:.4f}".format(max_diff)) bins = np.arange(-max_diff, max_diff, 2*max_diff/100) axs[i].hist(diffs_per_noise[i], bins=bins) axs[i].set_title('Noise level: {:.2f}'.format(noise_level[i])) plt.show() plt.close()