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