Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions clinica/iotools/bids_dataset_description.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from typing import IO

from attrs import define, fields
from cattr.gen import make_dict_unstructure_fn, override
from cattr.gen import make_dict_structure_fn, make_dict_unstructure_fn, override
from cattr.preconf.json import make_converter
from packaging.version import InvalidVersion, Version

Expand Down Expand Up @@ -34,6 +34,12 @@ def write(self, to: IO[str]):

json.dump(converter.unstructure(self), to, indent=4)

@classmethod
def from_file(cls, json_file: Path):
with open(json_file, "r") as fp:
content = json.load(fp)
return converter.structure(content, BIDSDatasetDescription)


def _rename(name: str) -> str:
"""Rename attributes following the specification for the JSON file.
Expand All @@ -49,17 +55,27 @@ def _rename(name: str) -> str:
# Register a JSON converter for the BIDS dataset description model.
converter = make_converter()
converter.register_unstructure_hook(Version, lambda dt: str(dt))
converter.register_structure_hook(Version, lambda ts, _: Version(ts))
bids_dataset_description_field_renaming = {
a.name: override(rename=_rename(a.name)) for a in fields(BIDSDatasetDescription)
}
converter.register_unstructure_hook(
BIDSDatasetDescription,
make_dict_unstructure_fn(
BIDSDatasetDescription,
converter,
**{
a.name: override(rename=_rename(a.name))
for a in fields(BIDSDatasetDescription)
},
**bids_dataset_description_field_renaming,
),
)
bids_dataset_field_renaming_structure_hook = make_dict_structure_fn(
BIDSDatasetDescription,
converter,
**bids_dataset_description_field_renaming,
)
converter.register_structure_hook(
BIDSDatasetDescription,
bids_dataset_field_renaming_structure_hook,
)


def get_bids_version(dataset_folder: Path) -> Version:
Expand Down
12 changes: 11 additions & 1 deletion clinica/iotools/utils/cli.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import List, Optional
from pathlib import Path
from typing import List, Optional, Union

import click

Expand All @@ -17,6 +18,15 @@ def cli() -> None:
pass


@cli.command()
@click.argument("dataset", type=click.Path(resolve_path=True))
def describe(dataset: Union[str, Path]):
"""Describe a dataset in BIDS or CAPS format."""
from .describe import describe as _describe

_describe(dataset)


@cli.command()
@bids_directory
@click.argument("output_bids_directory", type=click.Path(resolve_path=True))
Expand Down
115 changes: 115 additions & 0 deletions clinica/iotools/utils/describe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
from pathlib import Path
from typing import Optional, Union

from rich import box
from rich.console import Console
from rich.table import Table

from clinica.iotools.bids_dataset_description import BIDSDatasetDescription
from clinica.utils.caps import CAPSDatasetDescription
from clinica.utils.inputs import DatasetType

__all__ = ["describe"]


def describe(dataset: Union[str, Path]) -> None:
"""Describe a dataset in BIDS or CAPS format.

