Skip to content

Commit 0366215

Browse files
committed
Support manntype and FL_signature annotations
1 parent d61db26 commit 0366215

4 files changed

Lines changed: 61 additions & 8 deletions

File tree

Binary file not shown.
Binary file not shown.

annotate_bcr/1.0/annotate_glycosylation.py

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,17 @@
1515
num_glycosylation_sites Count of NxS/T motifs in query (x != Pro)
1616
glycosylation_imgt_positions IMGT positions of N residues in query, comma-separated
1717
glycosylation_motifs Corresponding NxS/T triplets in query, comma-separated
18+
glycosylation_imgt_regions IMGT FWR/CDR region of each query site, comma-separated
1819
num_acquired_glycosylation_sites Sites in query absent from germline (SHM-acquired)
1920
acquired_glycosylation_imgt_positions IMGT positions of acquired sites, comma-separated
21+
acquired_glycosylation_imgt_regions IMGT FWR/CDR region of each acquired site, comma-separated
22+
num_acquired_glycosylation_sites_cdr Acquired sites falling in CDR1/CDR2/CDR3
23+
manntype_ags POS/NEG: >=1 acquired site in a CDR (Tatterton et al. 2025
24+
AGS criterion). This is only the sequence-derived half of
25+
manntype classification; combine with a genomic FL signature
26+
(EZB LymphGen subtype or BCL2 translocation) downstream, as
27+
GAMBLR.results::collate_tatterton does for the published
28+
Tatterton cohort, to get the full manntype call.
2029
germline_aa_sequence Germline amino acid sequence used for numbering
2130
num_germline_glycosylation_sites Count of NxS/T motifs in germline
2231
germline_glycosylation_imgt_positions IMGT positions of N residues in germline
@@ -60,6 +69,27 @@ def _imgt_pos_str(pos_tuple):
6069
return f"{num}{ins.strip()}" if ins.strip() else str(num)
6170

6271

72+
# IMGT unique numbering V-domain region boundaries (Lefranc et al.), inclusive.
73+
IMGT_REGION_BOUNDARIES = [
74+
(1, 26, "FWR1"),
75+
(27, 38, "CDR1"),
76+
(39, 55, "FWR2"),
77+
(56, 65, "CDR2"),
78+
(66, 104, "FWR3"),
79+
(105, 117, "CDR3"),
80+
(118, 128, "FWR4"),
81+
]
82+
83+
84+
def _imgt_region(pos_tuple):
85+
"""Map an ANARCI IMGT position (number, insertion_letter) to its FWR/CDR region."""
86+
num, _ = pos_tuple
87+
for lo, hi, name in IMGT_REGION_BOUNDARIES:
88+
if lo <= num <= hi:
89+
return name
90+
return "NA"
91+
92+
6393
def number_with_imgt(aa_seq):
6494
"""
6595
Align aa_seq to IG/TCR germline HMMs with ANARCI using the IMGT scheme.
@@ -75,15 +105,15 @@ def find_glycosylation_sites(numbered):
75105
"""
76106
Scan IMGT-numbered residues for N-linked glycosylation motifs (NxS/T, x != Pro).
77107
78-
Returns [(imgt_position_string, motif_string), ...] for each site.
108+
Returns [(imgt_position_string, motif_string, region_string), ...] for each site.
79109
"""
80110
sites = []
81111
for i in range(len(numbered) - 2):
82-
_, n_aa = numbered[i]
83-
_, x_aa = numbered[i + 1]
84-
_, st_aa = numbered[i + 2]
112+
n_pos, n_aa = numbered[i]
113+
_, x_aa = numbered[i + 1]
114+
_, st_aa = numbered[i + 2]
85115
if n_aa == "N" and x_aa != "P" and st_aa in ("S", "T"):
86-
sites.append((_imgt_pos_str(numbered[i][0]), f"N{x_aa}{st_aa}"))
116+
sites.append((_imgt_pos_str(n_pos), f"N{x_aa}{st_aa}", _imgt_region(n_pos)))
87117
return sites
88118

89119

@@ -92,8 +122,13 @@ def classify_sites(query_sites, germline_sites):
92122
Return the subset of query_sites whose IMGT position is absent in germline_sites.
93123
These are SHM-acquired glycosylation sites.
94124
"""
95-
germline_positions = {pos for pos, _ in germline_sites}
96-
return [(pos, motif) for pos, motif in query_sites if pos not in germline_positions]
125+
germline_positions = {pos for pos, _, _ in germline_sites}
126+
return [site for site in query_sites if site[0] not in germline_positions]
127+
128+
129+
def sites_in_cdr(sites):
130+
"""Return the subset of sites (as returned by find_glycosylation_sites) in a CDR."""
131+
return [site for site in sites if site[2].startswith("CDR")]
97132

