Skip to content

Commit

Permalink
rm redondant lines
Browse files Browse the repository at this point in the history
  • Loading branch information
me-pic committed Apr 18, 2024
2 parents d796fe1 + c73922f commit 06e4f1d
Show file tree
Hide file tree
Showing 11 changed files with 439 additions and 2 deletions.
10 changes: 9 additions & 1 deletion phys2bids/cli/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,16 @@ def _get_parser():
help="Only print warnings to log file. Default is False.",
default=False,
)
optional.add_argument(
"-report", "--report",
dest="make_report",
action="store_true",
help="Generate a report with the data and generated folder structure. "
"Default is False.",
default=False,
)
optional.add_argument("-v", "--version", action="version", version=("%(prog)s " + __version__))

parser._action_groups.append(optional)

return parser
Expand Down
6 changes: 6 additions & 0 deletions phys2bids/phys2bids.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
from phys2bids import _version, bids, utils, viz
from phys2bids.cli.run import _get_parser
from phys2bids.physio_obj import BlueprintOutput
from phys2bids.reporting.html_report import generate_report
from phys2bids.slice4phys import slice4phys

from . import __version__
Expand Down Expand Up @@ -145,6 +146,7 @@ def phys2bids(
pad=9,
ch_name=[],
yml="",
make_report=False,
debug=False,
quiet=False,
):
Expand Down Expand Up @@ -538,6 +540,10 @@ def phys2bids(
),
)

# Only generate report if specified by the user
if make_report:
generate_report(conversion_path, logname, phys_out[key])


