Skip to content

Commit 5e408e5

Browse files
author
Chenying Zhao
authored
Merge pull request #116 from PennLINC/enh/mk_output_folder
[ENH] Add an option of creating a sub-folder in `<output_dir>` to zip all derivatives
2 parents e04a216 + 873871f commit 5e408e5

28 files changed

+1512
-256
lines changed

babs/babs.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
generate_cmd_set_envvar,
2626
generate_cmd_filterfile,
2727
generate_cmd_singularityRun_from_config, generate_cmd_unzip_inputds,
28+
get_info_zip_foldernames,
2829
generate_cmd_zipping_from_config,
2930
validate_type_session,
3031
validate_type_system,
@@ -2257,17 +2258,17 @@ def generate_bash_run_bidsapp(self, bash_path, input_ds, type_session):
22572258
When writing `singularity run` part, each chunk to write should start with " \\" + "\n\t",
22582259
meaning, starting with space, a backward slash, a return, and a tab.
22592260
"""
2260-
from .constants import PATH_FS_LICENSE_IN_CONTAINER
2261+
from .constants import PATH_FS_LICENSE_IN_CONTAINER, OUTPUT_MAIN_FOLDERNAME
22612262

22622263
type_session = validate_type_session(type_session)
2263-
output_foldername = "outputs" # folername of BIDS App outputs
22642264

22652265
# Check if the folder exist; if not, create it:
22662266
bash_dir = op.dirname(bash_path)
22672267
if not op.exists(bash_dir):
22682268
os.makedirs(bash_dir)
22692269

22702270
# check if `self.config` from the YAML file contains information we need:
2271+
# 1. check `singularity_run` section:
22712272
if "singularity_run" not in self.config:
22722273
# sanity check: there should be only one input ds
22732274
# otherwise need to specify in this section:
@@ -2295,10 +2296,14 @@ def generate_bash_run_bidsapp(self, bash_path, input_ds, type_session):
22952296
cmd_singularity_flags, flag_fs_license, path_fs_license, singuRun_input_dir = \
22962297
generate_cmd_singularityRun_from_config(self.config, input_ds)
22972298

2298-
print()
2299-
23002299
# TODO: also corporate the `call-fmt` in `datalad containers-add`
23012300

2301+
# 2. check `zip_foldernames` section:
2302+
dict_zip_foldernames, if_mk_output_folder, path_output_folder = \
2303+
get_info_zip_foldernames(self.config)
2304+
2305+
print()
2306+
23022307
# Check if the bash file already exist:
23032308
if op.exists(bash_path):
23042309
os.remove(bash_path) # remove it
@@ -2386,7 +2391,7 @@ def generate_bash_run_bidsapp(self, bash_path, input_ds, type_session):
23862391
cmd_head_singularityRun += " \\" + "\n\t"
23872392
cmd_head_singularityRun += singuRun_input_dir # inputs/data/<name>
23882393
cmd_head_singularityRun += " \\" + "\n\t"
2389-
cmd_head_singularityRun += output_foldername # output folder
2394+
cmd_head_singularityRun += path_output_folder # defined above
23902395

23912396
# currently all BIDS App support `participant` positional argu:
23922397
cmd_head_singularityRun += " \\" + "\n\t"
@@ -2411,15 +2416,16 @@ def generate_bash_run_bidsapp(self, bash_path, input_ds, type_session):
24112416
print(cmd_head_singularityRun + cmd_singularity_flags)
24122417

24132418
# Zip:
2414-
cmd_zip = generate_cmd_zipping_from_config(self.config, type_session, output_foldername)
2419+
cmd_zip = generate_cmd_zipping_from_config(dict_zip_foldernames, type_session)
24152420
bash_file.write(cmd_zip)
24162421

24172422
# Delete folders and files:
24182423
"""
24192424
rm -rf prep .git/tmp/wkdir
24202425
rm ${filterfile}
24212426
"""
2422-
cmd_clean = "rm -rf " + output_foldername + " " + ".git/tmp/wkdir" + "\n"
2427+
cmd_clean = "rm -rf " + OUTPUT_MAIN_FOLDERNAME + " " + ".git/tmp/wkdir" + "\n"
2428+
# ^^ rm the entire output folder `outputs`
24232429
if flag_filterfile is True:
24242430
cmd_clean += "rm ${filterfile}" + " \n"
24252431

babs/constants.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
11
MSG_NO_ALERT_IN_LOGS = "BABS: No alert message found in log files."
22
CHECK_MARK = u'\N{check mark}' # can be used by print(CHECK_MARK)
33
PATH_FS_LICENSE_IN_CONTAINER = "/SGLR/FREESURFER_HOME/license.txt"
4+
5+
# The upper layer of output folder - BABS expects there are sub-folers in it to zip:
6+
OUTPUT_MAIN_FOLDERNAME = "outputs"
7+
# Placeholder for creating a sub-folder to hold all outputs:
8+
PLACEHOLDER_MK_SUB_OUTPUT_FOLDER = "$TO_CREATE_FOLDER"

babs/utils.py

Lines changed: 110 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -442,20 +442,105 @@ def generate_cmd_set_envvar(env_var_name):
442442

443443
return cmd, env_var_value, env_var_value_in_container
444444

445-
446-
def generate_cmd_zipping_from_config(config, type_session, output_foldername="outputs"):
445+
def get_info_zip_foldernames(config):
447446
"""
448-
This is to generate bash command to zip BIDS App outputs.
447+
This is to get information from `zip_foldernames` section
448+
in the container configuration YAML file.
449+
Note that users have option to request creating a sub-folder in `outputs` folder,
450+
if the BIDS App does not do so (e.g., fMRIPrep new BIDS output layout).
451+
452+
Information:
453+
1. foldernames to zip
454+
2. whether the user requests creating a sub-folder
455+
3. path to the output dir to be used in the `singularity run`
449456
450457
Parameters:
451458
------------
452459
config: dictionary
453460
attribute `config` in class Container;
454461
got from `read_container_config_yaml()`
462+
463+
Returns:
464+
---------
465+
dict_zip_foldernames: dict
466+
`config["zip_foldernames"]` w/ placeholder key/value pair removed.
467+
if_mk_folder: bool
468+
whether requested to create a sub-folder in `outputs`.
469+
path_output_folder: str
470+
output folder used in `singularity run` of the BIDS App.
471+
see examples below.
472+
473+
Examples `path_output_folder` of BIDS App:
474+
-------------------------------------------------
475+
In `zip_foldernames` section:
476+
1. No placeholder: outputs
477+
2. placeholder = true & 1 folder: outputs/<foldername>
478+
479+
Notes:
480+
----------
481+
In fact, we use `OUTPUT_MAIN_FOLDERNAME` to define the 'outputs' string.
482+
"""
483+
484+
from .constants import OUTPUT_MAIN_FOLDERNAME, PLACEHOLDER_MK_SUB_OUTPUT_FOLDER
485+
486+
# Sanity check: this section should exist:
487+
if "zip_foldernames" not in config:
488+
raise Exception("The `container_config_yaml_file` does not contain"
489+
+ " the section `zip_foldernames`. Please add this section!")
490+
491+
# Check if placeholder to make a sub-folder in `outputs` folder:
492+
if_mk_folder = False
493+
if PLACEHOLDER_MK_SUB_OUTPUT_FOLDER in config["zip_foldernames"]:
494+
# check its value:
495+
# there cannot be two placeholders (w/ same strings);
496+
# otherwise error when loading yaml file
497+
value = config["zip_foldernames"][PLACEHOLDER_MK_SUB_OUTPUT_FOLDER]
498+
if value.lower() == "true": # lower case is "true"
499+
if_mk_folder = True
500+
501+
# Get the dict of foldernames + version number:
502+
dict_zip_foldernames = config["zip_foldernames"]
503+
if if_mk_folder:
504+
# remove key of placeholder if there is:
505+
_ = dict_zip_foldernames.pop(PLACEHOLDER_MK_SUB_OUTPUT_FOLDER)
506+
# ^^ the returned value is the value of this key
507+
508+
# sanity check: if there was placeholder, we expect only one output folder to create:
509+
if len(dict_zip_foldernames) == 1: # good
510+
pass
511+
elif len(dict_zip_foldernames) == 0: # only placeholder was provided:
512+
raise Exception("Only placeholder '" + PLACEHOLDER_MK_SUB_OUTPUT_FOLDER + "'"
513+
+ " is provided in section 'zip_foldernames'."
514+
+ " You should also provide"
515+
+ " a name of output folder to create and zip.")
516+
else: # len(dict_zip_foldernames) > 1: # more than one foldernames provided:
517+
raise Exception("You ask BABS to create more than one output folder,"
518+
+ " but BABS can only create one output folder."
519+
+ " Please only keep one of them in 'zip_foldernames' section.")
520+
521+
# Get the list of foldernames (w/o version number):
522+
list_foldernames = list(dict_zip_foldernames.keys())
523+
524+
# Generate the output folder path:
525+
path_output_folder = OUTPUT_MAIN_FOLDERNAME
526+
if if_mk_folder:
527+
the_folder = list_foldernames[0] # there is only one folder
528+
path_output_folder += "/" + the_folder
529+
530+
return dict_zip_foldernames, if_mk_folder, path_output_folder
531+
532+
533+
def generate_cmd_zipping_from_config(dict_zip_foldernames, type_session):
534+
"""
535+
This is to generate bash command to zip BIDS App outputs.
536+
537+
Parameters:
538+
------------
539+
dict_zip_foldernames: dictionary
540+
`config["zip_foldernames"]` w/ placeholder key/value pair removed.
541+
got from `get_info_zip_foldernames()`.
455542
type_session: str
456543
"multi-ses" or "single-ses"
457-
output_foldername: str
458-
the foldername of the outputs of BIDS App; default is "outputs".
459544
460545
Returns:
461546
---------
@@ -464,37 +549,34 @@ def generate_cmd_zipping_from_config(config, type_session, output_foldername="ou
464549
based on section `zip_foldernames` in the yaml file.
465550
"""
466551

552+
from .constants import OUTPUT_MAIN_FOLDERNAME
553+
467554
# cd to output folder:
468-
cmd = "cd " + output_foldername + "\n"
555+
cmd = "cd " + OUTPUT_MAIN_FOLDERNAME + "\n"
469556

470557
# 7z:
471558
if type_session == "multi-ses":
472559
str_sesid = "_${sesid}"
473560
else:
474561
str_sesid = ""
475562

476-
if "zip_foldernames" in config:
477-
value_temp = ""
478-
temp = 0
479-
480-
for key, value in config["zip_foldernames"].items():
481-
# each key is a foldername to be zipped;
482-
# each value is the version string;
483-
temp = temp + 1
484-
if (temp != 1) & (value_temp != value): # not matching last value
485-
warnings.warn("In section `zip_foldernames` in `container_config_yaml_file`: \n"
486-
"The version string of '" + key + "': '" + value + "'"
487-
+ " does not match with the last version string; "
488-
+ "we suggest using the same version string across all foldernames.")
489-
value_temp = value
490-
491-
cmd += "7z a ../${subid}" + str_sesid + "_" + \
492-
key + "-" + value + ".zip" + " " + key + "\n"
493-
# e.g., 7z a ../${subid}_${sesid}_fmriprep-0-0-0.zip fmriprep # this is multi-ses
494-
495-
else: # the yaml file does not have the section `zip_foldernames`:
496-
raise Exception("The `container_config_yaml_file` does not contain"
497-
+ " the section `zip_foldernames`. Please add this section!")
563+
# start to generate 7z commands:
564+
value_temp = ""
565+
temp = 0
566+
for key, value in dict_zip_foldernames.items():
567+
# each key is a foldername to be zipped;
568+
# each value is the version string;
569+
temp = temp + 1
570+
if (temp != 1) & (value_temp != value): # not matching last value
571+
warnings.warn("In section `zip_foldernames` in `container_config_yaml_file`: \n"
572+
"The version string of '" + key + "': '" + value + "'"
573+
+ " does not match with the last version string; "
574+
+ "we suggest using the same version string across all foldernames.")
575+
value_temp = value
576+
577+
cmd += "7z a ../${subid}" + str_sesid + "_" + \
578+
key + "-" + value + ".zip" + " " + key + "\n"
579+
# e.g., 7z a ../${subid}_${sesid}_fmriprep-0-0-0.zip fmriprep # this is multi-ses
498580

499581
# return to original dir:
500582
cmd += "cd ..\n"

0 commit comments

Comments
 (0)