@@ -197,25 +197,32 @@ def open_biocam_file_header(filename):
197197 min_digital = experiment_settings ["ValueConverter" ]["MinDigitalValue" ]
198198 scale_factor = experiment_settings ["ValueConverter" ]["ScaleFactor" ]
199199 sampling_rate = experiment_settings ["TimeConverter" ]["FrameRate" ]
200+ num_frames = rf ['TOC' ][- 1 ,- 1 ]
200201
202+ wellID = None
201203 for key in rf :
202204 if key [:5 ] == "Well_" :
205+ wellID = key
203206 num_channels = len (rf [key ]["StoredChIdxs" ])
204- if len (rf [key ]["Raw" ]) % num_channels :
205- raise RuntimeError (f"Length of raw data array is not multiple of channel number in { key } " )
206- num_frames = len (rf [key ]["Raw" ]) // num_channels
207+ if "Raw" in rf [key ]:
208+ if len (rf [key ]["Raw" ]) % num_channels :
209+ raise RuntimeError (f"Length of raw data array is not multiple of channel number in { key } " )
210+ if num_frames != len (rf [key ]["Raw" ]) // num_channels :
211+ raise RuntimeError (f"Estimated number of frames from TOC does not match length of raw data array in { key } " )
207212 break
208- try :
209- num_channels_x = num_channels_y = int (np .sqrt (num_channels ))
210- except NameError :
213+ if not wellID :
211214 raise RuntimeError ("No Well found in the file" )
215+ num_channels_x = num_channels_y = int (np .sqrt (num_channels ))
212216 if num_channels_x * num_channels_y != num_channels :
213217 raise RuntimeError (f"Cannot determine structure of the MEA plate with { num_channels } channels" )
214218 channels = 1 + np .concatenate (np .transpose (np .meshgrid (range (num_channels_x ), range (num_channels_y ))))
215219
216220 gain = scale_factor * (max_uv - min_uv ) / (max_digital - min_digital )
217221 offset = min_uv
218- read_function = readHDF5t_brw4
222+ if "Raw" in rf [wellID ]:
223+ read_function = readHDF5t_brw4
224+ elif "EventsBasedSparseRaw" in rf [wellID ]:
225+ read_function = readHDF5t_brw4_sparse
219226
220227 return dict (
221228 file_handle = rf ,
@@ -249,3 +256,115 @@ def readHDF5t_brw4(rf, t0, t1, nch):
249256 for key in rf :
250257 if key [:5 ] == "Well_" :
251258 return rf [key ]["Raw" ][nch * t0 : nch * t1 ].reshape ((t1 - t0 , nch ), order = "C" )
259+
260+
261+ def readHDF5t_brw4_sparse (rf , t0 , t1 , nch ):
262+ useSyntheticNoise = True
263+ noiseStdDev = None
264+ startFrame = t0
265+ numFrames = t1 - t0
266+ for key in rf :
267+ if key [:5 ] == "Well_" :
268+ wellID = key
269+ break
270+ # initialize an empty (fill with zeros) data collection
271+ data = np .zeros ((nch , numFrames ), dtype = np .int16 )
272+ # fill the data collection with Gaussian noise if requested
273+ if useSyntheticNoise :
274+ generateSyntheticNoise (rf , data , wellID , startFrame , numFrames , stdDev = noiseStdDev )
275+ # fill the data collection with the decoded event based sparse raw data
276+ decodeEventBasedRawData (rf , data , wellID , startFrame , numFrames )
277+ return data .T
278+
279+
280+ def decodeEventBasedRawData (rf , data , wellID , startFrame , numFrames ):
281+ # Source: Documentation by 3Brain
282+ # https://gin.g-node.org/NeuralEnsemble/ephy_testing_data/src/master/biocam/documentation_brw_4.x_bxr_3.x_bcmp_1.x_in_brainwave_5.x_v1.1.3.pdf
283+ # collect the TOCs
284+ toc = np .array (rf ["TOC" ])
285+ eventsToc = np .array (rf [wellID ]["EventsBasedSparseRawTOC" ])
286+ # from the given start position and duration in frames, localize the corresponding event positions
287+ # using the TOC
288+ tocStartIdx = np .searchsorted (toc [:, 1 ], startFrame )
289+ tocEndIdx = min (
290+ np .searchsorted (toc [:, 1 ], startFrame + numFrames , side = "right" ) + 1 ,
291+ len (toc ) - 1 )
292+ eventsStartPosition = eventsToc [tocStartIdx ]
293+ eventsEndPosition = eventsToc [tocEndIdx ]
294+ # decode all data for the given well ID and time interval
295+ binaryData = rf [wellID ]["EventsBasedSparseRaw" ][eventsStartPosition :eventsEndPosition ]
296+ binaryDataLength = len (binaryData )
297+ pos = 0
298+ while pos < binaryDataLength :
299+ chIdx = int .from_bytes (binaryData [pos :pos + 4 ], byteorder = "little" , signed = True )
300+ pos += 4
301+ chDataLength = int .from_bytes (binaryData [pos :pos + 4 ], byteorder = "little" , signed = True )
302+ pos += 4
303+ chDataPos = pos
304+ while pos < chDataPos + chDataLength :
305+ fromInclusive = int .from_bytes (binaryData [pos :pos + 8 ], byteorder = "little" , signed = True )
306+ pos += 8
307+ toExclusive = int .from_bytes (binaryData [pos :pos + 8 ], byteorder = "little" , signed = True )
308+ pos += 8
309+ rangeDataPos = pos
310+ for j in range (fromInclusive , toExclusive ):
311+ if j >= startFrame + numFrames :
312+ break
313+ if j >= startFrame :
314+ data [chIdx ][j - startFrame ] = int .from_bytes (
315+ binaryData [rangeDataPos :rangeDataPos + 2 ], byteorder = "little" , signed = True )
316+ rangeDataPos += 2
317+ pos += (toExclusive - fromInclusive ) * 2
318+
319+
320+ def generateSyntheticNoise (rf , data , wellID , startFrame , numFrames , stdDev = None ):
321+ # Source: Documentation by 3Brain
322+ # https://gin.g-node.org/NeuralEnsemble/ephy_testing_data/src/master/biocam/documentation_brw_4.x_bxr_3.x_bcmp_1.x_in_brainwave_5.x_v1.1.3.pdf
323+ # collect the TOCs
324+ toc = np .array (rf ["TOC" ])
325+ noiseToc = np .array (rf [wellID ]["NoiseTOC" ])
326+ # from the given start position in frames, localize the corresponding noise positions
327+ # using the TOC
328+ tocStartIdx = np .searchsorted (toc [:, 1 ], startFrame )
329+ noiseStartPosition = noiseToc [tocStartIdx ]
330+ noiseEndPosition = noiseStartPosition
331+ for i in range (tocStartIdx + 1 , len (noiseToc )):
332+ nextPosition = noiseToc [i ]
333+ if nextPosition > noiseStartPosition :
334+ noiseEndPosition = nextPosition
335+ break
336+ if noiseEndPosition == noiseStartPosition :
337+ for i in range (tocStartIdx - 1 , 0 , - 1 ):
338+ previousPosition = noiseToc [i ]
339+ if previousPosition < noiseStartPosition :
340+ noiseEndPosition = noiseStartPosition
341+ noiseStartPosition = previousPosition
342+ break
343+ # obtain the noise info at the start position
344+ noiseChIdxs = rf [wellID ]["NoiseChIdxs" ][noiseStartPosition :noiseEndPosition ]
345+ noiseMean = rf [wellID ]["NoiseMean" ][noiseStartPosition :noiseEndPosition ]
346+ if stdDev is None :
347+ noiseStdDev = rf [wellID ]["NoiseStdDev" ][noiseStartPosition :noiseEndPosition ]
348+ else :
349+ noiseStdDev = np .repeat (stdDev , noiseEndPosition - noiseStartPosition )
350+ noiseLength = noiseEndPosition - noiseStartPosition
351+ noiseInfo = {}
352+ meanCollection = []
353+ stdDevCollection = []
354+ for i in range (1 , noiseLength ):
355+ noiseInfo [noiseChIdxs [i ]] = [noiseMean [i ], noiseStdDev [i ]]
356+ meanCollection .append (noiseMean [i ])
357+ stdDevCollection .append (noiseStdDev [i ])
358+ # calculate the median mean and standard deviation of all channels to be used for
359+ # invalid channels
360+ dataMean = np .median (meanCollection )
361+ dataStdDev = np .median (stdDevCollection )
362+ # fill with Gaussian noise
363+ for chIdx in range (len (data )):
364+ if chIdx in noiseInfo :
365+ data [chIdx ] = np .array (np .random .normal (noiseInfo [chIdx ][0 ], noiseInfo [chIdx ][1 ],
366+ numFrames ), dtype = np .int16 )
367+ else :
368+ data [chIdx ] = np .array (np .random .normal (dataMean , dataStdDev , numFrames ),
369+ dtype = np .int16 )
370+
0 commit comments