Skip to content

Commit

Permalink
[ENH] generate a single report per subject for each action (#231)
Browse files Browse the repository at this point in the history
* generate single report

* add test

* fix type

* update requirements.txt

* use union

* rm type annotation
  • Loading branch information
Remi-Gau authored Aug 10, 2024
1 parent f04cb24 commit 72d0e81
Show file tree
Hide file tree
Showing 24 changed files with 300 additions and 81 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ logs
# files
*.nii
*.h5
*.html
*desc-bidsmreye_eyetrack.tsv
pybids_db
tmp.py
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ repos:
rev: v1.11.1
hooks:
- id: mypy
additional_dependencies: [pydantic, numpy]
additional_dependencies: [pydantic, numpy, types-Jinja2]
files: bidsmreye
args: [--config-file, pyproject.toml]

Expand Down
2 changes: 1 addition & 1 deletion bidsmreye/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from bidsmreye.bidsmreye import bidsmreye
from bidsmreye.defaults import default_log_level, log_levels
from bidsmreye.download import download
from bidsmreye.logging import bidsmreye_log
from bidsmreye.logger import bidsmreye_log

log = bidsmreye_log(name="bidsmreye")

Expand Down
2 changes: 1 addition & 1 deletion bidsmreye/bids_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
get_bidsname_config,
get_pybids_config,
)
from bidsmreye.logging import bidsmreye_log
from bidsmreye.logger import bidsmreye_log
from bidsmreye.methods import methods
from bidsmreye.utils import copy_license, create_dir_if_absent, return_regex

Expand Down
2 changes: 1 addition & 1 deletion bidsmreye/bidsmreye.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from bidsmreye._version import __version__
from bidsmreye.configuration import Config
from bidsmreye.defaults import default_log_level
from bidsmreye.logging import bidsmreye_log
from bidsmreye.logger import bidsmreye_log

log = bidsmreye_log(name="bidsmreye")

Expand Down
2 changes: 1 addition & 1 deletion bidsmreye/config/config_bidsname.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"mask": "sub-{subject}/[ses-{session}]/func/sub-{subject}[_ses-{session}]_task-{task}[_acq-{acquisition}][_ce-{ce}][_rec-{rec}][_dir-{dir}][_run-{run}][_space-{space}][_res-{res}][_den-{den}]_desc-eye_mask.p",
"report": "sub-{subject}/[ses-{session}]/func/sub-{subject}[_ses-{session}]_task-{task}[_acq-{acquisition}][_ce-{ce}][_rec-{rec}][_dir-{dir}][_run-{run}][_space-{space}][_res-{res}][_den-{den}]_desc-eye_report.html",
"report": "sub-{subject}/[ses-{session}]/figures/sub-{subject}[_ses-{session}]_task-{task}[_acq-{acquisition}][_ce-{ce}][_rec-{rec}][_dir-{dir}][_run-{run}][_space-{space}][_res-{res}][_den-{den}]_desc-eye_report.html",
"no_label": "sub-{subject}/[ses-{session}]/func/sub-{subject}[_ses-{session}]_task-{task}[_acq-{acquisition}][_ce-{ce}][_rec-{rec}][_dir-{dir}][_run-{run}][_space-{space}][_res-{res}][_den-{den}]_desc-nolabel_bidsmreye.npz",
"confounds_tsv": "sub-{subject}/[ses-{session}]/func/sub-{subject}[_ses-{session}]_task-{task}[_acq-{acquisition}][_ce-{ce}][_rec-{rec}][_dir-{dir}][_run-{run}][_space-{space}]_desc-bidsmreye_eyetrack.tsv",
"confounds_json": "sub-{subject}/[ses-{session}]/func/sub-{subject}[_ses-{session}]_task-{task}[_acq-{acquisition}][_ce-{ce}][_rec-{rec}][_dir-{dir}][_run-{run}][_space-{space}]_desc-bidsmreye_eyetrack.json",
Expand Down
2 changes: 1 addition & 1 deletion bidsmreye/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from attrs import asdict, converters, define, field
from bids import BIDSLayout # type: ignore

from bidsmreye.logging import bidsmreye_log
from bidsmreye.logger import bidsmreye_log

log = bidsmreye_log(name="bidsmreye")

Expand Down
2 changes: 1 addition & 1 deletion bidsmreye/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

import bidsmreye
from bidsmreye.defaults import available_models, default_model
from bidsmreye.logging import bidsmreye_log
from bidsmreye.logger import bidsmreye_log

