Skip to content

Commit

Permalink
Merge pull request #68 from Remi-Gau/templates
Browse files Browse the repository at this point in the history
[ENH] use bids metadata directly and adapt to new templates
  • Loading branch information
Remi-Gau committed Feb 7, 2024
2 parents cc5afbd + 35839f5 commit 97bb9ed
Show file tree
Hide file tree
Showing 11 changed files with 244 additions and 387 deletions.
1 change: 1 addition & 0 deletions bids/ext/reports/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,5 +127,6 @@ def cli(args: Sequence[str] = None, namespace=None) -> None:
if not common_patterns:
LOGGER.warning("No common patterns found.")
else:
output_dir.mkdir(parents=True, exist_ok=True)
with open(output_dir / "report.txt", "w") as f:
f.write(str(counter.most_common()[0][0]))
22 changes: 2 additions & 20 deletions bids/ext/reports/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,6 @@ def duration(all_imgs: list[Nifti1Image], metadata: dict[str, Any]) -> str:
return f"{min_dur}-{max_dur}"


def multiband_factor(metadata: dict[str, Any]) -> str:
"""Generate description of the multi-band acceleration applied, if used."""
return (
f'MB factor={metadata["MultibandAccelerationFactor"]}'
if metadata.get("MultibandAccelerationFactor", 1) > 1
else ""
)