def _main(argv=None):
options = _get_parser().parse_args(argv)
Expand Down
1 change: 1 addition & 0 deletions phys2bids/reporting/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Visual reporting tools for inspecting phys2bids workflow outputs."""
Binary file added phys2bids/reporting/assets/apple-icon-180x180.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
102 changes: 102 additions & 0 deletions phys2bids/reporting/assets/main.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
html, body {
margin: 0;
padding: 0;
font-family: 'Lato', sans-serif;
overflow-x: hidden;
overflow-y: scroll;
}
* {
box-sizing: border-box;
}

.header {
background: linear-gradient(90deg, rgba(0,240,141,1) 0%, rgba(0,73,133,1) 100%);
height: 70px;
width: 100%;
position: fixed;
overflow: hidden;
margin: 0;
z-index: 100;
}

.header a, span {
color: white;
text-decoration: none;
font-weight: 700;
}

.header_logo {
display: inline-block;
float: left;
}

.header_logo img{
height: 50px;
top: 0;
left: 0;
padding-top: 15px;
}

.header_links {
top: 0;
left: 0;
padding-top: 25px;
margin-left: 20px;
margin-right: 20px;
float: left;
display: inline-block;
}
.clear {
clear: both;
}

.content {
margin-top: 100px;
display: flex;
width: 100%;
}

.tree {
margin-left: 50px;
margin-right: 50px;
flex: 0.5;
min-width: 300px;
float: left;
}

.tree_text {
margin-top: 10px;
margin-bottom: 70px;
width: 100%;
}

.bk-root {
display: inline-block;
margin-top: 10px;
width: 100%;
}

.bokeh_plots {
margin-left: 50px;
margin-right: 50px;
flex: 1;
min-width: 500px;
float: left;
}

@media screen and (max-width: 600px) {
.content {
flex-wrap: wrap;
}
.tree {
flex-basis: 100%;
}
.bokeh_plots {
flex-basis: 100%;
}
}

.main{
margin-top: 100px;
margin-left: 100px;
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
233 changes: 233 additions & 0 deletions phys2bids/reporting/html_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
"""Reporting functionality for phys2bids."""
import sys
from distutils.dir_util import copy_tree
from os.path import join
from pathlib import Path
from string import Template
from bokeh.plotting import figure, ColumnDataSource
from bokeh.embed import components
from bokeh.layouts import gridplot

from phys2bids import _version


def _save_as_html(log_html_path, log_content, qc_html_path):
"""
Save an HTML report out to a file.
Parameters
----------
log_html_path : str
Body for HTML report with embedded figures
log_content: str
String containing the logs generated by phys2bids
qc_html_path : str
Path to the quality check section of the report
Returns
-------
html: HTML code of the report
Outcome
-------
Saves the html file
"""
resource_path = Path(__file__).resolve().parent
head_template_name = 'report_log_template.html'
head_template_path = resource_path.joinpath(head_template_name)
with open(str(head_template_path), 'r') as head_file:
head_tpl = Template(head_file.read())

html = head_tpl.substitute(version=_version.get_versions()['version'],
log_html_path=log_html_path, log_content=log_content,
qc_html_path=qc_html_path)
return html


def _update_fpage_template(tree_string, bokeh_id, bokeh_js, log_html_path, qc_html_path):
"""
Populate a report with content.
Parameters
----------
tree_string: str
Tree of files in directory.
bokeh_id : str
HTML div created by bokeh.embed.components
bokeh_js : str
Javascript created by bokeh.embed.components
log_html_path : str
Path to the log section of the report
qc_html_path : str
Path to the quality check section of the report
Returns
-------
body : Body for HTML report with embedded figures
"""
resource_path = Path(__file__).resolve().parent

body_template_name = 'report_plots_template.html'
body_template_path = resource_path.joinpath(body_template_name)
with open(str(body_template_path), 'r') as body_file:
body_tpl = Template(body_file.read())
body = body_tpl.substitute(tree=tree_string,
content=bokeh_id,
javascript=bokeh_js,
version=_version.get_versions()['version'],
log_html_path=log_html_path,
qc_html_path=qc_html_path)
return body


def _generate_file_tree(out_dir):
"""
Populate a report with content.
Parameters
----------
outdir : str
Path to the output directory
Returns
-------
tree_string: String with the tree of files in directory
"""
# prefix components:
space = ' '
branch = '│ '
# pointers:
tee = '├── '
last = '└── '

def tree(dir_path: Path, prefix: str = ''):
"""Generate tree structure.
Given a directory Path object
will yield a visual tree structure line by line
with each line prefixed by the same characters
from https://stackoverflow.com/questions/9727673/list-directory-tree-structure-in-python
"""
contents = list(dir_path.iterdir())
# contents each get pointers that are ├── with a final └── :
pointers = [tee] * (len(contents) - 1) + [last]
for pointer, path in zip(pointers, contents):
yield prefix + pointer + path.name
if path.is_dir(): # extend the prefix and recurse:
extension = branch if pointer == tee else space
# i.e. space because last, └── , above so no more |
yield from tree(path, prefix=prefix + extension)

tree_string = ''
for line in tree(Path(out_dir)):
tree_string += line + '<br>'
return tree_string


def _generate_bokeh_plots(phys_in, figsize=(250, 500)):
"""
Plot all the channels for visualizations as linked line plots for dynamic report.
Parameters
----------
phys_in: BlueprintInput object
Object returned by BlueprintInput class
figsize: tuple
Size of the figure expressed as (size_x, size_y),
Default is 250x750px
Outcome
-------
Creates new plot with path specified in outfile.
See Also
--------
https://phys2bids.readthedocs.io/en/latest/howto.html
"""
colors = ['#ff7a3c', '#008eba', '#ff96d3', '#3c376b', '#ffd439']

time = phys_in.timeseries.T[0] # assumes first phys_in.timeseries is time
ch_num = len(phys_in.ch_name)
if ch_num > len(colors):
colors *= 2

downsample = int(phys_in.freq / 100)
plot_list = []
for row, timeser in enumerate(phys_in.timeseries.T[1:]):
# build a data source for each plot, with only the data + index (time)
# for the purpose of reporting, data is downsampled 10x
# doesn't make much of a difference to the naked eye, fine for reports
source = ColumnDataSource(data=dict(
x=time[::downsample],
y=timeser[::downsample]))

i = row + 1

tools = ['wheel_zoom,pan,reset']
q = figure(plot_height=figsize[0], plot_width=figsize[1],
tools=tools,
title=f' Channel {i}: {phys_in.ch_name[i]}',
sizing_mode='stretch_both')
q.line('x', 'y', color=colors[i - 1], alpha=0.9, source=source)
q.xaxis.axis_label = 'Time (s)'
# hovertool commented for posterity because I (KB) will be triumphant
# eventually
# q.add_tools(HoverTool(tooltips=[
# (phys_in.ch_name[i], '@y{0.000} ' + phys_in.units[i]),
# ('HELP', '100 :D')
# ], mode='vline'))
plot_list.append([q])
p = gridplot(plot_list, toolbar_location='right',
plot_height=250, plot_width=750,
merge_tools=True)
script, div = components(p)
return script, div


def generate_report(out_dir, log_path, phys_in):
"""
Plot all the channels for visualizations as linked line plots for dynamic report.
Parameters
----------
out_dir : str
File path to a completed phys2bids output directory
log_path: path
Path to the logged output of phys2bids
phys_in: BlueprintInput object
Object returned by BlueprintInput class
Outcome
-------
Creates new plot with path specified in outfile.
See Also
--------
https://phys2bids.readthedocs.io/en/latest/howto.html
"""
# Copy assets into output folder
pkgdir = sys.modules['phys2bids'].__path__[0]
assets_path = join(pkgdir, 'reporting', 'assets')
copy_tree(assets_path, join(out_dir, 'assets'))

# Read log
with open(log_path, 'r') as f:
log_content = f.read()

log_content = log_content.replace('\n', '<br>')
log_html_path = join(out_dir, 'phys2bids_report_log.html')
qc_html_path = join(out_dir, 'phys2bids_report.html')

html = _save_as_html(log_html_path, log_content, qc_html_path)

with open(log_html_path, 'wb') as f:
f.write(html.encode('utf-8'))

# Read in output directory structure & create tree
tree_string = _generate_file_tree(out_dir)
bokeh_js, bokeh_div = _generate_bokeh_plots(phys_in, figsize=(250, 750))
html = _update_fpage_template(tree_string, bokeh_div, bokeh_js, log_html_path, qc_html_path)

with open(qc_html_path, 'wb') as f:
f.write(html.encode('utf-8'))
Loading

0 comments on commit 06e4f1d

Please sign in to comment.