595ae73a63776a07d8d47fad24b2f3a4cf23f040
max
  Mon Jul 21 13:26:47 2025 -0700
allowing 8-digit hex color codes, mouse-dev-cardiac-spatial, marc

diff --git src/cbPyLib/cellbrowser/cellbrowser.py src/cbPyLib/cellbrowser/cellbrowser.py
index 4d5493b..bafab71 100755
--- src/cbPyLib/cellbrowser/cellbrowser.py
+++ src/cbPyLib/cellbrowser/cellbrowser.py
@@ -2050,30 +2050,31 @@
     else:
         logging.debug("%s is not an MTX file" % path)
         return False
 
 def exprEncode(geneDesc, exprArr, matType):
     """ convert an array of numbers of type matType (int or float) to a compressed string of
     float32s
     The format of a record is:
     - 2 bytes: length of descStr, e.g. gene identifier or else
     - len(descStr) bytes: the descriptive string descStr
     - array of n 4-byte floats (n = number of cells) or 4-byte unsigned ints
     """
     geneDesc = str(geneDesc) # make sure no unicode
     geneIdLen = struct.pack("<H", len(geneDesc))
 
+    nanValue = None
     if matType=="float":
         nanValue = FLOATNAN
     else:
         nanValue = INTNAN
 
     # on cortex-dev, numpy was around 30% faster. Not a huge difference.
     if numpyLoaded:
         if matType=="float":
             exprArr = exprArr.astype("float32")
         elif matType=="int":
             exprArr = exprArr.astype("uint32")
         else:
             assert(False) # internal error
         exprStr = exprArr.tobytes()
         exprArr[np.isnan(exprArr)] = nanValue
@@ -2249,31 +2250,36 @@
     else:
         if atacChromCount > 100:
             errAbort("There are more than 100 genes that look like a chrom_start_end range but the atacSearch cellbrowser.conf"
                     " is not set. Please add this option or contact us if you are confused about this error.")
 
     if highCount==0:
         logging.warn("No single value in the matrix is > 100. It looks like this "
         "matrix has been log'ed. Our recommendation for visual inspection is to not transform matrices")
 
     if len(exprIndex)==0:
         errAbort("No genes from the expression matrix could be mapped to symbols."
             "Are you sure these are Ensembl IDs? Adapt geneIdType in cellbrowser.conf.")
 
     # keep a flag so the client later can figure out if the expression matrix contains any negative values
     # this is important for handling the 0-value
-    exprIndex["_range"] = (float(allMin),0) # float() in case it is -inf as a np.float32, must be native Python float, not numpy
+    matrixMin = float(allMin) # float() in case it is -inf as a np.float32, must be native Python float, not numpy
+    if matrixMin==float('-inf'): # JSON cannot encode -inf nor NaN, so we use "None" for that which will become null in JSON
+        matrixMin = None
+
+    exprIndex["_range"] = (matrixMin, 0)
+
     logging.info("Global minimum in matrix is: %f" % allMin)
 
     jsonOfh = open(jsonFname, "w")
     json.dump(exprIndex, jsonOfh)
     jsonOfh.close()
 
     jsonOfh = open(discretJsonFname, "w")
     json.dump(discretIndex, jsonOfh)
     jsonOfh.close()
 
     renameFile(tmpFname, binFname)
     renameFile(discretTmp, discretBinFname)
 
     return matType
 
@@ -2342,31 +2348,39 @@
         if len(row)!=2:
             errAbort("color file %s - line %d does not contain exactly two fields: %s" % (fname, lineNo, row))
         metaVal, color = row
 
         if metaVal.lower().startswith("color") or metaVal.lower().startswith("colour"):
             # tolerate a header line, otherwise will stop with "color not found"
             invColumns = True
             continue
         if color.lower().startswith("color") or color.lower().startswith("colour"):
             # tolerate a header line, otherwise will stop with "color not found"
             continue
 
         if invColumns:
             color, metaVal = row
 
-        color = color.strip().strip("#") # hbeale had a file with trailing spaces
+        color = color.strip() # hbeale had a 6-digit file with trailing spaces
+
+        if color.startswith("#") and len(color)==9:
+            # we got these a few times now: #abababff
+            logging.warn("Color %s looks like a hex color with alpha value, stripping last two characters"
+                    % color)
+            color = color[1:7]
+
+        color = color.strip("#") # hbeale had a 6-digit file with trailing spaces
 
         isHex = True
         if len(color)!=6: # colors can be no more than six hex digits
             isHex = False
         else:
             for c in color:
                 if (c not in "0123456789ABCDEFabcdef"):
                     isHex = False
                     break
 
         if not isHex:
             logging.debug("Not a six-digit hex color code. Trying to map '%s' to a hex color" % color)
             import webcolors # error? -> pip install webcolors
             try:
                 color = webcolors.name_to_hex(color, spec='css3').lstrip("#")