98133

99134
def _make_lookup_fn(seq_lookup):
@@ -139,8 +174,12 @@ def read_fasta(path):
139174
"num_glycosylation_sites",
140175
"glycosylation_imgt_positions",
141176
"glycosylation_motifs",
177+
"glycosylation_imgt_regions",
142178
"num_acquired_glycosylation_sites",
143179
"acquired_glycosylation_imgt_positions",
180+
"acquired_glycosylation_imgt_regions",
181+
"num_acquired_glycosylation_sites_cdr",
182+
"manntype_ags",
144183
"germline_aa_sequence",
145184
"num_germline_glycosylation_sites",
146185
"germline_glycosylation_imgt_positions",
@@ -154,6 +193,9 @@ def _sites_str(sites):
154193
def _motifs_str(sites):
155194
return ",".join(s[1] for s in sites) if sites else "NA"
156195

196+
def _regions_str(sites):
197+
return ",".join(s[2] for s in sites) if sites else "NA"
198+
157199

158200
def main():
159201
parser = argparse.ArgumentParser()
@@ -182,8 +224,12 @@ def main():
182224
"num_glycosylation_sites": 0,
183225
"glycosylation_imgt_positions": "NA",
184226
"glycosylation_motifs": "NA",
227+
"glycosylation_imgt_regions": "NA",
185228
"num_acquired_glycosylation_sites": 0,
186229
"acquired_glycosylation_imgt_positions": "NA",
230+
"acquired_glycosylation_imgt_regions": "NA",
231+
"num_acquired_glycosylation_sites_cdr": 0,
232+
"manntype_ags": "NA",
187233
"germline_aa_sequence": "NA",
188234
"num_germline_glycosylation_sites": 0,
189235
"germline_glycosylation_imgt_positions": "NA",
@@ -200,15 +246,20 @@ def main():
200246
query_sites = find_glycosylation_sites(numbered) if numbered else []
201247
germline_sites = find_glycosylation_sites(gl_numbered) if gl_numbered else []
202248
acquired_sites = classify_sites(query_sites, germline_sites)
249+
acquired_cdr_sites = sites_in_cdr(acquired_sites)
203250

204251
writer.writerow({
205252
"sequence_id": seq_id,
206253
"aa_sequence": aa_seq,
207254
"num_glycosylation_sites": len(query_sites),
208255
"glycosylation_imgt_positions": _sites_str(query_sites),
209256
"glycosylation_motifs": _motifs_str(query_sites),
257+
"glycosylation_imgt_regions": _regions_str(query_sites),
210258
"num_acquired_glycosylation_sites": len(acquired_sites),
211259
"acquired_glycosylation_imgt_positions": _sites_str(acquired_sites),
260+
"acquired_glycosylation_imgt_regions": _regions_str(acquired_sites),
261+
"num_acquired_glycosylation_sites_cdr": len(acquired_cdr_sites),
262+
"manntype_ags": "POS" if acquired_cdr_sites else "NEG",
212263
"germline_aa_sequence": gl_seq if gl_seq else "NA",
213264
"num_germline_glycosylation_sites": len(germline_sites),
214265
"germline_glycosylation_imgt_positions": _sites_str(germline_sites),

annotate_bcr/1.0/merge_annotation_tsv.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,10 @@ def present(*names):
9090

9191
# --- Glycosylation ---
9292
order += present("num_glycosylation_sites", "glycosylation_imgt_positions",
93-
"glycosylation_motifs",
93+
"glycosylation_motifs", "glycosylation_imgt_regions",
9494
"num_acquired_glycosylation_sites", "acquired_glycosylation_imgt_positions",
95+
"acquired_glycosylation_imgt_regions", "num_acquired_glycosylation_sites_cdr",
96+
"manntype_ags",
9597
"germline_aa_sequence",
9698
"num_germline_glycosylation_sites", "germline_glycosylation_imgt_positions",
9799
"germline_glycosylation_motifs")

0 commit comments

Comments
 (0)