Handling sbref images in Heudiconv

Hi there,

I am trying to figure out a good way to reference the sbref images in my data set in a way that will always catch the right DICOM. My professor informed me that if I referenced it as I would with any other scan (strings matching or dimensional variables), it has the potential for referencing the incorrect image e.g. generated by a false start or otherwise interrupted trial.

Basically if I have defined my functional run as info[MID] = [s.series_id], I am trying to figure out how to reference the row in the dicominfo.tsv that will always be directly above (like [s.series_id] - 1), since that will always refer to the correct one. I saw another post here that referenced it simply by matching a string in scan name, but I want to make sure no wires could be accidentally crossed here.

Any help would be appreciated!

Best,
Caleb Haynes

heudiconv will iterate through the scanner protocol in ascending order, so you’ll have to get a bit creative in your heuristic to solve this problem.

With the way you are assigning your functional run, only the most recent scan (that matches your criteria) is being saved. You could apply this logic to the sbref as well, but if another functional run was later started and cancelled the sbref would most likely be incorrect.

One way to ensure the sbref matches to the functional would be to create a dictionary that maps functional series id -> most recent sbref series id.

...
latest_sbref = None
func2sbref = {}
for s in seqinfo:
  ...
  if <sbref condition>:
    latest_sbref = s.series_id
  elif <functional condition>:
    info[MID] = [s.series_id]
    if latest_sbref is not None:
       func2sbref[s.series_id] = latest_sbref
  ...
# query the dictionary once you have exhausted all seqinfos
try:
  func_series_id = info[MID][0]
except Exception:
  func_series_id = None
sbref_series_id = func2sbref.get(func_series_id)
if sbref_series_id is not None:
  info[MIDsbref] = [sbref_series_id]
return info

I haven’t explicitly tested this but it should work.

2 Likes

This helps a lot, thank you so much. I’ll test this out tomorrow!

The problem I see with this approach is what happens if you collect a SMS run (generating a SBRef series) followed by a single-band run (I know, this is probably not frequent). The code above will assign the latest_sbref also to the single-band _bold run.
Maybe you should “delete” latest_sbref after assigning it to a functional run, so that it is not assigned to any other?

    if latest_sbref is not None:
       func2sbref[s.series_id] = latest_sbref
       latest_sbref = None
1 Like

Not sure if this gets around the problem mentioned above, but curious if anyone has developed something better for handing these sbref images. Working through this- would it be easier to do something like

list_of_ids = [s.series_id for s in seqinfo]
for s in seqinfo:
     if (s.dim4 == 265) and ('MID' in s.protocol_name):
        info[MID].append(s.series_id)
        idx = [list_of_ids.index(s.series_id)
        info[MID_sbref].append(list_of_ids[idx -1])

This was really helpful, thanks @mgxd!

In my case, I needed to keep multiple sbref images that corresponded with certain bold images. This code example only converted a single sbref. So I replaced your post-seqinfo loop code with this:

 try:
    func_series_list = info[MID] # not just the 0th
except Exception:
    func_series_list = None
# loop through all func runs
for func_series_id in func_series_list:
    sbref_series_id = func2sbref.get(func_series_id)
    if sbref_series_id is not None:
        info[MIDsbref].append(sbref_series_id) # append instead of =
return info

and it seems to output all the sbref files I need

1 Like