From d6241b6627d09dad97e7d9eaf60dfaee9ee18b31 Mon Sep 17 00:00:00 2001 From: Jim Robinson <933148+jrobinso@users.noreply.github.com> Date: Mon, 23 Oct 2023 20:04:57 -0700 Subject: [PATCH] Hubs 2 (#1721) * Export ucsc HUB class * Add method to extract all loadable track configurations for web-app menu --- dev/ucsc/hub.html | 6 +-- dev/ucsc/slowHub.html | 5 +- js/browser.js | 8 ++-- js/index.js | 4 +- js/jbrowse/circularViewUtils.js | 20 ++++---- js/ucsc/ucscHub.js | 85 +++++++++++++++++++++++---------- test/testHubs.js | 47 ++++++++++++++++++ test/testUCSC.js | 25 ---------- 8 files changed, 129 insertions(+), 71 deletions(-) create mode 100644 test/testHubs.js diff --git a/dev/ucsc/hub.html b/dev/ucsc/hub.html index 5cc243574..0531e15fe 100644 --- a/dev/ucsc/hub.html +++ b/dev/ucsc/hub.html @@ -35,12 +35,10 @@ const hubOptions = { - url: "https://hgdownload.soe.ucsc.edu/hubs/GCA/018/471/515/GCA_018471515.1/hub.txt", - includeTracks: "all" + includeTracks: true } - - const hub = await Hub.loadHub(hubOptions) + const hub = await Hub.loadHub("https://hgdownload.soe.ucsc.edu/hubs/GCA/018/471/515/GCA_018471515.1/hub.txt", hubOptions) const ref = hub.getGenomeConfig() const igvConfig = { diff --git a/dev/ucsc/slowHub.html b/dev/ucsc/slowHub.html index a74d865d8..31f50510d 100644 --- a/dev/ucsc/slowHub.html +++ b/dev/ucsc/slowHub.html @@ -16,12 +16,11 @@ const hubOptions = { - url: "https://hgdownload.soe.ucsc.edu/hubs/GCA/004/027/145/GCA_004027145.1/hub.txt", - includeTracks: "all" + includeTracks: true } + const hub = await Hub.loadHub("https://hgdownload.soe.ucsc.edu/hubs/GCA/004/027/145/GCA_004027145.1/hub.txt", hubOptions) - const hub = await Hub.loadHub(hubOptions) const igvConfig = { showChromosomeWidget: false, diff --git a/js/browser.js b/js/browser.js index 637794097..e568148c8 100755 --- a/js/browser.js +++ b/js/browser.js @@ -493,7 +493,9 @@ class Browser { return new XMLSession(string, knownGenomes) } else if (filename.endsWith("hub.txt")) { - const hub = await Hub.loadHub(options) + + const hub = await Hub.loadHub(urlOrFile, options) + if(chromosomeSelectWidget) { chromosomeSelectWidget.hide() } @@ -763,7 +765,7 @@ class Browser { let genomeConfig if (idOrConfig.url && StringUtils.isString(idOrConfig.url) && idOrConfig.url.endsWith("/hub.txt")) { - const hub = await Hub.loadHub(idOrConfig) + const hub = await Hub.loadHub(idOrConfig.url, idOrConfig) genomeConfig = hub.getGenomeConfig("genes") } else { genomeConfig = idOrConfig //await GenomeUtils.expandReference(this.alert, idOrConfig) @@ -793,7 +795,7 @@ class Browser { */ async loadTrackHub(options) { - const hub = await Hub.loadHub(options) + const hub = await Hub.loadHub(options.url, options) const genomeConfig = hub.getGenomeConfig() const initialLocus = hub.getDefaultPosition() if (initialLocus) { diff --git a/js/index.js b/js/index.js index 67a5b0505..e5c49dacb 100644 --- a/js/index.js +++ b/js/index.js @@ -11,6 +11,7 @@ import {registerFileFormats} from "./util/trackUtils.js" import {igvxhr} from "../node_modules/igv-utils/src/index.js" import {registerTrackClass, registerTrackCreatorFunction} from "./trackFactory.js" import TrackBase from "./trackBase.js" +import Hub from "./ucsc/ucscHub.js" const setApiKey = igvxhr.setApiKey @@ -45,6 +46,7 @@ export default { TrackBase, registerTrackClass, registerTrackCreatorFunction, - registerFileFormats + registerFileFormats, + Hub } diff --git a/js/jbrowse/circularViewUtils.js b/js/jbrowse/circularViewUtils.js index fb0a590b2..bdaa1a09a 100644 --- a/js/jbrowse/circularViewUtils.js +++ b/js/jbrowse/circularViewUtils.js @@ -149,15 +149,17 @@ const makeVCFChords = (features) => { function makeCircViewChromosomes(genome) { const regions = [] const colors = [] - for (let chrName of genome.wgChromosomeNames) { - const chr = genome.getChromosome(chrName) - colors.push(getChrColor(chr.name)) - regions.push( - { - name: chr.name, - bpLength: chr.bpLength - } - ) + if(genome.wgChromosomeNames) { + for (let chrName of genome.wgChromosomeNames) { + const chr = genome.getChromosome(chrName) + colors.push(getChrColor(chr.name)) + regions.push( + { + name: chr.name, + bpLength: chr.bpLength + } + ) + } } return regions } diff --git a/js/ucsc/ucscHub.js b/js/ucsc/ucscHub.js index 5bef81623..e1a41440a 100644 --- a/js/ucsc/ucscHub.js +++ b/js/ucsc/ucscHub.js @@ -9,32 +9,27 @@ import {igvxhr} from "../../node_modules/igv-utils/src/index.js" class Hub { - static async loadHub(options) { - - if (!options.url) { - throw Error("Expected url") - } + static supportedTypes = new Set(["bigBed", "bigWig", "bigGenePred"]) + static filterTracks = new Set(["cytoBandIdeo", "assembly", "gap", "gapOverlap", "allGaps", + "cpgIslandExtUnmasked", "windowMasker"]) - const stanzas = await loadStanzas(options) + static async loadHub(url, options) { + const stanzas = await loadStanzas(url, options) let groups if ("genome" === stanzas[1].type) { const genome = stanzas[1] if (genome.hasProperty("groups")) { - const idx = options.url.lastIndexOf("/") - const baseURL = options.url.substring(0, idx + 1) + const idx = url.lastIndexOf("/") + const baseURL = url.substring(0, idx + 1) const groupsTxtURL = baseURL + genome.getProperty("groups") - groups = await loadStanzas({url: groupsTxtURL}) + groups = await loadStanzas(groupsTxtURL) } } - return new Hub(options.url, stanzas, groups) + return new Hub(url, stanzas, groups) } - static supportedTypes = new Set(["bigBed", "bigWig", "bigGenePred"]) - static filterTracks = new Set(["cytoBandIdeo", "assembly", "gap", "gapOverlap", "allGaps", - "cpgIslandExtUnmasked", "windowMasker"]) - - constructor(url, stanzas, groups) { + constructor(url, stanzas, groupStanzas) { const idx = url.lastIndexOf("/") this.baseURL = url.substring(0, idx + 1) @@ -69,14 +64,14 @@ class Hub { } } - if (groups) { + if (groupStanzas) { + this.groupStanzas = groupStanzas this.groupPriorityMap = new Map() - for (let g of groups) { + for (let g of groupStanzas) { if (g.hasProperty("priority")) { this.groupPriorityMap.set(g.getProperty("name"), Number.parseInt(g.getProperty("priority")) * 10) } } - this.groupPriorities = new Map() } } @@ -84,6 +79,45 @@ class Hub { return this.genomeStanza.getProperty("defaultPos") } + getTrackConfigurations() { + + // Organize track configs by group + const trackConfigMap = new Map() + for (let c of this.#getTracksConfigs()) { + const name = c.group || "other" + if (trackConfigMap.has(name)) { + trackConfigMap.get(name).push(c) + } else { + trackConfigMap.set(name, [c]) + } + } + + // Build group structure + const t = [] + if (this.groupStanzas) { + for (let g of this.groupStanzas) { + const groupName = g.getProperty("name") + if(trackConfigMap.has(groupName)) { + t.push( + { + label: g.getProperty("label"), + tracks: trackConfigMap.get(g.getProperty("name")) + } + ) + } + } + } + + if(trackConfigMap.has("other")) { + t.push({ + label: "other", + tracks: trackConfigMap.get("other") + }) + } + + return t + } + /* Example genome stanza genome GCF_000186305.1 taxId 176946 @@ -123,6 +157,7 @@ isPcr dynablat-01.soe.ucsc.edu 4040 dynamic GCF/000/186/305/GCF_000186305.1 if (this.genomeStanza.hasProperty("twoBitBptURL")) { config.twoBitBptURL = this.baseURL + this.genomeStanza.getProperty("twoBitBptURL") } + if (this.genomeStanza.hasProperty("twoBitBptUrl")) { config.twoBitBptURL = this.baseURL + this.genomeStanza.getProperty("twoBitBptUrl") } @@ -161,7 +196,7 @@ isPcr dynablat-01.soe.ucsc.edu 4040 dynamic GCF/000/186/305/GCF_000186305.1 // Tracks. To prevent loading tracks set `includeTracks`to false if (includeTracks) { - config.tracks = this.#getTracksConfigs(includeTracks) + config.tracks = this.#getTracksConfigs(Hub.filterTracks) } return config @@ -170,14 +205,12 @@ isPcr dynablat-01.soe.ucsc.edu 4040 dynamic GCF/000/186/305/GCF_000186305.1 /** * Return collection of igv track config object, organized by "group* */ - #getTracksConfigs(group) { + #getTracksConfigs(filter) { return this.trackStanzas.filter(t => { return t.getProperty("visibility") !== "hide" && Hub.supportedTypes.has(t.format) && - !Hub.filterTracks.has(t.name) && - t.hasProperty("bigDataUrl") && - ("all" === group || group === t.getProperty("group")) - + (!filter || !Hub.filterTracks.has(t.name) && + t.hasProperty("bigDataUrl")) }) .map(t => this.#getTrackConfig(t)) } @@ -353,9 +386,9 @@ class Stanza { * @param url * @returns {Promise<*[]>} */ -async function loadStanzas(options) { +async function loadStanzas(url, options) { - const data = await igvxhr.loadString(options.url) + const data = await igvxhr.loadString(url) const lines = data.split(/\n|\r\n|\r/g) const nodes = [] diff --git a/test/testHubs.js b/test/testHubs.js new file mode 100644 index 000000000..cc97682d1 --- /dev/null +++ b/test/testHubs.js @@ -0,0 +1,47 @@ +import "./utils/mockObjects.js" +import {assert} from 'chai' +import Hub from "../js/ucsc/ucscHub.js" +import Trix from "../js/bigwig/trix.js" +import BWReader from "../js/bigwig/bwReader.js" +import BPTree from "../js/bigwig/bpTree.js" +import {isString} from "../node_modules/igv-utils/src/stringUtils.js" + + +suite("hub.txt", function () { + + + test("genome", async function () { + + const hub = await Hub.loadHub("test/data/hubs/hub.txt") + assert.ok(hub.hub) + assert.ok(hub.genomeStanza) + assert.equal(22, hub.trackStanzas.length) + + const genomeConfig = hub.getGenomeConfig() + //const genome = await Genome.loadGenome(genomeConfig) + + assert.ok(genomeConfig) + assert.equal("GCF_000186305.1", genomeConfig.id) + assert.equal("Python bivittatus", genomeConfig.name) + assert.ok(genomeConfig.twoBitBptURL) + assert.ok(genomeConfig.twoBitURL) + assert.ok(genomeConfig.chromAliasBbURL) + assert.ok(genomeConfig.cytobandBbURL) + }) + + test("track configs", async function () { + + const hub = await Hub.loadHub("test/data/hubs/hub.txt") + assert.ok(hub.hub) + assert.ok(hub.genomeStanza) + assert.equal(22, hub.trackStanzas.length) + + const trackConfigs = hub.getTrackConfigurations() + + assert.ok(trackConfigs.length > 0) + + }) + + +}) + diff --git a/test/testUCSC.js b/test/testUCSC.js index e29e76be5..cc52c7902 100644 --- a/test/testUCSC.js +++ b/test/testUCSC.js @@ -3,34 +3,9 @@ import {assert} from 'chai' import Hub from "../js/ucsc/ucscHub.js" import Trix from "../js/bigwig/trix.js" import BWReader from "../js/bigwig/bwReader.js" -import BPTree from "../js/bigwig/bpTree.js" -import {isString} from "../node_modules/igv-utils/src/stringUtils.js" - suite("ucsc utilities", function () { - - test("hub.txt", async function () { - - this.timeout(200000) - - const hub = await Hub.loadHub({url: "test/data/hubs/hub.txt"}) - assert.ok(hub.hub) - assert.ok(hub.genomeStanza) - assert.equal(22, hub.trackStanzas.length) - - const genomeConfig = hub.getGenomeConfig() - //const genome = await Genome.loadGenome(genomeConfig) - - assert.ok(genomeConfig) - assert.equal("GCF_000186305.1", genomeConfig.id) - assert.equal("Python bivittatus", genomeConfig.name) - assert.ok(genomeConfig.twoBitBptURL) - assert.ok(genomeConfig.twoBitURL) - assert.ok(genomeConfig.chromAliasBbURL) - assert.ok(genomeConfig.cytobandBbURL) - }) - test("trix", async function () { this.timeout(200000)