diff --git a/.dryrun_test_all.sh b/.dryrun_test_all.sh index bb0e5642..f6deb448 100755 --- a/.dryrun_test_all.sh +++ b/.dryrun_test_all.sh @@ -9,9 +9,8 @@ hippunfold test_data/bids_T1w test_out participant -np --modality T1w hippunfold test_data/bids_hippb500 test_out participant -np --modality hippb500 hippunfold test_data/bids_T1w_longitudinal test_out participant -np --modality T1w hippunfold test_data/bids_singleT2w_longitudinal test_out participant -np --modality T2w -hippunfold test_data/bids_segT2w test_out participant -np --modality segT2w -hippunfold . test_out participant -np --modality cropseg --path_cropseg test_data/data_cropseg/sub-{subject}_hemi-{hemi}_dseg.nii.gz -hippunfold . test_out participant -np --modality cropseg --path_cropseg test_data/data_cropseg_1hemi/sub-{subject}_hemi-{hemi}_dseg.nii.gz --hemi L +hippunfold test_data/bids_manualseg test_out participant -np --modality manualseg --path_manualseg test_data/bids_manualseg/sub-{subject}_hemi-{hemi}_dseg.nii.gz +hippunfold test_data/bids_manualseg_1hemi test_out participant -np --modality manualseg --hemi L hippunfold test_data/bids_singleT2w test_out participant -np --modality T2w --t1_reg_template hippunfold test_data/bids_singleT2w test_out participant -np --modality T2w --output_space T1w hippunfold test_data/bids_T1w test_out participant -np --modality T1w --use-template-seg diff --git a/.github/workflows/python-testing.yml b/.github/workflows/python-testing.yml index 7a51a32e..3292aa71 100644 --- a/.github/workflows/python-testing.yml +++ b/.github/workflows/python-testing.yml @@ -94,17 +94,13 @@ jobs: run: | poetry run hippunfold test_data/bids_singleT2w_longitudinal test_out participant -np --modality T2w - - name: Test manual seg T2w bids + - name: Test manualseg bids, with path override run: | - poetry run hippunfold test_data/bids_segT2w test_out participant -np --modality segT2w + poetry run hippunfold test_data/bids_manualseg test_out participant -np --modality manualseg --path_manualseg test_data/bids_manualseg/sub-{subject}_hemi-{hemi}_dseg.nii.gz - - name: Test cropseg bids, with path override + - name: Test manualseg bids, one hemisphere hemi run: | - poetry run hippunfold . test_out participant -np --modality cropseg --path_cropseg test_data/data_cropseg/sub-{subject}_hemi-{hemi}_dseg.nii.gz - - - name: Test cropseg bids, with path override, left hemi - run: | - poetry run hippunfold . test_out participant -np --modality cropseg --path_cropseg test_data/data_cropseg_1hemi/sub-{subject}_hemi-{hemi}_dseg.nii.gz --hemi L + poetry run hippunfold test_data/bids_manualseg_1hemi test_out participant -np --modality manualseg --hemi L - name: Test T2w with T1w template registration run: | diff --git a/hippunfold/config/snakebids.yml b/hippunfold/config/snakebids.yml index fd4c6662..96e08641 100644 --- a/hippunfold/config/snakebids.yml +++ b/hippunfold/config/snakebids.yml @@ -67,19 +67,7 @@ pybids_inputs: - acquisition - run - seg: - filters: - suffix: 'dseg' - extension: '.nii.gz' - datatype: 'anat' - invalid_filters: 'allow' - wildcards: - - subject - - session - - acquisition - - run - - cropseg: + manualseg: filters: suffix: 'dseg' extension: '.nii.gz' @@ -100,15 +88,13 @@ pybids_inputs: parse_args: --modality: - help: 'Type of image to run hippunfold on. Modality prefixed with seg will import an existing (manual) hippocampal tissue segmentation from that space, instead of running neural network (default: %(default)s)' + help: 'Type of image to run hippunfold on. For manualseg and be sure to match the label scheme applied in the look up table (lut) online. For best performance, please also make sure the input is approximately aligned to the template space, with separate hemi-L|R in the name(s). (default: %(default)s)' required: True choices: - T1w - T2w - hippb500 - - segT1w - - segT2w - - cropseg + - manualseg --template: diff --git a/hippunfold/resources/label_lut/manual_dseg.tsv b/hippunfold/resources/label_lut/manual_dseg.tsv new file mode 100644 index 00000000..12413b53 --- /dev/null +++ b/hippunfold/resources/label_lut/manual_dseg.tsv @@ -0,0 +1,9 @@ +index name abbreviation +1 hippocampal grey matter GM +2 SRLM or 'dark band' SRLM +3 neocortex (entorhinal or parahippocampal) MTLC +4 pial surface Pial +5 hippocampal-amygdalar transition area HATA +6 indusium griseum IndGris +7 cysts Cyst +8 dentate gyrus DG \ No newline at end of file diff --git a/hippunfold/resources/label_lut/subfields_dseg .tsv b/hippunfold/resources/label_lut/subfields_dseg .tsv new file mode 100644 index 00000000..cafbd646 --- /dev/null +++ b/hippunfold/resources/label_lut/subfields_dseg .tsv @@ -0,0 +1,9 @@ +#No. Label R G B A +1 subiculum 41 43 252 0 +2 CA1 102 209 216 0 +3 CA2 0 179 60 0 +4 CA3 255 182 0 0 +5 CA4 255 43 23 0 +6 dentate_gyrus 245 236 0 0 +7 SRLM 119 54 155 0 +8 cysts 255 255 255 0 \ No newline at end of file diff --git a/hippunfold/workflow/Snakefile b/hippunfold/workflow/Snakefile index 46b4a248..c59e9a52 100644 --- a/hippunfold/workflow/Snakefile +++ b/hippunfold/workflow/Snakefile @@ -18,24 +18,19 @@ template_modality = False # set the lists used for output spaces -if ( - config["modality"] == "T1w" - or config["modality"] == "segT1w" - or "T1w" in config["output_spaces"] -): +if config["modality"] == "T1w" or "T1w" in config["output_spaces"]: ref_spaces.append("T1w") crop_ref_spaces.append("cropT1w") limit_to_list.add("T1w") if ( - (config["modality"] == "T2w" or config["modality"] == "segT2w") - and "native" in config["output_spaces"] + config["modality"] == "T2w" and "native" in config["output_spaces"] ) or "T2w" in config["output_spaces"]: ref_spaces.append("T2w") crop_ref_spaces.append("cropT2w") limit_to_list.add("T2w") if ( config["modality"] == "hippb500" - or config["modality"] == "cropseg" + or config["modality"] == "manualseg" or "corobl" in config["output_spaces"] ): ref_spaces.append("corobl") @@ -64,13 +59,7 @@ elif "T1w" in config["modality"]: if config["skip_inject_template_labels"]: # TODO do we need this? config["autotop_labels"] = ["hipp"] - -# add seg (if chosen) and the modality to the inputs -if config["modality"][:3] == "seg": # if modality is segT2w, then add seg - limit_to_list.add("seg") - limit_to_list.add(config["modality"][3:]) -else: - limit_to_list.add(config["modality"]) +limit_to_list.add(config["modality"]) # if atlas doesn't contain alignment features if not config["atlas"] == ["multihist7"]: @@ -118,13 +107,9 @@ if "T1w" in limit_to_list: # include rules only as they are needed.. -if config["modality"] == "segT1w" or config["modality"] == "segT2w": - - include: "rules/preproc_seg.smk" - -elif config["modality"] == "cropseg": +if config["modality"] == "manualseg": - include: "rules/preproc_cropseg.smk" + include: "rules/preproc_manualseg.smk" else: diff --git a/hippunfold/workflow/rules/common.smk b/hippunfold/workflow/rules/common.smk index d273f50f..61fe338f 100644 --- a/hippunfold/workflow/rules/common.smk +++ b/hippunfold/workflow/rules/common.smk @@ -32,17 +32,8 @@ def get_avg_or_cp_scans_cmd(wildcards, input, output): return cmd -def get_modality_key(modality): - if modality[:3] == "seg": - return "seg" - else: - return modality - - def get_modality_suffix(modality): - if modality[:3] == "seg": - return modality[3:] - elif modality[:4] == "hipp": + if modality[:4] == "hipp": return modality[4:] else: return modality @@ -50,7 +41,7 @@ def get_modality_suffix(modality): def get_final_spec(): if len(config["hemi"]) == 2: - specs = inputs[get_modality_key(config["modality"])].expand( + specs = inputs[config["modality"]].expand( bids( root=root, datatype="surf", @@ -66,7 +57,7 @@ def get_final_spec(): allow_missing=True, ) else: - specs = inputs[get_modality_key(config["modality"])].expand( + specs = inputs[config["modality"]].expand( bids( root=root, datatype="surf", @@ -89,7 +80,7 @@ def get_final_spec(): def get_final_surf(): gii = [] gii.extend( - inputs[get_modality_key(config["modality"])].expand( + inputs[config["modality"]].expand( bids( root=root, datatype="surf", @@ -109,7 +100,7 @@ def get_final_surf(): ) ) gii.extend( - inputs[get_modality_key(config["modality"])].expand( + inputs[config["modality"]].expand( bids( root=root, datatype="surf", @@ -132,7 +123,7 @@ def get_final_surf(): def get_final_subfields(): - return inputs[get_modality_key(config["modality"])].expand( + return inputs[config["modality"]].expand( bids( root=root, datatype="anat", @@ -159,7 +150,7 @@ def get_final_coords(): coords = [] # compute all laplace coords by default (incl IO) coords.extend( - inputs[get_modality_key(config["modality"])].expand( + inputs[config["modality"]].expand( bids( root=root, datatype="coords", @@ -180,7 +171,7 @@ def get_final_coords(): ) ) coords.extend( - inputs[get_modality_key(config["modality"])].expand( + inputs[config["modality"]].expand( bids( root=root, datatype="coords", @@ -206,7 +197,7 @@ def get_final_transforms(): xfms = [] xfms.extend( - inputs[get_modality_key(config["modality"])].expand( + inputs[config["modality"]].expand( bids( root=root, datatype="warps", @@ -226,7 +217,7 @@ def get_final_transforms(): ) xfms.extend( - inputs[get_modality_key(config["modality"])].expand( + inputs[config["modality"]].expand( bids( root=root, datatype="warps", @@ -246,7 +237,7 @@ def get_final_transforms(): ) xfms.extend( - inputs[get_modality_key(config["modality"])].expand( + inputs[config["modality"]].expand( bids( root=root, datatype="warps", @@ -265,10 +256,9 @@ def get_final_transforms(): def get_final_anat(): anat = [] - if "T1w" in ref_spaces or "T2w" in ref_spaces: anat.extend( - inputs[get_modality_key(config["modality"])].expand( + inputs[config["modality"]].expand( bids( root=root, datatype="anat", @@ -293,7 +283,7 @@ def get_final_qc(): if not template_modality == False: qc.extend( - inputs[get_modality_key(config["modality"])].expand( + inputs[config["modality"]].expand( bids( root=root, datatype="qc", @@ -307,7 +297,7 @@ def get_final_qc(): ) ) qc.extend( - inputs[get_modality_key(config["modality"])].expand( + inputs[config["modality"]].expand( bids( root=root, datatype="qc", @@ -325,7 +315,7 @@ def get_final_qc(): ) ) qc.extend( - inputs[get_modality_key(config["modality"])].expand( + inputs[config["modality"]].expand( bids( root=root, datatype="qc", @@ -346,7 +336,7 @@ def get_final_qc(): ) if len(config["hemi"]) == 2: qc.extend( - inputs[get_modality_key(config["modality"])].expand( + inputs[config["modality"]].expand( bids( root=root, datatype="qc", @@ -364,7 +354,7 @@ def get_final_qc(): if (config["modality"] == "T1w") or (config["modality"] == "T2w"): if not config["use_template_seg"]: qc.extend( - inputs[get_modality_key(config["modality"])].expand( + inputs[config["modality"]].expand( bids( root=root, datatype="qc", @@ -401,27 +391,21 @@ def get_final_output(): final_output = [] modality_suffix = get_modality_suffix(config["modality"]) - modality_key = get_modality_key(config["modality"]) + modality_key = config["modality"] # use a zip list for subject/session: zip_list = inputs[modality_key].zip_lists if "session" in zip_list: zip_list = snakebids.filter_list( zip_list, - { - "session": inputs[get_modality_key(config["modality"])].zip_lists[ - "session" - ] - }, + {"session": inputs[config["modality"]].zip_lists["session"]}, ) - final_output.extend( expand( expand(subj_output, zip, allow_missing=True, **zip_list), modality_suffix=modality_suffix, ) ) - return final_output @@ -464,7 +448,7 @@ def get_final_work_tar(): def get_work_dir(wildcards): - folder_with_file = inputs[get_modality_key(config["modality"])].expand( + folder_with_file = inputs[config["modality"]].expand( bids(root=work, **inputs.subj_wildcards), **wildcards ) folder_without_file = os.path.dirname(folder_with_file[0]) diff --git a/hippunfold/workflow/rules/gifti.smk b/hippunfold/workflow/rules/gifti.smk index 884662c0..515b1d16 100644 --- a/hippunfold/workflow/rules/gifti.smk +++ b/hippunfold/workflow/rules/gifti.smk @@ -1331,9 +1331,7 @@ rule create_spec_file_hipp: metric=get_gifti_metric_types(wildcards.label), allow_missing=True, ), - subfields=lambda wildcards: inputs[ - get_modality_key(config["modality"]) - ].expand( + subfields=lambda wildcards: inputs[config["modality"]].expand( bids( root=root, datatype="surf", @@ -1363,9 +1361,7 @@ rule create_spec_file_hipp: space=["{space}", "unfold"], allow_missing=True, ), - cifti_metrics=lambda wildcards: inputs[ - get_modality_key(config["modality"]) - ].expand( + cifti_metrics=lambda wildcards: inputs[config["modality"]].expand( bids( root=root, datatype="surf", @@ -1378,9 +1374,7 @@ rule create_spec_file_hipp: cifti=get_cifti_metric_types(wildcards.label), allow_missing=True, ), - cifti_labels=lambda wildcards: inputs[ - get_modality_key(config["modality"]) - ].expand( + cifti_labels=lambda wildcards: inputs[config["modality"]].expand( bids( root=root, datatype="surf", diff --git a/hippunfold/workflow/rules/preproc_cropseg.smk b/hippunfold/workflow/rules/preproc_manualseg.smk similarity index 55% rename from hippunfold/workflow/rules/preproc_cropseg.smk rename to hippunfold/workflow/rules/preproc_manualseg.smk index aface7f0..23996799 100644 --- a/hippunfold/workflow/rules/preproc_cropseg.smk +++ b/hippunfold/workflow/rules/preproc_manualseg.smk @@ -1,7 +1,14 @@ -rule import_cropseg: +rule import_manualseg_to_corobl: input: - in_img=partial(get_single_bids_input, component="cropseg"), + in_img=partial(get_single_bids_input, component="manualseg"), + template_dir=Path(download_dir) / "template" / config["template"], + params: + std_to_cor=lambda wildcards: config["template_files"][config["template"]][ + "xfm_corobl" + ].format(**wildcards), + ref=lambda wildcards, input: Path(input.template_dir) + / config["template_files"][config["template"]]["crop_ref"].format(**wildcards), output: nii=bids( root=work, @@ -9,14 +16,15 @@ rule import_cropseg: **inputs.subj_wildcards, suffix="dseg.nii.gz", space="corobl", - hemi="{hemi,L|R}" + hemi="{hemi,L|R}", ), - group: - "subj" container: config["singularity"]["autotop"] + group: + "subj" shell: - "cp {input} {output}" + "ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS={threads} " + "antsApplyTransforms -d 3 --interpolation MultiLabel -i {input.in_img} -o {output.nii} -r {params.ref} -t {params.std_to_cor}" rule lr_flip_seg: diff --git a/hippunfold/workflow/rules/preproc_seg.smk b/hippunfold/workflow/rules/preproc_seg.smk deleted file mode 100644 index fbb221cb..00000000 --- a/hippunfold/workflow/rules/preproc_seg.smk +++ /dev/null @@ -1,79 +0,0 @@ -rule import_seg: - input: - in_img=partial(get_single_bids_input, component="seg"), - output: - bids(root=work, datatype="anat", **inputs.subj_wildcards, suffix="dseg.nii.gz"), - group: - "subj" - container: - config["singularity"]["autotop"] - shell: - "cp {input} {output}" - - -# now, transform to coronal oblique, xfm dependent on the space wildcard -rule warp_seg_to_corobl_crop: - input: - nii=bids( - root=work, datatype="anat", **inputs.subj_wildcards, suffix="dseg.nii.gz" - ), - xfm=bids( - root=work, - datatype="warps", - **inputs.subj_wildcards, - suffix="xfm.txt", - from_="{space}", - to="corobl", - desc="affine", - type_="itk" - ), - template_dir=Path(download_dir) / "template" / config["template"], - params: - ref=lambda wildcards, input: Path(input.template_dir) - / config["template_files"][config["template"]]["crop_ref"].format(**wildcards), - output: - nii=bids( - root=work, - datatype="anat", - **inputs.subj_wildcards, - suffix="dseg.nii.gz", - space="corobl", - hemi="{hemi,L|R}", - from_="{space}" - ), - container: - config["singularity"]["autotop"] - group: - "subj" - shell: - "ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS={threads} " - "antsApplyTransforms -d 3 --interpolation MultiLabel -i {input.nii} -o {output.nii} -r {params.ref} -t {input.xfm}" - - -rule lr_flip_seg: - input: - nii=bids( - root=work, - datatype="anat", - **inputs.subj_wildcards, - suffix="dseg.nii.gz", - space="corobl", - hemi="{hemi}", - from_="{space}" - ), - output: - nii=bids( - root=work, - datatype="anat", - **inputs.subj_wildcards, - suffix="dseg.nii.gz", - space="corobl", - hemi="{hemi,L}flip", - from_="{space}" - ), - container: - config["singularity"]["autotop"] - group: - "subj" - shell: - "c3d {input} -flip x -o {output}" diff --git a/hippunfold/workflow/rules/qc.smk b/hippunfold/workflow/rules/qc.smk index 6dbeb1ae..878fab22 100644 --- a/hippunfold/workflow/rules/qc.smk +++ b/hippunfold/workflow/rules/qc.smk @@ -40,7 +40,7 @@ rule qc_reg_to_template: rule get_subfield_vols_subj: """Export segmentation volume for a subject to TSV""" input: - segs=inputs[get_modality_key(config["modality"])].expand( + segs=inputs[config["modality"]].expand( bids( root=root, **inputs.subj_wildcards, @@ -120,7 +120,7 @@ def get_bg_img_for_subfield_qc(wildcards): hemi="{hemi}", **inputs.subj_wildcards, ) - elif config["modality"] == "cropseg": + elif config["modality"] == "manualseg": # blank image as bg return bids( root=work, diff --git a/hippunfold/workflow/rules/resample_final_to_crop_native.smk b/hippunfold/workflow/rules/resample_final_to_crop_native.smk index 04e04ba2..7402c148 100644 --- a/hippunfold/workflow/rules/resample_final_to_crop_native.smk +++ b/hippunfold/workflow/rules/resample_final_to_crop_native.smk @@ -2,7 +2,7 @@ rule create_native_crop_ref: """Create ref space for hires crop in native space TODO: expose the resampling factor and size as cmd line args""" input: - seg=inputs[get_modality_key(config["modality"])].expand( + seg=inputs[config["modality"]].expand( bids( root=root, datatype="anat", diff --git a/hippunfold/workflow/rules/shape_inject.smk b/hippunfold/workflow/rules/shape_inject.smk index 2851fe7a..922493ad 100644 --- a/hippunfold/workflow/rules/shape_inject.smk +++ b/hippunfold/workflow/rules/shape_inject.smk @@ -32,7 +32,7 @@ def flipped(wildcards): def get_input_for_shape_inject(wildcards): - if config["modality"] == "cropseg": + if config["modality"] == "manualseg": seg = bids( root=work, datatype="anat", @@ -41,19 +41,6 @@ def get_input_for_shape_inject(wildcards): space="corobl", hemi="{hemi}", ).format(**wildcards) - elif get_modality_key(config["modality"]) == "seg": - modality_suffix = get_modality_suffix(config["modality"]) - seg = ( - bids( - root=work, - datatype="anat", - **inputs.subj_wildcards, - suffix="dseg.nii.gz", - space="corobl", - hemi="{hemi}", - from_="{modality_suffix}", - ).format(**wildcards, modality_suffix=modality_suffix), - ) else: seg = bids( root=work, @@ -68,7 +55,7 @@ def get_input_for_shape_inject(wildcards): def get_input_splitseg_for_shape_inject(wildcards): - if config["modality"] == "cropseg": + if config["modality"] == "manualseg": seg = bids( root=work, datatype="anat", @@ -77,18 +64,6 @@ def get_input_splitseg_for_shape_inject(wildcards): space="corobl", hemi="{hemi}", ).format(**wildcards) - - elif get_modality_key(config["modality"]) == "seg": - modality_suffix = get_modality_suffix(config["modality"]) - seg = bids( - root=work, - datatype="anat", - **inputs.subj_wildcards, - suffix="dsegsplit", - space="corobl", - hemi="{hemi}", - from_="{modality_suffix}", - ).format(**wildcards, modality_suffix=modality_suffix) else: seg = bids( root=work, diff --git a/test_data/data_cropseg/dataset_description.json b/test_data/bids_manualseg/dataset_description.json similarity index 100% rename from test_data/data_cropseg/dataset_description.json rename to test_data/bids_manualseg/dataset_description.json diff --git a/test_data/data_cropseg/sub-001_hemi-L_dseg.nii.gz b/test_data/bids_manualseg/sub-001/anat/sub-001_hemi-L_dseg.nii.gz similarity index 100% rename from test_data/data_cropseg/sub-001_hemi-L_dseg.nii.gz rename to test_data/bids_manualseg/sub-001/anat/sub-001_hemi-L_dseg.nii.gz diff --git a/test_data/data_cropseg/sub-001_hemi-R_dseg.nii.gz b/test_data/bids_manualseg/sub-001/anat/sub-001_hemi-R_dseg.nii.gz similarity index 100% rename from test_data/data_cropseg/sub-001_hemi-R_dseg.nii.gz rename to test_data/bids_manualseg/sub-001/anat/sub-001_hemi-R_dseg.nii.gz diff --git a/test_data/data_cropseg_1hemi/dataset_description.json b/test_data/bids_manualseg_1hemi/dataset_description.json similarity index 100% rename from test_data/data_cropseg_1hemi/dataset_description.json rename to test_data/bids_manualseg_1hemi/dataset_description.json diff --git a/test_data/data_cropseg_1hemi/sub-001_hemi-L_dseg.nii.gz b/test_data/bids_manualseg_1hemi/sub-001/anat/sub-001_hemi-L_dseg.nii.gz similarity index 100% rename from test_data/data_cropseg_1hemi/sub-001_hemi-L_dseg.nii.gz rename to test_data/bids_manualseg_1hemi/sub-001/anat/sub-001_hemi-L_dseg.nii.gz