diff --git a/niworkflows/interfaces/bids.py b/niworkflows/interfaces/bids.py index 595a0c1ef16..43bf1a917cb 100644 --- a/niworkflows/interfaces/bids.py +++ b/niworkflows/interfaces/bids.py @@ -653,9 +653,21 @@ def _run_interface(self, runtime): } ) if self._metadata: - sidecar = Path(self._results["out_file"][0]).parent / ( - "%s.json" % _splitext(self._results["out_file"][0])[0] - ) + out_file = Path(self._results["out_file"][0]) + # 1.3.x hack + # For dtseries, we have been generating weird non-BIDS JSON files. + # We can safely keep producing them to avoid breaking derivatives, but + # only the existing keys should keep going into them. + if out_file.name.endswith(".dtseries.nii"): + legacy_metadata = {} + for key in ("grayordinates", "space", "surface", "surface_density", "volume"): + if key in self._metadata: + legacy_metadata[key] = self._metadata.pop(key) + if legacy_metadata: + sidecar = out_file.parent / f"{_splitext(str(out_file))[0]}.json" + sidecar.write_text(dumps(legacy_metadata, sort_keys=True, indent=2)) + # The future: the extension is the first . and everything after + sidecar = out_file.parent / f"{out_file.name.split('.', 1)[0]}.json" sidecar.write_text(dumps(self._metadata, sort_keys=True, indent=2)) self._results["out_meta"] = str(sidecar) return runtime diff --git a/niworkflows/interfaces/tests/test_bids.py b/niworkflows/interfaces/tests/test_bids.py index e57ca4c6b0d..d2d13a21d79 100644 --- a/niworkflows/interfaces/tests/test_bids.py +++ b/niworkflows/interfaces/tests/test_bids.py @@ -1,6 +1,7 @@ """Tests on BIDS compliance.""" import os from pathlib import Path +import json import numpy as np import nibabel as nb @@ -278,6 +279,46 @@ def test_DerivativesDataSink_build_path( assert Path(out).relative_to(tmp_path) == Path(base) / exp +def test_DerivativesDataSink_dtseries_json_hack(tmp_path): + cifti_fname = str(tmp_path / "test.dtseries.nii") + + axes = (nb.cifti2.SeriesAxis(start=0, step=2, size=20), + nb.cifti2.BrainModelAxis.from_mask(np.ones((5, 5, 5)))) + hdr = nb.cifti2.cifti2_axes.to_header(axes) + cifti = nb.Cifti2Image(np.zeros(hdr.matrix.get_data_shape(), dtype=np.float32), + header=hdr) + cifti.nifti_header.set_intent("ConnDenseSeries") + cifti.to_filename(cifti_fname) + + source_file = tmp_path / "bids" / "sub-01" / "func" / "sub-01_task-rest_bold.nii.gz" + source_file.parent.mkdir(parents=True) + source_file.touch() + + dds = bintfs.DerivativesDataSink( + in_file=cifti_fname, + base_directory=str(tmp_path), + source_file=str(source_file), + compress=False, + out_path_base="", + space="fsLR", + grayordinates="91k", + RepetitionTime=2.0, + ) + + res = dds.run() + + out_path = Path(res.outputs.out_file) + + assert out_path.name == "sub-01_task-rest_space-fsLR_bold.dtseries.nii" + old_sidecar = out_path.with_name("sub-01_task-rest_space-fsLR_bold.dtseries.json") + new_sidecar = out_path.with_name("sub-01_task-rest_space-fsLR_bold.json") + + assert old_sidecar.exists() + assert "grayordinates" in json.loads(old_sidecar.read_text()) + assert new_sidecar.exists() + assert "RepetitionTime" in json.loads(new_sidecar.read_text()) + + @pytest.mark.parametrize( "space, size, units, xcodes, zipped, fixed, data_dtype", [