Skip to content

Commit

Permalink
NWB_AppendSweepLowLevel: Fix and rework TTL channel saving
Browse files Browse the repository at this point in the history
The current code did not correctly store the stimsets for ITC hardware if
rack one is active with the ITC1600. As fixing it quickly would have made
complicated code worse, we rewrote the TTL channel export to be hardware
independent.
  • Loading branch information
t-b committed Oct 19, 2023
1 parent fba95a6 commit 39476ac
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 88 deletions.
94 changes: 32 additions & 62 deletions Packages/MIES/MIES_NeuroDataWithoutBorders.ipf
Original file line number Diff line number Diff line change
Expand Up @@ -972,7 +972,7 @@ End

threadsafe static Function NWB_AppendSweepLowLevel(STRUCT NWBAsyncParameters &s)
variable groupID, numEntries, i, j, ttlBits, dac, adc, col, refTime
variable ttlBit, hardwareType, DACUnassoc, ADCUnassoc, index
variable ttlBit, DACUnassoc, ADCUnassoc, index
string group, path, list, name, stimset, key
string channelSuffix, listOfStimsets, contents

Expand Down Expand Up @@ -1085,6 +1085,9 @@ threadsafe static Function NWB_AppendSweepLowLevel(STRUCT NWBAsyncParameters &s)

params.samplingRate = ConvertSamplingIntervalToRate(GetSamplingInterval(s.DAQConfigWave)) * KILO_TO_ONE

DFREF sweepDFR = NewFreeDataFolder()
SplitSweepIntoComponents(s.numericalValues, s.sweep, s.DAQDataWave, s.DAQConfigWave, TTL_RESCALE_OFF, targetDFR = sweepDFR, createBackup = 0)

for(i = 0; i < NUM_HEADSTAGES; i += 1)

if(!statusHS[i])
Expand All @@ -1109,7 +1112,7 @@ threadsafe static Function NWB_AppendSweepLowLevel(STRUCT NWBAsyncParameters &s)
params.channelType = IPNWB_CHANNEL_TYPE_ADC
col = AFH_GetDAQDataColumn(s.DAQConfigWave, params.channelNumber, params.channelType)
writtenDataColumns[col] = 1
WAVE params.data = ExtractOneDimDataFromSweep(s.DAQConfigWave, s.DAQDataWave, col)
WAVE params.data = GetDAQDataSingleColumnWaveNG(s.numericalValues, s.textualValues, s.sweep, sweepDFR, params.channelType, params.channelNumber)
NWB_GetTimeSeriesProperties(s.nwbVersion, s.numericalKeys, s.numericalValues, params, tsp)
params.groupIndex = IsFinite(params.groupIndex) ? params.groupIndex : GetNextFreeGroupIndex(s.locationID, path)
WriteSingleChannel(s.locationID, path, s.nwbVersion, params, tsp, compressionMode = s.compressionMode)
Expand All @@ -1121,7 +1124,7 @@ threadsafe static Function NWB_AppendSweepLowLevel(STRUCT NWBAsyncParameters &s)
params.channelType = IPNWB_CHANNEL_TYPE_DAC
col = AFH_GetDAQDataColumn(s.DAQConfigWave, params.channelNumber, params.channelType)
writtenDataColumns[col] = 1
WAVE params.data = ExtractOneDimDataFromSweep(s.DAQConfigWave, s.DAQDataWave, col)
WAVE params.data = GetDAQDataSingleColumnWaveNG(s.numericalValues, s.textualValues, s.sweep, sweepDFR, params.channelType, params.channelNumber)
NWB_GetTimeSeriesProperties(s.nwbVersion, s.numericalKeys, s.numericalValues, params, tsp)
params.groupIndex = IsFinite(params.groupIndex) ? params.groupIndex : GetNextFreeGroupIndex(s.locationID, path)
WAVE/T/Z params.epochs = EP_FetchEpochs(s.numericalValues, s.textualValues, s.sweep, params.channelNumber, params.channelType)
Expand All @@ -1133,79 +1136,46 @@ threadsafe static Function NWB_AppendSweepLowLevel(STRUCT NWBAsyncParameters &s)

NWB_ClearWriteChannelParams(params)

hardwareType = GetUsedHWDACFromLNB(s.numericalValues, s.sweep)
WAVE/Z/T ttlStimsets = GetTTLLabnotebookEntry(s.textualValues, LABNOTEBOOK_TTL_STIMSETS, s.sweep)
if(WaveExists(ttlStimsets))

