Skip to content

Commit

Permalink
Merge pull request #126 from jmtyszka/125-session-flag
Browse files Browse the repository at this point in the history
125 session flag
  • Loading branch information
jmtyszka authored Sep 7, 2023
2 parents db9f26e + 36ea5fc commit f69d526
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 77 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# BIDSKIT Changelog

## Version 2023.8.25
- Update Flywheel option to autodetect new .zip download archives

## Version 2023.2.16
- Fixed ses_clean error with --no-sessions

Expand Down
172 changes: 108 additions & 64 deletions bidskit/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@
"""

import os
import os.path as op
import sys
import argparse
import subprocess
import pkg_resources
from importlib.metadata import version
from glob import glob

from . import io as bio
Expand All @@ -51,55 +52,91 @@ def main():
# Parse command line arguments
parser = argparse.ArgumentParser(description='Convert DICOM files to BIDS-compliant Nifty structure')

parser.add_argument('-d', '--dataset', default='.',
help='BIDS dataset directory containing sourcedata subdirectory')

parser.add_argument('-s', '--subjects', nargs='+', default=[],
help='List of subject IDs to convert (eg --subjects alpha bravo charlie)')

parser.add_argument('--no-sessions', action='store_true', default=False,
help='Do not use session sub-directories')

parser.add_argument('--no-anon', action='store_true', default=False,
help='Do not anonymize BIDS output (eg for phantom data)')

parser.add_argument('--overwrite', action='store_true', default=False,
help='Overwrite existing files')

parser.add_argument('--skip-if-pruning', action='store_true', default=False,
help='Skip pruning of nonexistent IntendedFor items in json files')
parser.add_argument(
'-d', '--dataset', default='.',
help='BIDS dataset directory containing sourcedata subdirectory'
)

parser.add_argument(
'-subj', '--subjects', nargs='+', default=[],
help='List of subject IDs to convert (eg --subjects alpha bravo charlie)'
)

parser.add_argument(
'-sess', '--sessions', nargs='+', default=[],
help='List of session IDs to convert (eg --sessions pre 1 2)'
)

parser.add_argument(
'--no-sessions', action='store_true', default=False,
help='Do not use session sub-directories'
)

parser.add_argument(
'--no-anon', action='store_true', default=False,
help='Do not anonymize BIDS output (eg for phantom data)'
)

parser.add_argument(
'--overwrite', action='store_true', default=False,
help='Overwrite existing files'
)

parser.add_argument(
'--skip-if-pruning', action='store_true', default=False,
help='Skip pruning of nonexistent IntendedFor items in json files'
)

parser.add_argument('--clean-conv-dir', action='store_true', default=False,
help='Clean up conversion directory')

parser.add_argument('--bind-fmaps', action='store_true', default=False,
help='Bind fieldmaps to fMRI series using IntendedFor field')

parser.add_argument('--compression', required=False, default='o',
help='gzip compression flag for dcm2niix (y, o, i, n, 3 depending on dcm2niix version) [o]')

parser.add_argument('--recon', action='store_true', default=False,
help='Add recon- key to output filenames for bias- and distortion-corrected images')

parser.add_argument('--complex', action='store_true', default=False,
help='Add part- key to output filenames for complex-valued images')

parser.add_argument('--multiecho', action='store_true', default=False,
help='Add echo- key to output filenames')

parser.add_argument('--auto', action='store_true', default=False,
help='Automatically generate protocol translator from series descriptions and sequence parameters')

parser.add_argument('-fw', '--flywheel', action='store_true', default=False,
help='Curate Flywheel DICOM tarballs in top level of BIDS folder')

parser.add_argument('-V', '--version', action='store_true', default=False,
help='Display bidskit version number and exit')
parser.add_argument(
'--clean-conv-dir', action='store_true', default=False,
help='Clean up conversion directory'
)

parser.add_argument(
'--bind-fmaps', action='store_true', default=False,
help='Bind fieldmaps to fMRI series using IntendedFor field'
)

parser.add_argument(
'--compression', required=False, default='o',
help='gzip compression flag for dcm2niix (y, o, i, n, 3 depending on dcm2niix version) [o]'
)

parser.add_argument(
'--recon', action='store_true', default=False,
help='Add recon- key to output filenames for bias- and distortion-corrected images'
)

parser.add_argument(
'--complex', action='store_true', default=False,
help='Add part- key to output filenames for complex-valued images'
)

parser.add_argument(
'--multiecho', action='store_true', default=False,
help='Add echo- key to output filenames'
)

parser.add_argument(
'--auto', action='store_true', default=False,
help='Automatically generate protocol translator from series descriptions and sequence parameters'
)

parser.add_argument(
'-fw', '--flywheel', action='store_true', default=False,
help='Curate Flywheel DICOM zip archives in top level of BIDS folder'
)

parser.add_argument(
'-V', '--version', action='store_true', default=False,
help='Display bidskit version number and exit'
)

# Parse command line arguments
args = parser.parse_args()
dataset_dir = os.path.realpath(args.dataset)
dataset_dir = op.realpath(args.dataset)
subject_list = args.subjects
session_list = args.sessions
no_sessions = args.no_sessions
no_anon = args.no_anon
overwrite = args.overwrite
Expand All @@ -118,7 +155,7 @@ def main():
}

# Read installed version number
ver = pkg_resources.get_distribution('bidskit').version
ver = version('bidskit')

if args.version:
print('BIDSKIT {}'.format(ver))
Expand All @@ -129,12 +166,12 @@ def main():
print('BIDSKIT {}'.format(ver))
print('------------------------------------------------------------')

# Special handling for Flywheel DICOM tarballs
# Special handling for Flywheel DICOM zip archives
if args.flywheel:
print(f'Flywheel DICOM tarball processing')
print(f'Flywheel DICOM zip archive processing')
flywheel.unpack(dataset_dir)

if not os.path.isdir(os.path.join(dataset_dir, 'sourcedata')):
if not op.isdir(op.join(dataset_dir, 'sourcedata')):
print(f'* sourcedata folder not found in {dataset_dir}')
print('* bidskit expects this folder to exist and contain DICOM series')
print('* Please see the bidskit documentation at')
Expand Down Expand Up @@ -173,7 +210,7 @@ def main():
# This template should be completed by the user and the conversion rerun.
translator = btree.read_translator()

if translator and os.path.isdir(btree.work_dir):
if translator and op.isdir(btree.work_dir):

print('')
print('------------------------------------------------------------')
Expand Down Expand Up @@ -210,47 +247,55 @@ def main():
print('------------------------------------------------------------')

# Full path to subject directory in sourcedata/
src_subj_dir = os.path.realpath(os.path.join(btree.sourcedata_dir, sid))
src_subj_dir = op.realpath(op.join(btree.sourcedata_dir, sid))

# BIDS-compliant subject ID with prefix
sid_clean = sid.replace('-', '').replace('_', '')
subj_prefix = f'sub-{sid_clean:s}'

# Add full path to subject output directory to running list
out_subj_dir_list.append(os.path.join(dataset_dir, subj_prefix))
out_subj_dir_list.append(op.join(dataset_dir, subj_prefix))

# Create list of DICOM directories to convert
# This will be either a session or series folder list depending on no-sessions command line flag
if no_sessions:

dcm_dir_list = [src_subj_dir]

else:
dcm_dir_list = sorted(glob(os.path.join(src_subj_dir, '*')))

# Use list of session IDs in place of DICOM folder list if provided
if len(session_list) > 0:
dcm_dir_list = [op.join(src_subj_dir, sid) for sid in session_list]
else:
# Get list of DICOM session-level folders for this subject
dcm_dir_list = sorted(glob(op.join(src_subj_dir, '*')))

# Loop over DICOM directories in subject directory
for dcm_dir in dcm_dir_list:

if no_sessions:

# If session subdirs aren't being used, *_ses_dir = *sub_dir
# Use an empty ses_prefix with os.path.join to achieve this
# Use an empty ses_prefix with op.join to achieve this
ses_clean = ''
ses_prefix = ''

else:

ses = os.path.basename(os.path.realpath(dcm_dir))
ses = op.basename(op.realpath(dcm_dir))
ses_clean = ses.replace('-', '').replace('_', '')

ses_prefix = f'ses-{ses_clean:s}'
print(f' Processing session {ses}')
print(f'\n Processing session {ses}')

# Working conversion directories
work_subj_dir = os.path.join(btree.work_dir, subj_prefix)
work_conv_dir = os.path.join(work_subj_dir, ses_prefix)
work_subj_dir = op.join(btree.work_dir, subj_prefix)
work_conv_dir = op.join(work_subj_dir, ses_prefix)

# BIDS source directory directories
bids_subj_dir = os.path.join(dataset_dir, subj_prefix)
bids_ses_dir = os.path.join(bids_subj_dir, ses_prefix)
bids_subj_dir = op.join(dataset_dir, subj_prefix)
bids_ses_dir = op.join(bids_subj_dir, ses_prefix)

print(' Working subject directory : %s' % work_subj_dir)
if not no_sessions:
Expand All @@ -261,7 +306,7 @@ def main():

# Safely create working directory for current subject
# Flag for conversion if no working directory exists
if not os.path.isdir(work_conv_dir):
if not op.isdir(work_conv_dir):
os.makedirs(work_conv_dir)
needs_converting = True
else:
Expand Down Expand Up @@ -318,8 +363,7 @@ def main():

if not args.skip_if_pruning:

print('')
print('Subject directories to prune: ' + ', '.join(out_subj_dir_list))
print('Subject directories tagged for IntendedFor pruning: ' + ', '.join(out_subj_dir_list))

for bids_subj_dir in out_subj_dir_list:
fmaps.prune_intendedfors(bids_subj_dir, True)
Expand All @@ -329,7 +373,7 @@ def main():
if args.bind_fmaps:

print('')
print('Binding nearest fieldmap to each functional series')
print('Binding fieldmaps to functional runs using IntendedFor JSON field')
for bids_subj_dir in out_subj_dir_list:
fmaps.bind_fmaps(bids_subj_dir, no_sessions, nii_ext)

Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion bidskit/dcm2niix.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from . import io as bio
from . import translate as tr
from . import fmaps
from .json import (acqtime_mins)
from .bidsjson import (acqtime_mins)


def ordered_file_list(conv_dir, nii_ext):
Expand Down
21 changes: 11 additions & 10 deletions bidskit/flywheel.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Flywheel tarball support functions
Flywheel zip download support functions
MIT License
Expand Down Expand Up @@ -33,21 +33,22 @@

def unpack(dataset_dir):

# Create a sourcedata folder at the top level
src_dir = op.join(dataset_dir, 'sourcedata')
os.makedirs(src_dir, exist_ok=True)

# Look for one or more flywheel tarballs in the BIDS dataset root folder
fw_tarball_list = sorted(glob(op.join(dataset_dir, 'flywheel_*.tar')))
# Look for one or more flywheel zip archives at the top level
fw_zip_list = sorted(glob(op.join(dataset_dir, '*.zip')))

if len(fw_tarball_list) < 1:
print(f'* No Flywheel DICOM tarballs found in {dataset_dir} - exiting')
if len(fw_zip_list) < 1:
print(f'* No Flywheel DICOM zip archives found in {dataset_dir} - exiting')
else:

for tb_fname in fw_tarball_list:
for zip_fname in fw_zip_list:

# Untar tarball silently
print(f' Unpacking {tb_fname} to {src_dir}')
subprocess.run(['tar', 'xf', tb_fname, '-C', src_dir])
# Compress zip archive silently
print(f' Unpacking {zip_fname} to {src_dir}')
subprocess.run(['unzip', zip_fname, '-d', src_dir])

# bidskit uses sourcedata/<SUBJECT>/<SESSION> organization
# Flywheel uses sourcedata/<FWDIRNAME>/<GROUP>/<PROJECT>/<SUBJECT>/SESSION>
Expand All @@ -67,7 +68,7 @@ def unpack(dataset_dir):
else:
raise Exception(f'Neither sourcedata/flywheel or sourcedata/scitran exist following tar extraction')

# Assume only one group/project present in sourcedata following tarball unpacking
# Assume only one group/project present in sourcedata following unzipping
subj_dir_list = sorted(glob(op.join(fw_dir, '*', '*', '*')))
for subj_dir in subj_dir_list:
print(f' Moving {subj_dir} to {src_dir}')
Expand Down
2 changes: 1 addition & 1 deletion bidskit/fmaps.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from . import io as bio
from . import dcm2niix as d2n
from . import translate as tr
from .json import (acqtime_mins)
from .bidsjson import (acqtime_mins)


def bind_fmaps(bids_subj_dir, no_sessions, nii_ext):
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
# For a discussion on single-sourcing the version across setup.py and the
# project code, see
# https://packaging.python.org/en/latest/single_source_version.html
version='2023.8.24', # Required
version='2023.9.7', # Required

# This is a one-line description or tagline of what your project does. This
# corresponds to the "Summary" metadata field:
Expand Down

0 comments on commit f69d526

Please # to comment.