@@ -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