spikertools 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ # spikertools/__init__.py
2
+
3
+ from .models import Event
4
+ from .core import Session, Channel, Neuron, Events
5
+ from .plots import Plots
6
+
7
+ __all__ = ['Session', 'Channel', 'Event', 'Neuron', 'Events', 'Plots']
spikertools/core.py ADDED
@@ -0,0 +1,149 @@
1
+ # spikertools/core.py
2
+
3
+ from spikertools.models import Event, Neuron, Channel, Session, Events, Channels
4
+ from spikertools.plots import Plots
5
+ import numpy as np
6
+ from scipy.io import wavfile
7
+ import os
8
+ import re
9
+ from datetime import datetime
10
+
11
+ class Session:
12
+ def __init__(self, wav_file_path, events_file_path=None):
13
+ self.wav_file = wav_file_path
14
+ self.sample_rate, self.data = wavfile.read(wav_file_path)
15
+ print(f"Loaded WAV file: {wav_file_path}")
16
+ print(f"Sample rate: {self.sample_rate} Hz")
17
+ print(f"Data length: {len(self.data)} samples")
18
+
19
+ if events_file_path is None:
20
+ # Infer events file path
21
+ events_file = wav_file_path.replace('.wav', '-events.txt')
22
+ else:
23
+ events_file = events_file_path
24
+
25
+ print(f"Looking for events file: {events_file}")
26
+
27
+ # Initialize events and neurons before loading
28
+ self.events = Events([]) # Changed from list to Events
29
+ self.neurons = []
30
+
31
+ # Initialize the Plots class
32
+ self.plots = Plots(self)
33
+
34
+ if os.path.exists(events_file):
35
+ self._load_events(events_file)
36
+ print(f"Loaded {len(self.events)} events and {len(self.neurons)} neurons.")
37
+ else:
38
+ print("No events file found.")
39
+
40
+ # Initialize other attributes
41
+ self.channels = self._initialize_channels()
42
+ self.datetime = self._extract_datetime()
43
+
44
+ def _load_events(self, events_file):
45
+ with open(events_file, 'r') as f:
46
+ for line in f:
47
+ line = line.strip()
48
+ if not line or line.startswith('#'):
49
+ continue
50
+ parts = line.split(',')
51
+ if len(parts) != 2:
52
+ continue
53
+ name, timestamp = parts
54
+ name = name.strip()
55
+ try:
56
+ timestamp = float(timestamp.strip())
57
+ except ValueError:
58
+ print(f"Invalid timestamp: {timestamp} in line: {line}")
59
+ continue
60
+ self._add_event(name, timestamp)
61
+
62
+ def _add_event(self, name, timestamp):
63
+ # Normalize the event name by stripping leading/trailing spaces
64
+ name = name.strip()
65
+
66
+ # Assign colors to events and neurons
67
+ color = 'k' # Default color is black
68
+ color_map = {
69
+ '1': 'r', '2': 'g', '3': 'b', '4': 'c', '5': 'm',
70
+ 'Open': 'orange', 'Close': 'purple',
71
+ }
72
+ if name in color_map:
73
+ color = color_map[name]
74
+
75
+ # Check if event already exists
76
+ if self.events.has_event(name):
77
+ self.events[name].timestamps.append(timestamp)
78
+ return
79
+
80
+ # Handle threshold events for neurons
81
+ if 'thresh' in name.lower():
82
+ # Use regex to parse threshold events
83
+ match = re.match(r'_(neuron\d+)thresh(\w+)-(\d+)', name, re.IGNORECASE)
84
+ if match:
85
+ neuron_id, thresh_type_partial, thresh_value_str = match.groups()
86
+ thresh_value = -int(thresh_value_str) # Assuming thresholds are negative
87
+
88
+ # Find the neuron that corresponds to this threshold
89
+ target_neuron = None
90
+ for neuron in self.neurons:
91
+ if neuron_id in neuron.name:
92
+ target_neuron = neuron
93
+ break
94
+
95
+ if target_neuron:
96
+ if 'hig' in thresh_type_partial.lower():
97
+ target_neuron.thresh_high = thresh_value
98
+ #print(f"Set high threshold for {target_neuron.name} to {thresh_value}")
99
+ elif 'low' in thresh_type_partial.lower():
100
+ target_neuron.thresh_low = thresh_value
101
+ #print(f"Set low threshold for {target_neuron.name} to {thresh_value}")
102
+ else:
103
+ print(f"Unknown threshold type in event name: {name}")
104
+ else:
105
+ print(f"Neuron '{neuron_id}' not found for threshold event: {name}")
106
+ return # Threshold event processed; exit the method
107
+
108
+ # Check if neuron already exists for spike events
109
+ if name.startswith('_'):
110
+ for neuron in self.neurons:
111
+ if neuron.name == name:
112
+ neuron.timestamps.append(timestamp)
113
+ return
114
+
115
+ # If not, create a new neuron
116
+ neuron = Neuron(name, timestamps=[timestamp], color=color)
117
+ self.neurons.append(neuron)
118
+ #print(f"Added neuron: '{name}' with initial spike at timestamp: {timestamp}")
119
+ else:
120
+ # If not a neuron, treat as a regular event
121
+ event = Event(name, timestamps=[timestamp], color=color)
122
+ self.events.append(event)
123
+ #print(f"Added event: '{name}' at timestamp: {timestamp}")
124
+
125
+ def _initialize_channels(self):
126
+ # Initialize channels based on the data
127
+ channels = Channels([]) # Initialize empty Channels container (this uses models.Channels)
128
+ if self.data.ndim == 1:
129
+ channel = Channel(self.data, sample_rate=self.sample_rate, number=0) # This will now use models.Channel
130
+ channels.append(channel)
131
+ else:
132
+ for i in range(self.data.shape[1]):
133
+ channel_data = self.data[:, i]
134
+ channel = Channel(channel_data, sample_rate=self.sample_rate, number=i) # This will now use models.Channel
135
+ channels.append(channel)
136
+ return channels
137
+
138
+ def _extract_datetime(self):
139
+ # Extract datetime from the filename
140
+ basename = os.path.basename(self.wav_file)
141
+ match = re.search(r'(\d{4}-\d{2}-\d{2})_(\d{2}\.\d{2}\.\d{2})', basename)
142
+ if match:
143
+ date_str, time_str = match.groups()
144
+ date_parts = [int(part) for part in date_str.split('-')]
145
+ time_parts = [int(part) for part in time_str.split('.')]
146
+ return datetime(*date_parts, *time_parts)
147
+ return None
148
+
149
+