Parameters
----------
dataset : str or Path
The path to the BIDS or CAPS dataset to describe.
"""
description = (
CAPSDatasetDescription if _is_caps(dataset) else BIDSDatasetDescription
).from_file(Path(dataset) / "dataset_description.json")
console = Console()
console.print(_build_dataset_table(description))
if (processing_table := _build_processing_table(description)) is not None:
console.print(processing_table)


def _is_caps(dataset: Union[str, Path]) -> bool:
import json

from clinica.utils.inputs import DatasetType

dataset_description = Path(dataset) / "dataset_description.json"
with open(dataset_description, "r") as fp:
metadata = json.load(fp)

return metadata["DatasetType"] == DatasetType.DERIVATIVE


def _build_dataset_table(
description: Union[BIDSDatasetDescription, CAPSDatasetDescription],
) -> Table:
dataset_table = Table(title="Dataset information", box=box.ROUNDED)
for column, color in zip(
["Name", "Type", "BIDS Specifications Version"],
["bright_cyan", "bright_yellow", "bright_magenta"],
):
dataset_table.add_column(column, justify="right", style=color)
row = (description.name, description.dataset_type, str(description.bids_version))
if description.dataset_type == DatasetType.DERIVATIVE:
dataset_table.add_column(
"CAPS Specifications Version", justify="right", style="bright_green"
)
row = (
description.name,
description.dataset_type,
str(description.bids_version),
str(description.caps_version),
)
dataset_table.add_row(*row)

return dataset_table


def _build_processing_table(
description: Union[BIDSDatasetDescription, CAPSDatasetDescription],
) -> Optional[Table]:
if description.dataset_type == DatasetType.RAW:
return None
processing_table = Table(title="Processing information", box=box.ROUNDED)
for column, color in zip(
[
"Name",
"Date",
"Author",
"Machine",
"InputPath",
"ClinicaVersion",
"Dependencies",
],
[
"bright_cyan",
"bright_yellow",
"bright_magenta",
"bright_green",
"bright_red",
"bright_blue",
"bright_green",
],
):
processing_table.add_column(column, justify="right", style=color)
for processing in description.processing:
dependency_table = Table(title="Dependencies", box=box.ROUNDED)
for column in ("Name", "VersionConstraint", "InstalledVersion"):
dependency_table.add_column(column, justify="right")
for dependency in processing.dependencies:
dependency_table.add_row(
dependency.name,
str(dependency.version_constraint),
str(dependency.installed_version),
)
processing_table.add_row(
processing.name,
str(processing.date),
processing.author,
processing.machine,
processing.input_path,
str(processing.clinica_version),
dependency_table,
)
return processing_table
55 changes: 55 additions & 0 deletions docs/IO.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,61 @@
This page describes data handling tools provided by Clinica for [BIDS](http://bids.neuroimaging.io) and [CAPS](../CAPS/Introduction) compliant datasets.
These tools provide easy interaction mechanisms with datasets, including generating subject lists or merging all tabular data into a single TSV for analysis with external statistical software tools.

## `describe` - Describe a BIDS or CAPS dataset

This tool describes a BIDS or CAPS dataset, basically parsing the `dataset_description.json` file and displaying the information in the console in a nice way.

!!! note
This tool has been added in Clinica `0.9.0` and is not available in older versions.

```shell
clinica iotools describe DATASET_PATH
```

where:

- `DATASET_PATH`: path to the BIDS or CAPS dataset to be described.

**Examples:**

With a BIDS dataset:

```shell
clinica iotools describe ./bids
Dataset information
╭───────────────────────────────┬──────┬─────────────────────────────╮
│ Name │ Type │ BIDS Specifications Version │
├───────────────────────────────┼──────┼─────────────────────────────┤
│ The mother of all experiments │ raw │ 1.6.0 │
╰───────────────────────────────┴──────┴─────────────────────────────╯
```

With a CAPS dataset:

```shell
clinica iotools describe ./caps
Dataset information
╭──────────────────────────────────────┬────────────┬─────────────────────────────┬─────────────────────────────╮
│ Name │ Type │ BIDS Specifications Version │ CAPS Specifications Version │
├──────────────────────────────────────┼────────────┼─────────────────────────────┼─────────────────────────────┤
│ 05c5794b-2d20-4217-b727-c215d079ab35 │ derivative │ 1.9.0 │ 1.0.0 │
╰──────────────────────────────────────┴────────────┴─────────────────────────────┴─────────────────────────────╯
Processing information
╭────────────────────────────┬────────────────────────────┬───────────────────┬─────────────────┬────────────────────────────────────────────┬────────────────┬────────────────────────────────────────────╮
│ Name │ Date │ Author │ Machine │ InputPath │ ClinicaVersion │ Dependencies │
├────────────────────────────┼────────────────────────────┼───────────────────┼─────────────────┼────────────────────────────────────────────┼────────────────┼────────────────────────────────────────────┤
│ dwi-preprocessing-using-t1 │ 2024-09-01 13:41:05.529799 │ nicolas.gensollen │ UMR-COLLI-MP050 │ /Users/nicolas.gensollen/dwi_datasets/bids │ 0.9.0 │ Dependencies │
│ │ │ │ │ │ │ ╭───────────┬──────────────┬─────────────╮ │
│ │ │ │ │ │ │ │ Name │ VersionCons… │ InstalledV… │ │
│ │ │ │ │ │ │ ├───────────┼──────────────┼─────────────┤ │
│ │ │ │ │ │ │ │ ants │ >=2.3.0 │ 2.5.0 │ │
│ │ │ │ │ │ │ │ fsl │ >=6.0.1 │ 6.0.2 │ │
│ │ │ │ │ │ │ │ mrtrix │ >=0.0.0 │ 3.0.3 │ │
│ │ │ │ │ │ │ │ convert3d │ >=0.0.0 │ 1.0.0 │ │
│ │ │ │ │ │ │ ╰───────────┴──────────────┴─────────────╯ │
╰────────────────────────────┴────────────────────────────┴───────────────────┴─────────────────┴────────────────────────────────────────────┴────────────────┴────────────────────────────────────────────╯
```

## `create-subjects-visits` - Generate the list all subjects and visits of a given dataset

A TSV file with two columns (`participant_id` and `session_id`) containing the list of visits for each subject can be created as follows:
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ nav:
- OASIS-3 to BIDS: Converters/OASIS3TOBIDS.md
- UK Biobank to BIDS: Converters/UKBtoBIDS.md
- I/O tools:
- describe: IO#describe-Describe-a-BIDS-or-CAPS-dataset
- create-subjects-visits: IO#create-subjects-visits-generate-the-list-all-subjects-and-visits-of-a-given-dataset
- check-missing-modalities: IO#check-missing-modalities-check-missing-modalities-for-each-subject
- check-missing-processing: IO#check-missing-processing-check-missing-processing-in-a-caps-directory
Expand Down
55 changes: 54 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ pydra-freesurfer = "^0.0.9"
pydra-petpvc="^0.0.4"
pydra-fsl = "^0.0.22"
antspyx = "^0.4.2"
rich = "^13.8.0"

[tool.poetry.group.dev.dependencies]
pytest = "*"
Expand Down
Loading
Loading