CodeSOD: Back to the Lab
Matlab is special. Scientists and researchers love it. Programmers hate it, and not just because it uses 1-based arrays. I've worked on a number of projects where the task was "take this Matlab code and convert it to C so we can run it on an embedded CPU". Somehow, in that process, I've avoided learning much about Matlab.
Andre works on a team that uses Matlab to manage experimental scenarios. They wanted to do a simple task: generate a set of participant-specific images, store them in a database, and reference them later. Somewhere in the intersection of the database product they were using, the Matlab license they had, and other constraints, they discovered that there simply was no good way to do this.
Enter "Jude". Jude said, "Don't worry about it, I can hack something together."
I present the code in its entirety, but don't ask me to explain it. Instead, read the comments.
nMk = 1;%counting non-response triggers, this cycles with each trial
nPress = 0;%counting button presses, noting the position in the log
for v = 1:height(resVmrk)%read each trigger
switch nMk
%it's kinda roundabout, but the only recognisable part is the response
%yet I refer to it only by elision
%and instead count the stimuli to reconstruct the pattern
case 1%an almost reliable stimulus
nPress = nPress+1;%trial start
if strcmp(resVmrk.TriggerCode{v},'S1')%it must be a non-response
resVmrk.TriggerCode{v} = 'cross';%name it properly
nMk = 2;%and expect the next one
else%except when it is not
resLog.miss(nPress) = 1;%then note it down as missed
nMk = 0;%and skip to response
end
case 2%usually reliable
if strcmp(resVmrk.TriggerCode{v},'S1')%if the face loaded successfully
resVmrk.TriggerCode{v} = 'face';%note it
nMk = 3;%and proceed accordingly
if nPress<=height(resLog)%trailing triggers at the end should be ignored
resLog.facePos(nPress) = v;%note the position
end
else%if it failed to load it is a response
resVmrk.Dur(v-1:v+1) = 0;%mark the whole trial for deletion
resLog.miss(nPress) = 1;%and note it down as missing the face
nMk = 0;%and skip to response
end
case 3%this one is not reliable, and sometimes is duplicated instead of missing
if strcmp(resVmrk.TriggerCode{v},'S1')%if it is present at all
resVmrk.TriggerCode{v} = 'empty';%first name it
if v<height(resVmrk)%if it is not a trailing trigger, since it'll break the check otherwise
if ~strcmp(resVmrk.TriggerCode{v+1},'S1')%if the next trigger is a response
nMk = 0;%all is fine and it didn't freak out, proceed to response
else%otherwise
nMk = 3;%just treat as a double
%and then count how many excess triggers are actually here
nExcess = 1;%definitely one here already
while strcmp(resVmrk.TriggerCode{v+nExcess+1},'S1')
nExcess = nExcess+1;%and everything until the response
end
resVmrk.Dur(v-2:v+nExcess+2) = 0;%then mark the whole trial for deletion
%this overwrites the same positions several time, but the important part is to get the preceding two, because I don't know which one of them is correct one, so I delete the whole trial
if nPress<=height(resLog)
resLog.bad(nPress) = 1;%also note it down as borked
end
end
end
else
nMk = 0;%if it didn't happen at all simply proceed to response
end
case 0%this one reliably follows the response, so I address the response by elision
if strcmp(resVmrk.TriggerCode{v},'S1')%skip response itself
resVmrk.TriggerCode{v} = 'blink';%note the only reliable non-response (always following the response)
nMk = 1;%start the trial anew
if nPress<=height(resLog)%if it is not a trailing trigger
resLog.respPos(nPress) = v-1;%note down the response position
if resLog.miss(nPress)==1%and if it's a response without a stimulus
resVmrk.Dur(v-1:v) = 0;%mark it for deletion as well
end
end
end
end
end
Ah, the classic "for-case" antipattern. That's gross enough, but what the heck is happening inside each of those cases?
My personal favorite comment is this one: "%this overwrites the same positions several time, but the important part is to get the preceding two, because I don't know which one of them is correct one, so I delete the whole trial"
Now, you may suspect comments like "usually reliable" are about what we see in the dataset, but I'm not so certain. Andre writes:
After reverting the last discovered way for his creation to corrupt the data I was able to figure out that 20% of the logs provided corresponded to different (unknown) experiments altogether.
[Advertisement] Keep the plebs out of prod. Restrict NuGet feed privileges with ProGet. Learn more.