156d289f517d82c4d5a8c59981dfec21a715af8d lrnassar Mon Sep 21 09:22:45 2026 -0700 Fiber-seq QA fixes: take sample class from the lab's own table, pin the overlay draw order, and correct the description pages. refs #36210 Sample class had been guessed from the free-text cell type with a two-name exception list, which filed five lymphoblastoid lines as HPRC that are not. It now comes from a fifth column in fiberSeqSamples.tsv carrying the classification the lab supplies, and sampleClass()/NOT_HPRC are gone. Facet counts go from 25/16 to 20/21. The CpG difference track is a solidOverlay whose four files hold the same value at a shared base, so the tier drawn last is the colour the reader sees. Its children inherited a single priority from the parent, trackPriCmp ties on that, and slSort is not stable, so the paint order was arbitrary and p<0.01 was covering p<0.0001. The four levels and the two haplotype children now carry explicit priorities. At chr20:29,300,000-29,305,000 red goes from 2 image columns to 88. CpG haplotype children take their shortLabel prefix from their container, so they read " CpG Hap1" rather than colliding with the accessibility children's " Hap1". 82 labels were duplicated. Description pages: fiberSeqAcc.html attached the FIRE score's "fewer than four elements" cutoff to the percent accessible signal, which is a different quantity; two pages overstated how many of the lymphoblastoid lines come from HPRC; both pages told readers to query the API with container track names, which it refuses by design. Also documents GM12878's trio phasing and the FDR ceiling at 100, corrects the difference track's stated range, replaces a non-ASCII author name with numeric entities, and adds db= to hgTrackUi links. makeDoc: corrects a basesCovered figure that was out by a factor of a hundred, refreshes the peak file sizes after the PM00001 reissue, rewrites the sampleClass rationale, and records why multiWig children need their own priority. diff --git src/hg/makeDb/scripts/fiberSeq/fiberSeqTrackDb.py src/hg/makeDb/scripts/fiberSeq/fiberSeqTrackDb.py index 978de73183c..90d715505e6 100755 --- src/hg/makeDb/scripts/fiberSeq/fiberSeqTrackDb.py +++ src/hg/makeDb/scripts/fiberSeq/fiberSeqTrackDb.py @@ -65,64 +65,50 @@ ] # Shown behind an info icon on the Sample class column heading. SAMPLE_CLASS_DESCRIPTION = ( "HPRC = Lymphoblastoid (B-lymphocyte, EBV) cell lines from the NHGRI " "Human Pangenome Reference Consortium") # Sample class swatches, shown next to that facet's checkboxes. # Okabe-Ito colors for the swatches. SAMPLE_CLASS_COLORS = { "HPRC": "#0072B2", "Common Cell Line": "#D55E00", } -# Lymphoblastoid lines that are not from the Human Pangenome Reference -# Consortium: GM12878 is the ENCODE line and HG002 is Genome in a Bottle. Both -# are B-lymphocyte EBV lines like the HPRC samples, so cell type alone cannot -# tell them apart and they have to be named. -NOT_HPRC = {"GM12878", "HG002"} - - -def sampleClass(sample, cellType): - """Split a sample two ways for the Sample class column. - - HPRC is a lymphoblastoid (B-lymphocyte, EBV) line from the consortium; - everything else, including the two lymphoblastoid lines listed in NOT_HPRC, - is a common cell line.""" - if "lymphoblastoid" in cellType.lower() and sample not in NOT_HPRC: - return "HPRC" - return "Common Cell Line" - - def readSamples(path): - """Read fiberSeqSamples.tsv into a list of dicts, in file order.""" + """Read fiberSeqSamples.tsv into a list of dicts, in file order. + + sampleClass comes from the lab's own sample sheet, not from the cell type: + five of the lymphoblastoid lines are common cell lines rather than HPRC + samples, so there is nothing in the cell type that tells the two apart.""" samples = [] with open(path) as f: for line in f: if line.startswith("#") or not line.strip(): continue fields = line.rstrip("\n").split("\t") - if len(fields) < 4: - sys.exit("bad sample line, want 4 fields: %s" % line.rstrip()) - acc, sample, cellType, _hash = fields[:4] + if len(fields) < 5: + sys.exit("bad sample line, want 5 fields: %s" % line.rstrip()) + acc, sample, cellType, _hash, sampleClass = fields[:5] samples.append({ "accession": acc, "sample": sample, "cellType": cellType, - "sampleClass": sampleClass(sample, cellType), + "sampleClass": sampleClass, }) if not samples: sys.exit("no samples read from %s" % path) return samples def writeMetadata(path, samples): """The sample table shown on the track UI page. A plain column name gets facet checkboxes, a leading underscore means searchable and sortable but not faceted. Accession is the primaryKey but sits last, since it is the least interesting thing about a sample. Nothing requires the primaryKey to come first: facetedComposite.js only checks that the column exists, and every use of it is by name. It is still the default sort, because accession order keeps the @@ -373,73 +359,90 @@ "aggregate solidOverlay", "showSubtrackColorOnUi on", "type bigWig -100 100", "viewLimits -100:100", "autoScale off", "windowingFunction mean", "maxHeightPixels 100:50:8", "shortLabel %s CpG diff" % name, "longLabel %s CpG methylation difference between haplotypes, " "by significance threshold" % name, "onlyVisibility full", pri(), ]) # Least significant first, so the more significant levels draw on top. # Not "i": that is the sample index pri() builds its priority from. + # + # The priority is what makes that happen and is not decoration. This is + # a solid overlay and all four files hold the same value at a shared + # base, so the level that draws last is the colour the user sees. Order + # of declaration does not survive: makeContainerTrack() in + # hg/hgTracks/container.c sorts the children with trackPriCmp, which + # compares priority alone, and slSort is not a stable sort, so children + # left on the parent's inherited priority draw in an arbitrary order. for level, (fname, label, color) in enumerate(DIFF_LEVELS): out += stanza(12, [ "track fiberSeqCompendium_%s_cpgDiff_l%d" % (acc, level), "parent fiberSeqCompendium_%s_cpgDiff" % acc, "type bigWig", "bigDataUrl %s/%s/%s" % (gbdb, acc, fname), "color %s" % color, "shortLabel %s %s" % (name, label), "longLabel %s CpG haplotype difference, %s" % (name, label), + "priority %d" % (level + 1), ]) return out def hapOverlay(gbdb, acc, name, dataType, file1, file2, shortLabel, longLabel, childLongLabel, windowing, priority): """A haplotype 1 / haplotype 2 transparent overlay, used for both the accessibility and the CpG haplotype data types.""" out = stanza(8, [ "track fiberSeqCompendium_%s_%s" % (acc, dataType), "parent fiberSeqCompendium off", "container multiWig", "aggregate transparentOverlay", "showSubtrackColorOnUi on", "type bigWig 0 100", "viewLimits 0:100", "autoScale off", "alwaysZero on", "windowingFunction %s" % windowing, "maxHeightPixels 100:40:8", "shortLabel %s" % shortLabel, "longLabel %s" % longLabel, "onlyVisibility full", priority, ]) + # Haplotype 1 then haplotype 2. This overlay is transparent, so the order + # barely shows, but a child without its own priority inherits the parent's + # and then draws in whatever order slSort happens to leave it in - see the + # longer note on the cpgDiff children, where the same thing is visible. for hap, fname, color in (("h1", file1, HAP1_COLOR), ("h2", file2, HAP2_COLOR)): n = hap[1] out += stanza(12, [ "track fiberSeqCompendium_%s_%s_%s" % (acc, dataType, hap), "parent fiberSeqCompendium_%s_%s" % (acc, dataType), "type bigWig", "bigDataUrl %s/%s/%s" % (gbdb, acc, fname), "color %s" % color, - "shortLabel %s Hap%s" % (name, n), + "priority %s" % n, + # Take the prefix from the container's own shortLabel, so the CpG + # children come out " CpG Hap1" rather than colliding with + # the accessibility children's " Hap1". + "shortLabel %s%s" % (shortLabel.removesuffix("1/2"), n), "longLabel %s %s" % (childLongLabel, n), ]) return out def main(): ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) ap.add_argument("--data-dir", default="/hive/data/genomes/hg38/bed/fiberSeq", help="where the metadata and color files are written") ap.add_argument("--gbdb-dir", default="/gbdb/hg38/fiberSeq", help="bigDataUrl prefix, i.e. the symlink directory") ap.add_argument("--trackdb-dir", default=os.path.expanduser( "~/kent/src/hg/makeDb/trackDb/human/hg38"),