log = bidsmreye_log(name="bidsmreye")

Expand Down
2 changes: 1 addition & 1 deletion bidsmreye/generalize.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
list_subjects,
)
from bidsmreye.configuration import Config
from bidsmreye.logging import bidsmreye_log
from bidsmreye.logger import bidsmreye_log
from bidsmreye.quality_control import quality_control_output
from bidsmreye.utils import (
add_sidecar_in_root,
Expand Down
File renamed without changes.
32 changes: 14 additions & 18 deletions bidsmreye/methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@
import warnings
from pathlib import Path

import chevron

from bidsmreye._version import __version__
from bidsmreye.defaults import available_models, default_model
from bidsmreye.report import TEMPLATES_DIR, return_jinja_env
from bidsmreye.utils import create_dir_for_file


Expand Down Expand Up @@ -44,29 +43,26 @@ def methods(
if not model:
model = default_model()

is_known_models = False
is_known_model = False
is_default_model = False
if model in available_models():
is_known_models = True
is_known_model = True
if model == default_model():
is_default_model = True

if not is_known_models:
if not is_known_model:
warnings.warn(f"{model} is not a known model name.", stacklevel=3)

template_file = str(Path(__file__).parent / "templates" / "CITATION.mustache")
with open(template_file) as template:
output = chevron.render(
template=template,
data={
"version": __version__,
"model": model,
"is_default_model": is_default_model,
"is_known_models": is_known_models,
"qc_only": qc_only,
},
warn=True,
)
env = return_jinja_env(searchpath=TEMPLATES_DIR)
template = env.get_template("CITATION.jinja")

output = template.render(
version=__version__,
model=model,
is_default_model=is_default_model,
is_known_model=is_known_model,
qc_only=qc_only,
)

output_file.write_text(output)

Expand Down
6 changes: 5 additions & 1 deletion bidsmreye/prepare_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
save_sampling_frequency_to_json,
)
from bidsmreye.configuration import Config
from bidsmreye.logging import bidsmreye_log
from bidsmreye.logger import bidsmreye_log
from bidsmreye.report import generate_report
from bidsmreye.utils import (
check_if_file_found,
get_deepmreye_filename,
Expand Down Expand Up @@ -213,4 +214,7 @@ def prepare_data(cfg: Config) -> None:
)
for subject_label in subjects:
process_subject(cfg, layout_in, layout_out, subject_label)
generate_report(
output_dir=cfg.output_dir, subject_label=subject_label, action="prepare"
)
progress.update(subject_loop, advance=1)
18 changes: 13 additions & 5 deletions bidsmreye/quality_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from __future__ import annotations

import json
import logging
import math
from pathlib import Path

Expand All @@ -20,7 +19,8 @@
list_subjects,
)
from bidsmreye.configuration import Config
from bidsmreye.logging import bidsmreye_log
from bidsmreye.logger import bidsmreye_log
from bidsmreye.report import generate_report
from bidsmreye.utils import (
check_if_file_found,
create_dir_for_file,
Expand Down Expand Up @@ -142,9 +142,7 @@ def perform_quality_control(
add_qc_to_sidecar(confounds, sidecar_name)

fig = visualize_eye_gaze_data(confounds)
fig.update_layout(title=Path(confounds_tsv).name)
if log.isEnabledFor(logging.DEBUG):
fig.show()
fig.update_layout(showlegend=False, height=800)

create_dir_for_file(visualization_html_file)
fig.write_html(visualization_html_file)
Expand Down Expand Up @@ -184,6 +182,11 @@ def quality_control_output(cfg: Config) -> None:
)
for subject_label in subjects:
qc_subject(cfg, layout_out, subject_label)
generate_report(
output_dir=cfg.output_dir,
subject_label=subject_label,
action="generalize",
)
progress.update(subject_loop, advance=1)


Expand All @@ -203,6 +206,11 @@ def quality_control_input(cfg: Config) -> None:
)
for subject_label in subjects:
qc_subject(cfg, layout_in, subject_label, layout_out)
generate_report(
output_dir=cfg.output_dir,
subject_label=subject_label,
action="generalize",
)
progress.update(subject_loop, advance=1)


Expand Down
80 changes: 80 additions & 0 deletions bidsmreye/report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
"""Compile outputs from all tasks, spaces, runs into a single HTML."""

import datetime
from pathlib import Path

from jinja2 import Environment, FileSystemLoader, select_autoescape

from bidsmreye._version import __version__
from bidsmreye.logger import bidsmreye_log

