import re from server.common.errors import ColorFormatException HEX_COLOR_FORMAT = re.compile("^#[a-fA-F0-9]{6,6}$") # https://www.w3.org/TR/css-color-4/#named-colors CSS4_NAMED_COLORS = dict( aliceblue="#f0f8ff", antiquewhite="#faebd7", aqua="#00ffff", aquamarine="#7fffd4", azure="#f0ffff", beige="#f5f5dc", bisque="#ffe4c4", black="#000000", blanchedalmond="#ffebcd", blue="#0000ff", blueviolet="#8a2be2", brown="#a52a2a", burlywood="#deb887", cadetblue="#5f9ea0", chartreuse="#7fff00", chocolate="#d2691e", coral="#ff7f50", cornflowerblue="#6495ed", cornsilk="#fff8dc", crimson="#dc143c", cyan="#00ffff", darkblue="#00008b", darkcyan="#008b8b", darkgoldenrod="#b8860b", darkgray="#a9a9a9", darkgreen="#006400", darkgrey="#a9a9a9", darkkhaki="#bdb76b", darkmagenta="#8b008b", darkolivegreen="#556b2f", darkorange="#ff8c00", darkorchid="#9932cc", darkred="#8b0000", darksalmon="#e9967a", darkseagreen="#8fbc8f", darkslateblue="#483d8b", darkslategray="#2f4f4f", darkslategrey="#2f4f4f", darkturquoise="#00ced1", darkviolet="#9400d3", deeppink="#ff1493", deepskyblue="#00bfff", dimgray="#696969", dimgrey="#696969", dodgerblue="#1e90ff", firebrick="#b22222", floralwhite="#fffaf0", forestgreen="#228b22", fuchsia="#ff00ff", gainsboro="#dcdcdc", ghostwhite="#f8f8ff", gold="#ffd700", goldenrod="#daa520", gray="#808080", green="#008000", greenyellow="#adff2f", grey="#808080", honeydew="#f0fff0", hotpink="#ff69b4", indianred="#cd5c5c", indigo="#4b0082", ivory="#fffff0", khaki="#f0e68c", lavender="#e6e6fa", lavenderblush="#fff0f5", lawngreen="#7cfc00", lemonchiffon="#fffacd", lightblue="#add8e6", lightcoral="#f08080", lightcyan="#e0ffff", lightgoldenrodyellow="#fafad2", lightgray="#d3d3d3", lightgreen="#90ee90", lightgrey="#d3d3d3", lightpink="#ffb6c1", lightsalmon="#ffa07a", lightseagreen="#20b2aa", lightskyblue="#87cefa", lightslategray="#778899", lightslategrey="#778899", lightsteelblue="#b0c4de", lightyellow="#ffffe0", lime="#00ff00", limegreen="#32cd32", linen="#faf0e6", magenta="#ff00ff", maroon="#800000", mediumaquamarine="#66cdaa", mediumblue="#0000cd", mediumorchid="#ba55d3", mediumpurple="#9370db", mediumseagreen="#3cb371", mediumslateblue="#7b68ee", mediumspringgreen="#00fa9a", mediumturquoise="#48d1cc", mediumvioletred="#c71585", midnightblue="#191970", mintcream="#f5fffa", mistyrose="#ffe4e1", moccasin="#ffe4b5", navajowhite="#ffdead", navy="#000080", oldlace="#fdf5e6", olive="#808000", olivedrab="#6b8e23", orange="#ffa500", orangered="#ff4500", orchid="#da70d6", palegoldenrod="#eee8aa", palegreen="#98fb98", paleturquoise="#afeeee", palevioletred="#db7093", papayawhip="#ffefd5", peachpuff="#ffdab9", peru="#cd853f", pink="#ffc0cb", plum="#dda0dd", powderblue="#b0e0e6", purple="#800080", rebeccapurple="#663399", red="#ff0000", rosybrown="#bc8f8f", royalblue="#4169e1", saddlebrown="#8b4513", salmon="#fa8072", sandybrown="#f4a460", seagreen="#2e8b57", seashell="#fff5ee", sienna="#a0522d", silver="#c0c0c0", skyblue="#87ceeb", slateblue="#6a5acd", slategray="#708090", slategrey="#708090", snow="#fffafa", springgreen="#00ff7f", steelblue="#4682b4", tan="#d2b48c", teal="#008080", thistle="#d8bfd8", tomato="#ff6347", turquoise="#40e0d0", violet="#ee82ee", wheat="#f5deb3", white="#ffffff", whitesmoke="#f5f5f5", yellow="#ffff00", yellowgreen="#9acd32", ) def convert_color_to_hex_format(unknown): """ Try to convert color info to a hex triplet string https://en.wikipedia.org/wiki/Web_colors#Hex_triplet. The function accepts for the following formats: - A CSS4 color name, as supported by matplotlib https://matplotlib.org/3.1.0/gallery/color/named_colors.html - RGB tuple/list with values ranging from 0.0 to 1.0, as in [0.5, 0.75, 1.0] - RFB tuple/list with values ranging from 0 to 255, as in [128, 192, 255] - Hex triplet string, as in "#08c0ff" :param unknown: color info of unknown format :return: a hex triplet representing that color """ try: if type(unknown) in (list, tuple) and len(unknown) == 3: if all(0.0 <= ele <= 1.0 for ele in unknown): tup = tuple(int(ele * 255) for ele in unknown) elif all(0 <= ele <= 255 and isinstance(ele, int) for ele in unknown): tup = tuple(unknown) else: raise ColorFormatException("Unknown color iterable format!") return "#%02x%02x%02x" % tup elif isinstance(unknown, str) and unknown.lower() in CSS4_NAMED_COLORS: return CSS4_NAMED_COLORS[unknown.lower()] elif isinstance(unknown, str) and HEX_COLOR_FORMAT.match(unknown): return unknown.lower() else: raise ColorFormatException("Unknown color format type!") except Exception as e: raise ColorFormatException(e) def convert_anndata_category_colors_to_cxg_category_colors(data): """ Convert color information from anndata files to the cellxgene color data format as described below: { "": { "": "", ... }, ... } For more on the cxg color data structure, see https://github.com/chanzuckerberg/cellxgene/issues/1307. For more on the anndata color data structure, see https://github.com/chanzuckerberg/cellxgene/issues/1152#issuecomment-587276178. Handling of malformed data: - For any color info in a adata.uns[f"{category}_colors"] color array that convert_color_to_hex_format cannot convert to a hex triplet string, a ColorFormatException is raised - No category_name key group is returned for adata.uns[f"{category}_colors"] keys for which there is no adata.obs[f"{category}"] key :param data: the anndata file :return: cellxgene color data structure as described above """ cxg_colors = dict() color_key_suffix = "_colors" for uns_key in data.uns.keys(): # find uns array that describes colors for a category if not uns_key.endswith(color_key_suffix): continue # check to see if we actually have observations for that category category_name = uns_key[: -len(color_key_suffix)] if category_name not in data.obs.keys(): continue # create the cellxgene color entry for this category cxg_colors[category_name] = dict( zip(data.obs[category_name].cat.categories, [convert_color_to_hex_format(c) for c in data.uns[uns_key]]) ) return cxg_colors