Skip to content

Commit 13f6953

Browse files
committed
New proposal for gap tolerance
1 parent d9c8683 commit 13f6953

File tree

1 file changed

+85
-14
lines changed

1 file changed

+85
-14
lines changed

neo/rawio/neuralynxrawio/ncssections.py

Lines changed: 85 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -424,9 +424,76 @@ def _buildForMaxGap(ncsMemMap, nomFreq):
424424
nb = NcsSectionsFactory._parseForMaxGap(ncsMemMap, nb, maxGapToAllow)
425425

426426
return nb
427+
428+
@staticmethod
429+
def _buildNcsGeneric(ncsMemMap, nlxHdr, sampFreq, gapTolerance=0):
430+
"""
431+
Build
432+
433+
This replace:
434+
_buildGivenActualFrequency
435+
_buildForMaxGap
436+
"""
437+
438+
channel_id = ncsMemMap["channel_id"][0]
439+
440+
ncsSects = NcsSections()
441+
# TODO option to use this sampFreq or estimated one
442+
ncsSects.sampFreqUsed = sampFreq
443+
ncsSects.microsPerSampUsed = NcsSectionsFactory.get_micros_per_samp_for_freq(sampFreq)
444+
445+
# check if file is one block of records, which is often the case, and avoid full parse
446+
# need that last timestamp match the predicted one.
447+
predLastBlockStartTime = NcsSectionsFactory.calc_sample_time(
448+
sampFreq, ncsMemMap["timestamp"][0], NcsSection._RECORD_SIZE * ncsMemMap.shape[0] - 1
449+
)
450+
if (
451+
channel_id == ncsMemMap["channel_id"][-1]
452+
and ncsMemMap["sample_rate"][0] == ncsMemMap["sample_rate"][-1]
453+
and ncsMemMap["timestamp"][-1] == predLastBlockStartTime
454+
):
455+
lastBlkEndTime = NcsSectionsFactory.calc_sample_time(sampFreq, ncsMemMap["timestamp"][-1], ncsMemMap["nb_valid"][-1])
456+
n_samples = NcsSection._RECORD_SIZE * (ncsMemMap.size - 1) + ncsMemMap["nb_valid"][-1]
457+
section0 = NcsSection(
458+
startRec=0,
459+
startTime=ncsMemMap["timestamp"][0],
460+
endRec=ncsMemMap.size - 1,
461+
endTime=lastBlkEndTime,
462+
n_samples=n_samples
463+
)
464+
ncsSects.sects.append(section0)
465+
466+
else:
467+
# need to parse all data block to detect gaps
468+
# check when the predicted timestamp is outside the tolerance
469+
470+
delta = (ncsMemMap["timestamp"][1:] - ncsMemMap["timestamp"][:-1]).astype(np.int64)
471+
delta_prediction = ((ncsMemMap["nb_valid"][:-1] / ncsSects.sampFreqUsed) * 1e6).astype(np.int64)
472+
gap_inds = np.flatnonzero(np.abs(delta - delta_prediction) > gapTolerance)
473+
gap_inds += 1
474+
475+
sections_limits = [ 0 ] + gap_inds.tolist() + [len(ncsMemMap)]
476+
477+
for i in range(len(gap_inds) + 1):
478+
start = sections_limits[i]
479+
stop = sections_limits[i + 1]
480+
ncsSects.sects.append(
481+
NcsSection(
482+
startRec=start,
483+
startTime=ncsMemMap["timestamp"][start],
484+
endRec=stop-1,
485+
endTime=ncsMemMap["timestamp"][stop-1] + np.uint64(ncsMemMap["nb_valid"][stop-1] / ncsSects.sampFreqUsed * 1e6),
486+
n_samples=np.sum(ncsMemMap["nb_valid"][start:stop])
487+
)
488+
)
489+
490+
return ncsSects
491+
492+
493+
427494

428495
@staticmethod
429-
def build_for_ncs_file(ncsMemMap, nlxHdr):
496+
def build_for_ncs_file(ncsMemMap, nlxHdr, gapTolerance=None):
430497
"""
431498
Build an NcsSections object for an NcsFile, given as a memmap and NlxHeader,
432499
handling gap detection appropriately given the file type as specified by the header.
@@ -443,32 +510,36 @@ def build_for_ncs_file(ncsMemMap, nlxHdr):
443510
An NcsSections corresponding to the provided ncsMemMap and nlxHdr
444511
"""
445512
acqType = nlxHdr.type_of_recording()
513+
freq = nlxHdr["sampling_rate"]
446514

447-
# Old Neuralynx style with truncated whole microseconds for actual sampling. This
448-
# restriction arose from the sampling being based on a master 1 MHz clock.
449515
if acqType == "PRE4":
450-
freq = nlxHdr["sampling_rate"]
516+
# Old Neuralynx style with truncated whole microseconds for actual sampling. This
517+
# restriction arose from the sampling being based on a master 1 MHz clock.
518+
451519
microsPerSampUsed = math.floor(NcsSectionsFactory.get_micros_per_samp_for_freq(freq))
452520
sampFreqUsed = NcsSectionsFactory.get_freq_for_micros_per_samp(microsPerSampUsed)
453-
nb = NcsSectionsFactory._buildGivenActualFrequency(ncsMemMap, sampFreqUsed, math.floor(freq))
454-
nb.sampFreqUsed = sampFreqUsed
455-
nb.microsPerSampUsed = microsPerSampUsed
521+
if gapTolerance is None:
522+
gapTolerance = 0
523+
ncsSects = NcsSectionsFactory._buildNcsGeneric(ncsMemMap, nlxHdr, sampFreqUsed, gapTolerance=gapTolerance)
524+
ncsSects.sampFreqUsed = sampFreqUsed
525+
ncsSects.microsPerSampUsed = microsPerSampUsed
456526

457-
# digital lynx style with fractional frequency and micros per samp determined from
458-
# block times
459527
elif acqType in ["DIGITALLYNX", "DIGITALLYNXSX", "CHEETAH64", "CHEETAH560", "RAWDATAFILE"]:
460-
nomFreq = nlxHdr["sampling_rate"]
461-
nb = NcsSectionsFactory._buildForMaxGap(ncsMemMap, nomFreq)
528+
# digital lynx style with fractional frequency and micros per samp determined from block times
529+
if gapTolerance is None:
530+
gapTolerance = round(NcsSectionsFactory._maxGapSampFrac * 1e6 / freq)
531+
ncsSects = NcsSectionsFactory._buildNcsGeneric(ncsMemMap, nlxHdr, sampFreqUsed, gapTolerance=gapTolerance)
462532

463533
# BML & ATLAS style with fractional frequency and micros per samp
464534
elif acqType == "BML" or acqType == "ATLAS":
535+
if gapTolerance is None:
536+
gapTolerance = 0
465537
sampFreqUsed = nlxHdr["sampling_rate"]
466-
nb = NcsSectionsFactory._buildGivenActualFrequency(ncsMemMap, sampFreqUsed, math.floor(sampFreqUsed))
467-
538+
ncsSects = NcsSectionsFactory._buildNcsGeneric(ncsMemMap, nlxHdr, freq, gapTolerance=gapTolerance)
468539
else:
469540
raise TypeError("Unknown Ncs file type from header.")
470541

471-
return nb
542+
return ncsSects
472543

473544
@staticmethod
474545
def _verifySectionsStructure(ncsMemMap, ncsSects):

0 commit comments

Comments
 (0)