PennLINC / fw-heudiconv

Heuristic-based Data Curation on Flywheel
BSD 3-Clause "New" or "Revised" License
6 stars 11 forks source link

generating asl_context.tsv files for multiple asl acquisitions #85

Closed willtack closed 3 years ago

willtack commented 3 years ago

Describe the issue I'm attempting to curate a session with multiple different ASL acquisitions. To accomplish this, I'm having the heuristic generate separate aslcontext files for each acquisition. (In this case the context file is the same, but I have other projects with very different kinds of acquisitions)._ When attempting to return all three asl_context files at the end of AttachToSession(), I receive the below error. When I comment out the latter two, it works.

Let me know.

Thanks! Will

Were you running fw-heudiconv locally or from Flywheel's GUI?

If you ran it locally, please describe your setup:

Please paste your heuristic below:

import os

def create_key(template, outtype=('nii.gz',), annotation_classes=None):
    if template is None or not template:
        raise ValueError('Template must be a valid format string')
    return template, outtype, annotation_classes

# structurals
t1_sag = create_key(
    'sub-{subject}/ses-{session}/anat/sub-{subject}_{session}_acq-bravo_T1w')
t1_ax = create_key(
    'sub-{subject}/ses-{session}/anat/sub-{subject}_{session}_acq-ax_T1w')
t1_cor = create_key(
    'sub-{subject}/ses-{session}/anat/sub-{subject}_{session}_acq-cor_T1w')
flair_ax = create_key(
    'sub-{subject}/ses-{session}/anat/sub-{subject}_{session}_acq-ax_FLAIR')
flair_cor = create_key(
    'sub-{subject}/ses-{session}/anat/sub-{subject}_{session}_acq-cor_FLAIR')
t2 = create_key(
    'sub-{subject}/ses-{session}/anat/sub-{subject}_{session}_acq-ssfse_T2w')
tof = create_key(
    'sub-{subject}/ses-{session}/anat/sub-{subject}_{session}_acq-ax_angio')

# ASL scans
asl1 = create_key(
     'sub-{subject}/ses-{session}/perf/sub-{subject}_{session}_run-01_asl')
asl2 = create_key(
     'sub-{subject}/ses-{session}/perf/sub-{subject}_{session}_run-02_asl')
asl3 = create_key(
     'sub-{subject}/ses-{session}/perf/sub-{subject}_{session}_run-03_asl')
# these should probably be in derivatives
cbf1 = create_key(
     'sub-{subject}/ses-{session}/perf/sub-{subject}_{session}_run-01_cbf')
cbf2 = create_key(
     'sub-{subject}/ses-{session}/perf/sub-{subject}_{session}_run-02_cbf')
cbf3 = create_key(
     'sub-{subject}/ses-{session}/perf/sub-{subject}_{session}_run-03_cbf')

def infotodict(seqinfo):

    last_run = len(seqinfo)

    info = {t1_sag:[], t1_ax:[], t1_cor:[], flair_ax:[], flair_cor:[], t2:[],
            tof:[], asl1:[], asl2:[], asl3:[], cbf1:[], cbf2:[], cbf3:[]}

    # This function accomodates three runs
    def get_all_series(key1, key2, key3, s):
         if len(info[key1]) == 0:
             info[key1].append(s.series_id)
         elif len(info[key2]) == 0:
             info[key2].append(s.series_id)
         else:
             info[key3].append(s.series_id)

    # This function accomodates both runs
    def get_both_series(key1, key2, s):
         if len(info[key1]) == 0:
             info[key1].append(s.series_id)
         else:
             info[key2].append(s.series_id)

    # this doesn't need to be a function but using it anyway for aesthetic symmetry
    # with above function
    def get_series(key, s):
        info[key].append(s.series_id)

    for s in seqinfo:
        protocol = s.protocol_name
        if "BRAVO" in protocol:
            get_series(t1_sag,s)
        elif "T1_MPR" in protocol:
            if "AX" in protocol:
                get_series(t1_ax,s)
            elif "COR" in protocol:
                get_series(t1_cor,s)
        elif "FLAIR" in protocol:
            if "AX" in protocol:
                get_series(flair_ax,s)
            elif "COR" in protocol:
                get_series(flair_cor,s)
        # elif "SSFSE" in protocol:
        #     get_series(t2,s)
        # elif "TOF" in protocol:
        #     get_series(tof,s)
        elif "eASL:_Raw" in protocol:
            get_all_series(asl1,asl2,asl3,s)
        elif "eASL:_Flow" in protocol:
            get_all_series(cbf1,cbf2,cbf3,s)

    return info

