Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
224 changes: 147 additions & 77 deletions bindings/pyroot/pythonizations/python/ROOT/_jupyroot/helpers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import sys
import tempfile
import time
import ctypes
from contextlib import contextmanager
from datetime import datetime
from hashlib import sha1
Expand All @@ -40,6 +41,8 @@
# Keep display handle for canvases to be able update them
_canvasHandles = {}

_visualObjects = []

_jsMagicHighlight = """
Jupyter.CodeCell.options_default.highlight_modes['magic_{cppMIME}'] = {{'reg':[/^%%cpp/]}};
console.log("JupyROOT - %%cpp magic configured");
Expand Down Expand Up @@ -85,6 +88,47 @@
</script>
"""


_jsFileCode = """
<div style="width: 100%; height: {jsCanvasHeight}px; position: relative">
<div id="{jsDivId}">
</div>
</div>
<script>
function process_{jsDivId}() {{
function showFile(Core) {{
Core.settings.HandleKeys = false;
const binaryString = atob('{fileBase64}');
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++)
bytes[i] = binaryString.charCodeAt(i);
Core.buildGUI('{jsDivId}','notebook').then(h => h.openRootFile(bytes.buffer));
}}
const servers = ['/static/', 'https://jsroot.gsi.de/dev/', 'https://root.cern/js/dev/'],
path = 'build/jsroot';
if (typeof JSROOT !== 'undefined')
showFile(JSROOT);
else if (typeof requirejs !== 'undefined') {{
servers.forEach((s,i) => {{ servers[i] = s + path; }});
requirejs.config({{ paths: {{ 'jsroot' : servers }} }})(['jsroot'], showFile);
}} else {{
const config = document.getElementById('jupyter-config-data');
if (config)
servers[0] = (JSON.parse(config.innerHTML || '{{}}')?.baseUrl || '/') + 'static/';
else
servers.shift();
function loadJsroot() {{
return !servers.length ? 0 : import(servers.shift() + path + '.js').catch(loadJsroot).then(() => showFile(JSROOT));
}}
loadJsroot();
}}
}}
process_{jsDivId}();
</script>
"""



TBufferJSONErrorMessage = "The TBufferJSON class is necessary for JS visualisation to work and cannot be found. Did you enable the http module (-D http=ON for CMake)?"


Expand Down Expand Up @@ -154,6 +198,11 @@ def disableJSVisDebug():
_enableJSVisDebug = False


def addVisualObject(object, kind="none", option=""):
global _visualObjects
_visualObjects.append({ "object": object, "kind": kind, "option": option })


def _getPlatform():
return sys.platform

Expand Down Expand Up @@ -439,60 +488,43 @@ def __del__(self):

def GetCanvasDrawers():
lOfC = ROOT.gROOT.GetListOfCanvases()
return [NotebookDrawer(can) for can in lOfC if can.IsDrawn() or can.IsUpdated()]
return [NotebookDrawer(can, "tcanvas") for can in lOfC if can.IsDrawn() or can.IsUpdated()]


def GetRCanvasDrawers():
if not RCanvasAvailable():
return []
lOfC = ROOT.Experimental.RCanvas.GetCanvases()
return [NotebookDrawer(can.__smartptr__().get()) for can in lOfC if can.IsShown() or can.IsUpdated()]
return [NotebookDrawer(can.__smartptr__().get(), "rcanvas") for can in lOfC if can.IsShown() or can.IsUpdated()]

def GetVisualDrawers():
global _visualObjects
res = [NotebookDrawer(entry.get('object'), entry.get('kind'), entry.get('option')) for entry in _visualObjects]
_visualObjects.clear()
return res


def GetGeometryDrawer():
def GetGeometryDrawers():
if not hasattr(ROOT, "gGeoManager"):
return
return []
if not ROOT.gGeoManager:
return
if not ROOT.gGeoManager.GetUserPaintVolume():
return
vol = ROOT.gGeoManager.GetTopVolume()
if vol:
return NotebookDrawer(vol)
return []
vol = ROOT.gGeoManager.GetUserPaintVolume()
if not vol:
return []
return [NotebookDrawer(vol, "geom")]


def GetDrawers():
drawers = GetCanvasDrawers() + GetRCanvasDrawers()
geometryDrawer = GetGeometryDrawer()
if geometryDrawer:
drawers.append(geometryDrawer)
return drawers


def DrawGeometry():
drawer = GetGeometryDrawer()
if drawer:
drawer.Draw()
return GetCanvasDrawers() + GetRCanvasDrawers() + GetVisualDrawers() + GetGeometryDrawers()


def DrawCanvases():
drawers = GetCanvasDrawers()
def NotebookDraw():
drawers = GetDrawers()
for drawer in drawers:
drawer.Draw()


def DrawRCanvases():
rdrawers = GetRCanvasDrawers()
for drawer in rdrawers:
drawer.Draw()


def NotebookDraw():
DrawGeometry()
DrawCanvases()
DrawRCanvases()


class CaptureDrawnPrimitives(object):
"""
Capture the canvas which is drawn to display it.
Expand All @@ -514,15 +546,22 @@ class NotebookDrawer(object):
jsROOT.
"""

def __init__(self, theObject):
def __init__(self, theObject, kind="none", option=""):
self.drawableObject = theObject
self.drawOption = option
self.isRCanvas = False
self.isCanvas = False
self.isFile = False
self.isGeom = False
self.drawableId = str(ROOT.AddressOf(theObject)[0])
if hasattr(self.drawableObject, "ResolveSharedPtrs"):
if kind == "tfile":
self.isFile = True
elif kind == "rcanvas":
self.isRCanvas = True
else:
self.isCanvas = self.drawableObject.ClassName() == "TCanvas"
elif kind == "geom":
self.isGeom = True
elif kind == "tcanvas":
self.isCanvas = True

