1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
|
# -*- coding: utf-8 -*-
import os
from os import path as op
title = 'mne-python flow diagram'
font_face = 'Arial'
node_size = 12
node_small_size = 9
edge_size = 9
sensor_color = '#7bbeca'
source_color = '#ff6347'
legend = """
<<FONT POINT-SIZE="%s">
<TABLE BORDER="0" CELLBORDER="0" CELLSPACING="4" CELLPADDING="4">
<TR><TD BGCOLOR="%s"> </TD><TD ALIGN="left">
Sensor (M/EEG) space</TD></TR>
<TR><TD BGCOLOR="%s"> </TD><TD ALIGN="left">
Source (brain) space</TD></TR>
</TABLE></FONT>>""" % (edge_size, sensor_color, source_color)
legend = ''.join(legend.split('\n'))
nodes = dict(
T1='T1',
flashes='Flash5/30',
trans='Head-MRI trans',
recon='Freesurfer surfaces',
bem='BEM',
src='Source space\nmne.SourceSpaces',
cov='Noise covariance\nmne.Covariance',
fwd='Forward solution\nmne.forward.Forward',
inv='Inverse operator\nmne.minimum_norm.InverseOperator',
stc='Source estimate\nmne.SourceEstimate',
raw='Raw data\nmne.io.Raw',
epo='Epoched data\nmne.Epochs',
evo='Averaged data\nmne.Evoked',
pre='Preprocessed data\nmne.io.Raw',
legend=legend,
)
sensor_space = ('raw', 'pre', 'epo', 'evo', 'cov')
source_space = ('src', 'stc', 'bem', 'flashes', 'recon', 'T1')
edges = (
('T1', 'recon'),
('flashes', 'bem'),
('recon', 'bem'),
('recon', 'src', 'mne.setup_source_space'),
('src', 'fwd'),
('bem', 'fwd'),
('trans', 'fwd', 'mne.make_forward_solution'),
('fwd', 'inv'),
('cov', 'inv', 'mne.make_inverse_operator'),
('inv', 'stc'),
('evo', 'stc', 'mne.minimum_norm.apply_inverse'),
('raw', 'pre', 'raw.filter\n'
'mne.preprocessing.ICA\n'
'mne.preprocessing.compute_proj_eog\n'
'mne.preprocessing.compute_proj_ecg\n'
'...'),
('pre', 'epo', 'mne.Epochs'),
('epo', 'evo', 'epochs.average'),
('epo', 'cov', 'mne.compute_covariance'),
)
subgraphs = (
[('T1', 'flashes', 'recon', 'bem', 'src'),
('<Structural information<BR/><FONT POINT-SIZE="%s"><I>'
'Freesurfer / MNE-C</I></FONT>>' % node_small_size)],
)
def setup(app):
app.connect('builder-inited', generate_flow_diagram)
app.add_config_value('make_flow_diagram', True, 'html')
def setup_module():
# HACK: Stop nosetests running setup() above
pass
def generate_flow_diagram(app):
out_dir = op.join(app.builder.outdir, '_static')
if not op.isdir(out_dir):
os.makedirs(out_dir)
out_fname = op.join(out_dir, 'mne-python_flow.svg')
make_flow_diagram = app is None or \
bool(app.builder.config.make_flow_diagram)
if not make_flow_diagram:
print('Skipping flow diagram, webpage will have a missing image')
return
import pygraphviz as pgv
g = pgv.AGraph(name=title, directed=True)
for key, label in nodes.items():
label = label.split('\n')
if len(label) > 1:
label[0] = ('<<FONT POINT-SIZE="%s">' % node_size
+ label[0] + '</FONT>')
for li in range(1, len(label)):
label[li] = ('<FONT POINT-SIZE="%s"><I>' % node_small_size
+ label[li] + '</I></FONT>')
label[-1] = label[-1] + '>'
label = '<BR/>'.join(label)
else:
label = label[0]
g.add_node(key, shape='plaintext', label=label)
# Create and customize nodes and edges
for edge in edges:
g.add_edge(*edge[:2])
e = g.get_edge(*edge[:2])
if len(edge) > 2:
e.attr['label'] = ('<<I>' +
'<BR ALIGN="LEFT"/>'.join(edge[2].split('\n')) +
'<BR ALIGN="LEFT"/></I>>')
e.attr['fontsize'] = edge_size
# Change colors
for these_nodes, color in zip((sensor_space, source_space),
(sensor_color, source_color)):
for node in these_nodes:
g.get_node(node).attr['fillcolor'] = color
g.get_node(node).attr['style'] = 'filled'
# Create subgraphs
for si, subgraph in enumerate(subgraphs):
g.add_subgraph(subgraph[0], 'cluster%s' % si,
label=subgraph[1], color='black')
# Format (sub)graphs
for gr in g.subgraphs() + [g]:
for x in [gr.node_attr, gr.edge_attr]:
x['fontname'] = font_face
g.node_attr['shape'] = 'box'
# A couple of special ones
for ni, node in enumerate(('fwd', 'inv', 'trans')):
node = g.get_node(node)
node.attr['gradientangle'] = 270
colors = (source_color, sensor_color)
colors = colors if ni == 0 else colors[::-1]
node.attr['fillcolor'] = ':'.join(colors)
node.attr['style'] = 'filled'
del node
g.get_node('legend').attr.update(shape='plaintext', margin=0, rank='sink')
# put legend in same rank/level as inverse
leg = g.add_subgraph(['legend', 'inv'], name='legendy')
leg.graph_attr['rank'] = 'same'
g.layout('dot')
g.draw(out_fname, format='svg')
return g
# This is useful for testing/iterating to see what the result looks like
if __name__ == '__main__':
from mne.io.constants import Bunch
out_dir = op.abspath(op.join(op.dirname(__file__), '..', '_build', 'html'))
app = Bunch(builder=Bunch(outdir=out_dir,
config=Bunch(make_flow_diagram=True)))
g = generate_flow_diagram(app)
|