ROOT  6.06/09
Reference Guide
utils.py
Go to the documentation of this file.
1 from __future__ import print_function
2 import os
3 import sys
4 import select
5 import tempfile
6 import pty
7 import itertools
8 import re
9 import fnmatch
10 from hashlib import sha1
11 from contextlib import contextmanager
12 from subprocess import check_output
13 from IPython import get_ipython
14 from IPython.display import HTML
15 from IPython.core.extensions import ExtensionManager
16 import IPython.display
17 import ROOT
18 import cpptransformer
19 import cppcompleter
20 
21 
22 # We want iPython to take over the graphics
23 ROOT.gROOT.SetBatch()
24 
25 
26 cppMIME = 'text/x-c++src'
27 ipyMIME = 'text/x-ipython'
28 
29 _jsDefaultHighlight = """
30 // Set default mode for code cells
31 IPython.CodeCell.options_default.cm_config.mode = '{mimeType}';
32 // Set CodeMirror's current mode
33 var cells = IPython.notebook.get_cells();
34 cells[cells.length-1].code_mirror.setOption('mode', '{mimeType}');
35 // Set current mode for newly created cell
36 cells[cells.length-1].cm_config.mode = '{mimeType}';
37 """
38 
39 _jsMagicHighlight = "IPython.CodeCell.config_defaults.highlight_modes['magic_{cppMIME}'] = {{'reg':[/^%%cpp/]}};"
40 
41 
42 _jsNotDrawableClassesPatterns = ["TGraph[23]D","TH3*","TGraphPolar","TProf*","TEve*","TF[23]","TGeo*","TPolyLine3D"]
43 
44 
45 _jsROOTSourceDir = "https://root.cern.ch/js/dev/"
46 _jsCanvasWidth = 800
47 _jsCanvasHeight = 600
48 
49 _jsCode = """
50 <div id="{jsDivId}"
51  style="width: {jsCanvasWidth}px; height: {jsCanvasHeight}px">
52 </div>
53 
54 <script>
55 requirejs.config(
56 {{
57  paths: {{
58  'JSRootCore' : '{jsROOTSourceDir}/scripts/JSRootCore',
59  'JSRootPainter' : '{jsROOTSourceDir}/scripts/JSRootPainter',
60  }}
61 }}
62 );
63 require(['JSRootCore', 'JSRootPainter'],
64  function(Core, Painter) {{
65  var obj = Core.parse('{jsonContent}');
66  Painter.draw("{jsDivId}", obj, "{jsDrawOptions}");
67  }}
68 );
69 </script>
70 """
71 
72 _enableJSVis = False
73 _enableJSVisDebug = False
75  global _enableJSVis
76  _enableJSVis = True
77 
79  global _enableJSVis
80  _enableJSVis = False
81 
83  global _enableJSVis
84  global _enableJSVisDebug
85  _enableJSVis = True
86  _enableJSVisDebug = True
87 
89  global _enableJSVis
90  global _enableJSVisDebug
91  _enableJSVis = False
92  _enableJSVisDebug = False
93 
95  return sys.platform
96 
97 def _getLibExtension(thePlatform):
98  '''Return appropriate file extension for a shared library
99  >>> _getLibExtension('darwin')
100  '.dylib'
101  >>> _getLibExtension('win32')
102  '.dll'
103  >>> _getLibExtension('OddPlatform')
104  '.so'
105  '''
106  pExtMap = {
107  'darwin' : '.dylib',
108  'win32' : '.dll'
109  }
110  return pExtMap.get(thePlatform, '.so')
111 
113  print("Welcome to ROOTaaS %s" %ROOT.gROOT.GetVersion())
114 
115 @contextmanager
116 def _setIgnoreLevel(level):
117  originalLevel = ROOT.gErrorIgnoreLevel
118  ROOT.gErrorIgnoreLevel = level
119  yield
120  ROOT.gErrorIgnoreLevel = originalLevel
121 
122 def commentRemover( text ):
123  '''
124  >>> s="// hello"
125  >>> commentRemover(s)
126  ''
127  >>> s="int /** Test **/ main() {return 0;}"
128  >>> commentRemover(s)
129  'int main() {return 0;}'
130  '''
131  def blotOutNonNewlines( strIn ) : # Return a string containing only the newline chars contained in strIn
132  return "" + ("\n" * strIn.count('\n'))
133 
134  def replacer( match ) :
135  s = match.group(0)
136  if s.startswith('/'): # Matched string is //...EOL or /*...*/ ==> Blot out all non-newline chars
137  return blotOutNonNewlines(s)
138  else: # Matched string is '...' or "..." ==> Keep unchanged
139  return s
140 
141  pattern = re.compile(\
142  r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
143  re.DOTALL | re.MULTILINE)
144 
145  return re.sub(pattern, replacer, text)
146 
147 
148 # Here functions are defined to process C++ code
150  #code = commentRemover(code)
151  ROOT.gInterpreter.ProcessLine(code)
152 
154  #code = commentRemover(code)
155  ROOT.gInterpreter.Declare(code)
156 
157 def processCppCode(code):
158  processCppCodeImpl(code)
159 
160 def declareCppCode(code):
161  declareCppCodeImpl(code)
162 
163 def _checkOutput(command,errMsg=None):
164  out = ""
165  try:
166  out = check_output(command.split())
167  except:
168  if errMsg:
169  sys.stderr.write("%s (command was %s)\n" %(errMsg,command))
170  return out
171 
172 def _invokeAclicMac(fileName):
173  '''FIXME!
174  This function is a workaround. On osx, it is impossible to link against
175  libzmq.so, among the others. The error is known and is
176  "ld: can't link with bundle (MH_BUNDLE) only dylibs (MH_DYLIB)"
177  We cannot at the moment force Aclic to change the linker command in order
178  to exclude these libraries, so we launch a second root session to compile
179  the library, which we then load.
180  '''
181  command = 'root -l -q -b -e gSystem->CompileMacro(\"%s\",\"k\")*0'%fileName
182  out = _checkOutput(command, "Error ivoking ACLiC")
183  libNameBase = fileName.replace(".C","_C")
184  ROOT.gSystem.Load(libNameBase)
185 
186 def _codeToFilename(code):
187  '''Convert code to a unique file name
188 
189  >>> _codeToFilename("int f(i){return i*i;}")
190  'dbf7e731.C'
191  '''
192  fileNameBase = sha1(code).hexdigest()[0:8]
193  return fileNameBase + ".C"
194 
196  '''Dump code to file whose name is unique
197 
198  >>> _codeToFilename("int f(i){return i*i;}")
199  'dbf7e731.C'
200  '''
201  fileName = _codeToFilename(code)
202  with open (fileName,'w') as ofile:
203  ofile.write(code)
204  return fileName
205 
206 def invokeAclic(cell):
207  fileName = _dumpToUniqueFile(cell)
208  if _getPlatform() == 'darwin':
209  _invokeAclicMac(fileName)
210  else:
211  processCppCode(".L %s+" %fileName)
212 
213 class StreamCapture(object):
214  def __init__(self, stream, ip=get_ipython()):
215  nbStreamsPyStreamsMap={sys.stderr:sys.__stderr__,sys.stdout:sys.__stdout__}
216  self.shell = ip
217  self.nbStream = stream
218  self.pyStream = nbStreamsPyStreamsMap[stream]
219  self.pipe_out, self.pipe_in = pty.openpty()
220  os.dup2(self.pipe_in, self.pyStream.fileno())
221  # Platform independent flush
222  # With ctypes, the name of the libc library is not known a priori
223  # We use jitted function
224  flushFunctionName='_ROOTaaS_Flush'
225  if (not hasattr(ROOT,flushFunctionName)):
226  declareCppCode("void %s(){fflush(nullptr);};" %flushFunctionName)
227  self.flush = getattr(ROOT,flushFunctionName)
228 
229  def more_data(self):
230  r, _, _ = select.select([self.pipe_out], [], [], 0)
231  return bool(r)
232 
233  def post_execute(self):
234  out = ''
235  if self.pipe_out:
236  while self.more_data():
237  out += os.read(self.pipe_out, 8192)
238 
239  self.flush()
240  self.nbStream.write(out) # important to print the value printing output
241  return 0
242 
243  def register(self):
244  self.shell.events.register('post_execute', self.post_execute)
245 
246 class CaptureDrawnCanvases(object):
247  '''
248  Capture the canvas which is drawn to display it.
249  '''
250  def __init__(self, ip=get_ipython()):
251  self.shell = ip
252 
253  def _pre_execute(self):
254  pass
255 
256  def _post_execute(self):
257  for can in ROOT.gROOT.GetListOfCanvases():
258  if can.IsDrawn():
259  can.Draw()
260  can.ResetDrawn()
261 
262  def register(self):
263  self.shell.events.register('pre_execute', self._pre_execute)
264  self.shell.events.register('post_execute', self._post_execute)
265 
266 
267 captures = [StreamCapture(sys.stderr),
268  StreamCapture(sys.stdout),
270 
271 def toCpp():
272  '''
273  Change the mode of the notebook to CPP. It is preferred to use cell magic,
274  but this option is handy to set up servers and for debugging purposes.
275  '''
276  ip = get_ipython()
277  cpptransformer.load_ipython_extension(ip)
278  # Change highlight mode
279  IPython.display.display_javascript(_jsDefaultHighlight.format(mimeType = cppMIME), raw=True)
280  print("Notebook is in Cpp mode")
281 
282 class CanvasDrawer(object):
283  '''
284  Capture the canvas which is drawn and decide if it should be displayed using
285  jsROOT.
286  '''
287  jsUID = 0
288 
289  def __init__(self, thePad):
290  self.thePad = thePad
291 
293  """
294  Get the list of primitives in the pad, recursively descending into
295  histograms and graphs looking for fitted functions.
296  """
297  primitives = self.thePad.GetListOfPrimitives()
298  primitivesNames = map(lambda p: p.ClassName(), primitives)
299  #primitivesWithFunctions = filter(lambda primitive: hasattr(primitive,"GetListOfFunctions"), primitives)
300  #for primitiveWithFunctions in primitivesWithFunctions:
301  # for function in primitiveWithFunctions.GetListOfFunctions():
302  # primitivesNames.append(function.GetName())
303  return sorted(primitivesNames)
304 
305  def _getUID(self):
306  '''
307  Every DIV containing a JavaScript snippet must be unique in the
308  notebook. This methods provides a unique identifier.
309  '''
310  CanvasDrawer.jsUID += 1
311  return CanvasDrawer.jsUID
312 
313  def _canJsDisplay(self):
314  # to be optimised
315  if not _enableJSVis: return False
316  primitivesTypesNames = self._getListOfPrimitivesNamesAndTypes()
317  for unsupportedPattern in _jsNotDrawableClassesPatterns:
318  for primitiveTypeName in primitivesTypesNames:
319  if fnmatch.fnmatch(primitiveTypeName,unsupportedPattern):
320  print("The canvas contains an object of a type jsROOT cannot currently handle (%s). Falling back to a static png." %primitiveTypeName, file=sys.stderr)
321  return False
322  return True
323 
324  def _jsDisplay(self):
325  # Workaround to have ConvertToJSON work
326  pad = ROOT.gROOT.GetListOfCanvases().FindObject(ROOT.gPad.GetName())
327  json = ROOT.TBufferJSON.ConvertToJSON(pad, 3)
328  #print "JSON:",json
329 
330  # Here we could optimise the string manipulation
331  divId = 'root_plot_' + str(self._getUID())
332  thisJsCode = _jsCode.format(jsCanvasWidth = _jsCanvasWidth,
333  jsCanvasHeight = _jsCanvasHeight,
334  jsROOTSourceDir = _jsROOTSourceDir,
335  jsonContent=json.Data(),
336  jsDrawOptions="",
337  jsDivId = divId)
338 
339  # display is the key point of this hook
340  IPython.display.display(HTML(thisJsCode))
341  return 0
342 
343  def _pngDisplay(self):
344  ofile = tempfile.NamedTemporaryFile(suffix=".png")
345  with _setIgnoreLevel(ROOT.kError):
346  self.thePad.SaveAs(ofile.name)
347  img = IPython.display.Image(filename=ofile.name, format='png', embed=True)
348  IPython.display.display(img)
349  return 0
350 
351  def _display(self):
352  if _enableJSVisDebug:
353  self._pngDisplay()
354  self._jsDisplay()
355  else:
356  if self._canJsDisplay():
357  self._jsDisplay()
358  else:
359  self._pngDisplay()
360 
361 
362  def Draw(self):
363  self._display()
364  return 0
365 
366 def _PyDraw(thePad):
367  """
368  Invoke the draw function and intercept the graphics
369  """
370  drawer = CanvasDrawer(thePad)
371  drawer.Draw()
372 
373 
374 def setStyle():
375  style=ROOT.gStyle
376  style.SetFuncWidth(3)
377  style.SetHistLineWidth(3)
378  style.SetMarkerStyle(8)
379  style.SetMarkerSize(.5)
380  style.SetMarkerColor(ROOT.kBlue)
381  style.SetPalette(57)
382 
384  extNames = ["ROOTaaS.iPyROOT." + name for name in ["cppmagic"]]
385  ip = get_ipython()
386  extMgr = ExtensionManager(ip)
387  for extName in extNames:
388  extMgr.load_extension(extName)
389  cppcompleter.load_ipython_extension(ip)
390 
391  for capture in captures: capture.register()
392 
394  ROOT.toCpp = toCpp
395  ROOT.enableJSVis = enableJSVis
396  ROOT.disableJSVis = disableJSVis
397  ROOT.enableJSVisDebug = enableJSVisDebug
398  ROOT.disableJSVisDebug = disableJSVisDebug
399  ROOT.TCanvas.DrawCpp = ROOT.TCanvas.Draw
400  ROOT.TCanvas.Draw = _PyDraw
401 
403  ipDispJs = IPython.display.display_javascript
404  #Make sure clike JS lexer is loaded
405  ipDispJs("require(['codemirror/mode/clike/clike'], function(Clike) { console.log('ROOTaaS - C++ CodeMirror module loaded'); });", raw=True)
406  # Define highlight mode for %%cpp magic
407  ipDispJs(_jsMagicHighlight.format(cppMIME = cppMIME), raw=True)
408 
410  setStyle()
414  welcomeMsg()
415 
def _getLibExtension(thePlatform)
Definition: utils.py:97
def invokeAclic(cell)
Definition: utils.py:206
def __init__(self, thePad)
Definition: utils.py:289
def enhanceROOTModule()
Definition: utils.py:393
def declareCppCodeImpl(code)
Definition: utils.py:153
def loadExtensionsAndCapturers()
Definition: utils.py:383
def disableJSVisDebug()
Definition: utils.py:88
def _getListOfPrimitivesNamesAndTypes(self)
Definition: utils.py:292
def _dumpToUniqueFile(code)
Definition: utils.py:195
def _invokeAclicMac(fileName)
Definition: utils.py:172
def disableJSVis()
Definition: utils.py:78
def processCppCodeImpl(code)
Definition: utils.py:149
def enableCppHighlighting()
Definition: utils.py:402
def _setIgnoreLevel(level)
Definition: utils.py:116
def processCppCode(code)
Definition: utils.py:157
def _PyDraw(thePad)
Definition: utils.py:366
def declareCppCode(code)
Definition: utils.py:160
def enableJSVisDebug()
Definition: utils.py:82
def commentRemover(text)
Definition: utils.py:122
def _codeToFilename(code)
Definition: utils.py:186
def _getPlatform()
Definition: utils.py:94