// i has the following meaning:
// - ITC hardware: hardware channel
// - NI hardware: DAEphys TTL channel
for(i = 0; i < NUM_DA_TTL_CHANNELS; i += 1)

if(!WaveExists(ttlStimsets))
break
endif
WAVE/Z guiToHWChannelMap = GetActiveChannels(s.numericalValues, s.textualValues, s.sweep, XOP_CHANNEL_TYPE_TTL, TTLMode = TTL_GUITOHW_CHANNEL)
ASSERT_TS(WaveExists(guiToHWChannelMap), "Missing GUI to hardware channel map")

if(hardwareType == HARDWARE_ITC_DAC)
ttlBits = GetTTLBits(s.numericalValues, s.sweep, i)
if(!IsFinite(ttlBits))
continue
endif

elseif(hardwareType == HARDWARE_NI_DAC)
ttlBits = NaN
// i is the GUI channel number
for(i = 0; i < NUM_DA_TTL_CHANNELS; i += 1)

stimset = ttlStimsets[i]
if(IsEmpty(stimset))
continue
endif
else
ASSERT_TS(0, "unsupported hardware type")
endif

params.clampMode = NaN
params.channelNumber = i
params.channelType = IPNWB_CHANNEL_TYPE_TTL
params.electrodeNumber = NaN
params.electrodeName = ""
col = AFH_GetDAQDataColumn(s.DAQConfigWave, params.channelNumber, params.channelType)
writtenDataColumns[col] = 1

WAVE data = ExtractOneDimDataFromSweep(s.DAQConfigWave, s.DAQDataWave, col)

if(hardwareType == HARDWARE_ITC_DAC)
DFREF dfr = NewFreeDataFolder()
SplitTTLWaveIntoComponents(data, ttlBits, dfr, "_", TTL_RESCALE_OFF)

list = GetListOfObjects(dfr, ".*", typeFlag = COUNTOBJECTS_WAVES)
numEntries = ItemsInList(list)
for(j = 0; j < numEntries; j += 1)
name = StringFromList(j, list)
ttlBit = 2^str2num(name[1,inf])
ASSERT_TS((ttlBit & ttlBits) == ttlBit, "Invalid ttlBit")
WAVE/SDFR=dfr params.data = $name
path = "/stimulus/presentation"
params.channelSuffix = num2str(ttlBit)
params.clampMode = NaN
params.channelNumber = guiToHWChannelMap[i][%HWCHANNEL]
params.channelType = IPNWB_CHANNEL_TYPE_TTL
params.electrodeNumber = NaN
params.electrodeName = ""
col = AFH_GetDAQDataColumn(s.DAQConfigWave, params.channelNumber, params.channelType)
writtenDataColumns[col] = 1

path = "/stimulus/presentation"
params.stimset = stimset

if(IsFinite(guiToHWChannelMap[i][%TTLBITNR]))
params.channelSuffix = num2str(2^guiToHWChannelMap[i][%TTLBITNR])
params.channelSuffixDesc = NWB_SOURCE_TTL_BIT
params.stimset = ttlStimsets[log(ttlBit)/log(2)]
NWB_GetTimeSeriesProperties(s.nwbVersion, s.numericalKeys, s.numericalValues, params, tsp)
params.groupIndex = IsFinite(params.groupIndex) ? params.groupIndex : GetNextFreeGroupIndex(s.locationID, path)
WAVE/T/Z params.epochs = EP_FetchEpochs(s.numericalValues, s.textualValues, s.sweep, log(ttlBit)/log(2), params.channelType)

s.locationID = WriteSingleChannel(s.locationID, path, s.nwbVersion, params, tsp, compressionMode = s.compressionMode, nwbFilePath = s.nwbFilePath)
endfor
elseif(hardwareType == HARDWARE_NI_DAC)
WAVE params.data = data
path = "/stimulus/presentation"
params.stimset = stimset
WAVE/T/Z params.epochs = EP_FetchEpochs(s.numericalValues, s.textualValues, s.sweep, params.channelNumber, params.channelType)
endif