log = bidsmreye_log(name="bidsmreye")

TEMPLATES_DIR = Path(__file__).parent / "templates"


def return_jinja_env(searchpath=None) -> Environment:
if searchpath is None:
searchpath = TEMPLATES_DIR / "report"
return Environment(
loader=FileSystemLoader(searchpath),
autoescape=select_autoescape(),
lstrip_blocks=True,
trim_blocks=True,
)


def generate_report(output_dir: Path, subject_label: str, action: str) -> None:

env = return_jinja_env()
template = env.get_template("base.html")

if action == "prepare":
input_files = sorted(output_dir.glob(f"sub-{subject_label}/**/*report.html"))
elif action == "generalize":
input_files = sorted(output_dir.glob(f"sub-{subject_label}/**/*eyetrack.html"))

files = []
for html_report in input_files:
with open(html_report) as f:
content = f.read()

name: str = html_report.stem
if action == "prepare":
name = name.replace("_desc-eye_report", "_desc-preproc_bold")

files.append({"name": name, "content": content, "path": html_report})

date = datetime.datetime.now().astimezone().replace(microsecond=0).isoformat()

report = template.render(
action=action,
files=files,
subject_label=subject_label,
date=date,
version=__version__,
)

report_filename = (
output_dir / f"sub-{subject_label}" / f"sub-{subject_label}_{action}.html"
)

with open(report_filename, "w") as f:
f.write(report)

log.info(f"Report saved at: '{report_filename}'.")


if __name__ == "__main__":

cwd = Path("/home/remi/github/cpp-lln-lab/bidsMReye")

output_dir = cwd / "outputs" / "moae_fmriprep" / "derivatives" / "bidsmreye"
subject = "01"

output_dir = Path("/home/remi/gin/CPP/yin/bidsmreye")
subject_label = "02"

action = "prepare"

generate_report(output_dir, subject_label, action)
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ performed using [*bidsMReye*](https://github.com/cpp-lln-lab/bidsMReye) (version
a BIDS app relying on [deepMReye](https://github.com/DeepMReye/DeepMReye) (@deepmreye)
to decode eye motion from fMRI time series data.

{{^qc_only}}
{% if not qc_only %}
### data extraction

The data of each BOLD runs underwent co-registration conducted
Expand All @@ -28,19 +28,18 @@ across voxels (spatial normalization).

Voxels time series were used as inputs for generalization decoding
using a
{{#is_known_models}}
{% if is_known_model %}
pre-trained model {{ model }} from deepMReye from [OSF](https://osf.io/23t5v).
{{#is_default_model}}
{% if is_default_model %}
This model was trained on the following datasets:
guided fixations (@alexander_open_2017),
smooth pursuit (@nau_real-motion_2018, @polti_rapid_2022, @nau_hexadirectional_2018),
free viewing (@julian_human_2018).
{{/is_default_model}}
{{/is_known_models}}
{{^is_known_models}}
{% endif %}
{% else %}
model trained on calibration data from the current study.
{{/is_known_models}}
{{/qc_only}}
{% endif %}
{% endif %}

### quality control

Expand Down
59 changes: 59 additions & 0 deletions bidsmreye/templates/report/base.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />

<title>bidsmreye report - sub-{{ subject_label }} - {{ action }}</title>

<link rel="icon" type="image/x-icon" href="https://raw.githubusercontent.com/cpp-lln-lab/bidsMReye/main/docs/source/images/bidsMReye_logo.png">

<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
crossorigin="anonymous"
/>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
crossorigin="anonymous">
</script>

</head>

<body>

<header>{% include "header.html" %}</header>

<main class="container-fluid">

<h2 class="mt-5 pt-5">subject {{ subject_label }}: {{ action }}</h2>

{% for file in files %}
<section id="{{ file.name }}">
<div class="row pt-4 px-5">
<h3 class="pt-3">{{ file.name }}</h3>
<div class="rounded-2 {% if action == 'prepare' %} bg-black {% else %} bg-white {% endif %}">
<div class="m-2 p-2">{{ file.content|safe }}</div>
</div>
</div>
</section>
{% endfor %}

<section id="about">
<h2 class="mt-3">About</h2>
<p>
Report generated
<ul>
<li>on: {{ date }}</li>
<li>with bidsmreye {{ version }}</li>
</ul>
</p>
</section>

</main>

<footer>{% include "footer.html" %}</footer>

</body>
</html>
Loading

0 comments on commit 72d0e81

Please sign in to comment.