Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Creating nireports #22

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
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
79 changes: 79 additions & 0 deletions physioqc/data/bootstrap.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Copyright 2023 The NiPreps Developers <[email protected]>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# We support and encourage derived works from this project, please read
# about our expectations at
#
# https://www.nipreps.org/community/licensing/
#
###########################################################################
# Reports bootstrap file
# ======================
# This is a YAML-formatted file specifying how the NiReports assembler
# will search for "reportlets" and compose them into a report file,
# typically in HTML format.
###########################################################################

packagename: physioqc
title: '{filename} :: Respiration QC Report (rawdata)'
sections:
- name: Rawdata
reportlets:
- bids: {datatype: figures, desc: raw}
caption: Here we see the rawdata over time.
style:
max-height: 700px
- name: Average peak
reportlets:
- bids: {datatype: figures, desc: average}
caption: This panel shows the average peak, and its errobars (or traces).
style:
max-height: 700px
- name: Power spectrum
reportlets:
- bids: {datatype: figures, desc: power}
caption: Plot of the power spectrum for the raw signal.
style:
max-height: 700px
- name: Histogram of peak amplitudes
reportlets:
- bids: {datatype: figures, desc: histpeakamp}
caption: Histogram of peak amplitudes.
style:
max-height: 700px
- name: Histogram of peak distances
reportlets:
- bids: {datatype: figures, desc: histpeakdist}
caption: Histogram of peak distanes.
style:
max-height: 700px

- name: Others
nested: true
reportlets:
- metadata: "input"
settings:
# By default, only the first dictionary will be expanded.
# If folded is true, all will be folded. If false all expanded.
# If an ID is not provided, one should be generated automatically
id: 'about-metadata'
- custom: errors
path: '{reportlets_dir}/{run_uuid}'
captions: <em>PhysioQC</em> may have recorded failure conditions.
title: Errors

# Rating widget
plugins:
- module: nireports.assembler
path: data/rating-widget/bootstrap.yml
75 changes: 63 additions & 12 deletions physioqc/interfaces/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,19 @@

import matplotlib.pyplot as plt
import pandas as pd
from bids import layout

from physioqc.metrics.multimodal import peak_amplitude, peak_detection, peak_distance

# Save pattern as global
pattern = (
"sub-{subject}[_ses-{session}]_task-{task}[_acq-{acquisition}]"
+ "[_rec-{reconstruction}][_run-{run}][_echo-{echo}]"
+ "[_desc-{description}]_{suffix}.{extension}"
)

def generate_figures(figures, data, outdir):

def generate_figures(figures, data, outdir, entities):
"""
Generates all the figure needed to populate the visual report.
Save the figures in the 'figures' folder
Expand All @@ -17,9 +25,22 @@ def generate_figures(figures, data, outdir):
A list of functions to run to generate all the figures.
data : np.array or peakdet Physio object
Physiological data
outdir: str
The path to the output directory.
entities: dictionary
A dictionary of bids entities used to write out the files.
"""
# Create the output directory if not existing
os.makedirs(os.path.join(outdir, "figures"), exist_ok=True)
sub_folders = []
for k in ["subject", "session"]:
if k in entities:
sub_folders.append("-".join([k[:3], entities[k]]))

out_folder = os.path.join(outdir, *sub_folders, "figures")
os.makedirs(out_folder, exist_ok=True)

out_entities = {k: v for k, v in entities.items()}
out_entities.update({"description": "", "extension": ".svg"})

for figure in figures:
# Get the plot name from the name of the function that was ran
Expand All @@ -35,21 +56,35 @@ def generate_figures(figures, data, outdir):
peak_dist = peak_distance(data)
fig, _ = figure(peak_dist)

plot_name = "histogram_peak_distance"
out_entities["description"] = "histpeakdist"
# TO IMPLEMENT the subject name should be automatically read when the data are loaded
fig.savefig(os.path.join(outdir, "figures", f"sub-01_desc-{plot_name}.svg"))
fig.savefig(
os.path.join(
out_folder, layout.writing.build_path(out_entities, pattern)
)
)

# Plot histogram of peak amplitude
peak_ampl = peak_amplitude(data)
fig, _ = figure(peak_ampl)

plot_name = "histogram_peak_distance"
fig.savefig(os.path.join(outdir, "figures", f"sub-01_desc-{plot_name}.svg"))

