1717 "rnaseq" : "Bulk-Transcriptomics" ,
1818 "rna seq" : "Bulk-Transcriptomics" ,
1919 "rna seq." : "Bulk-Transcriptomics" ,
20+
2021 # Single-cell gene expression
2122 "sc gex" : "scRNA-Seq" ,
2223 "single cell rna-seq" : "scRNA-Seq" ,
2324 "scrna-seq" : "scRNA-Seq" ,
2425 "sc rna-seq" : "scRNA-Seq" ,
26+
2527 # ATAC-seq
2628 "bulk atacseq" : "Bulk-Epigenetics" ,
2729 "bulk atac-seq" : "Bulk-Epigenetics" ,
2830 "sc atac" : "scATAC-Seq" ,
2931 "sc atac-seq" : "scATAC-Seq" ,
32+
3033 # Multiome and others
3134 "10x multiome" : "10x Multiome" ,
3235 "resolveome" : "ResolveOME" ,
36+
3337 # Spatial platforms
3438 "spatial" : "Spatial" ,
3539 "visium" : "10x Visium" ,
3640 "10x visium" : "10x Visium" ,
3741 "xenium" : "10x Xenium" ,
3842 "xeniums" : "10x Xenium" ,
3943 "10x xenium" : "10x Xenium" ,
44+
4045 # Misc
4146 "mgx" : "Metagenomics" ,
4247 "workflow dev" : "Workflow development" ,
43- "workflow development" : "Workflow development" ,
44- "workflows" : "Workflow development" ,
45- "development" : "Workflow development" ,
48+ "workflow development" : "Workflow development" ,
49+ "workflows" : "Workflow development" ,
50+ "development" : "Workflow development" ,
4651 "other" : "Other" ,
4752}
4853
49- # Color palette for modalities (GitHub-style colors)
50- COLORS : Dict [str , str ] = {
51- "Bulk-Transcriptomics" : "#e34c26" ,
52- "scRNA-Seq" : "#3572A5" ,
53- "Bulk-Epigenetics" : "#178600" ,
54- "scATAC-Seq" : "#89e051" ,
55- "10x Multiome" : "#f1e05a" ,
56- "ResolveOME" : "#b07219" ,
57- "Spatial" : "#555555" ,
58- "10x Visium" : "#4F5D95" ,
59- "10x Xenium" : "#DA5B0B" ,
60- "Metagenomics" : "#701516" ,
61- "Workflow development" : "#384d54" ,
62- "Other" : "#cccccc" ,
63- }
64-
65-
6654def normalize (modality : str ) -> str :
6755 m = modality .strip ()
6856 if not m :
6957 return ""
7058 key = m .lower ()
7159 return ALIASES .get (key , m )
7260
73-
7461def extract_modalities_from_markdown (md_text : str ) -> List [str ]:
7562 modalities : List [str ] = []
7663 in_counts_section = False
64+
7765 for line in md_text .splitlines ():
7866 # Skip the generated counts section entirely
7967 if START in line :
@@ -84,19 +72,23 @@ def extract_modalities_from_markdown(md_text: str) -> List[str]:
8472 continue
8573 if in_counts_section :
8674 continue
75+
8776 if not line .startswith ("|" ):
8877 continue
8978 if re .match (r"^\|\s*Project\s*\|" , line ):
9079 continue
9180 if re .match (r"^\|\s*-+\s*\|" , line ):
9281 continue
82+
9383 cells = [c .strip () for c in line .split ("|" )]
9484 # Expect at least 4 cells: leading empty, Project, Modality, Repo/Count, trailing empty
9585 if len (cells ) < 4 :
9686 continue
87+
9788 modality_cell = cells [2 ] # Modality is the second visible column
9889 if not modality_cell :
9990 continue
91+
10092 parts = re .split (r"\s*,\s*" , modality_cell )
10193 for p in parts :
10294 n = normalize (p )
@@ -105,84 +97,7 @@ def extract_modalities_from_markdown(md_text: str) -> List[str]:
10597 return modalities
10698
10799
108- def get_color (modality : str ) -> str :
109- """Get color for a modality, or generate a default one."""
110- return COLORS .get (modality , "#cccccc" )
111-
112-
113- def build_badge_svg (counts : Counter ) -> str :
114- """Build an SVG badge similar to GitHub's language bar."""
115- if not counts :
116- return ""
117-
118- total = sum (counts .values ())
119- rows : List [Tuple [str , int ]] = sorted (
120- counts .items (), key = lambda x : (- x [1 ], x [0 ].lower ())
121- )
122-
123- # SVG dimensions
124- width = 600
125- bar_height = 8
126- legend_item_height = 25
127- legend_height = len (rows ) * legend_item_height
128- total_height = bar_height + 20 + legend_height
129-
130- # Build bar segments
131- x_pos = 0
132- bar_rects = []
133- for modality , count in rows :
134- percentage = (count / total ) * 100
135- segment_width = (count / total ) * width
136- color = get_color (modality )
137- bar_rects .append (
138- f'<rect x="{ x_pos :.2f} " y="0" width="{ segment_width :.2f} " height="{ bar_height } " '
139- f'fill="{ color } "><title>{ modality } : { count } ({ percentage :.1f} %)</title></rect>'
140- )
141- x_pos += segment_width
142-
143- # Build legend
144- legend_items = []
145- y_pos = bar_height + 20
146- for modality , count in rows :
147- percentage = (count / total ) * 100
148- color = get_color (modality )
149- legend_items .append (
150- f'<circle cx="6" cy="{ y_pos } " r="5" fill="{ color } "/>'
151- )
152- legend_items .append (
153- f'<text x="18" y="{ y_pos + 4 } " class="legend-text">{ modality } </text>'
154- )
155- legend_items .append (
156- f'<text x="{ width - 10 } " y="{ y_pos + 4 } " class="legend-percent" text-anchor="end">'
157- f'{ percentage :.1f} % ({ count } )</text>'
158- )
159- y_pos += legend_item_height
160-
161- svg = f'''<svg width="{ width } " height="{ total_height } " xmlns="http://www.w3.org/2000/svg">
162- <style>
163- .legend-text {{
164- font: 12px -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
165- fill: #24292f;
166- }}
167- .legend-percent {{
168- font: 12px -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
169- fill: #656d76;
170- font-weight: 600;
171- }}
172- </style>
173- <g id="bar">
174- { chr (10 ).join (f" { rect } " for rect in bar_rects )}
175- </g>
176- <g id="legend">
177- { chr (10 ).join (f" { item } " for item in legend_items )}
178- </g>
179- </svg>'''
180-
181- return f'<p align="center">\n { svg } \n </p>'
182-
183-
184100def build_table (counts : Counter ) -> str :
185- """Build a simple markdown table (fallback)."""
186101 rows : List [Tuple [str , int ]] = sorted (
187102 counts .items (), key = lambda x : (- x [1 ], x [0 ].lower ())
188103 )
@@ -194,18 +109,12 @@ def build_table(counts: Counter) -> str:
194109 lines .append (f"| { modality } | { cnt } |" )
195110 return "\n " .join (lines )
196111
197-
198- def upsert_section (md_text : str , badge_svg : str , table_md : str ) -> str :
199- """Insert both badge and table into README."""
112+ def upsert_section (md_text : str , table_md : str ) -> str :
200113 section = (
201114 f"{ START } \n "
202115 f"\n "
203- f"### Modality Distribution\n \n "
204- f"{ badge_svg } \n \n "
205- f"<details>\n "
206- f"<summary>View as table</summary>\n \n "
207- f"{ table_md } \n \n "
208- f"</details>\n "
116+ f"### Modality counts\n \n "
117+ f"{ table_md } \n "
209118 f"\n "
210119 f"{ END } "
211120 )
@@ -219,29 +128,21 @@ def upsert_section(md_text: str, badge_svg: str, table_md: str) -> str:
219128 sep = "\n \n " if not md_text .endswith ("\n " ) else "\n "
220129 return md_text + sep + section + "\n "
221130
222-
223131def main () -> int :
224132 if not README .exists ():
225133 print ("README.md not found at repo root." )
226134 return 1
227-
228135 md_text = README .read_text (encoding = "utf-8" )
229136 modalities = extract_modalities_from_markdown (md_text )
230137 counts = Counter (modalities )
231-
232- badge_svg = build_badge_svg (counts )
233138 table_md = build_table (counts )
234-
235- new_md = upsert_section (md_text , badge_svg , table_md )
236-
139+ new_md = upsert_section (md_text , table_md )
237140 if new_md != md_text :
238141 README .write_text (new_md , encoding = "utf-8" )
239142 print ("README.md updated with modality counts." )
240143 else :
241144 print ("No changes to README.md." )
242-
243145 return 0
244146
245-
246147if __name__ == "__main__" :
247148 raise SystemExit (main ())
0 commit comments