"""
Module to convert a flow into an HTML representation.
This is especially useful for hinet structures.
The code uses the visitor pattern to reach and convert all the nodes in a flow.
"""
from __future__ import with_statement
import tempfile
import os
import webbrowser
import cStringIO as StringIO
import mdp
import switchboard
# TODO: use
for whitespaces?
class NewlineWriteFile(object):
"""Decorator for file-like object.
Adds a newline character to each line written with write().
"""
def __init__(self, file_obj):
"""Wrap the given file-like object."""
self.file_obj = file_obj
def write(self, str_obj):
"""Write a string to the file object and append a newline character."""
self.file_obj.write(str_obj + "\n")
# forward all other methods
def __getattr__(self, attr):
return getattr(self.file_obj, attr)
class HiNetHTMLVisitor(object):
"""Class to convert a hinet flow to HTML.
This class implements the visitor pattern. It relies on the 'html'
extension to get custom representations for normal node classes.
"""
def __init__(self, html_file, show_size=False):
"""Initialize the HMTL converter.
html_file -- File object into which the representation is written
(only the write method is used).
show_size -- Show the approximate memory footprint of all nodes.
"""
self.show_size = show_size
self._file = NewlineWriteFile(html_file)
@mdp.with_extension("html")
def convert_flow(self, flow):
"""Convert the flow into HTML and write it into the internal file."""
f = self._file
self._open_node_env(flow, "flow")
for node in flow:
f.write('')
self._visit_node(node)
f.write(' |
')
self._close_node_env(flow, "flow")
_CSS_FILENAME = "hinet.css"
@classmethod
def hinet_css(cls):
"""Return the standard CSS string.
The CSS should be embedded in the final HTML file.
"""
css_filename = os.path.join(os.path.split(__file__)[0],
cls._CSS_FILENAME)
with open(css_filename, 'r') as css_file:
css = css_file.read()
return css
def _visit_node(self, node):
"""Translate a node and return the translation.
Depending on the type of the node this can be delegated to more
specific methods.
"""
if hasattr(node, "flow"):
self._visit_flownode(node)
elif isinstance(node, mdp.hinet.CloneLayer):
self._visit_clonelayer(node)
elif isinstance(node, mdp.hinet.SameInputLayer):
self._visit_sameinputlayer(node)
elif isinstance(node, mdp.hinet.Layer):
self._visit_layer(node)
else:
self._visit_standard_node(node)
def _visit_flownode(self, flownode):
f = self._file
self._open_node_env(flownode, "flownode")
for node in flownode.flow:
f.write('')
self._visit_node(node)
f.write(' |
')
self._close_node_env(flownode, "flownode")
def _visit_layer(self, layer):
f = self._file
self._open_node_env(layer, "layer")
f.write('')
for node in layer:
f.write('')
self._visit_node(node)
f.write(' | ')
f.write('
')
self._close_node_env(layer)
def _visit_clonelayer(self, layer):
f = self._file
self._open_node_env(layer, "layer")
f.write('')
f.write(str(layer) + '
')
f.write('%d repetitions' % len(layer))
f.write(' | ')
f.write('')
self._visit_node(layer.node)
f.write(' |
')
self._close_node_env(layer)
def _visit_sameinputlayer(self, layer):
f = self._file
self._open_node_env(layer, "layer")
f.write('%s |
' %
(len(layer), str(layer)))
f.write('')
for node in layer:
f.write('')
self._visit_node(node)
f.write(' | ')
f.write('
')
self._close_node_env(layer)
def _visit_standard_node(self, node):
f = self._file
self._open_node_env(node)
f.write('')
f.write(str(node))
f.write(' |
')
f.write('')
f.write(node.html_representation())
f.write(' |
')
self._close_node_env(node)
# helper methods for decoration
def _open_node_env(self, node, type_id="node"):
"""Open the HTML environment for the node internals.
node -- The node itself.
type_id -- The id string as used in the CSS.
"""
self._file.write('' % type_id)
self._write_node_header(node, type_id)
def _write_node_header(self, node, type_id="node"):
"""Write the header content for the node into the HTML file."""
f = self._file
if not (type_id=="flow" or type_id=="flownode"):
f.write('in-dim: %s |
' %
str(node.input_dim))
f.write('')
f.write('')
def _close_node_env(self, node, type_id="node"):
"""Close the HTML environment for the node internals.
node -- The node itself.
type_id -- The id string as used in the CSS.
"""
f = self._file
f.write(' ')
f.write(' |
')
if not (type_id=="flow" or type_id=="flownode"):
f.write('out-dim: %s' % str(node.output_dim))
if self.show_size:
f.write(' size: %s'
% mdp.utils.get_node_size_str(node))
f.write(' |
')
f.write('
')
class HTMLExtensionNode(mdp.ExtensionNode, mdp.Node):
"""Extension node for custom HTML representations of individual nodes.
This extension works together with the HiNetHTMLVisitor to allow the
polymorphic generation of representations for node classes.
"""
extension_name = "html"
def html_representation(self):
"""Return an HTML representation of the node."""
html_repr = self._html_representation()
if type(html_repr) is str:
return html_repr
else:
return "
\n".join(html_repr)
# override this method
def _html_representation(self):
"""Return either the final HTML code or a list of HTML lines."""
return ""
@mdp.extension_method("html", switchboard.Rectangular2dSwitchboard,
"_html_representation")
def _rect2d_switchoard_html(self):
lines = ['rec. field size (in channels): %d x %d = %d' %
(self.field_channels_xy[0], self.field_channels_xy[1],
self.field_channels_xy[0] * self.field_channels_xy[1]),
'# of rec. fields (out channels): %d x %d = %d' %
(self.out_channels_xy[0], self.out_channels_xy[1],
self.output_channels),
'rec. field distances (in channels): ' +
str(self.field_spacing_xy),
'channel width: %d' % self.in_channel_dim]
if not all(self.unused_channels_xy):
lines.append('unused channels: ' + str(self.unused_channels_xy))
return lines
@mdp.extension_method("html", switchboard.DoubleRect2dSwitchboard,
"_html_representation")
def _double_rect2d_switchoard_html(self):
lines = ['rec. field size (in channels): %d x %d = %d' %
(self.field_channels_xy[0], self.field_channels_xy[1],
self.field_channels_xy[0] * self.field_channels_xy[1]),
'# of long row rec. fields (out channels): ' +
str(self.long_out_channels_xy),
'total number of receptive fields: %d' %
self.output_channels,
'channel width: %d' % self.in_channel_dim]
if self.x_unused_channels or self.y_unused_channels:
lines.append('unused channels: ' + str(self.unused_channels_xy))
return lines
@mdp.extension_method("html", switchboard.DoubleRhomb2dSwitchboard,
"_html_representation")
def _double_rhomb2d_switchoard_html(self):
lines = ['rec. field size: %d' % self.diag_field_channels,
'# of rec. fields (out channels): %d x %d = %d' %
(self.out_channels_xy[0], self.out_channels_xy[1],
self.output_channels),
'channel width: %d' % self.in_channel_dim]
return lines
@mdp.extension_method("html", mdp.nodes.SFA2Node, "_html_representation")
def _sfa_html(self):
return 'expansion dim: ' + str(self._expnode.output_dim)
@mdp.extension_method("html", mdp.nodes.NormalNoiseNode,
"_html_representation")
def _noise_html(self):
return ['noise level: ' + str(self.noise_args[1]),
'noise offset: ' + str(self.noise_args[0])]
@mdp.extension_method("html", mdp.nodes.CutoffNode, "_html_representation")
def _cutoff_html(self):
return ['lower bound: ' + str(self.lower_bound),
'upper bound: ' + str(self.upper_bound)]
@mdp.extension_method("html", mdp.nodes.HistogramNode, "_html_representation")
def _hist_html(self):
return 'history data fraction: ' + str(self.hist_fraction)
@mdp.extension_method("html", mdp.nodes.AdaptiveCutoffNode,
"_html_representation")
def _adap_html(self):
return ['lower cutoff fraction: ' + str(self.lower_cutoff_fraction),
'upper cutoff fraction: ' + str(self.upper_cutoff_fraction),
'history data fraction: ' + str(self.hist_fraction)]
class HiNetXHTMLVisitor(HiNetHTMLVisitor):
"""Modified converter to create valid XHTML."""
def convert_flow(self, flow):
"""Convert the flow into XHTML and write it into the internal file."""
# first write the normal HTML into a buffer
orig_file = self._file
html_file = StringIO.StringIO()
self._file = NewlineWriteFile(html_file)
super(HiNetXHTMLVisitor, self).convert_flow(flow)
self._file = orig_file
# now convert it to XHTML
html_code = html_file.getvalue()
html_code = html_code.replace('
', '
')
html_code = html_code.replace(' ', ' ')
self._file.write(html_code)
## Helper functions ##
def show_flow(flow, filename=None, title="MDP flow display",
show_size=False, browser_open=True):
"""Write a flow into a HTML file, open it in the browser and
return the file name.
flow -- The flow to be shown.
filename -- Filename for the HTML file to be created. If None
a temporary file is created.
title -- Title for the HTML file.
show_size -- Show the approximate memory footprint of all nodes.
browser_open -- If True (default value) then the slideshow file is
automatically opened in a webbrowser.
"""
if filename is None:
(fd, filename) = tempfile.mkstemp(suffix=".html", prefix="MDP_")
html_file = os.fdopen(fd, 'w')
else:
html_file = open(filename, 'w')
html_file.write('\n\n%s\n' % title)
html_file.write('\n\n\n')
html_file.write('%s
\n' % title)
explanation = '(data flows from top to bottom)'
html_file.write('%s\n' % explanation)
html_file.write('
\n')
converter = mdp.hinet.HiNetHTMLVisitor(html_file, show_size=show_size)
converter.convert_flow(flow=flow)
html_file.write('\n')
html_file.close()
if browser_open:
webbrowser.open(os.path.abspath(filename))
return filename