from IPython.display import HTML, SVG, display from ipykernel.comm import Comm import json import random import time class _IGVComm: def __init__(self, id): # Use comm to send a message from the kernel self.comm = Comm(target_name=id, data={}) def send(self, message): self.comm.send(message) class Browser(object): # Always remember the *self* argument def __init__(self, config): """Initialize an igv Browser object. :param: config - A dictionary specifying the browser configuration. This will be converted to JSON and passed to igv.js "igv.createBrowser(config)" as described in the igv.js documentation. See https://github.com/igvteam/igv.js/wiki/Browser-Configuration-2.0 for configuration options. :type dict """ # Check for minimal igv.js requirements (the only required field for all tracks is url, which must be a string) if isinstance(config, dict) == False: raise Exception("config parameter must be a dictionary") has_genome = "genome" in config or "reference" in config if not has_genome: raise Exception("config must specify either 'genome' or 'reference'") id = self._gen_id() config["id"] = id self.igv_id = id self.config = config self.comm = _IGVComm("igvcomm") self._status = "initializing" self.locus = None self.eventHandlers = {} self.svg = None self.message_queue = [] # Add a callback for received messages. @self.comm.comm.on_msg def _recv(msg): data = json.loads(msg['content']['data']) if 'status' in data: self.status = data['status'] elif 'locus' in data: self.locus = data['locus'] elif 'svg' in data: self.svg = data['svg'] elif 'event' in data: if data['event'] in self.eventHandlers: handler = self.eventHandlers[data['event']] eventData = None if 'data' in data: eventData = data['data'] handler(eventData) @property def status(self): return self._status @status.setter def status(self, value): self._status = value if value == "ready" and len(self.message_queue) > 0: time.sleep(0.5) self._send(self.message_queue.pop(0)) def show(self): """ Create an igv.js "Browser" instance on the front end. """ display(HTML("""<div id="%s"></div>""" % (self.igv_id))) # DON'T check status before showing browser, msg = json.dumps({ "id": self.igv_id, "command": "create", "options": self.config }) self.comm.send(msg) def search(self, locus): """ Go to the specified locus. :param locus: Chromosome location of the form "chromsosome:start-end", or for supported genomes (hg38, hg19, and mm10) a gene name. :type str """ self._send({ "id": self.igv_id, "command": "search", "locus": locus }) def zoom_in(self): """ Zoom in by a factor of 2 """ self._send({ "id": self.igv_id, "command": "zoomIn" }) def zoom_out(self): """ Zoom out by a factor of 2 """ self._send({ "id": self.igv_id, "command": "zoomOut" }) def load_track(self, track): """ Load a track. Corresponds to the igv.js Browser function loadTrack (see https://github.com/igvteam/igv.js/wiki/Browser-Control-2.0#loadtrack). :param track: A dictionary specifying track options. See https://github.com/igvteam/igv.js/wiki/Tracks-2.0. :type dict """ # Check for minimal igv.js requirements (the only required field for all tracks is url, which must be a string) if isinstance(track, dict) == False: raise Exception("track parameter must be a dictionary") self._send({ "id": self.igv_id, "command": "loadTrack", "track": track }) def to_svg(self): """ Fetch the current IGV view as an SVG image and display it in this cell """ div_id = self._gen_id(); display(HTML("""<div id="%s"></div>""" % div_id)) self._send({ "id": self.igv_id, "div": div_id, "command": "toSVG" }) def get_svg(self): """ Fetch the current IGV view as an SVG image. To display the message call display_svg() """ self.svg = "FETCHING" return self._send({ "id": self.igv_id, "command": "toSVG" }) def display_svg(self): """ Display the current SVG image. You must call get_svg() before calling this method. """ if self.svg == None: return "Must call get_svg() first" elif self.svg == "FETCHING": return 'Awaiting SVG - try again in a few seconds' else: svg = self.svg self.svg == None display(SVG(svg)) def on(self, eventName, cb): """ Subscribe to an igv.js event. :param Name of the event. Currently only "locuschange" is supported. :type str :param cb - callback function taking a single argument. For the locuschange event this argument will contain a dictionary of the form {chr, start, end} :type function """ self.eventHandlers[eventName] = cb self._send({ "id": self.igv_id, "command": "on", "eventName": eventName }) def remove(self): """ Remove the igv.js Browser instance from the front end. The server Browser object should be disposed of after calling this method. """ self._send({ "id": self.igv_id, "command": "remove" }) def _send(self, msg): if self.status == "ready": self.status = "busy" self.comm.send(json.dumps(msg)) else: self.message_queue.append(msg) def _gen_id(self): return 'igv_' + str(random.randint(1, 10000000))