higlass-python icon indicating copy to clipboard operation
higlass-python copied to clipboard

feat: Jupyter comms server

Open manzt opened this issue 1 year ago • 3 comments
trafficstars

Depends on https://github.com/higlass/higlass/pull/1194. I have just got this working locally with Vite.

Screen Recording 2024-02-15 at 11 17 26 PM

manzt avatar Feb 16 '24 04:02 manzt

Notebooks test code with a widget to collect the Tilesets. A collection of tilesets (should be weakrefable), that handles fulfilling requests from the front end.

import higlass as hg
import ipywidgets
import traitlets
import itertools

class Tilesets(ipywidgets.DOMWidget):
    value = traitlets.Int(0).tag(sync=True)
    def __init__(self):
        super().__init__()
        self.on_msg(self._handle_custom_msg)
        self.ts = dict()

    def add(self, ts):
        self.ts[ts.tileset.uid] = ts.tileset
        return self
        
    def _handle_custom_msg(self, data, buffers):
        payload = data["payload"]
        uuid = data["uuid"]
        match payload:
            case { "type": "tileset_info", "tilesetUid": uid }:
                info = { uid: self.ts[uid].info() }
                self.send({ "uuid": uuid, "payload": info })
            case { "type": "tiles", "tileIds": tids }:
                all_tiles = []
                for uid, tids in itertools.groupby(
                    iterable=sorted(tids), key=lambda tid: tid.split(".")[0]
                ):
                    tiles = self.ts[uid].tiles(list(tids))
                    all_tiles.extend(tiles)
                data = {tid: tval for tid, tval in all_tiles}
                self.send({ "uuid": uuid, "payload": data })
            case _:
                raise ValueError("Something's wrong with the Internet")

ts = hg.cooler("./test.mcool")
tss = Tilesets().add(ts)
track = ts.track("heatmap")
hg.view(track, width=6).widget(ts=tss)

manzt avatar Feb 16 '24 04:02 manzt

Just other ideas. A tile request "coordinator", that can be shared among tracks. It waits an animation frame to see all the desired tiles, and then dispatches a single server request.

class TileRequestCoordinator {
 #model;
 #requests;
 #frameRequested = false;
 constructor(model) {
   this.#model = model;
   this.#requests = [];
 }
 async fetchTilesetInfo({ tilesetUid }) {
   let { data } = await send(this.#model, { type: "tileset_info", tilesetUid });
   return data;
 }
 async fetchTiles({ tileIds }) {
   if (!this.#frameRequested) {
     this.#frameRequested = true;
     requestAnimationFrame(() => this.#processRequests());
   }
   let { promise, resolve, reject } = Promise.withResolvers();
   this.#requests.push({ tileIds, resolve, reject });
   return promise;
 }
 async #processRequests() {
   this.#frameRequested = false;
   let ids = [...new Set(this.#requests.flatMap((r) => r.tileIds))];
   let { data: resp } = await send(this.#model, { type: "tiles", tileIds: ids });
   let data = tileResponseToData(resp, "jupyter", ids);
   for (let { tileIds, resolve } of this.#requests) {
     let tileData = Object.fromEntries(tileIds.map((id) => [id, data[id]]));
     resolve(tileData);
   }
   this.#requests.length = 0;
 }
 registerTileset() {
   throw new Error("Not implemented");
 }
}

manzt avatar Feb 16 '24 05:02 manzt

Relevant PR that benefits from a similar kind of architecture (and ability to drop jupyter-server-proxy):

  • https://github.com/banesullivan/localtileserver/pull/219

manzt avatar Jul 21 '24 20:07 manzt