diff --git a/config/DCE_deltaCt.config.yaml b/config/DCE_deltaCt.config.yaml index 9de5a42..0df6ab0 100644 --- a/config/DCE_deltaCt.config.yaml +++ b/config/DCE_deltaCt.config.yaml @@ -1,7 +1,6 @@ # General (previously Qbi.runner options) -#data_dir: null -output_dir: logs/qMRI_processes +# data_dir: null # no_audit: True # -- this is a madym option # Inputs @@ -10,7 +9,7 @@ T1_path: madym_output/T1_VFA/T1.nii.gz dce_im_ext: .nii.gz dce_meta_ext: .json -#roi_path: null +roi_path: null # Parameters dce_limits: [2, 9, 11, 15] @@ -21,6 +20,7 @@ equal_var: True # Output maps_dir: DCE_output_maps +output_dir: logs/qMRI_processes/DCE_deltaCt.log diff --git a/workflow/Snakefile b/workflow/Snakefile index 3826ec5..83ad8a1 100644 --- a/workflow/Snakefile +++ b/workflow/Snakefile @@ -2,3 +2,28 @@ # Please follow the best practices: # https://snakemake.readthedocs.io/en/stable/snakefiles/best_practices.html, # in particular regarding the standardized folder structure mentioned there. + +from snakemake.utils import min_version +min_version("6.15") + +# workdir: "path/to/workdir" + +# # Preprocessing +# include: "rules/OE_IR_T1_mapping.smk" +# include: "rules/DCE_VFA_T1_mapping.smk" +# include: "rules/OE_deltaR1.smk" + +include: "rules/DCE_deltaCt.smk" + +# include: "rules/DCE_ETM.smk" +# # include: "rules/OE_DCE_hypoxia_mapping.smk" + +# # Postprocessing +# include: "OE_DCE_summary.smk" + +rule all: + input: + expand("{maps_dir}/{key}{ext}", + maps_dir = data_path("maps_dir"), + key = ['C_t', 'delta_C', 'C_baseline', 'C_enhancing', 'C_p_vals', 'S_p_vals'], + ext = ".nii.gz"), \ No newline at end of file diff --git a/workflow/rules/DCE_deltaCt.smk b/workflow/rules/DCE_deltaCt.smk new file mode 100644 index 0000000..d7f30d1 --- /dev/null +++ b/workflow/rules/DCE_deltaCt.smk @@ -0,0 +1,40 @@ +''' +Wrapper for PreclinicalMRI.dynamic.compute_dataset_delta_Ct +''' +import os +from snakemake.utils import validate + +configfile: workflow.source_path("../../config/DCE_deltaCt.config.yaml") +validate(config, workflow.source_path("../schemas/DCE_deltaCt.schema.yaml")) + +data_dir = config.get("data_dir") +assert os.path.exists(data_dir), f"The folder {data_dir} does not exist" +workdir: data_dir + +def data_path(path_key): + rel_path = config.get(path_key) + return rel_path if rel_path is not None else [] + +rule DCE_deltaCt: + conda: + "../envs/conda_env.yml" + input: + images = f"{data_path('dce_path')}{config['dce_im_ext']}", + metadata = f"{data_path('dce_path')}{config['dce_meta_ext']}", + T1_path = data_path('T1_path'), + roi_path = data_path('roi_path') + output: + expand("{maps_dir}/{key}{ext}", + maps_dir = data_path("maps_dir"), + key = ['C_t', 'delta_C', 'C_baseline', 'C_enhancing', 'C_p_vals', 'S_p_vals'], + ext = ".nii.gz"), + params: + dce_limits = config["dce_limits"], + relax_coeff = config["relax_coeff"], + average_fun = config["average_fun"], + alternative = config["alternative"], + equal_var = config["equal_var"] + log: + config["output_dir"] + script: + "../scripts/DCE_deltaCt.py" diff --git a/workflow/schemas/DCE_deltaCt.schema.yaml b/workflow/schemas/DCE_deltaCt.schema.yaml index 3981900..7bcd588 100644 --- a/workflow/schemas/DCE_deltaCt.schema.yaml +++ b/workflow/schemas/DCE_deltaCt.schema.yaml @@ -7,7 +7,6 @@ properties: data_dir: type: string description: 'Base data path' - default: './test' roi_path: type: ["null", string] @@ -17,14 +16,19 @@ properties: #Output dir maps_dir: type: string - description: 'Location of saved output maps, relative to data_dir' + description: 'Relative path to output maps' default: 'DCE_output_maps' + output_dir: + type: string + description: 'Relative path to log file' + default: logs/qMRI_processes/DCE_deltaCt.log + #DCE input options dce_path: type: string description: 'Relative path to DCE 4D-image volumes' - default: 'dynamic' + default: 'dce/dce_dyn_echo1' dce_im_ext: type: string diff --git a/workflow/scripts/DCE_deltaCt.py b/workflow/scripts/DCE_deltaCt.py index a8f3e50..ed07fba 100644 --- a/workflow/scripts/DCE_deltaCt.py +++ b/workflow/scripts/DCE_deltaCt.py @@ -1,100 +1,40 @@ ''' -_summary_ +Wrapper for PreclinicalMRI.dynamic.compute_dataset_delta_Ct ''' import os import numpy as np +from contextlib import redirect_stdout, redirect_stderr -from QbiPy.tools.runner import QbiRunner, bool_option from QbiPy.image_io.analyze_format import write_nifti_img from PreclinicalMRI.dynamic import dce -#--------------------------------------------------------- -def add_options(runner): - parser = runner.parser - - #General input arguments - parser.add('--roi_path', type=str, nargs='?', default=None, - help='Relative path to roi mask file') - - #DCE input options - parser.add('--dce_path', type=str, nargs='?', default='dynamic', - help='Relative path to DCE 4D-image volumes') - parser.add('--dce_im_ext', type=str, nargs='?', default='.nii.gz', - help='File extension of the DCE volumes') - parser.add('--dce_meta_ext', type=str, nargs='?', default='.json', - help='File extension of the DCE volumes meta-info') - parser.add('--dce_limits', type=int, nargs=4, required=True, - help='Limits for the DCE volumes, 4 element integer list') - parser.add('--T1_path', type=str, nargs='?', default='madym_output/T1_VFA/T1.nii.gz', - help='Relative path to T1 image') - parser.add('--relax_coeff', type=float, nargs='?', default=3.4, - help='Relaxivity constant for contrast agent - units... ') - - #Summary stats options - parser.add('--average_fun', type=str, nargs='?', default='median', - help='Method used for temporal average {median, mean}.') - parser.add('--alternative', type=str, nargs='?', default='less', - help='Apply 1-sided or 2-sided t-test: less = baseline lower than enhancing. Use "two-sided" for 2-sided.') - parser.add('--equal_var', type=bool_option, nargs='?', default=True, - const = True, help='Assume equal variance in pre/post enhancing periods') - - # TODO: Unused? - # parser.add('--sig_level', type=float, nargs='?', default=0.05, - # help='Significance level for p-value map thresholds') - - #Output dir - parser.add('--maps_dir', type=str, nargs='?', default='DCE_output_maps', - help='Location of saved output maps, relative to data_dir') - -#--------------------------------------------------------- -def run_DCE_deltaCt( - data_dir:str, - dce_limits:np.array, - dce_path:str, - dce_im_ext:str, - dce_meta_ext:str, - T1_path:str, - roi_path:str, - relax_coeff:float, - average_fun:str, - alternative:str, - equal_var:bool, - maps_dir:str -): - print('Running DCE_deltaCt') - dce_data = dce.compute_dataset_delta_Ct( - data_dir, - dce_limits, - dce_path = dce_path, - dce_im_ext = dce_im_ext, - dce_meta_ext = dce_meta_ext, - T1_path = T1_path, - roi_path = roi_path, - relax_coeff = relax_coeff, - average_fun = average_fun, - alternative = alternative, - equal_var = equal_var, - debug = 0) - - maps_dir = os.path.join(data_dir, maps_dir) - os.makedirs(maps_dir, exist_ok = True) - - for key,value in dce_data.items(): - print(f'{key} has shape {value.shape}') - nii_filepath = os.path.join(maps_dir, key + '.nii.gz') - write_nifti_img( - img_data = value, - filename = nii_filepath, - sform_matrix=np.eye(4), - scale = 1.0, - dtype = None) - -#--------------------------------------------------------- -def main(args = None): - runner = QbiRunner() - add_options(runner) - runner.run(run_DCE_deltaCt, args) - -#---------------------------------------------------------- -if __name__ == '__main__': - main() +with open(snakemake.log[0], "w") as log: + with redirect_stderr(log), redirect_stdout(log): + + print('Running DCE_deltaCt') + dce_data = dce.compute_dataset_delta_Ct( + snakemake.config["data_dir"], + snakemake.config["dce_limits"], + dce_path = snakemake.config["dce_path"], + dce_im_ext = snakemake.config["dce_im_ext"], + dce_meta_ext = snakemake.config["dce_meta_ext"], + T1_path = snakemake.config["T1_path"], + roi_path = snakemake.config["roi_path"], + relax_coeff = snakemake.config["relax_coeff"], + average_fun = snakemake.config["average_fun"], + alternative = snakemake.config["alternative"], + equal_var = snakemake.config["equal_var"], + debug = 0) + + maps_dir = os.path.join(snakemake.config["data_dir"], snakemake.config["maps_dir"]) + os.makedirs(maps_dir, exist_ok = True) + + for key,value in dce_data.items(): + print(f'{key} has shape {value.shape}') + nii_filepath = os.path.join(maps_dir, key + '.nii.gz') + write_nifti_img( + img_data = value, + filename = nii_filepath, + sform_matrix=np.eye(4), + scale = 1.0, + dtype = None)