Package mvpa :: Package base :: Module report

[hide private]
[frames] | no frames]

# Source Code for Module mvpa.base.report

      1  # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
      2  # vi: set ft=python sts=4 ts=4 sw=4 et:
      3  ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
      4  #
      5  #   See COPYING file distributed along with the PyMVPA package for the
      6  #   copyright and license terms.
      7  #
      8  ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
      9  """Creating simple PDF reports using reportlab
     10  """
     11
     12  __docformat__ = 'restructuredtext'
     13
     14
     15  import os
     16  from datetime import datetime
     17
     18  import mvpa
     19  from mvpa.base import externals, verbose
     20
     21  if __debug__:
     22      from mvpa.base import debug
     23
     24  if externals.exists('reportlab', raiseException=True):
     25      import reportlab as rl
     26      from reportlab.platypus import  SimpleDocTemplate, Paragraph, Spacer, Image
     27      from reportlab.lib.styles import getSampleStyleSheet
     28      from reportlab.lib.units import inch
     29
     30      # Actually current reportlab's Image can't deal directly with .pdf images
     31      # Lets use png for now
     32      if externals.versions['reportlab'] >= '1112.2':
     33          _fig_ext_default = 'pdf'
     34      else:
     35          _fig_ext_default = 'png'
     36
     37
     38  __all__ = [ 'rl', 'Report', 'escapeXML' ]
     39
     40
     41 -def escapeXML(s):
     42      s = s.replace('&', '&amp;')
     43      s = s.replace('<', '&lt;')
     44      s = s.replace('>', '&gt;')
     45      return s
     46
     47 -class Report(object):
     48      """Simple PDF reports using reportlab
     49
     50      Named report 'report' generates 'report.pdf' and directory 'report/' with
     51      images which were requested to be included in the report
     52
     53      You can attach report to the existing 'verbose' with
     54
     55        report = Report()
     56        verbose.handlers += [report]
     57
     58      and then all verbose messages present on the screen will also be recorded
     59      in the report.  Use
     60        report.text("string")          to add arbitrary text
     61        report.xml("<H1>skajdsf</H1>") to add XML snippet
     62       or
     63        report.figure()  to add the current figure to the report.
     64        report.figures() to add existing figures to the report, but they
     65          might not be properly interleaved with verbose messages if there were
     66          any between the creations of the figures.
     67
     68      Inspired by Andy Connolly
     69      """
     70
     71 -    def __init__(self, name='report', title=None, path=None,
     72                   author=None, style="Normal",
     73                   fig_ext=None, font='Helvetica',
     74                   pagesize=None):
     75          """Initialize report
     76
     77          :Parameters:
     78            name : string
     79              Name of the report
     80            title : string or None
     81              Title to start the report, if None, name will be used
     82            path : string
     83              Top directory where named report will be stored. Has to be
     84              set now to have correct path for storing image renderings.
     85              Default: current directory
     86            author : string or None
     87              Optional author identity to be printed
     88            style : string
     89              Default Paragraph to be used. Must be the one of the known
     90              to reportlab styles, e.g. Normal
     91            fig_ext : string
     92              What extension to use for figures by default. If None, a default
     93              will be used. Since versions prior 2.2 of reportlab might do not
     94              support pdf, 'png' is default for those, 'pdf' otherwise
     95            font : string
     96              Name of the font to use
     97            pagesize : tuple of floats
     98              Optional page size if not to be default
     99          """
    100
    101          if pagesize is None:
    102              pagesize = rl.rl_config.defaultPageSize
    103          self.pagesize = pagesize
    104
    105          self.name = name
    106          self.author = author
    107          self.font = font
    108          self.title = title
    109          if fig_ext is None:
    110              self.fig_ext = _fig_ext_default
    111          else:
    112              self.fig_ext = fig_ext
    113
    114          if path is None:
    115              self._filename = name
    116          else:
    117              self._filename = os.path.join(path, name)
    118
    119          self.__nfigures = 0
    120
    121          try:
    122              styles = getSampleStyleSheet()
    123              self.style = styles.byName[style]
    124          except KeyError:
    125              raise ValueError, \
    126                    "Style %s is not know to reportlab. Known are %s" \
    127                    % (styles.keys())
    128
    129          self._story = []
    130
    131
    132      @property
    133 -    def __preamble(self):
    134          """Compose the beginning of the report
    135          """
    136          date = datetime.today().isoformat(' ')
    137
    138          owner = 'PyMVPA v. %s' % mvpa.__version__
    139          if self.author is not None:
    140              owner += '   Author: %s' % self.author
    141
    142          return [ Spacer(1, 0.8*inch),
    143                   Paragraph("Generated on " + date, self.style),
    144                   Paragraph(owner, self.style)] + self.__flowbreak
    145
    146
    147 -    def clear(self):
    148          """Clear the report
    149          """
    150          self._story = []
    151
    152
    153 -    def xml(self, line, style=None):
    154          """Adding XML string to the report
    155          """
    156          if __debug__ and not self in debug.handlers:
    157              debug("REP", "Adding xml '%s'" % line.strip())
    158          if style is None:
    159              style = self.style
    160          self._story.append(Paragraph(line, style=style))
    161
    162 -    def text(self, line, **kwargs):
    163          """Add a text string to the report
    164          """
    165          if __debug__ and not self in debug.handlers:
    166              debug("REP_", "Adding text '%s'" % line.strip())
    167          # we need to convert some of the characters to make it
    168          # legal XML
    169          line = escapeXML(line)
    170          self.xml(line, **kwargs)
    171
    172      write = text
    173      """Just an alias for .text, so we could simply provide report
    174      as a handler for verbose
    175      """
    176
    177
    178
    179 -    def figure(self, fig=None, name=None, savefig_kwargs={}, **kwargs):
    180          """Add a figure to the report
    181
    182          :Parameters:
    183            fig : None or string or `figure.Figure`
    184              Figure to place into report
    185                string : treat as a filename
    186                Figure : stores it into a file under directory
    187                         and embedds into the report
    188                None :   takes the current figure
    189            savefig_kwargs : dict
    190              Additional keyword arguments to provide savefig with (e.g. dpi)
    191            **kwargs
    192              Passed to :class:`reportlab.platypus.Image` constructor
    193          """
    194
    195          if externals.exists('pylab', raiseException=True):
    196              import pylab as P
    197              figure = P.matplotlib.figure
    198
    199          if fig is None:
    200              fig = P.gcf()
    201
    202          if isinstance(fig, figure.Figure):
    203              # Create directory if needed
    204              if not (os.path.exists(self._filename) and
    205                      os.path.isdir(self._filename)):
    206                  os.makedirs(self._filename)
    207
    208              # Figure out the name for image
    209              self.__nfigures += 1
    210              if name is None:
    211                  name = 'Figure#'
    212              name = name.replace('#', str(self.__nfigures))
    213
    214              # Save image
    215              fig_filename = os.path.join(self._filename,
    216                                          '%s.%s' % (name, self.fig_ext))
    217              if __debug__ and not self in debug.handlers:
    218                  debug("REP_", "Saving figure '%s' into %s"
    219                        % (fig, fig_filename))
    220
    221              fig.savefig(fig_filename, **savefig_kwargs)
    222
    223              # adjust fig to the one to be included
    224              fig = fig_filename
    225
    226          if __debug__ and not self in debug.handlers:
    227              debug("REP", "Adding figure '%s'" % fig)
    228
    229          im = Image(fig, **kwargs)
    230
    231          # If the inherent or provided width/height are too large -- shrink down
    232          imsize = (im.drawWidth, im.drawHeight)
    233
    234          # Reduce the size if necessary so reportlab does not puke later on
    235          r = [float(d)/m for d,m in zip(imsize, self.pagesize)]
    236          maxr = max(r)
    237          if maxr > 1.0:
    238              if __debug__ and not self in debug.handlers:
    239                  debug("REP_", "Shrinking figure by %.3g" % maxr)
    240              im.drawWidth  /= maxr
    241              im.drawHeight /= maxr
    242
    243          self._story.append(im)
    244
    245
    246 -    def figures(self, *args, **kwargs):
    247          """Adds all present figures at once
    248
    249          If called twice, it might add the same figure multiple times,
    250          so make sure to close all previous figures if you use
    251          figures() multiple times
    252          """
    253          if externals.exists('pylab', raiseException=True):
    254              import pylab as P
    255          figs = P.matplotlib._pylab_helpers.Gcf.figs
    256          if __debug__ and not self in debug.handlers:
    257              debug('REP', "Saving all %d present figures" % len(figs))
    258          for fid, f in figs.iteritems():
    259              self.figure(f.canvas.figure, *args, **kwargs)
    260
    261      @property
    262 -    def __flowbreak(self):
    263          return [Spacer(1, 0.2*inch),
    264                  Paragraph("-" * 150, self.style),
    265                  Spacer(1, 0.2*inch)]
    266
    267 -    def flowbreak(self):
    268          """Just a marker for the break of the flow
    269          """
    270          if __debug__ and not self in debug.handlers:
    271              debug("REP", "Adding flowbreak")
    272
    273          self._story.append(self.__flowbreak)
    274
    275
    276  ##     def __del__(self):
    277  ##         """Store report upon deletion
    278  ##         """
    279  ##         if __debug__ and not self in debug.handlers:
    280  ##             debug("REP", "Report is being deleted. Storing")
    281  ##         self.save()
    282
    283
    284 -    def save(self, add_preamble=True):
    285          """Saves PDF
    286
    287          :Parameters:
    288            add_preamble : bool
    289              Either to add preamble containing title/date/author information
    290          """
    291
    292          if self.title is None:
    293              title = self.name + " report"
    294          else:
    295              title = self.title
    296
    297          pageinfo = self.name + " data"
    298
    299          def myFirstPage(canvas, doc):
    300              canvas.saveState()
    301              canvas.setFont(self.font, 16)
    302              canvas.drawCentredString(self.pagesize[0]/2.0,
    303                                       self.pagesize[1]-108, title)
    304              canvas.setFont(self.font, 9)
    305              canvas.drawString(inch, 0.75 * inch,
    306                                "First Page / %s" % pageinfo)
    307              canvas.restoreState()
    308
    309          def myLaterPages(canvas, doc):
    310              canvas.saveState()
    311              canvas.setFont(self.font, 9)
    312              canvas.drawString(inch, 0.75 * inch,
    313                                "Page %d %s" % (doc.page, pageinfo))
    314              canvas.restoreState()
    315
    316          filename = self._filename + ".pdf"
    317          doc = SimpleDocTemplate(filename)
    318
    319          story = self._story
    320          if add_preamble:
    321              story = self.__preamble + story
    322
    323          if __debug__ and not self in debug.handlers:
    324              debug("REP", "Saving the report into %s" % filename)
    325
    326          doc.build(story,
    327                    onFirstPage=myFirstPage,
    328                    onLaterPages=myLaterPages)
    329