def echo_time_ms(files: list[BIDSFile]) -> str:
"""Generate description of echo times from metadata field.
Expand Down Expand Up @@ -168,15 +159,6 @@ def echo_times_fmap(files: list[BIDSFile]) -> tuple[float, float]:
return te1, te2


def inplane_accel(metadata: dict[str, Any]) -> str:
"""Generate description of in-plane acceleration factor, if any."""
return (
f'in-plane acceleration factor={metadata["ParallelReductionFactorInPlane"]}'
if metadata.get("ParallelReductionFactorInPlane", 1) > 1
else ""
)


def bvals(bval_file: str | Path) -> str:
"""Generate description of dMRI b-values."""
# Parse bval file
Expand Down Expand Up @@ -291,7 +273,7 @@ def variants(metadata: dict[str, Any], config: dict[str, dict[str, str]]) -> str
variants = metadata.get("SequenceVariant", "")
if isinstance(variants, str):
variants = [
config["seqvar"].get(var, "UNKNOwN SEQUENCE VARIANT") for var in variants.split("_")
config["seqvar"].get(var, "UNKNOWN SEQUENCE VARIANT") for var in variants.split("_")
]
return list_to_str(variants)

Expand Down Expand Up @@ -321,7 +303,7 @@ def sequence(metadata: dict[str, Any], config: dict[str, dict[str, str]]) -> str
if seq_abbrs[0] and seqs_as_str:
seqs_as_str += f" ({'/'.join(seq_abbrs)})"
else:
seqs_as_str = "UNKNOwN SEQUENCE"
seqs_as_str = "UNKNOWN SEQUENCE"

return seqs_as_str

Expand Down
75 changes: 48 additions & 27 deletions bids/ext/reports/parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,21 @@
LOGGER = pybids_reports_logger()


def institution_info(files: list[BIDSFile]):
first_file = files[0]
metadata = first_file.get_metadata()
if metadata.get("InstitutionName"):
return templates.institution_info(metadata)
else:
return ""


def mri_scanner_info(files: list[BIDSFile]):
first_file = files[0]
metadata = first_file.get_metadata()
return templates.mri_scanner_info(metadata)


def common_mri_desc(
img: None | nib.Nifti1Image,
metadata: dict[str, Any],
Expand All @@ -34,9 +49,7 @@ def common_mri_desc(

return {
**metadata,
"field_strength": metadata.get("MagneticFieldStrength", "UNKNOWN"),
"tr": tr,
"flip_angle": metadata.get("FlipAngle", "UNKNOWN"),
"fov": parameters.field_of_view(img),
"matrix_size": parameters.matrix_size(img),
"voxel_size": parameters.voxel_size(img),
Expand Down Expand Up @@ -93,11 +106,8 @@ def func_info(files: list[BIDSFile], config: dict[str, dict[str, str]], layout:

desc_data = {
**common_mri_desc(img, metadata, config),
**device_info(metadata),
"echo_time": parameters.echo_time_ms(files),
"slice_order": parameters.slice_order(metadata),
"multiband_factor": parameters.multiband_factor(metadata),
"inplane_accel": parameters.inplane_accel(metadata),
"nb_runs": parameters.nb_runs(all_runs),
"task_name": metadata.get("TaskName", task_name),
"multi_echo": parameters.multi_echo(files),
Expand Down Expand Up @@ -181,7 +191,6 @@ def dwi_info(files: list[BIDSFile], config: dict[str, dict[str, str]], layout: B
"nb_runs": parameters.nb_runs(all_runs),
"bvals": parameters.bvals(bval_file),
"dmri_dir": dmri_dir,
"multiband_factor": parameters.multiband_factor(metadata),
}

return templates.dwi_info(desc_data)
Expand Down Expand Up @@ -213,17 +222,16 @@ def fmap_info(layout: BIDSLayout, files: list[BIDSFile], config: dict[str, dict[
if img is None:
files_not_found_warning(Path(first_file.path).relative_to(layout.root))

dir = "UNKNOWN PHASE ENCODING"
direction = "UNKNOWN PHASE ENCODING"
if PhaseEncodingDirection := metadata.get("PhaseEncodingDirection"):
dir = config["dir"].get(PhaseEncodingDirection, "UNKNOWN PHASE ENCODING")
direction = config["dir"].get(PhaseEncodingDirection, "UNKNOWN PHASE ENCODING")

desc_data = {
**common_mri_desc(img, metadata, config),
"te_1": parameters.echo_times_fmap(files)[0],
"te_2": parameters.echo_times_fmap(files)[1],
"slice_order": parameters.slice_order(metadata),
"dir": dir,
"multiband_factor": parameters.multiband_factor(metadata),
"dir": direction,
"intended_for": parameters.intendedfor_targets(metadata, layout),
}

Expand All @@ -250,17 +258,7 @@ def meg_info(files: list[BIDSFile]) -> str:
first_file = files[0]
metadata = first_file.get_metadata()

desc_data = {**device_info(metadata), **metadata}

return templates.meg_info(desc_data)


def device_info(metadata: dict[str, Any]) -> dict[str, Any]:
"""Extract device information from metadata."""
return {
"manufacturer": metadata.get("Manufacturer", "MANUFACTURER"),
"model_name": metadata.get("ManufacturersModelName", "MODEL"),
}
return templates.meg_info(metadata)


def final_paragraph(metadata: dict[str, Any]) -> str:
Expand Down Expand Up @@ -306,11 +304,26 @@ def parse_files(
# Group files into individual runs
data_files = collect_associated_files(layout, data_files, extra_entities=["run"])

# print(data_files)
# Will only get institution from the first file.
# This assumes that ALL files from ALL datatypes
# were acquired in the same institution.
description_list = [institution_info(data_files[0])]

# description_list = [general_acquisition_info(data_files[0][0].get_metadata())]
description_list = []
# %% MRI
mri_datatypes = ["anat", "func", "fmap", "perf", "dwi"]
mri_scanner_info_done = False
for group in data_files:

if group[0].entities["datatype"] not in mri_datatypes:
continue

# assume all MRI data was acquires on the same scanner
if not mri_scanner_info_done:
description_list.append(mri_scanner_info(group))
mri_scanner_info_done = True

group_description = ""

if group[0].entities["datatype"] == "func":
group_description = func_info(group, config, layout)

Expand All @@ -335,7 +348,17 @@ def parse_files(
] == "phasediff":
group_description = fmap_info(layout, group, config)

elif group[0].entities["datatype"] in [
description_list.append(group_description)

# %% other
for group in data_files:

if group[0].entities["datatype"] in mri_datatypes:
continue

group_description = ""

if group[0].entities["datatype"] in [
"eeg",
"meg",
"pet",
Expand All @@ -346,11 +369,9 @@ def parse_files(
"microscopy",
]:
LOGGER.warning(f" '{group[0].entities['datatype']}' not yet supported.")
group_description = ""

else:
LOGGER.warning(f" '{group[0].filename}' not yet supported.")
group_description = ""

description_list.append(group_description)

Expand Down
2 changes: 1 addition & 1 deletion bids/ext/reports/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ def _report_subject(self, subject: str, **kwargs: Any) -> str:

# Assume all data were converted the same way and use the first nifti
# file's json for conversion information.
description = "\n\t".join(description_list)
description = "\n".join(description_list)
if metadata:
description += f"\n\n{parsing.final_paragraph(metadata)}"
return description
20 changes: 16 additions & 4 deletions bids/ext/reports/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ def render(template_name: str, data: dict[str, Any] | None = None) -> str:
return tmp


def highlight_missing_tags(foo: str) -> str:
def highlight_missing_tags(foo: str, color="cyan") -> str:
"""Highlight missing tags in a rendered template."""
foo = f"[blue]{foo}[/blue]"
foo = foo.replace("{{", "[/blue][red]{{")
foo = foo.replace("}}", "}}[/red][blue]")
foo = f"[{color}]{foo}[/{color}]"
open_del = "{{"
foo = foo.replace("{{", f"[/{color}][red]{open_del}")
close_del = "}}"
foo = foo.replace("}}", f"{close_del}[/red][{color}]")
return foo


Expand All @@ -40,6 +42,16 @@ def footer() -> str:
return f"This section was (in part) generated automatically using pybids {__version__}."


def mri_scanner_info(desc_data: dict[str, Any]) -> str:
"""Generate mri scanner info report."""
return render(template_name="mri_scanner_info.mustache", data=desc_data)


def institution_info(desc_data: dict[str, Any]) -> str:
"""Generate institution report."""
return render(template_name="institution.mustache", data=desc_data)


def anat_info(desc_data: dict[str, Any]) -> str:
"""Generate anatomical report."""
return render(template_name="anat.mustache", data=desc_data)
Expand Down
32 changes: 2 additions & 30 deletions bids/ext/reports/tests/test_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
("RM", "research mode (RM)"),
("SE", "spin echo (SE)"),
("SE_EP", "spin echo and echo planar (SE/EP)"),
("spam egg", "UNKNOwN SEQUENCE"),
("spam egg", "UNKNOWN SEQUENCE"),
],
)
def test_sequence(ScanningSequence, expected_seq, testconfig):
Expand All @@ -39,7 +39,7 @@ def test_sequence(ScanningSequence, expected_seq, testconfig):
("SS", "steady state"),
("TRSS", "time reversed steady state"),
("MP_SS", "MAG prepared and steady state"),
("spam", "UNKNOwN SEQUENCE VARIANT"),
("spam", "UNKNOWN SEQUENCE VARIANT"),
],
)
def test_variants(SequenceVariant, expected_var, testconfig):
Expand Down Expand Up @@ -94,34 +94,6 @@ def test_describe_func_duration_smoke():
assert duration == expected


def test_multiband_factor_smoke(testmeta, testmeta_light):
# when
multiband_factor = parameters.multiband_factor(testmeta)
# then
expected = "MB factor=2"
assert multiband_factor == expected

# when
multiband_factor = parameters.multiband_factor(testmeta_light)
# then
expected = ""
assert multiband_factor == expected


def test_inplane_accel_smoke(testmeta, testmeta_light):
# when
multiband_factor = parameters.inplane_accel(testmeta)
# then
expected = "in-plane acceleration factor=2"
assert multiband_factor == expected

# when
multiband_factor = parameters.inplane_accel(testmeta_light)
# then
expected = ""
assert multiband_factor == expected


@pytest.mark.parametrize(
"slice_times, expected",
[
Expand Down
10 changes: 10 additions & 0 deletions bids/ext/reports/tests/test_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@
from bids.ext.reports import parsing


def test_institution_info(testlayout):
files = testlayout.get(
subject="01",
session="01",
extension=[".nii.gz"],
)
desc = parsing.institution_info(files)
assert desc == ""


def test_anat_info_smoke(testlayout, testconfig):
"""Smoke test for parsing.anat_info.
Expand Down
5 changes: 2 additions & 3 deletions bids/ext/reports/tests/test_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@
import json
from pathlib import Path

from bids.ext.reports import parsing, templates
from bids.ext.reports import templates


def test_pet():
with open(Path(__file__).parent / "data" / "sub-01_ses-01_pet.json") as f:
metadata = json.load(f)

desc_data = {**parsing.device_info(metadata), **metadata}
templates.pet_info(desc_data)
templates.pet_info(metadata)
4 changes: 2 additions & 2 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
sys.path.insert(0, os.path.abspath(os.path.pardir))
sys.path.insert(0, os.path.abspath("sphinxext"))

from github_link import make_linkcode_resolve
from github_link import make_linkcode_resolve # noqa

import bids.ext.reports
import bids.ext.reports # noqa

__version__ = bids.ext.reports.__version__

Expand Down
Loading

0 comments on commit 97bb9ed

Please sign in to comment.