3522a9acc8256a35230b07f2e7ab3fc08c827f33
mspeir
  Sat Sep 5 17:19:20 2026 -0700
singleCellSignalsPeaks: correct the hg38 description page counts and cite BrainVar, refs #38219

From the v503 code review. Commit 3aac3982aa2 dropped 5 hg38 subtracks and
updated the makeDoc, but the dataset list on the description page was missed, so
the page disagreed with both the .ra and the facet menu:

- Risk Loci in Alzheimer's and Parkinson's: 1 peak subtrack, not 2
(neuro-degen-atac/peaks.bb was the one dropped)
- BrainVar: 9 signal subtracks, not 13, and the text still described tracks
"for all nuclei together" -- the 4 combined-stage tracks are exactly the ones
dropped, so every remaining BrainVar track carries a life stage

Counts re-derived from the .ra by type (bigWig = signal, else peak): the other 7
hg38 datasets and all 9 mm10 datasets were already right, 929 and 587 total.

BrainVar was also the only hg38 dataset with no citation, while its methods text
had grown quite specific. It now cites Werling et al. 2020, which described the
cohort, with the caveat that the single-nucleus data shown here were not part of
that paper -- the same distinction the Cell Browser desc.conf makes. Citing it
bare would credit a bulk RNA-seq and WGS study for 10x Multiome data. References
are alphabetical, so hg38 is now 10 and mm10 still 7.

copySingleCellSignalsPeaksFiles.py: colors_json() reads the palette in a with
block, and a malformed R,G,B row now raises the SystemExit the rest of the file
uses, naming the file, line number and offending field, instead of a bare
ValueError or TypeError from int() or the %02X format. Output is unchanged.

makeDoc: the mm10 cell-class note now records that the bare "Progenitor" row in
celltype-class.tsv is live rather than leftover -- build_stanzas' class_key()
collapses plurals, so BrainVar's "Progenitors" looks up under the singular key
and takes its class and color from that one row. Retiring or qualifying the row
would grey out that track. A non-neural label needs a specific cell type added
instead, which is how "Nephron progenitors" comes out Stromal. Also fixed a
contradiction there: the file claimed celltype-class.tsv is built by
build_celltype_crosswalks.py a hundred lines above the note saying, correctly,
that it is hand-curated and not generated.

The submitters confirmed BrainVar is 100 bp tiles, so the 1 kb in their methods
text is an error and the page is right; recorded in the hg38 makeDoc.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

diff --git src/hg/makeDb/scripts/singleCellSignalsPeaks/copySingleCellSignalsPeaksFiles.py src/hg/makeDb/scripts/singleCellSignalsPeaks/copySingleCellSignalsPeaksFiles.py
index cbf63d44b87..8674d5fff4e 100644
--- src/hg/makeDb/scripts/singleCellSignalsPeaks/copySingleCellSignalsPeaksFiles.py
+++ src/hg/makeDb/scripts/singleCellSignalsPeaks/copySingleCellSignalsPeaksFiles.py
@@ -92,36 +92,42 @@
     if not os.path.isfile(pal):
         pal = os.path.join(build, "celltype-crosswalks", "celltype-palette.tsv")
     return pal
 
 def colors_json(pal):
     """Render the palette as the faceted UI's colorSettingsUrl JSON.
 
     facetedComposite.js wants {facetName: {facetValue: cssColor}} and draws a swatch
     beside each checkbox of any facet named in it. The facet name must be the metadata
     column ("Cell_class") and each key must be the column value verbatim -- the lookup
     is an exact string match, so a case or spacing difference silently drops the swatch.
     The whole palette is emitted, not just the classes this assembly uses: extra keys are
     ignored by the JS, and it keeps hg38 and mm10 sharing one class->color mapping.
     """
     colors = {}
-    for line in open(pal):
+    with open(pal) as fh:
+        for lineNo, line in enumerate(fh, 1):
             f = line.rstrip("\n").split("\t")
             if len(f) < 2 or not f[0].strip():
                 continue
+            try:
                 rgb = [int(x) for x in f[1].split(",")]
                 colors[f[0]] = "#%02X%02X%02X" % tuple(rgb)
+            except (ValueError, TypeError):
+                raise SystemExit("ERROR: %s line %d has %r where an R,G,B triple of "
+                                 "integers was expected, so the facet color swatch for "
+                                 "%r cannot be written." % (pal, lineNo, f[1], f[0]))
     if not colors:
         raise SystemExit("ERROR: no class/color rows read from %s, so the facet color "
                          "swatches cannot be written." % pal)
     return json.dumps({"Cell_class": colors}, indent=4, sort_keys=True) + "\n"
 
 def load_relpath_to_abs(manifest, asm):
     m = {}
     with open(manifest) as fh:
         hdr = fh.readline().rstrip("\n").split("\t")
         ai, ui, asmi = hdr.index("abs_path"), hdr.index("track_url"), hdr.index("assembly")
         for line in fh:
             f = line.rstrip("\n").split("\t")
             if len(f) <= max(ai, ui, asmi) or f[asmi] != asm:
                 continue
             m[urlparse(f[ui]).path.lstrip("/")] = f[ai]