bids-standard / pybids

Python tools for querying and manipulating BIDS datasets.
https://bids-standard.github.io/pybids/
MIT License
225 stars 122 forks source link

Unable to retrieve json sidecar based on entities of nifti (after replacing `extension`) #1072

Open psadil opened 4 months ago

psadil commented 4 months ago

I'm not entirely sure that this is expected to work, but it was a bit surprising to me. One of the entities associated with the fmap nifti is "fmap": "epi". But getting files with that entity excludes the associated json sidecar. I expected the json sidecar to have the same entities as the nifti, but for the extension.

import tempfile
from pathlib import Path
import nibabel as nb
import numpy as np
import json

import bids

sub = "0"

description = {"BIDSVersion": "1.9.0", "Name": "test"}

with tempfile.TemporaryDirectory() as d:
    bidsdir = Path(d)
    (bidsdir / "dataset_description.json").write_text(json.dumps(description))
    fmapdir = bidsdir / f"sub-{sub}" / "fmap"
    fmapdir.mkdir(parents=True)
    for direction in ["AP", "PA"]:
        img = fmapdir / f"sub-{sub}_acq-dwib0_dir-{direction}_epi.nii.gz"
        nb.nifti1.Nifti1Image(np.zeros((4, 4)), affine=np.eye(4)).to_filename(
            img
        )
        sidecar_api = fmapdir / img.name.replace(".nii.gz", ".json")
        ped = "i" if direction == "AP" else "i-"
        sidecar_api.write_text(
            json.dumps({"PhaseEncodingDirection": ped, "TotalReadoutTime": 1})
        )

    test_layout = bids.BIDSLayout(bidsdir)
    img_file: bids.layout.models.BIDSImageFile = test_layout.get(
        subject=sub, suffix="epi", extension=".nii.gz", direction="AP"
    )[0]
    # show bids file
    print(f"{img_file=}")
    entities = img_file.get_entities()
    entities_with_extension = {
        **{k: v for k, v in entities.items() if k != "extension"},
        **{"extension": ".json"},
    }
    # confirm that the new entities has the .json ending
    # (but what's that extra fmap: epi?)
    print(f"{entities_with_extension=}")
    files = test_layout.get(**entities_with_extension)
    # this is empty?
    print(f"{files=}")

    files_without_fmap_entity = test_layout.get(
        **{k: v for k, v in entities_with_extension.items() if k != "fmap"}
    )
    # seems like the issue was the extra fmap entity
    print(f"{files_without_fmap_entity=}")
img_file=<BIDSImageFile filename='/var/folders/v_/kcpb096s1m3_37ctfd2sp2xm0000gn/T/tmpq0b7dk7z/sub-0/fmap/sub-0_acq-dwib0_dir-AP_epi.nii.gz'>

entities_with_extension={'acquisition': 'dwib0', 'datatype': 'fmap', 'direction': 'AP', 'fmap': 'epi', 'subject': '0', 'suffix': 'epi', 'extension': '.json'}

files=[]

files_without_fmap_entity=[<BIDSJSONFile filename='/var/folders/v_/kcpb096s1m3_37ctfd2sp2xm0000gn/T/tmpiwo5vuhu/sub-0/fmap/sub-0_acq-dwib0_dir-AP_epi.json'>]
bids.__version__
'0.16.5'
effigies commented 4 months ago

If all you want to do is get the same file but .json, then I think bidsfile.path.replace(file.entities['extension'], '.json') will do the trick.

The entities are a bit of a mess, due to the conflation of all path components (and, as you note, fmap) in entities.

https://github.com/bids-standard/pybids/blob/d442c8b28ac179a94b1c02080a6dc10b61541c93/bids/layout/config/bids.json#L104-L107

It seems unlikely that anybody's using that undocumented "entity", that is just the same as suffix for fieldmaps, but removing it properly would take a deprecation cycle. Feel free to add a config option:

https://github.com/bids-standard/pybids/blob/master/bids/config.py

There's not a lot of churn, so the deprecation is likely to take a while, but there's no reason we can't start it.