baseline_response_properties.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. import os
  2. import logging
  3. import numpy as np
  4. import pandas as pd
  5. import rlxnix as rlx
  6. from scipy.stats import circmean
  7. from scipy.signal import vectorstrength
  8. from joblib import Parallel, delayed
  9. from ..util import read_file_list, detect_eod_times
  10. def burst_fraction(spike_times, eod_period, threshold=1.5):
  11. """Calculate the fraction of spontaneous spikes that are fired in bursts (0. -> 1.0).
  12. Bursts are assumed for spikes that follow preceding spikes with a inter-spike-interval
  13. less than threshold times the eod period.
  14. Parameters
  15. ----------
  16. spike_times : np.array
  17. the spike times in seconds
  18. eod_period : float
  19. the duration of the EOD period in seconds
  20. threshold : float, optional
  21. The multiplier of the EOD period that defines the burst threshold, by default 1.5
  22. Returns
  23. -------
  24. float
  25. the burst fraction.
  26. """
  27. logging.info("\t\tCalculating burst fraction ...")
  28. isis = np.diff(spike_times)
  29. bf = len(isis[isis < threshold * eod_period]) / len(isis)
  30. return bf
  31. def vector_strength(spike_times, eod_times):
  32. """Calculates the vector strength. Characterizes the temporal locking of the spontaneous P-unit spikes
  33. to the EOD using scipy's implementation of the vector strength.
  34. Parameters
  35. ----------
  36. spike_times : np.array
  37. the spike times in seconds
  38. eod_times : np.array
  39. The eod times, i.e. zero crossings in seconds
  40. Returns
  41. -------
  42. np.array
  43. the phase of each spike in radiant
  44. np.array
  45. the absolute delay between spike and eod time in seconds
  46. np.array
  47. the phase of each spike relative to the eod period.
  48. float
  49. the vector strength
  50. """
  51. logging.info("\t\tCalculating vector strength ...")
  52. valid_spikes = spike_times[(spike_times >= eod_times[0]) & (spike_times < eod_times[-1])]
  53. indices = np.searchsorted(eod_times, valid_spikes)
  54. starts = eod_times[indices-1]
  55. ends = eod_times[indices]
  56. delays_abs = valid_spikes - starts
  57. delays_rel = delays_abs / (ends - starts)
  58. phases = delays_rel * 2 * np.pi
  59. vs, _ = vectorstrength(phases, 2 * np.pi)
  60. return phases, delays_abs, delays_rel, vs
  61. def baseline_properties(baseline_repro):
  62. """Analyzes the spontaneous baseline activity and returns these properties
  63. in a dictionary
  64. Parameters
  65. ----------
  66. baseline_repro : rlxnix.plugins.efish.baseline.Baseline
  67. The run of the Baseline Repro that contains the spontaneous activity.
  68. Returns
  69. -------
  70. dict
  71. dictionary of baseline properties.
  72. """
  73. logging.info(f"\tBaseline properties ...")
  74. spike_times = baseline_repro.spikes()
  75. eod, time = baseline_repro.eod(trace_name='LocalEOD-1')
  76. bf = burst_fraction(spike_times, 1./baseline_repro.eod_frequency)
  77. eod_times, _ = detect_eod_times(time, eod - np.mean(eod), threshold=0.5*np.max(eod), running_average=3)
  78. eodf = len(eod_times) / baseline_repro.duration
  79. phi, da, _, vs = vector_strength(spike_times, eod_times)
  80. properties = {"eod_frequency": eodf,
  81. "eod_period": 1/eodf,
  82. "firing_rate": baseline_repro.baseline_rate,
  83. "cv": baseline_repro.baseline_cv,
  84. "vector_strength": vs, "burst_fraction": bf,
  85. "phase": circmean(phi), "phase_time": np.mean(da)}
  86. return properties
  87. def analyze_cell(dataset_name, data_folder):
  88. logging.info("Analyzing cell {dataset_name} ...")
  89. print(dataset_name)
  90. filename = os.path.join(data_folder, dataset_name + ".nix")
  91. if not os.path.exists(filename):
  92. logging.error(f"NIX file for dataset {dataset_name} not found {filename}!")
  93. return None
  94. d = rlx.Dataset(filename)
  95. baseline_repros = d.repro_runs("BaselineActivity")
  96. if len(baseline_repros) == 0:
  97. return None
  98. baseline = baseline_repros[0] # for now just analyze the first one
  99. baseline_results = baseline_properties(baseline)
  100. baseline_results["dataset_id"] = dataset_name
  101. return baseline_results
  102. def run_baseline_analysis(list_folder, data_folder, num_cores=1):
  103. logging.basicConfig(level=logging._nameToLevel["WARNING"], force=True)
  104. datasets = read_file_list(os.path.join(list_folder, "baseline_datasets.dat"))
  105. processed_list = Parallel(n_jobs=num_cores)(delayed(analyze_cell)(dataset, data_folder) for dataset in datasets)
  106. df = pd.DataFrame(processed_list)
  107. return df