@datagrok/proteomics 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -3
- package/CLAUDE.md +178 -0
- package/README.md +195 -3
- package/detectors.js +152 -9
- package/dist/package-test.js +1 -1744
- package/dist/package-test.js.map +1 -1
- package/dist/package.js +1 -146
- package/dist/package.js.map +1 -1
- package/docs/personas-and-capabilities.md +165 -0
- package/files/demo/README.md +264 -0
- package/files/demo/cptac-spike-in.txt +1571 -0
- package/files/demo/enrichment-demo.csv +120 -0
- package/files/demo/fragpipe-smoke-test.tsv +11 -0
- package/files/demo/proteinGroups.txt +28 -0
- package/files/demo/spectronaut-hye-candidates.tsv +94 -0
- package/files/demo/spectronaut-hye-demo.tsv +8761 -0
- package/files/demo/spectronaut-hye-mix.tsv +8761 -0
- package/files/demo/spectronaut-hye-precursor-golden.json +938 -0
- package/files/demo/spectronaut-hye-precursor-golden.tsv +235 -0
- package/files/demo/spectronaut-hye-precursor.tsv +493 -0
- package/images/enrichment-crosslink.png +0 -0
- package/images/enrichment-term-selected.png +0 -0
- package/images/hero.png +0 -0
- package/images/pipeline.svg +80 -0
- package/package.json +87 -62
- package/scripts/deqms_de.R +60 -0
- package/scripts/limma_de.R +42 -0
- package/scripts/vsn_normalize.R +19 -0
- package/src/analysis/differential-expression.ts +450 -0
- package/src/analysis/enrichment-export.ts +101 -0
- package/src/analysis/enrichment.ts +602 -0
- package/src/analysis/experiment-setup.ts +199 -0
- package/src/analysis/imputation.ts +407 -0
- package/src/analysis/log2-scale.ts +139 -0
- package/src/analysis/normalization.ts +255 -0
- package/src/analysis/pca.ts +254 -0
- package/src/analysis/spc-storage.ts +515 -0
- package/src/analysis/spc.ts +544 -0
- package/src/analysis/subcellular-location.ts +431 -0
- package/src/demo/enrichment-demo.ts +94 -0
- package/src/demo/proteomics-demo.ts +123 -0
- package/src/global.d.ts +15 -0
- package/src/menu.ts +133 -0
- package/src/package-api.ts +136 -14
- package/src/package-test.ts +45 -20
- package/src/package.g.ts +161 -0
- package/src/package.ts +1029 -17
- package/src/panels/protein-focus.ts +63 -0
- package/src/panels/published-analysis-panel.ts +151 -0
- package/src/panels/uniprot-panel.ts +349 -0
- package/src/parsers/fragpipe-parser.ts +200 -0
- package/src/parsers/generic-parser.ts +197 -0
- package/src/parsers/maxquant-parser.ts +162 -0
- package/src/parsers/shared-utils.ts +163 -0
- package/src/parsers/spectronaut-candidates-parser.ts +307 -0
- package/src/parsers/spectronaut-parser.ts +604 -0
- package/src/publishing/assert-published-shape.ts +260 -0
- package/src/publishing/post-open-recovery.ts +104 -0
- package/src/publishing/publish-project.ts +515 -0
- package/src/publishing/publish-settings.ts +59 -0
- package/src/publishing/publish-state.ts +316 -0
- package/src/publishing/share-dialog.ts +171 -0
- package/src/publishing/trim-dataframe.ts +247 -0
- package/src/tests/analysis.ts +658 -0
- package/src/tests/enrichment-export.ts +61 -0
- package/src/tests/enrichment-visualization.ts +340 -0
- package/src/tests/enrichment.ts +224 -0
- package/src/tests/fragpipe-e2e.ts +74 -0
- package/src/tests/fragpipe-parser.ts +147 -0
- package/src/tests/gene-label-resolver.ts +387 -0
- package/src/tests/generic-parser.ts +152 -0
- package/src/tests/group-mean-correlation.ts +139 -0
- package/src/tests/log2-scale.ts +93 -0
- package/src/tests/organisms.ts +56 -0
- package/src/tests/parsers.ts +182 -0
- package/src/tests/publish-roundtrip.ts +584 -0
- package/src/tests/publish-spike.ts +377 -0
- package/src/tests/qc-dashboard.ts +210 -0
- package/src/tests/smart-pathway-filter.ts +193 -0
- package/src/tests/spc-formula-lines-spike.ts +129 -0
- package/src/tests/spc.ts +640 -0
- package/src/tests/spectronaut-candidates-e2e.ts +140 -0
- package/src/tests/spectronaut-candidates-parser.ts +398 -0
- package/src/tests/spectronaut-parser.ts +668 -0
- package/src/tests/subcellular-location.ts +361 -0
- package/src/tests/uniprot-panel.ts +202 -0
- package/src/tests/volcano.ts +603 -0
- package/src/utils/column-detection.ts +28 -0
- package/src/utils/gene-label-resolver.ts +443 -0
- package/src/utils/organisms.ts +82 -0
- package/src/utils/proteomics-types.ts +30 -0
- package/src/viewers/enrichment-viewers.ts +274 -0
- package/src/viewers/group-mean-correlation.ts +218 -0
- package/src/viewers/heatmap.ts +168 -0
- package/src/viewers/pca-plot.ts +169 -0
- package/src/viewers/qc-computations.ts +266 -0
- package/src/viewers/qc-dashboard.ts +176 -0
- package/src/viewers/spc-dashboard.ts +755 -0
- package/src/viewers/volcano.ts +690 -0
- package/test-console-output-1.log +2055 -0
- package/test-record-1.mp4 +0 -0
- package/tools/derive-precursor-golden-sidecar.mjs +81 -0
- package/tools/generate-enrichment-fixture.sh +160 -0
- package/tools/generate-spectronaut-candidates-fixture.mjs +212 -0
- package/tools/generate-spectronaut-precursor-fixture.mjs +128 -0
- package/tools/spectronaut-aggregate.sh +46 -0
- package/tools/spectronaut-aggregate.sql +80 -0
- package/tsconfig.json +18 -71
- package/webpack.config.js +86 -45
- package/.eslintignore +0 -1
- package/.eslintrc.json +0 -56
- package/LICENSE +0 -674
- package/package.png +0 -0
- package/scripts/number_antibody.py +0 -190
- package/scripts/number_antibody_abnumber.py +0 -177
- package/scripts/number_antibody_anarci.py +0 -200
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
#name: Antpack Antibody Numbering
|
|
2
|
-
#description: Assigns antibody numbering (IMGT/Kabat/Chothia/AHo) using AntPack
|
|
3
|
-
#language: python
|
|
4
|
-
#environment: channels: [conda-forge, defaults], dependencies: [python=3.9, pip, pip: [antpack==0.3.8.6,pyside6,qt_material]]
|
|
5
|
-
#meta.role: antibodyNumbering
|
|
6
|
-
#input: dataframe df
|
|
7
|
-
#input: column seqCol {semType: Macromolecule}
|
|
8
|
-
#input: string scheme {choices: ["imgt", "kabat", "chothia", "aho"]} [Numbering scheme]
|
|
9
|
-
#output: dataframe result
|
|
10
|
-
|
|
11
|
-
import json
|
|
12
|
-
import pandas as pd
|
|
13
|
-
|
|
14
|
-
try:
|
|
15
|
-
from antpack import SingleChainAnnotator
|
|
16
|
-
except ImportError:
|
|
17
|
-
raise ImportError(
|
|
18
|
-
'AntPack is not installed. Please install it with:\n'
|
|
19
|
-
' pip install antpack==0.3.8.5\n'
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
# Antpack github: https://gidthub.com/jlparkI/AntPack
|
|
23
|
-
# While antpack is currently under non-commercial licence, the version used here (0.3.8.6 as specified in environment) is released unde GPL, allowing us to distribute this plugin with same GPL.
|
|
24
|
-
|
|
25
|
-
# AntPack uses "martin" for modern Chothia numbering
|
|
26
|
-
SCHEME_MAP = {
|
|
27
|
-
'imgt': 'imgt',
|
|
28
|
-
'kabat': 'kabat',
|
|
29
|
-
'chothia': 'martin',
|
|
30
|
-
'aho': 'aho',
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
# Region definitions per scheme, keyed by (scheme, chain_group).
|
|
34
|
-
# chain_group is 'Heavy' or 'Light'.
|
|
35
|
-
# Each region: (name, start_position, end_position).
|
|
36
|
-
IMGT_REGIONS = {
|
|
37
|
-
'Heavy': [
|
|
38
|
-
('FR1', 1, 26), ('CDR1', 27, 38), ('FR2', 39, 55),
|
|
39
|
-
('CDR2', 56, 65), ('FR3', 66, 104), ('CDR3', 105, 117), ('FR4', 118, 128),
|
|
40
|
-
],
|
|
41
|
-
'Light': [
|
|
42
|
-
('FR1', 1, 26), ('CDR1', 27, 38), ('FR2', 39, 55),
|
|
43
|
-
('CDR2', 56, 65), ('FR3', 66, 104), ('CDR3', 105, 117), ('FR4', 118, 127),
|
|
44
|
-
],
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
KABAT_REGIONS = {
|
|
48
|
-
'Heavy': [
|
|
49
|
-
('FR1', 1, 30), ('CDR1', 31, 35), ('FR2', 36, 49),
|
|
50
|
-
('CDR2', 50, 65), ('FR3', 66, 94), ('CDR3', 95, 102), ('FR4', 103, 113),
|
|
51
|
-
],
|
|
52
|
-
'Light': [
|
|
53
|
-
('FR1', 1, 23), ('CDR1', 24, 34), ('FR2', 35, 49),
|
|
54
|
-
('CDR2', 50, 56), ('FR3', 57, 88), ('CDR3', 89, 97), ('FR4', 98, 107),
|
|
55
|
-
],
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
CHOTHIA_REGIONS = {
|
|
59
|
-
'Heavy': [
|
|
60
|
-
('FR1', 1, 25), ('CDR1', 26, 32), ('FR2', 33, 51),
|
|
61
|
-
('CDR2', 52, 56), ('FR3', 57, 94), ('CDR3', 95, 102), ('FR4', 103, 113),
|
|
62
|
-
],
|
|
63
|
-
'Light': [
|
|
64
|
-
('FR1', 1, 25), ('CDR1', 26, 32), ('FR2', 33, 49),
|
|
65
|
-
('CDR2', 50, 52), ('FR3', 53, 90), ('CDR3', 91, 96), ('FR4', 97, 107),
|
|
66
|
-
],
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
AHO_REGIONS = {
|
|
70
|
-
'Heavy': [
|
|
71
|
-
('FR1', 1, 24), ('CDR1', 25, 40), ('FR2', 41, 55),
|
|
72
|
-
('CDR2', 56, 78), ('FR3', 79, 108), ('CDR3', 109, 138), ('FR4', 139, 149),
|
|
73
|
-
],
|
|
74
|
-
'Light': [
|
|
75
|
-
('FR1', 1, 24), ('CDR1', 25, 40), ('FR2', 41, 55),
|
|
76
|
-
('CDR2', 56, 78), ('FR3', 79, 108), ('CDR3', 109, 138), ('FR4', 139, 149),
|
|
77
|
-
],
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
SCHEME_REGIONS = {
|
|
81
|
-
'imgt': IMGT_REGIONS,
|
|
82
|
-
'kabat': KABAT_REGIONS,
|
|
83
|
-
'chothia': CHOTHIA_REGIONS,
|
|
84
|
-
'aho': AHO_REGIONS,
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def extract_sequence(raw_seq):
|
|
89
|
-
"""Extract single-letter amino acid sequence from various formats."""
|
|
90
|
-
if not raw_seq or not isinstance(raw_seq, str):
|
|
91
|
-
return ''
|
|
92
|
-
s = raw_seq.strip()
|
|
93
|
-
s = s.replace('-', '').replace('.', '')
|
|
94
|
-
valid = set('ACDEFGHIKLMNPQRSTVWY')
|
|
95
|
-
return ''.join(c for c in s.upper() if c in valid)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
# Map user-facing scheme to AntPack scheme name
|
|
99
|
-
antpack_scheme = SCHEME_MAP.get(scheme, 'imgt')
|
|
100
|
-
|
|
101
|
-
# Create the annotator once for all sequences
|
|
102
|
-
annotator = SingleChainAnnotator(chains=['H', 'K', 'L'], scheme=antpack_scheme)
|
|
103
|
-
|
|
104
|
-
# Extract sequences from the input column
|
|
105
|
-
col_name = seqCol
|
|
106
|
-
sequences = []
|
|
107
|
-
for i in range(df.shape[0]):
|
|
108
|
-
raw = df[col_name].iloc[i]
|
|
109
|
-
sequences.append(extract_sequence(str(raw) if raw is not None else ''))
|
|
110
|
-
|
|
111
|
-
# Process each sequence
|
|
112
|
-
position_names_list = []
|
|
113
|
-
chain_types = []
|
|
114
|
-
region_annotations_list = []
|
|
115
|
-
numbering_results = []
|
|
116
|
-
numbering_maps = []
|
|
117
|
-
|
|
118
|
-
regions_def = SCHEME_REGIONS.get(scheme, IMGT_REGIONS)
|
|
119
|
-
|
|
120
|
-
for seq in sequences:
|
|
121
|
-
if not seq:
|
|
122
|
-
position_names_list.append('')
|
|
123
|
-
chain_types.append('')
|
|
124
|
-
region_annotations_list.append('[]')
|
|
125
|
-
numbering_results.append('')
|
|
126
|
-
numbering_maps.append('')
|
|
127
|
-
continue
|
|
128
|
-
|
|
129
|
-
numbering, pct_identity, chain_type_code, err = annotator.analyze_seq(seq)
|
|
130
|
-
|
|
131
|
-
if err or pct_identity < 0.5:
|
|
132
|
-
position_names_list.append('')
|
|
133
|
-
chain_types.append('')
|
|
134
|
-
region_annotations_list.append('[]')
|
|
135
|
-
numbering_results.append('')
|
|
136
|
-
numbering_maps.append('')
|
|
137
|
-
continue
|
|
138
|
-
|
|
139
|
-
# Determine chain type
|
|
140
|
-
if chain_type_code == 'H':
|
|
141
|
-
chain_type = 'Heavy'
|
|
142
|
-
elif chain_type_code in ('K', 'L'):
|
|
143
|
-
chain_type = 'Light'
|
|
144
|
-
else:
|
|
145
|
-
chain_type = 'Unknown'
|
|
146
|
-
|
|
147
|
-
# Build position names, numbering detail, and position-to-character-index map.
|
|
148
|
-
# numbering is a list with the same length as the input sequence;
|
|
149
|
-
# each element is a position code string or '-' for residues outside the variable region.
|
|
150
|
-
pos_names = []
|
|
151
|
-
numbering_detail = []
|
|
152
|
-
pos_to_char_idx = {}
|
|
153
|
-
for i, pos_code in enumerate(numbering):
|
|
154
|
-
if pos_code != '-':
|
|
155
|
-
pos_names.append(pos_code)
|
|
156
|
-
numbering_detail.append({'position': pos_code, 'aa': seq[i]})
|
|
157
|
-
pos_to_char_idx[pos_code] = i
|
|
158
|
-
|
|
159
|
-
position_names_list.append(', '.join(pos_names))
|
|
160
|
-
chain_types.append(chain_type)
|
|
161
|
-
|
|
162
|
-
# Build region annotations
|
|
163
|
-
chain_key = chain_type if chain_type in regions_def else 'Heavy'
|
|
164
|
-
region_defs = regions_def.get(chain_key, [])
|
|
165
|
-
annotations = []
|
|
166
|
-
for region_name, start, end in region_defs:
|
|
167
|
-
annotations.append({
|
|
168
|
-
'id': f'{scheme}-{chain_type}-{region_name}'.lower(),
|
|
169
|
-
'name': region_name,
|
|
170
|
-
'description': f'{region_name} ({scheme.upper()} {start}-{end})',
|
|
171
|
-
'start': str(start),
|
|
172
|
-
'end': str(end),
|
|
173
|
-
'visualType': 'region',
|
|
174
|
-
'category': 'structure',
|
|
175
|
-
'sourceScheme': scheme.upper(),
|
|
176
|
-
'autoGenerated': True,
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
region_annotations_list.append(json.dumps(annotations))
|
|
180
|
-
numbering_results.append(json.dumps(numbering_detail))
|
|
181
|
-
numbering_maps.append(json.dumps(pos_to_char_idx))
|
|
182
|
-
|
|
183
|
-
# Build result DataFrame
|
|
184
|
-
result = pd.DataFrame({
|
|
185
|
-
'position_names': position_names_list,
|
|
186
|
-
'chain_type': chain_types,
|
|
187
|
-
'annotations_json': region_annotations_list,
|
|
188
|
-
'numbering_detail': numbering_results,
|
|
189
|
-
'numbering_map': numbering_maps,
|
|
190
|
-
})
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
#name: Number Antibody Sequences (AbNumber)
|
|
2
|
-
#description: [Legacy] Assigns antibody numbering (IMGT/Kabat/Chothia/AHo) using AbNumber
|
|
3
|
-
#language: python
|
|
4
|
-
#environment: channels: [conda-forge, bioconda, defaults], dependencies: [python=3.9, pip, hmmer=3.3.2, pip: [abnumber]]
|
|
5
|
-
#input: dataframe df
|
|
6
|
-
#input: column seqCol {semType: Macromolecule}
|
|
7
|
-
#input: string scheme {choices: ["imgt", "kabat", "chothia", "aho"]} [Numbering scheme]
|
|
8
|
-
#output: dataframe result
|
|
9
|
-
|
|
10
|
-
import json
|
|
11
|
-
import pandas as pd
|
|
12
|
-
|
|
13
|
-
try:
|
|
14
|
-
from abnumber import Chain
|
|
15
|
-
except ImportError:
|
|
16
|
-
raise ImportError(
|
|
17
|
-
'AbNumber is not installed. Please install it with:\n'
|
|
18
|
-
' conda install -c bioconda abnumber\n'
|
|
19
|
-
'or:\n'
|
|
20
|
-
' pip install abnumber\n'
|
|
21
|
-
'HMMER must also be available on PATH.'
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
# Region definitions per scheme, keyed by (scheme, chain_group).
|
|
25
|
-
# chain_group is 'Heavy' or 'Light'.
|
|
26
|
-
# Each region: (name, start_position, end_position).
|
|
27
|
-
IMGT_REGIONS = {
|
|
28
|
-
'Heavy': [
|
|
29
|
-
('FR1', 1, 26), ('CDR1', 27, 38), ('FR2', 39, 55),
|
|
30
|
-
('CDR2', 56, 65), ('FR3', 66, 104), ('CDR3', 105, 117), ('FR4', 118, 128),
|
|
31
|
-
],
|
|
32
|
-
'Light': [
|
|
33
|
-
('FR1', 1, 26), ('CDR1', 27, 38), ('FR2', 39, 55),
|
|
34
|
-
('CDR2', 56, 65), ('FR3', 66, 104), ('CDR3', 105, 117), ('FR4', 118, 127),
|
|
35
|
-
],
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
KABAT_REGIONS = {
|
|
39
|
-
'Heavy': [
|
|
40
|
-
('FR1', 1, 30), ('CDR1', 31, 35), ('FR2', 36, 49),
|
|
41
|
-
('CDR2', 50, 65), ('FR3', 66, 94), ('CDR3', 95, 102), ('FR4', 103, 113),
|
|
42
|
-
],
|
|
43
|
-
'Light': [
|
|
44
|
-
('FR1', 1, 23), ('CDR1', 24, 34), ('FR2', 35, 49),
|
|
45
|
-
('CDR2', 50, 56), ('FR3', 57, 88), ('CDR3', 89, 97), ('FR4', 98, 107),
|
|
46
|
-
],
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
CHOTHIA_REGIONS = {
|
|
50
|
-
'Heavy': [
|
|
51
|
-
('FR1', 1, 25), ('CDR1', 26, 32), ('FR2', 33, 51),
|
|
52
|
-
('CDR2', 52, 56), ('FR3', 57, 94), ('CDR3', 95, 102), ('FR4', 103, 113),
|
|
53
|
-
],
|
|
54
|
-
'Light': [
|
|
55
|
-
('FR1', 1, 25), ('CDR1', 26, 32), ('FR2', 33, 49),
|
|
56
|
-
('CDR2', 50, 52), ('FR3', 53, 90), ('CDR3', 91, 96), ('FR4', 97, 107),
|
|
57
|
-
],
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
AHO_REGIONS = {
|
|
61
|
-
'Heavy': [
|
|
62
|
-
('FR1', 1, 24), ('CDR1', 25, 40), ('FR2', 41, 55),
|
|
63
|
-
('CDR2', 56, 78), ('FR3', 79, 108), ('CDR3', 109, 138), ('FR4', 139, 149),
|
|
64
|
-
],
|
|
65
|
-
'Light': [
|
|
66
|
-
('FR1', 1, 24), ('CDR1', 25, 40), ('FR2', 41, 55),
|
|
67
|
-
('CDR2', 56, 78), ('FR3', 79, 108), ('CDR3', 109, 138), ('FR4', 139, 149),
|
|
68
|
-
],
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
SCHEME_REGIONS = {
|
|
72
|
-
'imgt': IMGT_REGIONS,
|
|
73
|
-
'kabat': KABAT_REGIONS,
|
|
74
|
-
'chothia': CHOTHIA_REGIONS,
|
|
75
|
-
'aho': AHO_REGIONS,
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def extract_sequence(raw_seq):
|
|
80
|
-
"""Extract single-letter amino acid sequence from various formats."""
|
|
81
|
-
if not raw_seq or not isinstance(raw_seq, str):
|
|
82
|
-
return ''
|
|
83
|
-
s = raw_seq.strip()
|
|
84
|
-
s = s.replace('-', '').replace('.', '')
|
|
85
|
-
valid = set('ACDEFGHIKLMNPQRSTVWY')
|
|
86
|
-
return ''.join(c for c in s.upper() if c in valid)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def number_single_sequence(seq, scheme_name):
|
|
90
|
-
"""Number a single sequence using AbNumber's Chain object.
|
|
91
|
-
|
|
92
|
-
Returns (chain, error_msg) where chain is an abnumber.Chain or None.
|
|
93
|
-
"""
|
|
94
|
-
if not seq:
|
|
95
|
-
return None, 'empty sequence'
|
|
96
|
-
try:
|
|
97
|
-
chain = Chain(seq, scheme=scheme_name)
|
|
98
|
-
return chain, None
|
|
99
|
-
except Exception as e:
|
|
100
|
-
return None, str(e)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
# Extract sequences from the input column
|
|
104
|
-
col_name = seqCol
|
|
105
|
-
sequences = []
|
|
106
|
-
for i in range(df.shape[0]):
|
|
107
|
-
raw = df[col_name].iloc[i]
|
|
108
|
-
sequences.append(extract_sequence(str(raw) if raw is not None else ''))
|
|
109
|
-
|
|
110
|
-
# Process each sequence
|
|
111
|
-
position_names_list = []
|
|
112
|
-
chain_types = []
|
|
113
|
-
region_annotations_list = []
|
|
114
|
-
numbering_results = []
|
|
115
|
-
|
|
116
|
-
regions_def = SCHEME_REGIONS.get(scheme, IMGT_REGIONS)
|
|
117
|
-
|
|
118
|
-
for seq in sequences:
|
|
119
|
-
chain, err = number_single_sequence(seq, scheme)
|
|
120
|
-
|
|
121
|
-
if chain is None:
|
|
122
|
-
position_names_list.append('')
|
|
123
|
-
chain_types.append('')
|
|
124
|
-
region_annotations_list.append('[]')
|
|
125
|
-
numbering_results.append('')
|
|
126
|
-
continue
|
|
127
|
-
|
|
128
|
-
# Determine chain type
|
|
129
|
-
ct = chain.chain_type # 'H', 'K', or 'L'
|
|
130
|
-
if ct == 'H':
|
|
131
|
-
chain_type = 'Heavy'
|
|
132
|
-
elif ct in ('K', 'L'):
|
|
133
|
-
chain_type = 'Light'
|
|
134
|
-
else:
|
|
135
|
-
chain_type = 'Unknown'
|
|
136
|
-
|
|
137
|
-
# Build position names from numbering
|
|
138
|
-
pos_names = []
|
|
139
|
-
numbering_detail = []
|
|
140
|
-
for position, aa in chain:
|
|
141
|
-
pos_name = str(position.number)
|
|
142
|
-
if position.letter and position.letter.strip():
|
|
143
|
-
pos_name += position.letter.strip()
|
|
144
|
-
pos_names.append(pos_name)
|
|
145
|
-
numbering_detail.append({'position': pos_name, 'aa': aa})
|
|
146
|
-
|
|
147
|
-
position_names_list.append(', '.join(pos_names))
|
|
148
|
-
|
|
149
|
-
chain_types.append(chain_type)
|
|
150
|
-
|
|
151
|
-
# Build region annotations
|
|
152
|
-
chain_key = chain_type if chain_type in regions_def else 'Heavy'
|
|
153
|
-
region_defs = regions_def.get(chain_key, [])
|
|
154
|
-
annotations = []
|
|
155
|
-
for region_name, start, end in region_defs:
|
|
156
|
-
annotations.append({
|
|
157
|
-
'id': f'{scheme}-{chain_type}-{region_name}'.lower(),
|
|
158
|
-
'name': region_name,
|
|
159
|
-
'description': f'{region_name} ({scheme.upper()} {start}-{end})',
|
|
160
|
-
'start': str(start),
|
|
161
|
-
'end': str(end),
|
|
162
|
-
'visualType': 'region',
|
|
163
|
-
'category': 'structure',
|
|
164
|
-
'sourceScheme': scheme.upper(),
|
|
165
|
-
'autoGenerated': True,
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
region_annotations_list.append(json.dumps(annotations))
|
|
169
|
-
numbering_results.append(json.dumps(numbering_detail))
|
|
170
|
-
|
|
171
|
-
# Build result DataFrame
|
|
172
|
-
result = pd.DataFrame({
|
|
173
|
-
'position_names': position_names_list,
|
|
174
|
-
'chain_type': chain_types,
|
|
175
|
-
'annotations_json': region_annotations_list,
|
|
176
|
-
'numbering_detail': numbering_results,
|
|
177
|
-
})
|
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
#name: Number Antibody Sequences (ANARCI)
|
|
2
|
-
#description: [Legacy] Assigns antibody numbering (IMGT/Kabat/Chothia/AHo) using ANARCI directly
|
|
3
|
-
#language: python
|
|
4
|
-
#environment: channels: [conda-forge, bioconda, defaults], dependencies: [python=3.9, pip, hmmer=3.3.2, anarci]
|
|
5
|
-
#input: dataframe df
|
|
6
|
-
#input: column seqCol {semType: Macromolecule}
|
|
7
|
-
#input: string scheme {choices: ["imgt", "kabat", "chothia", "aho"]} [Numbering scheme]
|
|
8
|
-
#output: dataframe result
|
|
9
|
-
|
|
10
|
-
import json
|
|
11
|
-
import pandas as pd
|
|
12
|
-
|
|
13
|
-
try:
|
|
14
|
-
from anarci import anarci, number
|
|
15
|
-
except ImportError:
|
|
16
|
-
raise ImportError(
|
|
17
|
-
'ANARCI is not installed. Please install it with:\n'
|
|
18
|
-
' conda install -c bioconda anarci hmmer\n'
|
|
19
|
-
'or:\n'
|
|
20
|
-
' pip install anarci\n'
|
|
21
|
-
'HMMER must also be available on PATH.'
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
# IMGT region boundaries (used for all schemes after numbering)
|
|
25
|
-
IMGT_REGIONS = {
|
|
26
|
-
'Heavy': [
|
|
27
|
-
('FR1', 1, 26), ('CDR1', 27, 38), ('FR2', 39, 55),
|
|
28
|
-
('CDR2', 56, 65), ('FR3', 66, 104), ('CDR3', 105, 117), ('FR4', 118, 128),
|
|
29
|
-
],
|
|
30
|
-
'Light': [
|
|
31
|
-
('FR1', 1, 26), ('CDR1', 27, 38), ('FR2', 39, 55),
|
|
32
|
-
('CDR2', 56, 65), ('FR3', 66, 104), ('CDR3', 105, 117), ('FR4', 118, 127),
|
|
33
|
-
],
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
KABAT_REGIONS = {
|
|
37
|
-
'Heavy': [
|
|
38
|
-
('FR1', 1, 30), ('CDR1', 31, 35), ('FR2', 36, 49),
|
|
39
|
-
('CDR2', 50, 65), ('FR3', 66, 94), ('CDR3', 95, 102), ('FR4', 103, 113),
|
|
40
|
-
],
|
|
41
|
-
'Light': [
|
|
42
|
-
('FR1', 1, 23), ('CDR1', 24, 34), ('FR2', 35, 49),
|
|
43
|
-
('CDR2', 50, 56), ('FR3', 57, 88), ('CDR3', 89, 97), ('FR4', 98, 107),
|
|
44
|
-
],
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
CHOTHIA_REGIONS = {
|
|
48
|
-
'Heavy': [
|
|
49
|
-
('FR1', 1, 25), ('CDR1', 26, 32), ('FR2', 33, 51),
|
|
50
|
-
('CDR2', 52, 56), ('FR3', 57, 94), ('CDR3', 95, 102), ('FR4', 103, 113),
|
|
51
|
-
],
|
|
52
|
-
'Light': [
|
|
53
|
-
('FR1', 1, 25), ('CDR1', 26, 32), ('FR2', 33, 49),
|
|
54
|
-
('CDR2', 50, 52), ('FR3', 53, 90), ('CDR3', 91, 96), ('FR4', 97, 107),
|
|
55
|
-
],
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
AHO_REGIONS = {
|
|
59
|
-
'Heavy': [
|
|
60
|
-
('FR1', 1, 24), ('CDR1', 25, 40), ('FR2', 41, 55),
|
|
61
|
-
('CDR2', 56, 78), ('FR3', 79, 108), ('CDR3', 109, 138), ('FR4', 139, 149),
|
|
62
|
-
],
|
|
63
|
-
'Light': [
|
|
64
|
-
('FR1', 1, 24), ('CDR1', 25, 40), ('FR2', 41, 55),
|
|
65
|
-
('CDR2', 56, 78), ('FR3', 79, 108), ('CDR3', 109, 138), ('FR4', 139, 149),
|
|
66
|
-
],
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
SCHEME_REGIONS = {
|
|
70
|
-
'imgt': IMGT_REGIONS,
|
|
71
|
-
'kabat': KABAT_REGIONS,
|
|
72
|
-
'chothia': CHOTHIA_REGIONS,
|
|
73
|
-
'aho': AHO_REGIONS,
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def extract_sequence(raw_seq):
|
|
78
|
-
"""Extract single-letter amino acid sequence from various formats."""
|
|
79
|
-
if not raw_seq or not isinstance(raw_seq, str):
|
|
80
|
-
return ''
|
|
81
|
-
s = raw_seq.strip()
|
|
82
|
-
# If it looks like FASTA single-letter (only AA chars and gaps)
|
|
83
|
-
s = s.replace('-', '').replace('.', '')
|
|
84
|
-
# Filter to valid amino acid characters
|
|
85
|
-
valid = set('ACDEFGHIKLMNPQRSTVWY')
|
|
86
|
-
return ''.join(c for c in s.upper() if c in valid)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def number_sequences(sequences, scheme_name):
|
|
90
|
-
"""Number a list of sequences using ANARCI."""
|
|
91
|
-
# Prepare input for ANARCI
|
|
92
|
-
seq_list = [(f'seq_{i}', seq) for i, seq in enumerate(sequences) if seq]
|
|
93
|
-
|
|
94
|
-
if not seq_list:
|
|
95
|
-
return [], [], []
|
|
96
|
-
|
|
97
|
-
# Run ANARCI
|
|
98
|
-
numbered_seqs, alignment_details, hit_tables = anarci(
|
|
99
|
-
seq_list,
|
|
100
|
-
scheme=scheme_name,
|
|
101
|
-
output=False,
|
|
102
|
-
allow=set(['H', 'K', 'L']),
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
return numbered_seqs, alignment_details, hit_tables
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
# Extract sequences from the input column
|
|
109
|
-
col_name = seqCol
|
|
110
|
-
sequences = []
|
|
111
|
-
for i in range(df.shape[0]):
|
|
112
|
-
raw = df[col_name].iloc[i]
|
|
113
|
-
sequences.append(extract_sequence(str(raw) if raw is not None else ''))
|
|
114
|
-
|
|
115
|
-
# Number all sequences
|
|
116
|
-
numbered_seqs, alignment_details, hit_tables = number_sequences(sequences, scheme)
|
|
117
|
-
|
|
118
|
-
# Process results
|
|
119
|
-
position_names_list = []
|
|
120
|
-
chain_types = []
|
|
121
|
-
region_annotations_list = []
|
|
122
|
-
numbering_results = []
|
|
123
|
-
|
|
124
|
-
regions_def = SCHEME_REGIONS.get(scheme, IMGT_REGIONS)
|
|
125
|
-
|
|
126
|
-
for idx in range(len(sequences)):
|
|
127
|
-
if numbered_seqs[idx] is None or len(numbered_seqs[idx]) == 0:
|
|
128
|
-
position_names_list.append('')
|
|
129
|
-
chain_types.append('')
|
|
130
|
-
region_annotations_list.append('[]')
|
|
131
|
-
numbering_results.append('')
|
|
132
|
-
continue
|
|
133
|
-
|
|
134
|
-
# Get the best hit (first domain)
|
|
135
|
-
numbering = numbered_seqs[idx][0]
|
|
136
|
-
chain_info = alignment_details[idx][0]
|
|
137
|
-
|
|
138
|
-
if numbering is None or chain_info is None:
|
|
139
|
-
position_names_list.append('')
|
|
140
|
-
chain_types.append('')
|
|
141
|
-
region_annotations_list.append('[]')
|
|
142
|
-
numbering_results.append('')
|
|
143
|
-
continue
|
|
144
|
-
|
|
145
|
-
# Extract chain type
|
|
146
|
-
chain_type_code = chain_info['chain_type']
|
|
147
|
-
if chain_type_code == 'H':
|
|
148
|
-
chain_type = 'Heavy'
|
|
149
|
-
elif chain_type_code in ('K', 'L'):
|
|
150
|
-
chain_type = 'Light'
|
|
151
|
-
else:
|
|
152
|
-
chain_type = 'Unknown'
|
|
153
|
-
|
|
154
|
-
# Build position names from numbering
|
|
155
|
-
pos_names = []
|
|
156
|
-
for (pos_num, insertion_code), aa in numbering:
|
|
157
|
-
if aa != '-':
|
|
158
|
-
pos_name = str(pos_num)
|
|
159
|
-
if insertion_code != ' ':
|
|
160
|
-
pos_name += insertion_code
|
|
161
|
-
pos_names.append(pos_name)
|
|
162
|
-
|
|
163
|
-
position_names_list.append(', '.join(pos_names))
|
|
164
|
-
chain_types.append(chain_type)
|
|
165
|
-
|
|
166
|
-
# Build region annotations
|
|
167
|
-
chain_key = chain_type if chain_type in regions_def else 'Heavy'
|
|
168
|
-
region_defs = regions_def.get(chain_key, [])
|
|
169
|
-
annotations = []
|
|
170
|
-
for region_name, start, end in region_defs:
|
|
171
|
-
region_type = 'CDR' if 'CDR' in region_name else 'FR'
|
|
172
|
-
annotations.append({
|
|
173
|
-
'id': f'{scheme}-{chain_type}-{region_name}'.lower(),
|
|
174
|
-
'name': region_name,
|
|
175
|
-
'description': f'{region_name} ({scheme.upper()} {start}-{end})',
|
|
176
|
-
'start': str(start),
|
|
177
|
-
'end': str(end),
|
|
178
|
-
'visualType': 'region',
|
|
179
|
-
'category': 'structure',
|
|
180
|
-
'sourceScheme': scheme.upper(),
|
|
181
|
-
'autoGenerated': True,
|
|
182
|
-
})
|
|
183
|
-
|
|
184
|
-
region_annotations_list.append(json.dumps(annotations))
|
|
185
|
-
|
|
186
|
-
# Full numbering as JSON for debugging/reference
|
|
187
|
-
numbering_json = json.dumps([
|
|
188
|
-
{'position': f'{pos_num}{insertion_code.strip()}', 'aa': aa}
|
|
189
|
-
for (pos_num, insertion_code), aa in numbering
|
|
190
|
-
if aa != '-'
|
|
191
|
-
])
|
|
192
|
-
numbering_results.append(numbering_json)
|
|
193
|
-
|
|
194
|
-
# Build result DataFrame
|
|
195
|
-
result = pd.DataFrame({
|
|
196
|
-
'position_names': position_names_list,
|
|
197
|
-
'chain_type': chain_types,
|
|
198
|
-
'annotations_json': region_annotations_list,
|
|
199
|
-
'numbering_detail': numbering_results,
|
|
200
|
-
})
|