Skip to content

Commit 8e3ff76

Browse files
authored
Merge pull request #28 from UWB-Biocomputing/Issue.25_Generation-of-Synthetic-Data
Issue.25 generation of synthetic data
2 parents bd1c161 + 07d50d9 commit 8e3ff76

File tree

5 files changed

+770
-0
lines changed

5 files changed

+770
-0
lines changed

Avalanches/TestCases/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Generation of Synthetic Data for Avalanche Analysis Test Cases
2+
3+
## About
4+
5+
Scripts contained in this directory are used to generate test data for the Avalanche Analysis program.
6+
7+
8+
## Data Format
9+
Data is generated and saved as a .csv file in the data subdirectory. The data consists of a time stamp followed by the numerical representation of a spike's 2D co-ordinate grid location. Default grid is 100x100 and spike location values are from 1 to 10000.
10+
11+
Multiple spikes may be associated with the same timestamp.
12+
13+
Example:
14+
| Time | Spike1 | Spike2 | Spike3 | Spike4 |
15+
|-----|------|------|------|------|
16+
| 279 | 2142 | | | |
17+
| 281 | 2036 | 1733 | | |
18+
| 328 | 1034 | 1530 | 2489 | |
19+
| 379 | 1124 | 7254 | 3574 | 6581 |
20+
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
##############################################################################
2+
# Author: Lawrence Scott
3+
# Project: Test Case Data Generation for Neuronal Avalanche Detection Program
4+
# Creation Date: 4/13/2025
5+
# Date of Last Modification: 4/30/2025
6+
##############################################################################
7+
# Purpose: To create test cases with a known number of spikes and avalanches
8+
# to verify algorithmic accuracy and performance.
9+
10+
import csv
11+
import math
12+
import os
13+
import pathlib
14+
import random
15+
import sys
16+
17+
# ---------------------------------------------------------------------------
18+
# Generate Grid
19+
# Creates a 2d array of 100x100 with values ranging from 1 to 10000
20+
# Increase from 1 (top left), to 10000 (bottom right)
21+
# Serves as storage array for (x,y) neuron grid
22+
def makeGrid():
23+
neuron = 1
24+
data = [[0 for x in range(100)] for y in range(100)]
25+
for i in range(100):
26+
for p in range(100):
27+
data[i][p] = neuron
28+
neuron += 1
29+
30+
# Verification Check
31+
#print(f'First Neuron: {data[0][0]}, Last Neuron: {data[99][99]}')
32+
return data
33+
34+
# ---------------------------------------------------------------------------
35+
# Generate Spike
36+
# Randomly selects an (x,y) co-ordinate for a spike
37+
def makeSpike():
38+
return random.randint(0, 99), random.randint(0, 99)
39+
40+
# ---------------------------------------------------------------------------
41+
# Generate Spike - Threshold
42+
# Selects an (x,y) co-ordinate for a spike within the avalanche threshold
43+
def makeSpikeSpecific(neuronXY):
44+
x = random.randint(-8, 8)
45+
tempX = neuronXY[0] + x
46+
if (tempX < 0):
47+
x2 = 0
48+
elif (tempX > 99):
49+
x2 = 99
50+
else:
51+
x2 = tempX
52+
# d = sqrt(x^2-y^2) < 8; define spatial threshold less than 8
53+
y = math.sqrt(max(0, 63-(x2-neuronXY[0])**2))
54+
y = random.randint(-int(y), int(y))
55+
tempY = neuronXY[1] + y
56+
if (tempY < 0):
57+
y2 = 0
58+
elif (tempY > 99):
59+
y2 = 99
60+
else:
61+
y2 = tempY
62+
return x2, y2
63+
64+
# ---------------------------------------------------------------------------
65+
# Spatial threshold check
66+
# Threshold distance to be included in an avalanche must be less than 8
67+
# d = sqrt((x2-x1)^2 + (y2-y1)^2) < 8
68+
def spatialCheck(neuronA, neuronB):
69+
return math.sqrt((neuronA[0]-neuronB[0])**2 + (neuronA[1]-neuronB[1])**2) < 8
70+
71+
# ---------------------------------------------------------------------------
72+
# Generate Avalanche
73+
# Takes the available number of spikes and generates an avalanche
74+
def generateAvalanche(currentTime, numSpikes):
75+
76+
# DECLARE VARIABLES
77+
# number of spikes left to generate
78+
spikesRemaining = numSpikes
79+
80+
# serves as the temporal queue for spikes, contains tuples(timestamp, [neuron array])
81+
temporalQueue = []
82+
83+
# generate the first spike in the data set
84+
currentSet = []
85+
currentSpike = makeSpike()
86+
spikesRemaining = spikesRemaining - 1
87+
currentSet.append(currentSpike)
88+
tempTup = currentTime, currentSet
89+
temporalQueue.append(tempTup)
90+
currentSet = []
91+
92+
# temporal threshold is 50
93+
currentTime = currentTime + random.randint(0, 49)
94+
95+
while spikesRemaining > 0:
96+
nextSpike = makeSpikeSpecific(currentSpike)
97+
98+
# If spikes are not spatially close, generate a new spike
99+
while not spatialCheck(currentSpike, nextSpike):
100+
nextSpike = makeSpikeSpecific(currentSpike)
101+
102+
currentSet.append(nextSpike)
103+
spikesRemaining = spikesRemaining - 1
104+
chance = random.randint(0, 100)
105+
# Chance defines the frequency at which multiple spikes will appear in the same time period
106+
if chance < 70 or spikesRemaining == 0:
107+
tempTup = currentTime, currentSet
108+
temporalQueue.append(tempTup)
109+
currentTime = currentTime + random.randint(0, 49)
110+
currentSet = []
111+
112+
currentSpike = nextSpike
113+
114+
# Debugging
115+
#for item in temporalQueue:
116+
# print(f'{item}')
117+
118+
return temporalQueue
119+
120+
# ---------------------------------------------------------------------------
121+
# data generator
122+
# Arguments: spike count, avalanche count, single nonavalanche spike count
123+
# NOTE: the number of single, nonavalanche, spikes is an upper limit.
124+
# the algorithm will randomly select how many of these spikes
125+
# to intermix between the avalanches
126+
def makeStuff(numSpikes, numAvalanches, singleSpikes):
127+
# Define and check for the minimum number of spikes per avalanche
128+
# 2 spikes per avalanche
129+
spikesPerAvalancheControl = 2
130+
if (numSpikes / numAvalanches) < spikesPerAvalancheControl:
131+
raise Exception("Invalid number of spikes for the given number of avalanches.")
132+
133+
# DECLARE VARIABLES
134+
spikesRemainingAva = numSpikes # number of spikes left to generate
135+
avalanchesRemaining = numAvalanches # number of avalanches left to generate
136+
spikesRemainingSingle = singleSpikes # number of single spikes left to generate
137+
138+
currentTime = random.randint(0, 600)
139+
140+
# serves as the temporal queue for spikes, contains tuples(timestamp, [neuron array])
141+
temporalQueue = []
142+
143+
while avalanchesRemaining > 0:
144+
if (spikesRemainingAva / avalanchesRemaining) >= spikesPerAvalancheControl:
145+
availableSpikes = 2 + spikesRemainingAva - (spikesPerAvalancheControl * avalanchesRemaining)
146+
spikesToUse = random.randint(2, availableSpikes)
147+
tempQueue = generateAvalanche(currentTime, spikesToUse)
148+
spikesRemainingAva = spikesRemainingAva - spikesToUse
149+
avalanchesRemaining = avalanchesRemaining - 1
150+
temporalQueue = temporalQueue + tempQueue
151+
152+
currentTime = currentTime + random.randint(51, 1400)
153+
154+
if spikesRemainingSingle > 0:
155+
spikesToUse = random.randint(1, spikesRemainingSingle)
156+
for i in range(0, spikesToUse):
157+
tempSpike = makeSpike()
158+
tempSet = []
159+
tempSet.append(tempSpike)
160+
tempTup = currentTime, tempSet
161+
temporalQueue.append(tempTup)
162+
currentTime = currentTime + random.randint(51, 1400)
163+
spikesRemainingSingle = spikesRemainingSingle - spikesToUse
164+
165+
# Debugging
166+
#for item in temporalQueue:
167+
# print(f'{item}')
168+
169+
return temporalQueue
170+
# ---------------------------------------------------------------------------
171+
# main()
172+
if __name__ == '__main__':
173+
174+
if len(sys.argv) > 1:
175+
if sys.argv[1].__eq__("help") or sys.argv[1].__eq__("Help"):
176+
print('-----Help-----')
177+
print('First Argument: number of spikes available for avalanches')
178+
print('Second Argument: number of avalanches')
179+
print('Third Argument: number of single nonavalanche spikes')
180+
print('Ex: python basicAvalanches.py 20 2 25')
181+
else:
182+
avaSpikes = int(sys.argv[1])
183+
avalanches = int(sys.argv[2])
184+
singleSpikes = int(sys.argv[3])
185+
output = makeStuff(avaSpikes, avalanches, singleSpikes)
186+
grid = makeGrid()
187+
results = []
188+
tempArray = []
189+
for item in output:
190+
tempArray.append(item[0])
191+
for ele in item[1]:
192+
tempArray.append(grid[ele[0]][ele[1]])
193+
results.append(tempArray)
194+
tempArray = []
195+
196+
# Generate data directory if does not exist
197+
pathlib.Path("./data").mkdir(parents=True, exist_ok=True)
198+
i = 0
199+
path = './data/basicAvalanches' + str(i) + '.csv'
200+
while (os.path.isfile(path)):
201+
i = i + 1
202+
path = './data/basicAvalanches' + str(i) + '.csv'
203+
204+
with open(path, 'w', newline='') as csvfile:
205+
write = csv.writer(csvfile)
206+
write.writerows(results)
207+
208+
print(f'File Generated: {path}')
209+
210+
else:
211+
print('Must provide input arguments.')
212+
print('Ex: python basicAvalanches.py 20 2 25')

0 commit comments

Comments
 (0)