bdf448295c10bee74b6baa773f6733ce33171996 markd Thu Oct 1 20:56:37 2026 -0700 Store the ProCapNet minus strand negated, and fix group autoscale. refs #35528 Two of the three problems Jairo found on the composite. The negate control did not work on ProCapNet. Its minus-strand files held positive values and were flipped at display time with trackDb negateValues. The composite's control sets one shared value, which replaced the per-track setting and sent both strands the same way, with no route back to the default short of a cart reset. encode4ProCap was fine because ENCODE publishes its minus strand negative already. So put the sign in the data: proCapNetPredToFixedStep gains --negate, proCapNetPredBuild passes it for the minus strand, and negateValues is gone from the stanzas. The twelve existing files were rewritten rather than rebuilt, since the downloads had been deleted; each was checked against its original for identical nBasesCovered, a mirrored value range and exactly negated values. The originals are kept in previousPred, about 197 GB, until QA is done. Group autoscale on the composite did nothing, which was my error: the multiWig containers and the contribution leaves each carried autoScale on, and the child setting wins. The wiggle settings now live only on the composite. The third, an empty row when both strands of a cell line are deselected, is #38441 and is not fixed here. Note in both makedocs and in the generator that this arrangement only partly works and is expected to become a superTrack of multiWig overlays: under a composite, hgTrackUi lists leaves rather than containers, so an overlay gets no inline config block and leaves an empty row when deselected. diff --git src/hg/makeDb/outside/proCapNet/proCapNetTrackDb src/hg/makeDb/outside/proCapNet/proCapNetTrackDb index ba32f977028..35d62c484d8 100755 --- src/hg/makeDb/outside/proCapNet/proCapNetTrackDb +++ src/hg/makeDb/outside/proCapNet/proCapNetTrackDb @@ -1,218 +1,219 @@ #!/usr/bin/env python3 """Generate the transcriptionStart.ra trackDb file for one assembly. Layout is a superTrack holding one composite per data source: proCapNet for the model predictions and sequence-contribution scores, encode4ProCap for the experimental PRO-cap signal. hs1 has predictions only. These are traditional composites, not faceted ones. A faceted composite is routed to facetedCompositeUi(), which returns before cfgByCfgType(), so the wiggle controls are never drawn; signal tracks need them. A container multiWig under a traditional composite still draws as one overlay row per cell line. What it does not get is an inline config block on the composite page, because compositeUiSubtracks() lists descendant leaves and a multiWig is not a leaf; each overlay is configured from its own hgTrackUi page instead. Subtrack names are <composite>_<cell>_<dataType>. + +STATUS: this only partly works and is expected to be replaced by a superTrack +holding the multiWig overlays directly. hgTrackUi lists descendant leaves, not +containers, so an overlay gets no inline config block and deselecting both +strands of a cell line leaves an empty row (#38441). A superTrack gives each +overlay its own page and no flattening, at the cost of the matrix and the sample +class filter. """ import argparse import re from pycbio.sys import cli, fileOps from pycbio.tsv import TsvReader GBDB = "/gbdb/{db}/{track}" def parseArgs(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("db") parser.add_argument("experimentsTsv") parser.add_argument("outRa") return cli.parseOptsArgsWithLogging(parser) def subGroupTag(name): """A subGroup tag has to be a bare identifier, but cell line names carry punctuation (Caco-2). Only the tag is sanitized; the label keeps the real name.""" return re.sub(r"[^A-Za-z0-9]", "", name) def stanza(indent, lines): pad = " " * indent return "".join(pad + line + "\n" for line in lines) + "\n" def superStanza(): return stanza(0, [ "track transcriptionStart", "superTrack on show", "shortLabel Transcription Initiation (TSS)", "longLabel Transcription initiation (TSS)", "group rna", ]) def compositeStanza(track, shortLabel, longLabel, experiments, dataTypes, priority): """The matrix runs over the leaf bigWigs, which is the level hgTrackDb checks and the level compositeUiSubtracks() lists, so each strand is a cell of its own. The multiWig containers carry no subGroups: a container spans both strands, so it has no single value for the data type dimension, and hgTrackDb -strict rejects a leaf whose groups the parent does not declare. dataTypes is a list of (tag, label) pairs, the matrix's X dimension.""" cells = " ".join(f"{subGroupTag(e.cell)}={e.cell}" for e in experiments) types = " ".join(f"{tag}={label}" for tag, label in dataTypes) classes = " ".join(f"{subGroupTag(c)}={c}" for c in sorted({e.sampleClass for e in experiments})) return stanza(4, [ f"track {track}", "parent transcriptionStart", "compositeTrack on", "type bigWig", f"shortLabel {shortLabel}", f"longLabel {longLabel}", f"subGroup1 cellLine Cell_line {cells}", f"subGroup2 dataType Data_type {types}", f"subGroup3 sampleClass Sample_class {classes}", "dimensions dimensionX=dataType dimensionY=cellLine dimA=sampleClass", "filterComposite dimA", "sortOrder cellLine=+ dataType=+", # a config wrench on every subtrack, so one cell line can be rescaled # without touching the rest "configurable on", "autoScale group", "alwaysZero on", "maxHeightPixels 100:40:8", "windowingFunction maximum", "noInherit on", "visibility hide", f"priority {priority}", ]) def strandOverlay(db, composite, exp, dataType, subDir, fileTag, shortLabel, longLabel, childLabel, priority): """One cell line's two strands as an overlay, plus strand drawn up and minus - strand drawn down. The ProCapNet minus-strand files hold positive values and - are flipped with negateValues; the ENCODE minus-strand signal is already - negative and is not flipped. + strand drawn down. Both sources store the minus strand negated, so neither + needs the trackDb negateValues setting. That setting used to be on the + ProCapNet minus tracks, and it broke the composite's own negate control: that + control sets one shared value for the composite, which replaced the per-track + setting and sent both strands the same way, with no route back to the default + short of a cart reset. fileTag is the part of the file name that says what the file holds, so a bigWig downloaded on its own still names its source: the model for the predictions, the ENCODE experiment accession for the measurements. - The parent line says "on", which makes these default-on and is what checks - the signal data type in the faceted UI. The contribution scores stay off, so - opening the composite gives signal rather than every data type at once.""" + The parent line says "on", which makes these default-on. The contribution + scores stay off, so opening the composite gives signal rather than every data + type at once.""" gbdb = GBDB.format(db=db, track=composite) + subDir - negate = ["negateValues on"] if dataType == "pred" else [] out = stanza(8, [ f"track {composite}_{exp.cell}_{dataType}", f"parent {composite} on", "container multiWig", "aggregate solidOverlay", "showSubtrackColorOnUi on", "type bigWig", - "autoScale on", - "alwaysZero on", - "maxHeightPixels 100:40:8", - "windowingFunction maximum", f"color {exp.color}", f"shortLabel {shortLabel}", f"longLabel {longLabel}", "onlyVisibility full", f"priority {priority}", ]) out += stanza(12, [ f"track {composite}_{exp.cell}_{dataType}_pos", f"parent {composite}_{exp.cell}_{dataType}", f"subGroups cellLine={subGroupTag(exp.cell)} dataType={dataType}Pos" f" sampleClass={subGroupTag(exp.sampleClass)}", "type bigWig", f"bigDataUrl {gbdb}/{exp.cell}.{fileTag}.pos.bw", f"color {exp.color}", f"shortLabel {shortLabel} +", f"longLabel {childLabel}, plus strand", ]) out += stanza(12, [ f"track {composite}_{exp.cell}_{dataType}_neg", f"parent {composite}_{exp.cell}_{dataType}", f"subGroups cellLine={subGroupTag(exp.cell)} dataType={dataType}Neg" f" sampleClass={subGroupTag(exp.sampleClass)}", "type bigWig", f"bigDataUrl {gbdb}/{exp.cell}.{fileTag}.neg.bw", f"color {exp.color}", f"altColor {exp.color}", - ] + negate + [ f"shortLabel {shortLabel} -", f"longLabel {childLabel}, minus strand", ]) return out def contribStanza(db, exp, priority): gbdb = GBDB.format(db=db, track="proCapNet") return stanza(8, [ f"track proCapNet_{exp.cell}_contrib", f"subGroups cellLine={subGroupTag(exp.cell)} dataType=contrib" f" sampleClass={subGroupTag(exp.sampleClass)}", "parent proCapNet off", "type bigWig", f"bigDataUrl {gbdb}/contrib/{exp.cell}.proCapNet-contrib.bw", "logo on", - "autoScale on", - "alwaysZero on", - "maxHeightPixels 100:40:8", f"color {exp.color}", f"shortLabel {exp.cell} Contribution", f"longLabel {exp.cell} ({exp.modelAcc}) ProCapNet sequence-contribution scores", "onlyVisibility full", f"priority {priority}", ]) def proCapNetComposite(db, experiments): dataTypes = [("predPos", "Predicted_+"), ("predNeg", "Predicted_-")] if db == "hg38": dataTypes.append(("contrib", "Contribution_scores")) out = compositeStanza("proCapNet", "ProCapNet", "ProCapNet predicted PRO-cap", experiments, dataTypes, 2) for priority, exp in enumerate(experiments, 1): out += strandOverlay(db, "proCapNet", exp, "pred", "/pred", "proCapNet", f"{exp.cell} Predicted", f"{exp.cell} ({exp.modelAcc}) ProCapNet predicted PRO-cap," f" plus up minus down", f"{exp.cell} ({exp.modelAcc}) ProCapNet predicted PRO-cap", priority) if db == "hg38": for priority, exp in enumerate(experiments, 11): out += contribStanza(db, exp, priority) return out def encode4ProCapComposite(db, experiments): out = compositeStanza("encode4ProCap", "PRO-cap", "PRO-cap nascent RNA transcription start sites from ENCODE 4", experiments, [("procapPos", "PRO-cap_+"), ("procapNeg", "PRO-cap_-")], 1) for priority, exp in enumerate(experiments, 1): out += strandOverlay(db, "encode4ProCap", exp, "procap", "", exp.procapAcc, f"{exp.cell} PRO-cap", f"{exp.cell} ({exp.procapAcc}) PRO-cap transcription start" f" sites, plus up minus down", f"{exp.cell} ({exp.procapAcc}) PRO-cap transcription start sites", priority) return out def proCapNetTrackDb(opts, args): experiments = list(TsvReader(args.experimentsTsv)) fileOps.ensureFileDir(args.outRa) with fileOps.AtomicFileOpen(args.outRa) as fh: print(f"# Generated by hg/makeDb/outside/proCapNet/proCapNetTrackDb for {args.db}.", file=fh) print("# Do not edit by hand, edit the script and regenerate.\n", file=fh) fh.write(superStanza()) if args.db == "hg38": fh.write(encode4ProCapComposite(args.db, experiments)) fh.write(proCapNetComposite(args.db, experiments)) def main(): opts, args = parseArgs() with cli.ErrorHandler(noStackExcepts=(OSError, cli.PycbioException)): proCapNetTrackDb(opts, args) main()