"""
tissuumaps.jupyter
====================================
Module used to run TissUUmaps from a Jupyter Notebook or from Jupyter Lab.
"""
import json
import logging
import os
import threading
import time
import uuid
import warnings
from pathlib import Path
import click
from IPython.core.display import display
from IPython.display import HTML, Javascript
from tissuumaps import views
def secho(text, file=None, nl=None, err=None, color=None, **styles):
pass
def echo(text, file=None, nl=None, err=None, color=None, **styles):
pass
click.echo = echo
click.secho = secho
[docs]def opentmap(path, port=5100, host="localhost", height=700):
"""
Open a tmap project
Args:
path (str): The path to a tmap file
port (int): The port to run the TissUUmaps server
host (str): The host to run the TissUUmaps server
height (int): The height of the jupyter iframe
Returns:
TissUUmapsViewer: The TissUUmaps viewer
"""
path = os.path.abspath(path)
parts = Path(path).parts
server = TissUUmapsServer(slideDir=parts[0], port=port, host=host)
return server.viewer(parts[-1] + "?path=" + os.path.join(*parts[1:-1]), height)
[docs]def loaddata(
images=[],
csvFiles=[],
xSelector="x",
ySelector="y",
keySelector=None,
nameSelector=None,
colorSelector=None,
piechartSelector=None,
shapeSelector=None,
scaleSelector=None,
fixedShape=None,
scaleFactor=1,
colormap=None,
compositeMode="source-over",
boundingBox=None,
port=5100,
host="localhost",
height=700,
tmapFilename="_project",
plugins=[],
):
"""
Load data in TissUUmaps
Args:
images (list | str): List of images or single image to display
csvFiles (list | str): List of csv files or single csv file to display
xSelector (str): Name of the csv column defining the X coordinates
ySelector (str): Name of the csv column defining the Y coordinates
keySelector (str): Name of the csv column defining the grouping key
nameSelector (str): Name of the csv column defining the group name
colorSelector (str): Name of the csv column defining the group color
piechartSelector (str): Name of the csv column defining pie-charts
shapeSelector (str): Name of the csv column defining markers' shape
scaleSelector (str): Name of the csv column defining markers' scale
fixedShape (int): Name of the markers' shape
scaleFactor (int): Global scale of markers
colormap (str): Name of the colormap used if colorSelector is set
compositeMode: (str): Composite mode used for images
boundingBox (list): [X,Y,W,H] of the bounding box to display
port (int): The port to run the TissUUmaps server
host (str): The host to run the TissUUmaps server
height (int): The height of the jupyter iframe
tmapFilename (str): Name of the project file that will be created
plugins (list): List of plugins to add to the tmap project
Returns:
TissUUmapsViewer: The TissUUmaps viewer
"""
# make str input to arrays:
if isinstance(images, str):
images = [images]
if isinstance(csvFiles, str):
csvFiles = [csvFiles]
# make all paths absolute:
images = [os.path.abspath(f) for f in images]
csvFiles = [os.path.abspath(f) for f in csvFiles]
# make all paths relative to project file:
rootPath = os.path.commonpath(
[os.path.dirname(f) for f in images] + [os.path.dirname(f) for f in csvFiles]
)
images = [os.path.relpath(f, rootPath) for f in images]
csvFiles = [os.path.relpath(f, rootPath) for f in csvFiles]
# Create json TMAP file:
jsonTmap = {
"compositeMode": compositeMode,
"filename": "",
"layers": [
{"name": os.path.basename(layer), "tileSource": layer + ".dzi"}
for layer in images
],
"hideTabs": True,
"markerFiles": [],
"plugins": plugins,
}
if boundingBox:
jsonTmap["boundingBox"] = {
"x": boundingBox[0],
"y": boundingBox[1],
"width": boundingBox[2],
"height": boundingBox[3],
}
if csvFiles:
expectedHeader = {
"X": xSelector,
"Y": ySelector,
"gb_col": keySelector,
"gb_name": nameSelector,
"cb_cmap": colormap,
"cb_col": colorSelector,
"scale_col": scaleSelector,
"scale_factor": scaleFactor,
"pie_col": piechartSelector,
"shape_col": shapeSelector,
"shape_fixed": fixedShape,
"cb_gr_dict": "",
"shape_gr_dict": "",
"opacity": 1,
}
expectedRadios = {
"cb_col": colorSelector is not None,
"cb_gr": colorSelector is None,
"cb_gr_rand": False,
"cb_gr_dict": False,
"cb_gr_key": True,
"pie_check": piechartSelector is not None,
"scale_check": scaleSelector is not None,
"shape_gr": shapeSelector is None and fixedShape is None,
"shape_gr_rand": shapeSelector is None and fixedShape is None,
"shape_gr_dict": False,
"shape_col": shapeSelector is not None,
"shape_fixed": fixedShape is not None,
}
if len(csvFiles) == 1:
csvFiles = csvFiles[0]
jsonTmap["markerFiles"] = [
{
"comment": "All markers",
"path": csvFiles,
"title": "Download markers",
"autoLoad": True,
"hideSettings": True,
"expectedHeader": expectedHeader,
"expectedRadios": expectedRadios,
"uid": "markers",
}
]
tmapFile = os.path.join(rootPath, f"{tmapFilename}.tmap")
print("Creating project file", tmapFile)
with open(tmapFile, "w") as f:
json.dump(jsonTmap, f)
return opentmap(os.path.abspath(tmapFile), port=port, host=host, height=height)
[docs]class TissUUmapsViewer:
"""
Class representing a TissUUmaps viewer instance
"""
def __init__(self, server, image, height=700):
self.server = server
self.image = image
self.id = "tissUUmapsViewer_" + str(uuid.uuid1()).replace("-", "")[0:10]
iframe = (
'<iframe src="{src}" style="width: {width}; '
'height: {height}; border: none" id="{id}" allowfullscreen></iframe>'
)
src = f"http://{self.server.host}:{self.server.port}/{self.image}"
print("Loading url: ", src)
self.htmlIFrame = HTML(
iframe.format(width="100%", height=str(height) + "px", src=src, id=self.id)
)
display(self.htmlIFrame)
time.sleep(2)
[docs] def screenshot(self):
"""
Capture the TissUUmaps viewport and display image in the Notebook.
"""
screenshot_id = self.id + "_" + str(uuid.uuid1()).replace("-", "")[0:6]
display(
Javascript(
"""
function listenToMessages_"""
+ screenshot_id
+ """(evt){
if (evt.data.type != "screenshot") return;
window.removeEventListener("message", listenToMessages_"""
+ screenshot_id
+ """);
try {
IPython.notebook.kernel.execute(
`from IPython.display import update_display, HTML`
)
IPython.notebook.kernel.execute(
`obj = HTML("<img src='`+evt.data.img+`'/>")`
)
IPython.notebook.kernel.execute(
`update_display(obj, display_id="display_out_"""
+ screenshot_id
+ """"
)`)
} catch (e) { // vscode or jupyterLab can not communicate back...
if (e instanceof ReferenceError) {
document.getElementById("img_out_"""
+ screenshot_id
+ """").src = evt.data.img;
let newNode = document.createElement("span");
newNode.innerHTML = "Warning: run viewer.screenshot in a \
classical Jupyter Notebook if you want the screenshot \
to be saved.";
document.getElementById("img_out_"""
+ screenshot_id
+ """").parentElement.insertBefore(
newNode, document.getElementById("img_out_"""
+ screenshot_id
+ """"));
}
}
}
var iframe = document.getElementById('"""
+ self.id
+ """');
window.addEventListener("message", listenToMessages_"""
+ screenshot_id
+ """);
iframe.contentWindow.postMessage({'module':'','function':'','arguments':'[]'},'*');
"""
)
)
display(
HTML("<img src='' id='img_out_" + screenshot_id + "'/>"),
display_id="display_out_" + screenshot_id,
)
[docs]class TissUUmapsServer:
"""
Class representing a TissUUmaps server instance
"""
def __init__(self, slideDir, port=5000, host="0.0.0.0"):
log = logging.getLogger("werkzeug")
log.setLevel(logging.ERROR)
log = logging.getLogger("pyvips")
log.setLevel(logging.ERROR)
warnings.filterwarnings("ignore")
self.started = False
self.port = port
self.host = host
self.slideDir = slideDir
views.app.config["SLIDE_DIR"] = slideDir
def startServer():
views.setup(views.app)
views.app.run(host="0.0.0.0", port=self.port, threaded=True, debug=False)
if TissUUmapsServer.is_port_in_use(self.port):
log.warning(
f"Port {self.port} already in use. "
"Impossible to start TissUUmaps server."
)
return
thread = threading.Thread(target=startServer)
thread.setdaemon = False
thread.start()
self.started = True
@staticmethod
def is_port_in_use(port):
import socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
return s.connect_ex(("localhost", port)) == 0
def viewer(self, image, height=700):
viewer = TissUUmapsViewer(self, image, height)
return viewer
if __name__ == "__main__":
pass