diff --git a/giga_auto_qc/assessments.py b/giga_auto_qc/assessments.py index d7acd25..2fbbdac 100644 --- a/giga_auto_qc/assessments.py +++ b/giga_auto_qc/assessments.py @@ -8,7 +8,7 @@ from nibabel import Nifti1Image from nilearn.image import load_img, resample_to_img -from nilearn.masking import intersect_masks, _load_mask_img +from nilearn.masking import intersect_masks, load_mask_img from bids import BIDSLayout @@ -169,7 +169,7 @@ def _check_mask_affine( header_info = {"affine": []} key_to_header = {} for this_mask in mask_imgs: - _, affine = _load_mask_img(this_mask, allow_empty=True) + _, affine = load_mask_img(this_mask, allow_empty=True) affine_hashable = str(affine) header_info["affine"].append(affine_hashable) if affine_hashable not in key_to_header: @@ -281,6 +281,7 @@ def calculate_functional_metrics( "mean_fd_raw": fds_mean_raw, "mean_fd_scrubbed": fds_mean_scrub, "proportion_kept": proportion_kept, + "total_frames": timeseries_length, } func_filter = { @@ -334,6 +335,12 @@ def calculate_anat_metrics( """ if verbose > 0: print("Calculate the anatomical dice score.") + # check if the derivative was created with anatomical fast-track + check_anat = fmriprep_bids_layout.get(datatype="anat", return_type="file") + if not check_anat: + print("`anat/` not present in the derivatives. " "Skip anatomical QC.") + return pd.DataFrame() + metrics = {} for sub in tqdm(subjects): anat_filter = { @@ -397,6 +404,12 @@ def quality_accessments( > qulaity_control_standards["functional_dice"] ) functional_metrics["pass_func_qc"] = keep_fd * keep_proportion * keep_func + if anatomical_metrics.empty: + functional_metrics["pass_anat_qc"] = np.nan + functional_metrics["pass_all_qc"] = functional_metrics[ + "pass_func_qc" + ].copy() + return functional_metrics # get the anatomical pass / fail pass_anat_qc = {} diff --git a/giga_auto_qc/tests/test_assessments.py b/giga_auto_qc/tests/test_assessments.py index 1c58e3d..af92381 100644 --- a/giga_auto_qc/tests/test_assessments.py +++ b/giga_auto_qc/tests/test_assessments.py @@ -31,6 +31,17 @@ def test_quality_accessments(): ) assert metrics["pass_all_qc"].astype(int).sum() == 1 + metrics = assessments.quality_accessments( + functional_metrics=functional_metrics, + anatomical_metrics=pd.DataFrame(), + qulaity_control_standards=qc, + ) + assert ( + metrics["pass_all_qc"].astype(int).sum() + == metrics["pass_func_qc"].astype(int).sum() + ) + assert metrics["pass_anat_qc"].isnull().all() == True + def test_dice_coefficient(): """Check the dice coefficient is calculated correctly.""" diff --git a/giga_auto_qc/utils.py b/giga_auto_qc/utils.py index 8665c50..ac29ca0 100644 --- a/giga_auto_qc/utils.py +++ b/giga_auto_qc/utils.py @@ -56,6 +56,7 @@ def parse_scan_information(metrics: pd.DataFrame) -> pd.DataFrame: """ Parse the identifier into BIDS entities: subject, session, task, run. If session and run are not present, the information will not be parsed. + Parts of the identifier that do not match are ignored. Parameters ---------- @@ -73,20 +74,37 @@ def parse_scan_information(metrics: pd.DataFrame) -> pd.DataFrame: # get all unique entities headers_members = set() for id in metrics.index: - examplar = id.split("_") - new_headers = set([e.split("-")[0] for e in examplar]) + exemplar = id.split("_") + new_headers = set( + [ + e.split("-")[0] + for e in exemplar + if e.split("-")[0] in BIDS_ENTITIES + ] + ) headers_members.update(new_headers) ordered_header = [None] * len(BIDS_ENTITIES) for header in headers_members: - ordered_header[BIDS_ENTITIES[header]] = header + if header in BIDS_ENTITIES: + ordered_header[BIDS_ENTITIES[header]] = header headers = [h for h in ordered_header if h is not None] # remove none identifiers = pd.DataFrame( metrics.index.tolist(), index=metrics.index, columns=["identifier"] ) - identifiers[headers] = identifiers["identifier"].str.split( - "_", expand=True - ) + split_data = identifiers["identifier"].str.split("_", expand=True) + + # filter split data to only include columns corresponding to recognised headers + split_data = split_data.iloc[ + :, + [ + i + for i, part in enumerate(identifiers["identifier"][0].split("_")) + if part.split("-")[0] in BIDS_ENTITIES + ], + ] + identifiers[headers] = split_data + identifiers = identifiers.drop("identifier", axis=1) for h in headers: identifiers[h] = identifiers[h].str.replace(f"{h}-", "") diff --git a/giga_auto_qc/workflow.py b/giga_auto_qc/workflow.py index dd09981..844729e 100644 --- a/giga_auto_qc/workflow.py +++ b/giga_auto_qc/workflow.py @@ -51,6 +51,12 @@ def workflow(args): derivatives=True, reset_database=args.reindex_bids, ) + if fmriprep_bids_layout is None: + raise ValueError( + f"Cannot index directory in {bids_dir}. " + "Please ensure the path is a fMRIPrep output directory." + ) + # check output path output_dir.mkdir(parents=True, exist_ok=True)