def __del__(self):
if self.isRCanvas:
Expand All @@ -531,8 +570,8 @@ def __del__(self):
elif self.isCanvas:
self.drawableObject.ResetDrawn()
self.drawableObject.ResetUpdated()
else:
ROOT.gGeoManager.SetUserPaintVolume(None)
elif self.isGeom:
self.drawableObject.GetGeoManager().SetUserPaintVolume(ROOT.nullptr)

def _getListOfPrimitivesNamesAndTypes(self):
"""
Expand Down Expand Up @@ -561,6 +600,10 @@ def _canJsDisplay(self):
# RCanvas clways displayed with jsroot
if self.isRCanvas:
return True
if self.isFile:
return True
if self.isGeom:
return True
# check if jsroot was disabled
if not _enableJSVis:
return False
Expand All @@ -581,41 +624,66 @@ def _canJsDisplay(self):
return False
return True

def _getJsCode(self):
# produce JSON for the canvas
if self.isRCanvas:
json = self.drawableObject.CreateJSON()
else:
json = produceCanvasJson(self.drawableObject).Data()
def _getFileJsCode(self):
sz = self.drawableObject.GetSize()
if sz > 10000000 and self.drawOption != "force":
return f"File size {sz} is too large for JSROOT display. Use 'force' draw option to show file nevertheless"
Comment thread
silverweed marked this conversation as resolved.
Outdated
Comment thread
linev marked this conversation as resolved.
Outdated
Comment thread
linev marked this conversation as resolved.
Outdated
Comment thread
vepadulano marked this conversation as resolved.
Outdated

# create plain buffer and get pointer on it
u_buffer = (ctypes.c_ubyte * sz)(*range(sz))
addrc = ctypes.cast(ctypes.pointer(u_buffer), ctypes.c_char_p)

if self.drawableObject.ReadBuffer(addrc, 0, sz):
return f"Fail to read file {self.drawableObject.GetName()} buffer of size {sz}"
Comment thread
linev marked this conversation as resolved.
Outdated

base64 = ROOT.TBase64.Encode(addrc, sz)

divId = self._getUniqueDivId()

thisJsCode = _jsFileCode.format(
jsCanvasHeight=_jsCanvasHeight,
jsDivId=divId,
fileBase64=base64
)
return thisJsCode

def _getJsCode(self):
if self.isFile:
return self._getFileJsCode()

options = ""
width = _jsCanvasWidth
height = _jsCanvasHeight
jsonzip = ROOT.TBufferJSON.zipJSON(json)
options = "all"

if self.isCanvas:
if self.drawableObject.GetWindowWidth() > 0:
width = self.drawableObject.GetWindowWidth()
if self.drawableObject.GetWindowHeight() > 0:
height = self.drawableObject.GetWindowHeight()
options = ""
json = ""

# produce JSON for the draw object
if self.isRCanvas:
if self.drawableObject.GetWidth() > 0:
width = self.drawableObject.GetWidth()
if self.drawableObject.GetHeight() > 0:
height = self.drawableObject.GetHeight()
options = ""
json = self.drawableObject.CreateJSON()
if self.drawableObject.GetWidth() > 0:
width = self.drawableObject.GetWidth()
if self.drawableObject.GetHeight() > 0:
height = self.drawableObject.GetHeight()
elif self.isCanvas:
json = produceCanvasJson(self.drawableObject).Data()
if self.drawableObject.GetWindowWidth() > 0:
width = self.drawableObject.GetWindowWidth()
if self.drawableObject.GetWindowHeight() > 0:
height = self.drawableObject.GetWindowHeight()
elif self.isGeom:
json = ROOT.TBufferJSON.ConvertToJSON(self.drawableObject, 23).Data()
options = "all"
else:
return f"Class {self.drawableObject.ClassName()} not supported yet"

zip = ROOT.TBufferJSON.zipJSON(json)

thisJsCode = _jsCode.format(
jsCanvasWidth=width,
jsCanvasHeight=height,
jsonLength=len(json),
jsonZip=jsonzip,
jsonZip=zip,
jsDrawOptions=options,
jsDivId=divId,
jsDivId=self._getUniqueDivId(),
)
return thisJsCode

Expand All @@ -627,6 +695,8 @@ def _getDrawId(self):
return self.drawableObject.GetName() + self.drawableId
if self.isRCanvas:
return self.drawableObject.GetUID()
if self.isFile:
return "File" + self.drawableId
# all other objects do not support update and can be ignored
return ""

Expand Down Expand Up @@ -660,26 +730,26 @@ def _getPngImage(self):
return img

def _pngDisplay(self):
global _canvasHandles
name = self._getDrawId()
updated = self._getUpdated()
img = self._getPngImage()
if updated and name and (name in _canvasHandles):
_canvasHandles[name].update(img)
elif name:
_canvasHandles[name] = display.display(img, display_id=True)
else:
display.display(img)
if self.isCanvas or self.isRCanvas:
global _canvasHandles
name = self._getDrawId()
updated = self._getUpdated()
img = self._getPngImage()
if updated and name and (name in _canvasHandles):
_canvasHandles[name].update(img)
elif name:
_canvasHandles[name] = display.display(img, display_id=True)
else:
display.display(img)

def _display(self):
if _enableJSVisDebug:
self._pngDisplay()
self._jsDisplay()
elif self._canJsDisplay():
self._jsDisplay()
else:
if self._canJsDisplay():
self._jsDisplay()
else:
self._pngDisplay()
self._pngDisplay()

def GetDrawableObjects(self):
if _enableJSVisDebug:
Expand Down