def AttachToSession():

    data = ['deltam', 'm0scan']
    data = '\n'.join(data)
    data = 'volume_type\n' + data # the data is now a string

    asl_context1 = {
      'name': 'sub-{subject}/{session}/perf/sub-{subject}_{session}_run-01_aslcontext.tsv',
      'data': data,
      'type': 'text/tab-separated-values'
    }
    asl_context2 = {
      'name': 'sub-{subject}/{session}/perf/sub-{subject}_{session}_run-02_aslcontext.tsv',
      'data': data,
      'type': 'text/tab-separated-values'
    }
    asl_context3 = {
      'name': 'sub-{subject}/{session}/perf/sub-{subject}_{session}_run-03_aslcontext.tsv',
      'data': data,
      'type': 'text/tab-separated-values'
    }

    return asl_context1#, asl_context2, asl_context3

MetadataExtras = {
   asl1: {
   "PulseSequenceType": "3D_fse",
       "PulseSequenceDetails" : "eASL",
       "Manufacturer": "GE"
       "RepetitionTime":4.6,
       "LabelingType": "PCASL",
       "LabelingDuration": 3.5,
       "PostLabelingDelay": 2,
       "BackgroundSuppression": True,
       "BackgroundSuppressionNumberPulses": 5,
       "M0": True,
       "LabelingOrientation":"transversal",
       "VascularCrushing": False,
       "PulseDuration": 0.0005,
       "LabelingEfficiency":0.64},
    asl2: {
    "PulseSequenceType": "3D_fse",
        "PulseSequenceDetails" : "eASL",
        "Manufacturer": "GE"
        "RepetitionTime":4.6,
        "LabelingType": "PCASL",
        "LabelingDuration": 2,
        "PostLabelingDelay": 3.5,
        "BackgroundSuppression": True,
        "BackgroundSuppressionNumberPulses": 5,
        "M0": True,
        "LabelingOrientation":"transversal",
        "VascularCrushing": False,
        "PulseDuration": 0.0005,
        "LabelingEfficiency":0.64},
    asl3: {
    "PulseSequenceType": "3D_fse",
        "PulseSequenceDetails" : "eASL",
        "Manufacturer": "GE"
        "RepetitionTime":4.6,
        "LabelingType": "PCASL",
        "LabelingDuration": 2,
        "PostLabelingDelay": 3.5,
        "BackgroundSuppression": True,
        "BackgroundSuppressionNumberPulses": 5,
        "M0": True,
        "LabelingOrientation":"transversal",
        "VascularCrushing": False,
        "PulseDuration": 0.0005,
        "LabelingEfficiency":0.64},
}

Please paste any relevant output below

INFO: Processing session attachments based on heuristic file
Traceback (most recent call last):
  File "/usr/local/bin/fw-heudiconv-curate", line 8, in <module>
    sys.exit(main())
  File "/usr/local/lib/python3.7/site-packages/fw_heudiconv/cli/curate.py", line 331, in main
    dry_run=args.dry_run)
  File "/usr/local/lib/python3.7/site-packages/fw_heudiconv/cli/curate.py", line 238, in convert_to_bids
    dry_run=dry_run
  File "/usr/local/lib/python3.7/site-packages/fw_heudiconv/backend_funcs/convert.py", line 397, in upload_attachment
    attachment_dict['name'] = force_template_format(attachment_dict['name'])
TypeError: tuple indices must be integers or slices, not str

Add any additional context or information below

TinasheMTapera commented 3 years ago

@willtack I think the problem is you've specified the file's paths relative to the BIDS root; I suggest instead doing the following:

def AttachToSession():

    data = ['deltam', 'm0scan']
    data = '\n'.join(data)
    data = 'volume_type\n' + data # the data is now a string

    asl_context1 = {
      'name': '{subject}_{session}_run-01_aslcontext.tsv',
      'data': data,
      'type': 'text/tab-separated-values'
    }
    asl_context2 = {
      'name': '{subject}_{session}_run-02_aslcontext.tsv',
      'data': data,
      'type': 'text/tab-separated-values'
    }
    asl_context3 = {
      'name': '{subject}_{session}_run-03_aslcontext.tsv',
      'data': data,
      'type': 'text/tab-separated-values'
    }

    return [asl_context1, asl_context2, asl_context3]

I know this won't directly solve your problem of having ASL split across multiple runs. Unfortunately the AttachToSession function will attach whatever it is given to whatever sessions are found using the subject and session flags.

Since the input data is the same, though, it should not have any bearing on your analyses (at worst, you'll have a tsv that points to no files; not the worst problem to have).

TinasheMTapera commented 3 years ago

To be clear, you could always create multiple heuristics too (for each session type) in the case that the session data is different each time

TinasheMTapera commented 3 years ago

@willtack can we close this issue?