out_entities["description"] = "histpeakamp"
# TO IMPLEMENT the subject name should be automatically read when the data are loaded
fig.savefig(
os.path.join(
out_folder, layout.writing.build_path(out_entities, pattern)
)
)
else:
fig, _ = figure(data)
# Save the figure
fig.savefig(os.path.join(outdir, "figures", f"sub-01_desc-{plot_name}.svg"))
out_entities["description"] = plot_name
# TO IMPLEMENT the subject name should be automatically read when the data are loaded
fig.savefig(
os.path.join(
out_folder, layout.writing.build_path(out_entities, pattern)
)
)


def run_metrics(metrics_dict, data):
Expand Down Expand Up @@ -90,7 +125,7 @@ def run_metrics(metrics_dict, data):
return metrics_df


def save_metrics(metrics_df, outdir, to_csv=False):
def save_metrics(metrics_df, outdir, entities, to_csv=False):
"""
Save the metrics in the defined output path

Expand All @@ -109,8 +144,24 @@ def save_metrics(metrics_df, outdir, to_csv=False):
A dataframe containing the value of each metric
"""
# TO IMPLEMENT : there may be a bug associated to the next line IsDirectoryError
os.makedirs(outdir, exist_ok=True)
sub_folders = []
for k in ["subject", "session"]:
if k in entities:
sub_folders.append("-".join([k[:3], entities[k]]))

out_folder = os.path.join(outdir, *sub_folders)
os.makedirs(out_folder, exist_ok=True)

out_entities = {k: v for k, v in entities.items()}

if to_csv:
metrics_df.to_csv(os.path.join(outdir, "metrics.csv"), index=False)
out_entities.update({"description": "metrics", "extension": ".csv"})
metrics_df.to_csv(
os.path.join(out_folder, layout.writing.build_path(out_entities, pattern)),
index=False,
)
else:
metrics_df.to_json(os.path.join(outdir, "metrics.json"))
out_entities.update({"description": "metrics", "extension": ".json"})
metrics_df.to_json(
os.path.join(out_folder, layout.writing.build_path(out_entities, pattern))
)
43 changes: 41 additions & 2 deletions physioqc/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import numpy as np
import peakdet as pk
from bids import layout
from nireports.assembler.report import Report

from physioqc.cli.run import _get_parser
from physioqc.interfaces.interfaces import generate_figures, run_metrics, save_metrics
Expand All @@ -25,6 +27,10 @@
std,
)

BOOTSTRAP_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), "data"))

print("BOOTSTRAP path", BOOTSTRAP_PATH)


def save_bash_call(outdir):
"""
Expand Down Expand Up @@ -74,6 +80,9 @@ def physioqc(

figures = [plot_average_peak, plot_histogram, plot_power_spectrum, plot_raw_data]

# Stand in for further BIDS support:
bids_entities = layout.parse_file_entities(filename=filename)

# Load the data
d = np.genfromtxt(filename)

Expand All @@ -88,10 +97,40 @@ def physioqc(
metrics_df = run_metrics(metrics, data)

# Generate figures
generate_figures(figures, data, outdir)
generate_figures(figures, data, outdir, bids_entities)

# Save the metrics in the output folder
save_metrics(metrics_df, outdir)
save_metrics(metrics_df, outdir, bids_entities)

metric_dict = metrics_df.to_dict(orient="list")
metric_dict = {i: j for i, j in zip(metric_dict["Metric"], metric_dict["Value"])}
metadata = {
"about-metadata": {
"Metrics": metric_dict,
"Version": {"version": "pre functional, Definitely does not work yet ;)"},
}
}

filters = {k: bids_entities[k] for k in ["subject"]}

sub_folders = []
for k in ["subject", "session"]:
if k in bids_entities:
sub_folders.append("-".join([k[:3], bids_entities[k]]))

out_folder = os.path.join(outdir, *sub_folders, "figures")

robj = Report(
outdir,
"test",
reportlets_dir=out_folder,
bootstrap_file=os.path.join(BOOTSTRAP_PATH, "bootstrap.yml"),
metadata=metadata,
plugin_meta={},
**filters,
)

robj.generate_report()


def _main(argv=None):
Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ install_requires =
pandas
peakdet
duecredit
nireports
tests_require =
pytest >=5.3
test_suite = pytest
Expand Down