NWB_GetTimeSeriesProperties(s.nwbVersion, s.numericalKeys, s.numericalValues, params, tsp)
params.groupIndex = IsFinite(params.groupIndex) ? params.groupIndex : GetNextFreeGroupIndex(s.locationID, path)
params.groupIndex = IsFinite(params.groupIndex) ? params.groupIndex : GetNextFreeGroupIndex(s.locationID, path)
WAVE params.data = GetDAQDataSingleColumnWaveNG(s.numericalValues, s.textualValues, s.sweep, sweepDFR, params.channelType, i)
WAVE/T/Z params.epochs = EP_FetchEpochs(s.numericalValues, s.textualValues, s.sweep, i, params.channelType)

s.locationID = WriteSingleChannel(s.locationID, path, s.nwbVersion, params, tsp, compressionMode = s.compressionMode, nwbFilePath = s.nwbFilePath)
endif

NWB_ClearWriteChannelParams(params)
endfor
NWB_ClearWriteChannelParams(params)
endfor
endif

NWB_ClearWriteChannelParams(params)

Expand Down
20 changes: 15 additions & 5 deletions Packages/tests/UTF_TestNWBExportV1.ipf
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ static Function TestTimeSeries(fileID, device, groupID, channel, sweep, pxpSweep
string channel, device
DFREF pxpSweepsDFR

variable channelGroupID, num_samples, starting_time, session_start_time, actual, scale, scale_ref
variable channelGroupID, num_samples, starting_time, session_start_time, actual, scale, scale_ref, GUIchannelNumber, ttlBit
variable clampMode, gain, gain_ref, resolution, conversion, rate_ref, rate, samplingInterval, samplingInterval_ref
string stimulus, stimulus_expected, neurodata_type_ref, neurodata_type, channelName
string electrode_name, electrode_name_ref, key, unit_ref, unit, base_unit_ref
Expand Down Expand Up @@ -358,6 +358,8 @@ static Function TestTimeSeries(fileID, device, groupID, channel, sweep, pxpSweep
samplingInterval_ref = DimDelta(loadedFromNWB, ROWS)
CHECK_CLOSE_VAR(samplingInterval, samplingInterval_ref, tol=1e-7)

GUIchannelNumber = params.channelNumber

// stimulus_description
stimulus = ReadTextDataSetAsString(channelGroupID, "stimulus_description")
if(params.channelType == XOP_CHANNEL_TYPE_DAC && IsNaN(params.electrodeNumber))
Expand All @@ -368,17 +370,25 @@ static Function TestTimeSeries(fileID, device, groupID, channel, sweep, pxpSweep
WAVE/T/Z TTLStimsets = GetTTLLabnotebookEntry(textualValues, LABNOTEBOOK_TTL_STIMSETS, sweep)
CHECK_WAVE(TTLStimsets, TEXT_WAVE)

if(IsNaN(params.ttlBit))
stimulus_expected = TTLStimsets[params.channelNumber]
else
stimulus_expected = TTLStimsets[log(params.ttlBit)/log(2)]
if(IsFinite(params.ttlBit))
WAVE/Z channelMapHWToGUI = GetActiveChannels(numericalValues, textualValues, sweep, params.channelType, TTLMode = TTL_HWTOGUI_CHANNEL)
CHECK_WAVE(channelMapHWToGUI, NUMERIC_WAVE)

ttlBit = log(params.ttlBit)/log(2)
CHECK_GE_VAR(ttlBit, 0)

GUIchannelNumber = channelMapHWToGUI[params.channelNumber][ttlBit]
endif

stimulus_expected = TTLStimsets[GUIchannelNumber]
else
WAVE/Z/T wvText = GetLastSetting(textualValues, sweep, "Stim Wave Name", DATA_ACQUISITION_MODE)
CHECK_WAVE(wvText, TEXT_WAVE)
stimulus_expected = wvText[params.electrodeNumber]
endif

CHECK_EQUAL_STR(stimulus, stimulus_expected)
CHECK_GE_VAR(GUIchannelNumber, 0)

// electrode_name, only present for associated channels
if(IsFinite(params.electrodeNumber))
Expand Down
38 changes: 17 additions & 21 deletions Packages/tests/UTF_TestNWBExportV2.ipf
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ static Function TestTimeSeries(fileID, filepath, device, groupID, channel, sweep
DFREF pxpSweepsDFR
WAVE/Z epochs

variable channelGroupID, starting_time, session_start_time, actual, idx, index, channelNumber, ttlBit
variable channelGroupID, starting_time, session_start_time, actual, idx, index, GUIchannelNumber, ttlBit
variable clampMode, gain, gain_ref, resolution, conversion, headstage, rate_ref, rate, samplingInterval, samplingInterval_ref
string stimulus, stimulus_expected, channelName, str, path, neurodata_type
string electrode_name, electrode_name_ref, key, unit_ref, unit, base_unit_ref
Expand Down Expand Up @@ -388,6 +388,8 @@ static Function TestTimeSeries(fileID, filepath, device, groupID, channel, sweep
samplingInterval_ref = DimDelta(loadedFromNWB, ROWS)
CHECK_CLOSE_VAR(samplingInterval, samplingInterval_ref, tol=1e-7)

GUIchannelNumber = params.channelNumber

// stimulus_description
stimulus = ReadTextAttributeAsString(channelGroupID, ".", "stimulus_description")
if(params.channelType == XOP_CHANNEL_TYPE_DAC && IsNaN(params.electrodeNumber))
Expand All @@ -398,17 +400,25 @@ static Function TestTimeSeries(fileID, filepath, device, groupID, channel, sweep
WAVE/T/Z TTLStimsets = GetTTLLabnotebookEntry(textualValues, LABNOTEBOOK_TTL_STIMSETS, sweep)
CHECK_WAVE(TTLStimsets, TEXT_WAVE)

if(IsNaN(params.ttlBit))
stimulus_expected = TTLStimsets[params.channelNumber]
else
stimulus_expected = TTLStimsets[log(params.ttlBit)/log(2)]
if(IsFinite(params.ttlBit))
WAVE/Z channelMapHWToGUI = GetActiveChannels(numericalValues, textualValues, sweep, params.channelType, TTLMode = TTL_HWTOGUI_CHANNEL)
CHECK_WAVE(channelMapHWToGUI, NUMERIC_WAVE)

ttlBit = log(params.ttlBit)/log(2)
CHECK_GE_VAR(ttlBit, 0)

GUIchannelNumber = channelMapHWToGUI[params.channelNumber][ttlBit]
endif

stimulus_expected = TTLStimsets[GUIchannelNumber]
else
WAVE/Z/T wvText = GetLastSetting(textualValues, sweep, "Stim Wave Name", DATA_ACQUISITION_MODE)
CHECK_WAVE(wvText, TEXT_WAVE)
stimulus_expected = wvText[params.electrodeNumber]
endif

CHECK_EQUAL_STR(stimulus, stimulus_expected)
CHECK_GE_VAR(GUIchannelNumber, 0)

// electrode_name, only present for associated channels
if(IsFinite(params.electrodeNumber))
Expand Down Expand Up @@ -536,23 +546,9 @@ static Function TestTimeSeries(fileID, filepath, device, groupID, channel, sweep
WAVE/T/Z epochsSingleChannel = WaveRef(epochs, row=idx)
CHECK_WAVE(epochsSingleChannel, TEXT_WAVE)

channelNumber = params.channelNumber
ttlBit = NaN

if(params.channelType == XOP_CHANNEL_TYPE_TTL && IsFinite(params.ttlBit))
WAVE/Z channelMapHWToGUI = GetActiveChannels(numericalValues, textualValues, sweep, params.channelType, TTLMode = TTL_HWTOGUI_CHANNEL)
CHECK_WAVE(channelMapHWToGUI, NUMERIC_WAVE)

ttlBit = log(params.ttlBit)/log(2)
CHECK_GE_VAR(ttlBit, 0)

channelNumber = channelMapHWToGUI[params.channelNumber][ttlBit]
CHECK_GE_VAR(channelNumber, 0)
endif

WAVE/Z epochsLBN = EP_FetchEpochs(numericalValues, textualValues, sweep, channelNumber, params.channelType)
WAVE/Z epochsLBN = EP_FetchEpochs(numericalValues, textualValues, sweep, GUIchannelNumber, params.channelType)
CHECK_WAVE(epochsLBN, TEXT_WAVE)
INFO("Channeltype: %s, GUI channel number %d, hardware channel number %d, TTL bit %d", s0 = StringFromList(params.channelType, CHANNEL_NAMES), n0 = channelNumber, n1 = params.channelNumber, n2 = ttlBit)
INFO("Channeltype: %s, GUI channel number %d, hardware channel number %d, TTL bit %d", s0 = StringFromList(params.channelType, CHANNEL_NAMES), n0 = GUIchannelNumber, n1 = params.channelNumber, n2 = ttlBit)
CHECK_EQUAL_TEXTWAVES(epochsLBN, epochsSingleChannel)
endif
End
Expand Down

0 comments on commit 39476ac

Please sign in to comment.