From ab46b4593e36b5bb35bc6fc9f8b2f984cdbaf06d Mon Sep 17 00:00:00 2001 From: Fawaz Dabbaghie Date: Tue, 3 May 2022 15:10:20 +0200 Subject: [PATCH] low coverage edges subcommand that takes a TSV of nodes and their corrisponding chromosomes and a GFA with edge counts like output of MBG and reports the edges that are connecting two different chromosomes --- GFASubgraph/Graph.py | 66 ++++++++++++++++++------- GFASubgraph/Node.py | 45 +++++++++++++---- GFASubgraph/graph_io.py | 99 ++++++++++++++++++++++++++++++++----- GFASubgraph/main.py | 88 +++++++++++++++++++++++++++------ GFASubgraph/main_helpers.py | 25 +++++++++- GFASubgraph/x11_colors.py | 68 +------------------------ 6 files changed, 267 insertions(+), 124 deletions(-) diff --git a/GFASubgraph/Graph.py b/GFASubgraph/Graph.py index e8e817b..720dff3 100644 --- a/GFASubgraph/Graph.py +++ b/GFASubgraph/Graph.py @@ -1,6 +1,7 @@ from GFASubgraph.graph_io import read_gfa, write_gfa from GFASubgraph.connected_components import all_components from GFASubgraph.bfs import bfs +from GFASubgraph.Node import Node import sys import logging import os @@ -11,55 +12,73 @@ class Graph: Graph object containing the important information about the graph """ - __slots__ = ['nodes', 'b_chains', 'child_parent'] + __slots__ = ['nodes', 'edge_counts'] - def __init__(self, graph_file=None): + def __init__(self, graph_file=None, edge_count=False): if graph_file is not None: if not os.path.exists(graph_file): - print("graph file {} does not exist".format(graph_file)) + print("Error! Check log file.") + logging.error("graph file {} does not exist".format(graph_file)) sys.exit() # loading nodes from file self.nodes = read_gfa(gfa_file_path=graph_file) + if edge_count: + self.edge_counts = self.get_edges_counts(graph_file) else: self.nodes = dict() - # elif graph_file.endswith(".vg"): - # self.nodes = read_vg(vg_file_path=graph_file, k=k, modified=modified, coverage=coverage) - - self.b_chains = set() # list of BubbleChain objects - # self.bubbles = set() - # self.k = 1 - self.child_parent = dict() + self.edge_counts = dict() def __len__(self): """ overloading the length function """ - return len(self.nodes) def __str__(self): """ overloading the string function for printing """ - - return "The graph has {} Nodes and {} chains".format( - len(self.nodes), len(self.b_chains)) + return "The graph has {} Nodes".format(len(self.nodes)) def __contains__(self, key): """ overloading the in operator to check if node exists in graph """ - return key in self.nodes + def __getitem__(self, key): + """ + overloading the bracket operator + """ + try: + return self.nodes[key] + except KeyError: + return None + + def __setitem__(self, key, value): + """ + overloading setting an item in nodes + """ + if isinstance(value, Node): + self.nodes[key] = value + else: + raise ValueError("the object given to set should be a Node object") + + def __delitem__(self, key): + """ + overloading deleting item + """ + del self.nodes[key] + def reset_visited(self): """ resets all nodes.visited to false """ - for n in self.nodes.values(): n.visited = False +# todo make remove start and remove end separate so I can use the same functions +# for removing one edge def remove_node(self, n_id): """ remove a node and its corresponding edges @@ -86,9 +105,7 @@ def remove_lonely_nodes(self): """ remove singular nodes with no neighbors """ - nodes_to_remove = [n.id for n in self.nodes.values() if len(n.neighbors()) == 0] - for i in nodes_to_remove: self.remove_node(i) @@ -109,7 +126,6 @@ def write_graph(self, set_of_nodes=None, write_gfa(self, set_of_nodes=set_of_nodes, output_file=output_file, append=append) - def bfs(self, start, size): """ Returns a neighborhood of size given around start node @@ -134,3 +150,15 @@ def output_components(self, output_dir): logging.info("Writing Component {}...".format(output_file)) self.write_graph(set_of_nodes=cc, output_file=output_file, append=False) + + def remove_edge(self, edge): + n1, side1, n2, side2, overlap = edge + if side1 == 0: + self.nodes[n1].remove_from_start(n2, side2, overlap) + else: + self.nodes[n1].remove_from_end(n2, side2, overlap) + + if side2 == 0: + self.nodes[n2].remove_from_start(n1, side1, overlap) + else: + self.nodes[n2].remove_from_end(n1, side1, overlap) diff --git a/GFASubgraph/Node.py b/GFASubgraph/Node.py index 6c91465..78e3991 100644 --- a/GFASubgraph/Node.py +++ b/GFASubgraph/Node.py @@ -1,17 +1,19 @@ import sys +import logging class Node: - __slots__ = ['id', 'seq', 'seq_len', 'start', 'end', 'visited', 'optional'] + __slots__ = ['id', 'seq', 'seq_len', 'start', 'end', 'visited', 'optional', 'chromosome'] - def __init__(self, identifier, kc=0, km=0): + def __init__(self, identifier): self.id = identifier self.seq = "" - self.seq_len = 0 - self.start = [] - self.end = [] + self.seq_len = 0 + self.start = set() + self.end = set() self.visited = False self.optional = "" + self.chromosome = None def __sizeof__(self): size = self.id.__sizeof__() + self.seq_len.__sizeof__() + self.visited.__sizeof__() @@ -19,14 +21,14 @@ def __sizeof__(self): if len(self.start) == 0: size += self.start.__sizeof__() else: - for i in range(len(self.start)): - size += sys.getsizeof(self.start[i]) + for i in self.start: + size += sys.getsizeof(i) if len(self.end) == 0: size += self.end.__sizeof__() else: - for i in range(len(self.end)): - size += sys.getsizeof(self.end[i]) + for i in self.end: + size += sys.getsizeof(i) return size @@ -42,7 +44,9 @@ def in_direction(self, node, direction): """ returns true if node is a neighbor in that direction """ - + # todo if I make start and end into dicts + # I can then easily check if the node in that direction by check if (node, 0) or (node, 1) in self.start + # same goes for self.end if direction == 0: if node in [x[0] for x in self.start]: return True @@ -63,3 +67,24 @@ def children(self, direction): return [x[0] for x in self.end] else: raise Exception("Trying to access a wrong direction in node {}".format(self.id)) + + # todo add functions to add edges to start and end that graph_io can then use + # should maybe have an option whether the edge is (neighbor, direction, overlap) or + # (neighbor, direction, overlap, count) + def remove_from_start(self, neighbor, side, overlap): + """ + remove the neighbor edge from the start going to side in neighbor + """ + try: + self.start.remove((neighbor, side, overlap)) + except KeyError: + logging.warning(f"Could not remove edge {(neighbor, side, overlap)} from {self.id}'s start as it does not exist") + + def remove_from_end(self, neighbor, side, overlap): + """ + remove the neighbor edge from the end going to side in neighbor + """ + try: + self.end.remove((neighbor, side, overlap)) + except KeyError: + logging.warning(f"Could not remove edge {(neighbor, side, overlap)} from {self.id}'s end as it does not exist") diff --git a/GFASubgraph/graph_io.py b/GFASubgraph/graph_io.py index f172ff1..0e392f4 100644 --- a/GFASubgraph/graph_io.py +++ b/GFASubgraph/graph_io.py @@ -35,12 +35,12 @@ def write_gfa(graph, set_of_nodes=None, # else: if nodes[n1].optional: # if there are extra tags, write them as is - line = str("\t".join(["S", str(n1), nodes[n1].seq + nodes[n1].optional])) + line = str("\t".join(["S", str(n1), nodes[n1].seq, nodes[n1].optional])) # line = str("\t".join(("S", str(n1), nodes[n1].seq, nodes[n1].optional))) else: line = str("\t".join(["S", str(n1), nodes[n1].seq])) - f.write(line + "\n") + f.write(line) # writing edges edges = [] @@ -107,10 +107,17 @@ def read_gfa(gfa_file_path): for e in edges: line = e.split() - k = str(line[1]) + # I take the overlap in 5 and see if there are any more tags and make a dict out of them + k = line[1] + if k not in nodes: # if the edge is there but not the node + continue + overlap = int(line[5][:-1]) - neighbor = str(line[3]) + neighbor = line[3] + if neighbor not in nodes: + continue + if line[2] == "-": from_start = True else: @@ -121,32 +128,98 @@ def read_gfa(gfa_file_path): else: to_end = False - if from_start and to_end : # from start to end L x - y - + if from_start and to_end: # from start to end L x - y - if (neighbor, 1, overlap) not in nodes[k].start: - nodes[k].start.append((neighbor, 1, overlap)) + nodes[k].start.add((neighbor, 1, overlap)) if (k, 0, overlap) not in nodes[neighbor].end: - nodes[neighbor].end.append((k, 0, overlap)) + nodes[neighbor].end.add((k, 0, overlap)) elif from_start and not to_end: # from start to start L x - y + if (neighbor, 0, overlap) not in nodes[k].start: - nodes[k].start.append((neighbor, 0, overlap)) + nodes[k].start.add((neighbor, 0, overlap)) if (k, 0, overlap) not in nodes[neighbor].start: - nodes[neighbor].start.append((k, 0, overlap)) + nodes[neighbor].start.add((k, 0, overlap)) elif not from_start and not to_end: # from end to start L x + y + if (neighbor, 0, overlap) not in nodes[k].end: - nodes[k].end.append((neighbor, 0, overlap)) + nodes[k].end.add((neighbor, 0, overlap)) if (k, 1, overlap) not in nodes[neighbor].start: - nodes[neighbor].start.append((k, 1, overlap)) + nodes[neighbor].start.add((k, 1, overlap)) elif not from_start and to_end: # from end to end L x + y - if (neighbor, 1, overlap) not in nodes[k].end: - nodes[k].end.append((neighbor, 1, overlap)) + nodes[k].end.add((neighbor, 1, overlap)) if (k, 1, overlap) not in nodes[neighbor].end: - nodes[neighbor].end.append((k, 1, overlap)) + nodes[neighbor].end.add((k, 1, overlap)) return nodes + + +def get_edges_counts(graph_file): + edge_counts = dict() + with open(graph_file, "r") as infile: + for l in infile: + count = -1 + if l.startswith("L"): + + line = l.split() + if len(line) < 7: + logging.warning(f"the edge {' '.join(line)} does not have counts") + count = 0 + else: + if not line[6].startswith("ec"): + logging.warning(f'the edge {" ".join(line)} does not have the ec tag') + count = 0 + + # I take the overlap in 5 and see if there are any more tags and make a dict out of them + k = str(line[1]) + overlap = int(line[5][:-1]) + if count == -1: + count = int(line[6].split(":")[-1]) + + neighbor = str(line[3]) + if line[2] == "-": + from_start = True + else: + from_start = False + + if line[4] == "-": + to_end = True + else: + to_end = False + + if from_start and to_end: # from start to end L x - y - + edge_counts[(k, 0, neighbor, 1, overlap)] = count + # if (neighbor, 1, overlap) not in nodes[k].start: + # nodes[k].start.add((neighbor, 1, overlap)) + # if (k, 0, overlap) not in nodes[neighbor].end: + # nodes[neighbor].end.add((k, 0, overlap)) + + elif from_start and not to_end: # from start to start L x - y + + edge_counts[(k, 0, neighbor, 0, overlap)] = count + # if (neighbor, 0, overlap) not in nodes[k].start: + # nodes[k].start.add((neighbor, 0, overlap)) + # + # if (k, 0, overlap) not in nodes[neighbor].start: + # nodes[neighbor].start.add((k, 0, overlap)) + + elif not from_start and not to_end: # from end to start L x + y + + edge_counts[(k, 1, neighbor, 0, overlap)] = count + # if (neighbor, 0, overlap) not in nodes[k].end: + # nodes[k].end.add((neighbor, 0, overlap)) + # + # if (k, 1, overlap) not in nodes[neighbor].start: + # nodes[neighbor].start.add((k, 1, overlap)) + + elif not from_start and to_end: # from end to end L x + y - + edge_counts[(k, 1, neighbor, 1, overlap)] = count + # if (neighbor, 1, overlap) not in nodes[k].end: + # nodes[k].end.add((neighbor, 1, overlap)) + # + # if (k, 1, overlap) not in nodes[neighbor].end: + # nodes[neighbor].end.add((k, 1, overlap)) + return edge_counts diff --git a/GFASubgraph/main.py b/GFASubgraph/main.py index 5671247..c97427d 100755 --- a/GFASubgraph/main.py +++ b/GFASubgraph/main.py @@ -2,9 +2,10 @@ import random import logging import pdb +import pickle import multiprocessing as mp from GFASubgraph.main_helpers import * -from GFASubgraph.graph_io import write_gfa +from GFASubgraph.graph_io import write_gfa, get_edges_counts from GFASubgraph.Graph import Graph from GFASubgraph.connected_components import all_components from GFASubgraph.x11_colors import color_list @@ -31,7 +32,7 @@ help='Command for outputting each connected component in a separate GFA file') comp_parser.add_argument("--output_dir", dest="output_dir", metavar="OUTPUT_DIR", - type=str, default=".", help="Output neighborhood file") + type=str, default=".", help="Output neighborhood file") comp_parser.add_argument("-n", "--n-components", dest="n_comps", type=int, default=0, help="If you want to output the n largest components in node size. Default: all") @@ -47,7 +48,7 @@ default=None, help="Give the starting node(s) for neighborhood extraction") bfs_parser.add_argument("--cores", dest="cores", default=1, type=int, - help="number of threads") + help="number of threads") bfs_parser.add_argument("--neighborhood_size", dest="bfs_len", metavar="SIZE", default=100, type=int, help="With -s --start option, size of neighborhood to extract. Default: 100") @@ -57,8 +58,8 @@ ########################## Alignment subgraph ############################### -alignment_subgraph = subparsers.add_parser('alignment_subgraph', - help='Command for outputting each connected component in a separate GFA file') +alignment_subgraph = subparsers.add_parser('alignment_subgraph', help='Command for outputting each ' + 'connected component in a separate GFA file') alignment_subgraph.add_argument("--input_gaf", dest="input_gaf", metavar="INPUT_GAF", type=str, default=None, help="The input alignment gaf file") @@ -74,6 +75,28 @@ alignment_subgraph.add_argument("--prefix", dest="prefix", type=str, default="alignment_subgraph", help="prefix for the output files") + +########################## Remove low coverage edges ############################### +low_cov_edges = subparsers.add_parser('low_cov_edges', + help='Command for outputting each connected component in a separate GFA file') + + +low_cov_edges.add_argument("--coverage_cutoff", dest="edge_cov_cutoff", default=1, + type=int, help="the neighborhood size around each node in the alignment path, default: 0") + + +low_cov_edges.add_argument("--neighborhood_size", dest="bfs_len", metavar="SIZE", default=50, + type=int, help="the neighborhood size around low-coverage nodes default: 50") + + +low_cov_edges.add_argument("--nodes_info", dest="nodes_info", type=str, + default=None, help="Two column TSV of node ids and their chromosome") + +low_cov_edges.add_argument("--output_edges", dest="out_edges", + type=str, default="problem_edges.pickle", + help="pickled dict with problem edges and the chromosomes around them") + + args = parser.parse_args() log_file = args.log_file @@ -174,7 +197,6 @@ def main(): processes = [] queue = mp.Queue() - n_sentinals = 0 # leftovers for p in processes: @@ -211,12 +233,12 @@ def main(): else: to_extract = [] with open(args.alignment_list, "r") as infile: - for l in infile: - l = l.strip() - if l not in alignments: - logging.warning(f"The alignment {l} in {args.alignment_list} is not present in {args.input_gaf}") + for line in infile: + line = line.strip() + if line not in alignments: + logging.warning(f"The alignment {line} in {args.alignment_list} is not present in {args.input_gaf}") else: - to_extract.append(l.strip()) + to_extract.append(line.strip()) final_nodes = set() # finished preparing and now need to go through each alignment @@ -225,21 +247,59 @@ def main(): # also outputting a CSV file with node names and coloring based on each alignment logging.info("Extracting the alignments...") with open(args.prefix + "colors.csv", "w") as out_csv: - out_csv.write("Name,Colour,Alignment Name\n") + out_csv.write("Name,Colour,Alignment Name,Alignment length, Alignment ID, Alignment coordinates\n") for align_name in to_extract: color = random.choice(color_list) logging.info(f"Extracting the alignment {align_name} and will be coloured {color}") - align_path = alignments[align_name] + align_path, align_size, align_ident = alignments[align_name] extract_alignments(align_path, graph, args.bfs_len, final_nodes) # adds to final_nodes for n in align_path: # I am only coloring the path - out_csv.write(f"{n},{color},{align_name}\n") + out_csv.write(f"{n},{color},{align_name},{align_size},{align_ident}\n") out_graph = args.prefix + 'subgraph.gfa' logging.info(f"Writing the output graph {out_graph}") graph.write_graph(set_of_nodes=final_nodes, output_file=out_graph) +############################################## Edge coverage + + if args.subcommands == "low_cov_edges": + if not args.nodes_info: + error("You need to give the nodes info TSV, first column is node " + "ids and second is chr, no header", args.log_file) + # it's a hacky section here but this still under testing, whether I need this feature or not + # adding the chromosome to the nodes + with open(args.nodes_info, "r") as infile: + for line in infile: + line = line.strip().split() + if line[0] in graph: + graph[line[0]].chromosome = line[1] + else: + logging.warning(f"The nodes {line[0]} in the TSV is not present in the graph") + + logging.info(f"Loading edge counts from {args.in_graph}") + edges = get_edges_counts(args.in_graph) + logging.info("Checking for low cov edges and their neighborhood") + low_counts = [] + for e, c in edges.items(): + if c <= args.edge_cov_cutoff: + low_counts.append(e) + logging.info(f"There were {len(low_counts)} edges with counts equal or less than threshold of " + f"{args.edge_cov_cutoff}") + # problem_edges are the low cov edges with two difference chromosomes around them + # the value (tmp) is a dict of chromosomes and list of edges for that chromosome + problem_edges = dict() + for e in edges: + tmp = check_candidate_edges(graph, e, args.bfs_len) + if tmp: + problem_edges[e] = tmp + logging.info(f"There were {len(problem_edges)} edges with two different chromosomes around them") + with open(args.out_edges, "wb") as outfile: + logging.info("Pickling the info") + pickle.dump(problem_edges, outfile) + + if __name__ == "__main__": main() diff --git a/GFASubgraph/main_helpers.py b/GFASubgraph/main_helpers.py index fa6d592..554db50 100644 --- a/GFASubgraph/main_helpers.py +++ b/GFASubgraph/main_helpers.py @@ -2,6 +2,7 @@ import sys import os from GFASubgraph.bfs import bfs +from collections import defaultdict def bfs_queue(graph, n, length, queue): @@ -36,11 +37,16 @@ def read_gaf(in_gaf, log_file): for l in infile: l = l.strip().split("\t") name = l[0] + where_id = [idx for idx in range(len(l)) if "id:f" in l[idx]][0] + l_length = int(l[3]) - int(l[2]) # alignment length + l_id = l[where_id].split(":")[-1] + coordinates = f"{l[2]}_{l[3]}" + name = name + "_" + coordinates path = l[5] if path[0] in {"<", ">"}: # making sure it is where the path is path = path[1:].replace(">", ",") # removing the first > or < path = path.replace("<", ",") - alignments[name] = path.split(",") + alignments[name] = [path.split(","), l_length, l_id] return alignments @@ -57,3 +63,20 @@ def extract_alignments(alignment_nodes, graph, n_size, final_nodes): set_of_nodes = bfs(graph, n, n_size) for n_id in set_of_nodes: final_nodes.add(n_id) # if the node already exists won't be added twice + + +def check_candidate_edges(graph, edge, n_size): + # as I'm doing BFS, doesn't matter from which edge I start + # both nodes will be included + + # edge is (n1, side1, n2, side2, overlap) + start_node = edge[0] + neighborhood = bfs(graph, start_node, n_size) + chrom_set = defaultdict(list) + for n in neighborhood: + if graph[n].chromosome: + chrom_set[graph[n].chromosome].append(n) + if len(chrom_set.keys()) == 1: + return None + else: + return chrom_set diff --git a/GFASubgraph/x11_colors.py b/GFASubgraph/x11_colors.py index 6549ac6..34f5fa1 100644 --- a/GFASubgraph/x11_colors.py +++ b/GFASubgraph/x11_colors.py @@ -1,67 +1 @@ -color_list = ["snow", "ghost white", "GhostWhite", "white smoke", "WhiteSmoke", "gainsboro", "floral white", - "FloralWhite", "old lace", "OldLace", "linen", "antique white", "AntiqueWhite", "papaya whip", - "PapayaWhip", "blanched almond", "BlanchedAlmond", "bisque", "peach puff", "PeachPuff", "navajo white", - "NavajoWhite", "moccasin", "cornsilk", "ivory", "lemon chiffon", "LemonChiffon", "seashell", "honeydew", - "mint cream", "MintCream", "azure", "alice blue", "AliceBlue", "lavender", "lavender blush", - "LavenderBlush", "misty rose", "MistyRose", "white", "black", "dark slate gray", "DarkSlateGray", - "dark slate grey", "DarkSlateGrey", "dim gray", "DimGray", "dim grey", "DimGrey", "slate gray", - "SlateGray", "slate grey", "SlateGrey", "light slate gray", "LightSlateGray", "light slate grey", - "LightSlateGrey", "gray", "grey", "light grey", "LightGrey", "light gray", "LightGray", "midnight blue", - "MidnightBlue", "navy", "navy blue", "NavyBlue", "cornflower blue", "CornflowerBlue", "dark slate blue", - "DarkSlateBlue", "slate blue", "SlateBlue", "medium slate blue", "MediumSlateBlue", "light slate blue", - "LightSlateBlue", "medium blue", "MediumBlue", "royal blue", "RoyalBlue", "blue", "dodger blue", - "DodgerBlue", "deep sky blue", "DeepSkyBlue", "sky blue", "SkyBlue", "light sky blue", "LightSkyBlue", - "steel blue", "SteelBlue", "light steel blue", "LightSteelBlue", "light blue", "LightBlue", - "powder blue", "PowderBlue", "pale turquoise", "PaleTurquoise", "dark turquoise", "DarkTurquoise", - "medium turquoise", "MediumTurquoise", "turquoise", "cyan", "light cyan", "LightCyan", "cadet blue", - "CadetBlue", "medium aquamarine", "MediumAquamarine", "aquamarine", "dark green", "DarkGreen", - "dark olive green", "DarkOliveGreen", "dark sea green", "DarkSeaGreen", "sea green", "SeaGreen", - "medium sea green", "MediumSeaGreen", "light sea green", "LightSeaGreen", "pale green", "PaleGreen", - "spring green", "SpringGreen", "lawn green", "LawnGreen", "green", "chartreuse", "medium spring green", - "MediumSpringGreen", "green yellow", "GreenYellow", "lime green", "LimeGreen", "yellow green", - "YellowGreen", "forest green", "ForestGreen", "olive drab", "OliveDrab", "dark khaki", "DarkKhaki", - "khaki", "pale goldenrod", "PaleGoldenrod", "light goldenrod yellow", "LightGoldenrodYellow", - "light yellow", "LightYellow", "yellow", "gold", "light goldenrod", "LightGoldenrod", "goldenrod", - "dark goldenrod", "DarkGoldenrod", "rosy brown", "RosyBrown", "indian red", "IndianRed", - "saddle brown", "SaddleBrown", "sienna", "peru", "burlywood", "beige", "wheat", "sandy brown", - "SandyBrown", "tan", "chocolate", "firebrick", "brown", "dark salmon", "DarkSalmon", "salmon", - "light salmon", "LightSalmon", "orange", "dark orange", "DarkOrange", "coral", "light coral", - "LightCoral", "tomato", "orange red", "OrangeRed", "red", "hot pink", "HotPink", "deep pink", - "DeepPink", "pink", "light pink", "LightPink", "pale violet red", "PaleVioletRed", "maroon", - "medium violet red", "MediumVioletRed", "violet red", "VioletRed", "magenta", "violet", "plum", - "orchid", "medium orchid", "MediumOrchid", "dark orchid", "DarkOrchid", "dark violet", "DarkViolet", - "blue violet", "BlueViolet", "purple", "medium purple", "MediumPurple", "thistle", "snow1", "snow2", - "snow3", "snow4", "seashell1", "seashell2", "seashell3", "seashell4", "AntiqueWhite1", "AntiqueWhite2", - "AntiqueWhite3", "AntiqueWhite4", "bisque1", "bisque2", "bisque3", "bisque4", "PeachPuff1", "PeachPuff2", - "PeachPuff3", "PeachPuff4", "NavajoWhite1", "NavajoWhite2", "NavajoWhite3", "", "LemonChiffon1", - "LemonChiffon2", "LemonChiffon3", "LemonChiffon4", "cornsilk1", "cornsilk2", "cornsilk3", "cornsilk4", - "ivory1", "ivory2", "ivory3", "ivory4", "honeydew1", "honeydew2", "honeydew3", "honeydew4", - "LavenderBlush1", "LavenderBlush2", "LavenderBlush3", "LavenderBlush4", "MistyRose1", "MistyRose2", - "MistyRose3", "MistyRose4", "azure1", "SlateBlue1", "SlateBlue2", - "SlateBlue3", "SlateBlue4", "RoyalBlue1", "RoyalBlue2", "RoyalBlue3", "RoyalBlue4", "blue1", "blue2", - "blue3", "blue4", "DodgerBlue1", "DodgerBlue2", "DodgerBlue3", "DodgerBlue4", "SteelBlue1", "SteelBlue2", - "SteelBlue3", "SteelBlue4", "DeepSkyBlue1", "DeepSkyBlue2", "DeepSkyBlue3", "DeepSkyBlue4", "SkyBlue1", - "SkyBlue2", "SkyBlue3", "SkyBlue4", "LightSkyBlue1", "LightSkyBlue2", "LightSkyBlue3", "LightSkyBlue4", - "SlateGray1", "SlateGray2", "SlateGray3", "SlateGray4", "LightSteelBlue1", "LightSteelBlue2", - "LightSteelBlue3", "LightSteelBlue4", "LightBlue1", "LightBlue2", "LightBlue3", "LightBlue4", - "LightCyan1", "LightCyan2", "LightCyan3", "LightCyan4", "PaleTurquoise1", "PaleTurquoise2", - "PaleTurquoise3", "PaleTurquoise4", "CadetBlue1", "CadetBlue2", "CadetBlue3", "CadetBlue4", - "turquoise1", "turquoise2", "turquoise3", "turquoise4", "cyan1", "cyan2", "cyan3", "cyan4", - "DarkSlateGray1", "DarkSlateGray2", "DarkSlateGray3", "DarkSlateGray4", "aquamarine1", "aquamarine2", - "aquamarine3", "aquamarine4", "DarkSeaGreen1", "DarkSeaGreen2", "DarkSeaGreen3", "DarkSeaGreen4", - "SeaGreen1", "SeaGreen2", "SeaGreen3", "", "PaleGreen1", "PaleGreen2", "PaleGreen3", "", "SpringGreen1", - "SpringGreen2", "SpringGreen3", "DarkOliveGreen1", "DarkOliveGreen2", "khaki1", "khaki2", "khaki3", - "LightGoldenrod1", "LightGoldenrod2", "LightGoldenrod3", "", "LightYellow1", "LightYellow2", - "LightYellow3", "LightYellow4", "RosyBrown1", "RosyBrown2", "RosyBrown3", "RosyBrown4", "IndianRed1", - "burlywood1", "burlywood2", "burlywood3", "", "wheat1", "wheat2", "wheat3", "wheat4", - "LightSalmon1", "LightSalmon2", "DebianRed", "DeepPink1", "DeepPink2", "DeepPink3", "HotPink1", - "HotPink2", "HotPink3", "HotPink4", "pink1", "pink2", "pink3", "pink4", "LightPink1", "LightPink2", - "LightPink3", "LightPink4", "PaleVioletRed1", "PaleVioletRed2", "PaleVioletRed3", "", "maroon1", - "maroon2", "maroon3", "", "VioletRed1", "VioletRed2", "VioletRed3", "", "magenta1", "magenta2", - "magenta3", "magenta4", "orchid1", "orchid2", "orchid3", "orchid4", "plum1", "plum2", "plum3", "plum4", - "MediumOrchid1", "MediumOrchid2", "MediumOrchid3", "MediumOrchid4", "DarkOrchid1", "DarkOrchid2", - "DarkOrchid3", "DarkOrchid4", "purple1", "purple2", "purple3", "purple4", "MediumPurple1", - "MediumPurple2", "MediumPurple3", "MediumPurple4", "thistle1", "thistle2", "thistle3", "thistle4", - "gray100", - "dark grey", "DarkGrey", "dark gray", "DarkGray", "dark blue", "DarkBlue", "dark cyan", "DarkCyan", - "dark magenta", "DarkMagenta", "dark red", "DarkRed", "light green", "LightGreen"] +color_list = ['snow', 'ghost white', 'GhostWhite', 'white smoke', 'WhiteSmoke', 'gainsboro', 'floral white', 'FloralWhite', 'old lace', 'OldLace', 'linen', 'antique white', 'AntiqueWhite', 'papaya whip', 'PapayaWhip', 'blanched almond', 'BlanchedAlmond', 'bisque', 'peach puff', 'PeachPuff', 'navajo white', 'NavajoWhite', 'moccasin', 'cornsilk', 'ivory', 'lemon chiffon', 'LemonChiffon', 'seashell', 'honeydew', 'mint cream', 'MintCream', 'azure', 'alice blue', 'AliceBlue', 'lavender', 'lavender blush', 'LavenderBlush', 'misty rose', 'MistyRose', 'white', 'black', 'dark slate gray', 'DarkSlateGray', 'dark slate grey', 'DarkSlateGrey', 'dim gray', 'DimGray', 'dim grey', 'DimGrey', 'slate gray', 'SlateGray', 'slate grey', 'SlateGrey', 'light slate gray', 'LightSlateGray', 'light slate grey', 'LightSlateGrey', 'gray', 'grey', 'light grey', 'LightGrey', 'light gray', 'LightGray', 'midnight blue', 'MidnightBlue', 'navy', 'navy blue', 'NavyBlue', 'cornflower blue', 'CornflowerBlue', 'dark slate blue', 'DarkSlateBlue', 'slate blue', 'SlateBlue', 'medium slate blue', 'MediumSlateBlue', 'light slate blue', 'LightSlateBlue', 'medium blue', 'MediumBlue', 'royal blue', 'RoyalBlue', 'blue', 'dodger blue', 'DodgerBlue', 'deep sky blue', 'DeepSkyBlue', 'sky blue', 'SkyBlue', 'light sky blue', 'LightSkyBlue', 'steel blue', 'SteelBlue', 'light steel blue', 'LightSteelBlue', 'light blue', 'LightBlue', 'powder blue', 'PowderBlue', 'pale turquoise', 'PaleTurquoise', 'dark turquoise', 'DarkTurquoise', 'medium turquoise', 'MediumTurquoise', 'turquoise', 'cyan', 'light cyan', 'LightCyan', 'cadet blue', 'CadetBlue', 'medium aquamarine', 'MediumAquamarine', 'aquamarine', 'dark green', 'DarkGreen', 'dark olive green', 'DarkOliveGreen', 'dark sea green', 'DarkSeaGreen', 'sea green', 'SeaGreen', 'medium sea green', 'MediumSeaGreen', 'light sea green', 'LightSeaGreen', 'pale green', 'PaleGreen', 'spring green', 'SpringGreen', 'lawn green', 'LawnGreen', 'green', 'chartreuse', 'medium spring green', 'MediumSpringGreen', 'green yellow', 'GreenYellow', 'lime green', 'LimeGreen', 'yellow green', 'YellowGreen', 'forest green', 'ForestGreen', 'olive drab', 'OliveDrab', 'dark khaki', 'DarkKhaki', 'khaki', 'pale goldenrod', 'PaleGoldenrod', 'light goldenrod yellow', 'LightGoldenrodYellow', 'light yellow', 'LightYellow', 'yellow', 'gold', 'light goldenrod', 'LightGoldenrod', 'goldenrod', 'dark goldenrod', 'DarkGoldenrod', 'rosy brown', 'RosyBrown', 'indian red', 'IndianRed', 'saddle brown', 'SaddleBrown', 'sienna', 'peru', 'burlywood', 'beige', 'wheat', 'sandy brown', 'SandyBrown', 'tan', 'chocolate', 'firebrick', 'brown', 'dark salmon', 'DarkSalmon', 'salmon', 'light salmon', 'LightSalmon', 'orange', 'dark orange', 'DarkOrange', 'coral', 'light coral', 'LightCoral', 'tomato', 'orange red', 'OrangeRed', 'red', 'hot pink', 'HotPink', 'deep pink', 'DeepPink', 'pink', 'light pink', 'LightPink', 'pale violet red', 'PaleVioletRed', 'maroon', 'medium violet red', 'MediumVioletRed', 'violet red', 'VioletRed', 'magenta', 'violet', 'plum', 'orchid', 'medium orchid', 'MediumOrchid', 'dark orchid', 'DarkOrchid', 'dark violet', 'DarkViolet', 'blue violet', 'BlueViolet', 'purple', 'medium purple', 'MediumPurple', 'thistle', 'snow1', 'snow2', 'snow3', 'snow4', 'seashell1', 'seashell2', 'seashell3', 'seashell4', 'AntiqueWhite1', 'AntiqueWhite2', 'AntiqueWhite3', 'AntiqueWhite4', 'bisque1', 'bisque2', 'bisque3', 'bisque4', 'PeachPuff1', 'PeachPuff2', 'PeachPuff3', 'PeachPuff4', 'NavajoWhite1', 'NavajoWhite2', 'NavajoWhite3', 'LemonChiffon1', 'LemonChiffon2', 'LemonChiffon3', 'LemonChiffon4', 'cornsilk1', 'cornsilk2', 'cornsilk3', 'cornsilk4', 'ivory1', 'ivory2', 'ivory3', 'ivory4', 'honeydew1', 'honeydew2', 'honeydew3', 'honeydew4', 'LavenderBlush1', 'LavenderBlush2', 'LavenderBlush3', 'LavenderBlush4', 'MistyRose1', 'MistyRose2', 'MistyRose3', 'MistyRose4', 'azure1', 'SlateBlue1', 'SlateBlue2', 'SlateBlue3', 'SlateBlue4', 'RoyalBlue1', 'RoyalBlue2', 'RoyalBlue3', 'RoyalBlue4', 'blue1', 'blue2', 'blue3', 'blue4', 'DodgerBlue1', 'DodgerBlue2', 'DodgerBlue3', 'DodgerBlue4', 'SteelBlue1', 'SteelBlue2', 'SteelBlue3', 'SteelBlue4', 'DeepSkyBlue1', 'DeepSkyBlue2', 'DeepSkyBlue3', 'DeepSkyBlue4', 'SkyBlue1', 'SkyBlue2', 'SkyBlue3', 'SkyBlue4', 'LightSkyBlue1', 'LightSkyBlue2', 'LightSkyBlue3', 'LightSkyBlue4', 'SlateGray1', 'SlateGray2', 'SlateGray3', 'SlateGray4', 'LightSteelBlue1', 'LightSteelBlue2', 'LightSteelBlue3', 'LightSteelBlue4', 'LightBlue1', 'LightBlue2', 'LightBlue3', 'LightBlue4', 'LightCyan1', 'LightCyan2', 'LightCyan3', 'LightCyan4', 'PaleTurquoise1', 'PaleTurquoise2', 'PaleTurquoise3', 'PaleTurquoise4', 'CadetBlue1', 'CadetBlue2', 'CadetBlue3', 'CadetBlue4', 'turquoise1', 'turquoise2', 'turquoise3', 'turquoise4', 'cyan1', 'cyan2', 'cyan3', 'cyan4', 'DarkSlateGray1', 'DarkSlateGray2', 'DarkSlateGray3', 'DarkSlateGray4', 'aquamarine1', 'aquamarine2', 'aquamarine3', 'aquamarine4', 'DarkSeaGreen1', 'DarkSeaGreen2', 'DarkSeaGreen3', 'DarkSeaGreen4', 'SeaGreen1', 'SeaGreen2', 'SeaGreen3', 'PaleGreen1', 'PaleGreen2', 'PaleGreen3', 'SpringGreen1', 'SpringGreen2', 'SpringGreen3', 'DarkOliveGreen1', 'DarkOliveGreen2', 'khaki1', 'khaki2', 'khaki3', 'LightGoldenrod1', 'LightGoldenrod2', 'LightGoldenrod3', 'LightYellow1', 'LightYellow2', 'LightYellow3', 'LightYellow4', 'RosyBrown1', 'RosyBrown2', 'RosyBrown3', 'RosyBrown4', 'IndianRed1', 'burlywood1', 'burlywood2', 'burlywood3', 'wheat1', 'wheat2', 'wheat3', 'wheat4', 'LightSalmon1', 'LightSalmon2', 'DebianRed', 'DeepPink1', 'DeepPink2', 'DeepPink3', 'HotPink1', 'HotPink2', 'HotPink3', 'HotPink4', 'pink1', 'pink2', 'pink3', 'pink4', 'LightPink1', 'LightPink2', 'LightPink3', 'LightPink4', 'PaleVioletRed1', 'PaleVioletRed2', 'PaleVioletRed3', 'maroon1', 'maroon2', 'maroon3', 'VioletRed1', 'VioletRed2', 'VioletRed3', 'magenta1', 'magenta2', 'magenta3', 'magenta4', 'orchid1', 'orchid2', 'orchid3', 'orchid4', 'plum1', 'plum2', 'plum3', 'plum4', 'MediumOrchid1', 'MediumOrchid2', 'MediumOrchid3', 'MediumOrchid4', 'DarkOrchid1', 'DarkOrchid2', 'DarkOrchid3', 'DarkOrchid4', 'purple1', 'purple2', 'purple3', 'purple4', 'MediumPurple1', 'MediumPurple2', 'MediumPurple3', 'MediumPurple4', 'thistle1', 'thistle2', 'thistle3', 'thistle4', 'gray100', 'dark grey', 'DarkGrey', 'dark gray', 'DarkGray', 'dark blue', 'DarkBlue', 'dark cyan', 'DarkCyan', 'dark magenta', 'DarkMagenta', 'dark red', 'DarkRed', 'light green', 'LightGreen']