0b293869739f3112a74d2ee38f47785a7c245884 max Wed Sep 16 06:29:25 2026 -0700 HPRC v2 contrib hub: pcLAI defaults to dense, and genark addContrib wires the beta/public hub tiers Mark reported that the pcLAI track shows only one color on HG00408 pat (GCA_041900255.1) and that you cannot see where ancestry changes. The colors are correct: a fresh download of the HPRC source BED matches the built pclai.bb on (chrom, start, end, itemRgb) for all 25,475 windows. That haplotype is simply 99.8% one ancestry cluster, which is common in this set (71 of 460 haplotypes have a single centroid, 228 are >=99% one centroid). The display was the real problem, so hprcPclai goes from visibility pack to visibility dense plus onlyVisibility dense. At chromosome scale pack stacks the 100 kb windows into ~50 rows of 1-2 px slivers; dense draws one colored bar where the ancestry blocks are legible. While deploying that, beta.hub.txt turned out to be a week stale: it still had the pre-2026-09-09 pcaSegment field name and the double-prefixed pclaiRefPanel.json dataUrl, so the details-page scatterplot was not drawing on hgwbeta. genark addContrib only ever wired alpha.hub.txt, leaving beta and public to the next clade build. It now wires those two as well for a collection already named in betaGenArk.txt / publicGenArk.txt. This never promotes anything - the tier argument still owns the release lists - and mkGenomes.pl inlines the same per-assembly trackDb, so a later clade build converges on the same content. Ran over 462 assemblies, alpha and beta; genark checkContrib reports no contrib problems, refs #35415 diff --git src/utils/genark/genark src/utils/genark/genark index 030f75ba1be..fdd7ae6652c 100755 --- src/utils/genark/genark +++ src/utils/genark/genark @@ -324,75 +324,84 @@ if want: text = text.rstrip("\n") + "\n" + name + "\n" else: keep = [l for l in text.split("\n") if l.strip() != name] text = "\n".join(keep).rstrip("\n") + "\n" with open(path, "w") as fh: fh.write(text) changed.append(key) return changed def addContrib(args): """Install a contrib track collection into the GenArk assembly hubs: symlink its data files + docs into <buildDir>/contrib/<name>/, write a per-assembly <name>.trackDb.txt with hub-root-relative paths, and wire that - block into each assembly's served hub.txt. Everything is written into the + block into each assembly's alpha.hub.txt, plus beta.hub.txt / public.hub.txt + for a collection already on those release lists. Everything is written into the assembly's GenArk build directory (see buildDir); the served /gbdb/genark and asmHubs/<acc> symlink trees are left alone. --remove undoes all of it.""" name = args.name.rstrip("/") root = os.path.join(CONTRIB, name) if not os.path.isdir(root): sys.exit("error: no such contrib collection: %s" % root) docsDir = os.path.join(root, "docs") docs = [] if os.path.isdir(docsDir): docs = sorted(f for f in os.listdir(docsDir) if f.endswith(".html")) # Files at the collection root that all assemblies share, such as a scatterplot # background panel named by a detailsScript dataUrl. Linked flat next to the # docs, so contrib/<name>/<file> resolves for every assembly. shared = sorted(f for f in os.listdir(root) if f.endswith((".json", ".tsv")) and os.path.isfile(os.path.join(root, f))) accs = sorted(d for d in os.listdir(root) if ACC_RE.match(d) and os.path.isdir(os.path.join(root, d))) if not accs: sys.exit("error: no accession directories (GCA_*/GCF_*) under %s" % root) trackNames = contribTrackNames(root, accs) + # Which single-file hub tiers to wire. alpha always; beta and public only for a + # collection already on the matching release list (this never promotes anything, + # setTierMembership below is what changes the lists). + tiers = ["alpha"] + [k for k in ("beta", "public") + if name in listMembers(TIER_LISTS[k])] done = 0 skipped = 0 for acc in accs: accDir = os.path.join(root, acc) asmDir = buildDir(acc) if asmDir is None: sys.stderr.write("skip %s: no GenArk build directory under " "%s/{genbankBuild,refseqBuild}\n" % (acc, ASMHUBS)) skipped += 1 continue dest = os.path.join(asmDir, "contrib", name) - # the alpha-tier single-file hub in the build dir (asmHubs/<acc>/alpha.hub.txt - # and /gbdb/genark/<acc>/alpha.hub.txt are symlinks to this); asmId is the - # build directory basename, which carries the assembly-name suffix. - asmId = os.path.basename(asmDir) - # Only the alpha tier for now: <asmId>.singleFile.hub.txt is what - # /gbdb/genark/<acc>/hub.txt points at and is served as the assembly's - # default hub, so a contrib collection under test stays out of it. - hubTxt = os.path.join(asmDir, "alpha.hub.txt") + # The single-file hub tiers in the build dir. alpha.hub.txt is what + # asmHubs/<acc>/alpha.hub.txt and /gbdb/genark/<acc>/alpha.hub.txt point at. + # beta.hub.txt and public.hub.txt are normally composed by mkGenomes.pl at the + # next clade build, which inlines exactly the per-assembly trackDb written just + # below, so wiring them here as well only makes a trackDb fix reach hgwbeta (and + # the public tier) without waiting for that rebuild; the rebuild then converges + # on the same content rather than fighting it. <asmId>.singleFile.hub.txt is + # what /gbdb/genark/<acc>/hub.txt points at and is served as the assembly's + # default hub, so it is deliberately never touched. + hubTxts = [os.path.join(asmDir, "%s.hub.txt" % t) for t in tiers] if args.remove: + for hubTxt in hubTxts: if os.path.exists(hubTxt): wireHubTxt(hubTxt, name, "", remove=True, dryRun=args.dry_run) if args.dry_run: print(" rm -rf %s" % dest) elif os.path.isdir(dest): shutil.rmtree(dest) done += 1 continue if args.dry_run: print("# %s -> %s" % (acc, dest)) else: os.makedirs(dest, exist_ok=True) # symlink data files (.bb / .bw) from the collection's accession dir @@ -404,40 +413,43 @@ symlink(os.path.join(docsDir, d), os.path.join(dest, d), args.dry_run) # and the collection's shared root-level data files for f in shared: symlink(os.path.join(root, f), os.path.join(dest, f), args.dry_run) # per-assembly trackDb with hub-root-relative paths, and wire it into hub.txt srcTdb = os.path.join(accDir, "trackDb.txt") if os.path.isfile(srcTdb): tdb = rewriteTrackDb(srcTdb, name) destTdb = os.path.join(dest, "%s.trackDb.txt" % name) if args.dry_run: print(" write %s (%d bytes)" % (destTdb, len(tdb))) else: with open(destTdb, "w") as fh: fh.write(tdb) + for hubTxt in hubTxts: if os.path.exists(hubTxt): wireHubTxt(hubTxt, name, tdb, remove=False, dryRun=args.dry_run, trackNames=trackNames) else: - sys.stderr.write("warn %s: no hub.txt to wire at %s\n" % (acc, hubTxt)) + sys.stderr.write("warn %s: no hub.txt to wire at %s\n" + % (acc, hubTxt)) done += 1 verb = "removed from" if args.remove else "installed into" - print("addContrib %s: %s %d assemblies, skipped %d (no assembly hub)" - % (name, verb, done, skipped)) + print("addContrib %s: %s %d assemblies, skipped %d (no assembly hub), " + "hub tiers wired: %s" + % (name, verb, done, skipped, " ".join(tiers))) tier = getattr(args, "tier", "alpha") changed = setTierMembership(name, tier, args.remove, args.dry_run) if args.remove: if changed: print(" also removed from: %s" % ", ".join(os.path.basename(TIER_LISTS[k]) for k in changed)) elif tier == "alpha": print(" tier alpha: visible now at " "https://genome-test.gi.ucsc.edu/h/<accession>, and nowhere else. " "The pushes exclude every hub.txt tier file, so this cannot leak.") else: if changed: print(" tier %s: added to %s" % (tier, ", ".join(os.path.basename(TIER_LISTS[k]) for k in changed)))