@datagrok/bio 2.27.2 → 2.27.3
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/CLAUDE.md +50 -0
- package/dist/682.js +1 -1
- package/dist/682.js.map +1 -1
- package/dist/705.js +1 -1
- package/dist/705.js.map +1 -1
- package/dist/909.js +2 -0
- package/dist/909.js.map +1 -0
- package/dist/immunum_bg.wasm +0 -0
- package/dist/package-test.js +3 -3
- package/dist/package-test.js.map +1 -1
- package/dist/package.js +3 -3
- package/dist/package.js.map +1 -1
- package/package.json +3 -1
- package/src/package-api.ts +15 -1
- package/src/package-test.ts +1 -0
- package/src/package.g.ts +12 -1
- package/src/package.ts +22 -4
- package/src/tests/antibody-numbering-tests.ts +190 -0
- package/src/utils/annotations/numbering-ui.ts +34 -90
- package/src/utils/antibody-numbering/immunum-client.ts +45 -0
- package/src/utils/antibody-numbering/immunum-glue.js +275 -0
- package/src/utils/antibody-numbering/immunum.worker.ts +159 -0
- package/src/utils/antibody-numbering/number-antibody.ts +105 -0
- package/src/utils/antibody-numbering/types.ts +48 -0
- package/test-console-output-1.log +588 -533
- package/test-record-1.mp4 +0 -0
- package/webpack.config.js +13 -0
- package/dist/282.js +0 -2
- package/dist/282.js.map +0 -1
- package/dist/287.js +0 -2
- package/dist/287.js.map +0 -1
- package/dist/422.js +0 -2
- package/dist/422.js.map +0 -1
- package/dist/767.js +0 -2
- package/dist/767.js.map +0 -1
- package/src/utils/antibody-numbering (WIP)/alignment.ts +0 -578
- package/src/utils/antibody-numbering (WIP)/annotator.ts +0 -120
- package/src/utils/antibody-numbering (WIP)/data/blosum62.ts +0 -55
- package/src/utils/antibody-numbering (WIP)/data/consensus-aho.ts +0 -155
- package/src/utils/antibody-numbering (WIP)/data/consensus-imgt.ts +0 -162
- package/src/utils/antibody-numbering (WIP)/data/consensus-kabat.ts +0 -157
- package/src/utils/antibody-numbering (WIP)/data/consensus-martin.ts +0 -152
- package/src/utils/antibody-numbering (WIP)/data/consensus.ts +0 -36
- package/src/utils/antibody-numbering (WIP)/data/regions.ts +0 -63
- package/src/utils/antibody-numbering (WIP)/index.ts +0 -31
- package/src/utils/antibody-numbering (WIP)/testdata.ts +0 -5356
- package/src/utils/antibody-numbering (WIP)/types.ts +0 -69
- /package/dist/{8473fcbfb6e85ca6c852.wasm → wasmCluster.wasm} +0 -0
- /package/dist/{9a8fbf37666e32487835.wasm → wasmDbscan.wasm} +0 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
//
|
|
4
|
+
// Browser/worker-friendly port of node_modules/immunum/immunum.js (v1.1.0).
|
|
5
|
+
// The upstream file uses require('fs').readFileSync to load the WASM at top
|
|
6
|
+
// level, which breaks in the browser. This file is byte-identical up to the
|
|
7
|
+
// wasm-bindgen glue; the only change is that the WASM bytes are supplied via
|
|
8
|
+
// an explicit initImmunum() call instead of being read from disk at import
|
|
9
|
+
// time. See README.md in the immunum package for the upstream source.
|
|
10
|
+
|
|
11
|
+
let wasm = null;
|
|
12
|
+
|
|
13
|
+
export class Annotator {
|
|
14
|
+
__destroy_into_raw() {
|
|
15
|
+
const ptr = this.__wbg_ptr;
|
|
16
|
+
this.__wbg_ptr = 0;
|
|
17
|
+
AnnotatorFinalization.unregister(this);
|
|
18
|
+
return ptr;
|
|
19
|
+
}
|
|
20
|
+
free() {
|
|
21
|
+
const ptr = this.__destroy_into_raw();
|
|
22
|
+
wasm.__wbg_annotator_free(ptr, 0);
|
|
23
|
+
}
|
|
24
|
+
constructor(chains, scheme, min_confidence) {
|
|
25
|
+
const ptr0 = passArrayJsValueToWasm0(chains, wasm.__wbindgen_malloc);
|
|
26
|
+
const len0 = WASM_VECTOR_LEN;
|
|
27
|
+
const ptr1 = passStringToWasm0(scheme, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
28
|
+
const len1 = WASM_VECTOR_LEN;
|
|
29
|
+
const ret = wasm.annotator_new(ptr0, len0, ptr1, len1,
|
|
30
|
+
isLikeNone(min_confidence) ? 0x100000001 : Math.fround(min_confidence));
|
|
31
|
+
if (ret[2]) {
|
|
32
|
+
throw takeFromExternrefTable0(ret[1]);
|
|
33
|
+
}
|
|
34
|
+
this.__wbg_ptr = ret[0] >>> 0;
|
|
35
|
+
AnnotatorFinalization.register(this, this.__wbg_ptr, this);
|
|
36
|
+
return this;
|
|
37
|
+
}
|
|
38
|
+
number(sequence) {
|
|
39
|
+
const ptr0 = passStringToWasm0(sequence, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
40
|
+
const len0 = WASM_VECTOR_LEN;
|
|
41
|
+
return wasm.annotator_number(this.__wbg_ptr, ptr0, len0);
|
|
42
|
+
}
|
|
43
|
+
segment(sequence) {
|
|
44
|
+
const ptr0 = passStringToWasm0(sequence, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
45
|
+
const len0 = WASM_VECTOR_LEN;
|
|
46
|
+
return wasm.annotator_segment(this.__wbg_ptr, ptr0, len0);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (typeof Symbol !== 'undefined' && Symbol.dispose) Annotator.prototype[Symbol.dispose] = Annotator.prototype.free;
|
|
50
|
+
|
|
51
|
+
const AnnotatorFinalization = (typeof FinalizationRegistry === 'undefined')
|
|
52
|
+
? {register: () => {}, unregister: () => {}}
|
|
53
|
+
: new FinalizationRegistry((ptr) => wasm.__wbg_annotator_free(ptr >>> 0, 1));
|
|
54
|
+
|
|
55
|
+
function __wbg_get_imports() {
|
|
56
|
+
const import0 = {
|
|
57
|
+
__proto__: null,
|
|
58
|
+
__wbg___wbindgen_debug_string_5398f5bb970e0daa: function(arg0, arg1) {
|
|
59
|
+
const ret = debugString(arg1);
|
|
60
|
+
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
61
|
+
const len1 = WASM_VECTOR_LEN;
|
|
62
|
+
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
|
|
63
|
+
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
|
|
64
|
+
},
|
|
65
|
+
__wbg___wbindgen_string_get_395e606bd0ee4427: function(arg0, arg1) {
|
|
66
|
+
const obj = arg1;
|
|
67
|
+
const ret = typeof(obj) === 'string' ? obj : undefined;
|
|
68
|
+
const ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
69
|
+
const len1 = WASM_VECTOR_LEN;
|
|
70
|
+
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
|
|
71
|
+
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
|
|
72
|
+
},
|
|
73
|
+
__wbg___wbindgen_throw_6ddd609b62940d55: function(arg0, arg1) {
|
|
74
|
+
throw new Error(getStringFromWasm0(arg0, arg1));
|
|
75
|
+
},
|
|
76
|
+
__wbg_new_49d5571bd3f0c4d4: function() {
|
|
77
|
+
return new Map();
|
|
78
|
+
},
|
|
79
|
+
__wbg_new_ab79df5bd7c26067: function() {
|
|
80
|
+
return new Object();
|
|
81
|
+
},
|
|
82
|
+
__wbg_set_7eaa4f96924fd6b3: function() {
|
|
83
|
+
return handleError(function(arg0, arg1, arg2) {
|
|
84
|
+
return Reflect.set(arg0, arg1, arg2);
|
|
85
|
+
}, arguments);
|
|
86
|
+
},
|
|
87
|
+
__wbg_set_bf7251625df30a02: function(arg0, arg1, arg2) {
|
|
88
|
+
return arg0.set(arg1, arg2);
|
|
89
|
+
},
|
|
90
|
+
__wbindgen_cast_0000000000000001: function(arg0) {
|
|
91
|
+
return arg0;
|
|
92
|
+
},
|
|
93
|
+
__wbindgen_cast_0000000000000002: function(arg0, arg1) {
|
|
94
|
+
return getStringFromWasm0(arg0, arg1);
|
|
95
|
+
},
|
|
96
|
+
__wbindgen_init_externref_table: function() {
|
|
97
|
+
const table = wasm.__wbindgen_externrefs;
|
|
98
|
+
const offset = table.grow(4);
|
|
99
|
+
table.set(0, undefined);
|
|
100
|
+
table.set(offset + 0, undefined);
|
|
101
|
+
table.set(offset + 1, null);
|
|
102
|
+
table.set(offset + 2, true);
|
|
103
|
+
table.set(offset + 3, false);
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
return {
|
|
107
|
+
__proto__: null,
|
|
108
|
+
'./immunum_bg.js': import0,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function addToExternrefTable0(obj) {
|
|
113
|
+
const idx = wasm.__externref_table_alloc();
|
|
114
|
+
wasm.__wbindgen_externrefs.set(idx, obj);
|
|
115
|
+
return idx;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function debugString(val) {
|
|
119
|
+
const type = typeof val;
|
|
120
|
+
if (type == 'number' || type == 'boolean' || val == null) return `${val}`;
|
|
121
|
+
if (type == 'string') return `"${val}"`;
|
|
122
|
+
if (type == 'symbol') {
|
|
123
|
+
const description = val.description;
|
|
124
|
+
return description == null ? 'Symbol' : `Symbol(${description})`;
|
|
125
|
+
}
|
|
126
|
+
if (type == 'function') {
|
|
127
|
+
const name = val.name;
|
|
128
|
+
return (typeof name == 'string' && name.length > 0) ? `Function(${name})` : 'Function';
|
|
129
|
+
}
|
|
130
|
+
if (Array.isArray(val)) {
|
|
131
|
+
const length = val.length;
|
|
132
|
+
let debug = '[';
|
|
133
|
+
if (length > 0) debug += debugString(val[0]);
|
|
134
|
+
for (let i = 1; i < length; i++) debug += ', ' + debugString(val[i]);
|
|
135
|
+
debug += ']';
|
|
136
|
+
return debug;
|
|
137
|
+
}
|
|
138
|
+
const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val));
|
|
139
|
+
let className;
|
|
140
|
+
if (builtInMatches && builtInMatches.length > 1) {
|
|
141
|
+
className = builtInMatches[1];
|
|
142
|
+
} else {
|
|
143
|
+
return toString.call(val);
|
|
144
|
+
}
|
|
145
|
+
if (className == 'Object') {
|
|
146
|
+
try {
|
|
147
|
+
return 'Object(' + JSON.stringify(val) + ')';
|
|
148
|
+
} catch (_) {
|
|
149
|
+
return 'Object';
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (val instanceof Error) return `${val.name}: ${val.message}\n${val.stack}`;
|
|
153
|
+
return className;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
let cachedDataViewMemory0 = null;
|
|
157
|
+
function getDataViewMemory0() {
|
|
158
|
+
if (cachedDataViewMemory0 === null ||
|
|
159
|
+
cachedDataViewMemory0.buffer.detached === true ||
|
|
160
|
+
(cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
|
|
161
|
+
cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
|
|
162
|
+
}
|
|
163
|
+
return cachedDataViewMemory0;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function getStringFromWasm0(ptr, len) {
|
|
167
|
+
ptr = ptr >>> 0;
|
|
168
|
+
return decodeText(ptr, len);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
let cachedUint8ArrayMemory0 = null;
|
|
172
|
+
function getUint8ArrayMemory0() {
|
|
173
|
+
if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0)
|
|
174
|
+
cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
|
|
175
|
+
return cachedUint8ArrayMemory0;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function handleError(f, args) {
|
|
179
|
+
try {
|
|
180
|
+
return f.apply(this, args);
|
|
181
|
+
} catch (e) {
|
|
182
|
+
const idx = addToExternrefTable0(e);
|
|
183
|
+
wasm.__wbindgen_exn_store(idx);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function isLikeNone(x) {
|
|
188
|
+
return x === undefined || x === null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function passArrayJsValueToWasm0(array, malloc) {
|
|
192
|
+
const ptr = malloc(array.length * 4, 4) >>> 0;
|
|
193
|
+
for (let i = 0; i < array.length; i++) {
|
|
194
|
+
const add = addToExternrefTable0(array[i]);
|
|
195
|
+
getDataViewMemory0().setUint32(ptr + 4 * i, add, true);
|
|
196
|
+
}
|
|
197
|
+
WASM_VECTOR_LEN = array.length;
|
|
198
|
+
return ptr;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function passStringToWasm0(arg, malloc, realloc) {
|
|
202
|
+
if (realloc === undefined) {
|
|
203
|
+
const buf = cachedTextEncoder.encode(arg);
|
|
204
|
+
const ptr = malloc(buf.length, 1) >>> 0;
|
|
205
|
+
getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
|
|
206
|
+
WASM_VECTOR_LEN = buf.length;
|
|
207
|
+
return ptr;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
let len = arg.length;
|
|
211
|
+
let ptr = malloc(len, 1) >>> 0;
|
|
212
|
+
const mem = getUint8ArrayMemory0();
|
|
213
|
+
|
|
214
|
+
let offset = 0;
|
|
215
|
+
for (; offset < len; offset++) {
|
|
216
|
+
const code = arg.charCodeAt(offset);
|
|
217
|
+
if (code > 0x7F) break;
|
|
218
|
+
mem[ptr + offset] = code;
|
|
219
|
+
}
|
|
220
|
+
if (offset !== len) {
|
|
221
|
+
if (offset !== 0) arg = arg.slice(offset);
|
|
222
|
+
ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
|
|
223
|
+
const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
|
|
224
|
+
const ret = cachedTextEncoder.encodeInto(arg, view);
|
|
225
|
+
offset += ret.written;
|
|
226
|
+
ptr = realloc(ptr, len, offset, 1) >>> 0;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
WASM_VECTOR_LEN = offset;
|
|
230
|
+
return ptr;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function takeFromExternrefTable0(idx) {
|
|
234
|
+
const value = wasm.__wbindgen_externrefs.get(idx);
|
|
235
|
+
wasm.__externref_table_dealloc(idx);
|
|
236
|
+
return value;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const cachedTextDecoder = new TextDecoder('utf-8', {ignoreBOM: true, fatal: true});
|
|
240
|
+
cachedTextDecoder.decode();
|
|
241
|
+
function decodeText(ptr, len) {
|
|
242
|
+
return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const cachedTextEncoder = new TextEncoder();
|
|
246
|
+
if (!('encodeInto' in cachedTextEncoder)) {
|
|
247
|
+
cachedTextEncoder.encodeInto = function(arg, view) {
|
|
248
|
+
const buf = cachedTextEncoder.encode(arg);
|
|
249
|
+
view.set(buf);
|
|
250
|
+
return {read: arg.length, written: buf.length};
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
let WASM_VECTOR_LEN = 0;
|
|
255
|
+
|
|
256
|
+
/** Instantiate the immunum WASM module from raw bytes or an ArrayBuffer.
|
|
257
|
+
* Must be called once before constructing any Annotator. Safe to call multiple
|
|
258
|
+
* times — subsequent calls are no-ops. */
|
|
259
|
+
export async function initImmunum(wasmSource) {
|
|
260
|
+
if (wasm) return;
|
|
261
|
+
const imports = __wbg_get_imports();
|
|
262
|
+
let inst;
|
|
263
|
+
if (wasmSource instanceof WebAssembly.Module) {
|
|
264
|
+
inst = await WebAssembly.instantiate(wasmSource, imports);
|
|
265
|
+
} else {
|
|
266
|
+
const result = await WebAssembly.instantiate(wasmSource, imports);
|
|
267
|
+
inst = result.instance;
|
|
268
|
+
}
|
|
269
|
+
wasm = inst.exports;
|
|
270
|
+
wasm.__wbindgen_start();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export function isImmunumReady() {
|
|
274
|
+
return wasm !== null;
|
|
275
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// @ts-ignore -- file-loader emits the .wasm file to the package's dist/ dir.
|
|
2
|
+
// The returned string is the path relative to the emitted bundle; we take only
|
|
3
|
+
// the basename because the worker already lives in the same dist/ directory.
|
|
4
|
+
import wasmEmittedPath from 'immunum/immunum_bg.wasm';
|
|
5
|
+
// @ts-ignore -- plain JS port of immunum.js
|
|
6
|
+
import {Annotator, initImmunum} from './immunum-glue.js';
|
|
7
|
+
|
|
8
|
+
import type {ImmunumNumberingRow, ImmunumWorkerRequest, ImmunumWorkerResponse} from './types';
|
|
9
|
+
|
|
10
|
+
const ctx: Worker = self as any;
|
|
11
|
+
|
|
12
|
+
const DEFAULT_CHAINS = ['H', 'K', 'L'];
|
|
13
|
+
let _annotatorCache: {key: string; annotator: any} | null = null;
|
|
14
|
+
let _initPromise: Promise<void> | null = null;
|
|
15
|
+
|
|
16
|
+
function resolveWasmUrl(): string {
|
|
17
|
+
// Worker script URL (e.g. http://host/packages/Bio/dist/NNN.js) is the right
|
|
18
|
+
// base — the wasm sits in the same dist/ folder. Stripping off any path
|
|
19
|
+
// prefix from file-loader's output (which may include a publicPath like
|
|
20
|
+
// 'dist/') keeps us at the basename regardless of how webpack resolved it.
|
|
21
|
+
const path = wasmEmittedPath as unknown as string;
|
|
22
|
+
const basename = path.substring(path.lastIndexOf('/') + 1);
|
|
23
|
+
return new URL(basename, self.location.href).toString();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function ensureInit(): Promise<void> {
|
|
27
|
+
if (!_initPromise) {
|
|
28
|
+
_initPromise = (async () => {
|
|
29
|
+
const wasmUrl = resolveWasmUrl();
|
|
30
|
+
const resp = await fetch(wasmUrl);
|
|
31
|
+
if (!resp.ok) throw new Error(`Failed to fetch immunum WASM from ${wasmUrl} (HTTP ${resp.status})`);
|
|
32
|
+
const bytes = await resp.arrayBuffer();
|
|
33
|
+
await initImmunum(bytes);
|
|
34
|
+
})();
|
|
35
|
+
}
|
|
36
|
+
return _initPromise;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function getAnnotator(chains: string[], scheme: string, minConfidence: number | null): any {
|
|
40
|
+
const key = `${scheme}|${chains.join(',')}|${minConfidence ?? 'null'}`;
|
|
41
|
+
if (_annotatorCache && _annotatorCache.key === key) return _annotatorCache.annotator;
|
|
42
|
+
if (_annotatorCache) {
|
|
43
|
+
try { _annotatorCache.annotator.free(); } catch { /* ignore */ }
|
|
44
|
+
}
|
|
45
|
+
const annotator = new Annotator(chains, scheme, minConfidence ?? undefined);
|
|
46
|
+
_annotatorCache = {key, annotator};
|
|
47
|
+
return annotator;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const VALID_AA = new Set('ACDEFGHIKLMNPQRSTVWY');
|
|
51
|
+
|
|
52
|
+
/** Normalizes a raw input cell to a single-letter AA sequence: strips gaps, whitespace,
|
|
53
|
+
* non-standard characters, and uppercases. Matches the Python `extract_sequence` helper
|
|
54
|
+
* used by the antpack reference script so downstream char-index maps line up. */
|
|
55
|
+
function extractSequence(raw: string | null | undefined): string {
|
|
56
|
+
if (!raw || typeof raw !== 'string') return '';
|
|
57
|
+
let out = '';
|
|
58
|
+
const s = raw.trim();
|
|
59
|
+
for (let i = 0; i < s.length; i++) {
|
|
60
|
+
const ch = s[i].toUpperCase();
|
|
61
|
+
if (VALID_AA.has(ch)) out += ch;
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function chainToGroup(chain: string | null): string {
|
|
67
|
+
if (!chain) return '';
|
|
68
|
+
if (chain === 'H') return 'Heavy';
|
|
69
|
+
if (chain === 'K' || chain === 'L') return 'Light';
|
|
70
|
+
return '';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function numberOne(annotator: any, rawSeq: string): ImmunumNumberingRow {
|
|
74
|
+
const seq = extractSequence(rawSeq);
|
|
75
|
+
if (!seq || seq.length < 10) {
|
|
76
|
+
return {
|
|
77
|
+
positionNames: '',
|
|
78
|
+
chainType: '',
|
|
79
|
+
chainCode: '',
|
|
80
|
+
numberingDetail: [],
|
|
81
|
+
numberingMap: {},
|
|
82
|
+
confidence: 0,
|
|
83
|
+
error: seq ? 'sequence too short' : 'empty sequence',
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let result;
|
|
88
|
+
try {
|
|
89
|
+
result = annotator.number(seq);
|
|
90
|
+
} catch (e: any) {
|
|
91
|
+
return {
|
|
92
|
+
positionNames: '', chainType: '', chainCode: '',
|
|
93
|
+
numberingDetail: [], numberingMap: {}, confidence: 0,
|
|
94
|
+
error: (e && e.message) || String(e),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!result || result.error || !result.numbering) {
|
|
99
|
+
return {
|
|
100
|
+
positionNames: '', chainType: '', chainCode: '',
|
|
101
|
+
numberingDetail: [], numberingMap: {}, confidence: result?.confidence ?? 0,
|
|
102
|
+
error: result?.error ?? 'no alignment',
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// numbering is a Map<posCode, residue>; iterate in insertion (IMGT) order.
|
|
107
|
+
// Each entry corresponds to a single residue at consecutive input indices
|
|
108
|
+
// starting at query_start (immunum semantics).
|
|
109
|
+
const posNames: string[] = [];
|
|
110
|
+
const numberingDetail: {position: string; aa: string}[] = [];
|
|
111
|
+
const numberingMap: Record<string, number> = {};
|
|
112
|
+
|
|
113
|
+
let charIdx = (result.query_start ?? 0) | 0;
|
|
114
|
+
const numbering: Map<string, string> = result.numbering;
|
|
115
|
+
for (const [posCode, residue] of numbering.entries()) {
|
|
116
|
+
posNames.push(posCode);
|
|
117
|
+
numberingDetail.push({position: posCode, aa: residue});
|
|
118
|
+
numberingMap[posCode] = charIdx;
|
|
119
|
+
charIdx++;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
positionNames: posNames.join(', '),
|
|
124
|
+
chainType: chainToGroup(result.chain),
|
|
125
|
+
chainCode: result.chain ?? '',
|
|
126
|
+
numberingDetail,
|
|
127
|
+
numberingMap,
|
|
128
|
+
confidence: result.confidence ?? 0,
|
|
129
|
+
error: '',
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
ctx.addEventListener('message', async (e: MessageEvent) => {
|
|
134
|
+
const req: ImmunumWorkerRequest = e.data.req;
|
|
135
|
+
const port: MessagePort = e.ports[0];
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
await ensureInit();
|
|
139
|
+
|
|
140
|
+
if (req.op === 'init') {
|
|
141
|
+
port.postMessage({ok: true} satisfies ImmunumWorkerResponse);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (req.op === 'number') {
|
|
146
|
+
const chains = req.chains && req.chains.length > 0 ? req.chains : DEFAULT_CHAINS;
|
|
147
|
+
const annotator = getAnnotator(chains, req.scheme, req.minConfidence ?? null);
|
|
148
|
+
const rows: ImmunumNumberingRow[] = new Array(req.sequences.length);
|
|
149
|
+
for (let i = 0; i < req.sequences.length; i++)
|
|
150
|
+
rows[i] = numberOne(annotator, req.sequences[i]);
|
|
151
|
+
port.postMessage({ok: true, rows} satisfies ImmunumWorkerResponse);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
port.postMessage({ok: false, error: `Unknown op: ${(req as any).op}`} satisfies ImmunumWorkerResponse);
|
|
156
|
+
} catch (err: any) {
|
|
157
|
+
port.postMessage({ok: false, error: (err && err.message) || String(err)} satisfies ImmunumWorkerResponse);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import * as DG from 'datagrok-api/dg';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
NumberingScheme, ChainType, SCHEME_REGIONS,
|
|
5
|
+
} from '@datagrok-libraries/bio/src/utils/macromolecule/numbering-schemes';
|
|
6
|
+
|
|
7
|
+
import {numberSequencesWithImmunum} from './immunum-client';
|
|
8
|
+
|
|
9
|
+
/** Normalizes an incoming scheme string to the immunum library's accepted values.
|
|
10
|
+
* The engine only supports 'imgt' and 'kabat'; the dialog's scheme dropdown is
|
|
11
|
+
* populated from the `choices` option on this function's `scheme` parameter, so
|
|
12
|
+
* in practice only those two values reach here. */
|
|
13
|
+
function toImmunumScheme(scheme: string): 'imgt' | 'kabat' {
|
|
14
|
+
return (scheme || '').toLowerCase() === 'kabat' ? 'kabat' : 'imgt';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Converts the scheme string to the enum value used by the region-table lookup. */
|
|
18
|
+
function toSchemeEnum(scheme: string): NumberingScheme {
|
|
19
|
+
return (scheme || '').toLowerCase() === 'kabat' ? NumberingScheme.Kabat : NumberingScheme.IMGT;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Immunum returns 'Heavy' / 'Light' but region tables key on Heavy / Light_Kappa
|
|
23
|
+
* / Light_Lambda. Use chain code to disambiguate: K → Light_Kappa, L → Light_Lambda.
|
|
24
|
+
* Unknown chains fall back to Heavy so the dialog still gets *some* region set. */
|
|
25
|
+
function toRegionChainKey(chainGroup: string, chainCode: string): ChainType {
|
|
26
|
+
if (chainGroup === 'Light')
|
|
27
|
+
return chainCode === 'L' ? ChainType.Light_Lambda : ChainType.Light_Kappa;
|
|
28
|
+
return ChainType.Heavy;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Builds a row's region-annotation JSON matching the antpack script output:
|
|
32
|
+
* each entry has id/name/description/start/end/visualType/category/sourceScheme.
|
|
33
|
+
* The `start` and `end` are **scheme-position codes** (strings), not character
|
|
34
|
+
* indices — the dialog resolves them to chars via numbering_map. */
|
|
35
|
+
function buildRegionAnnotations(
|
|
36
|
+
schemeLabel: string, chainGroup: string, chainCode: string,
|
|
37
|
+
): any[] {
|
|
38
|
+
if (!chainGroup) return [];
|
|
39
|
+
const schemeEnum = toSchemeEnum(schemeLabel);
|
|
40
|
+
const regions = SCHEME_REGIONS[schemeEnum];
|
|
41
|
+
const chainKey = toRegionChainKey(chainGroup, chainCode);
|
|
42
|
+
const regionDefs = regions?.[chainKey];
|
|
43
|
+
if (!regionDefs) return [];
|
|
44
|
+
const schemeLower = schemeLabel.toLowerCase();
|
|
45
|
+
return regionDefs.map((r) => ({
|
|
46
|
+
id: `${schemeLower}-${chainGroup}-${r.name}`.toLowerCase(),
|
|
47
|
+
name: r.name,
|
|
48
|
+
description: `${r.name} (${schemeLabel.toUpperCase()} ${r.startPosition}-${r.endPosition})`,
|
|
49
|
+
start: r.startPosition,
|
|
50
|
+
end: r.endPosition,
|
|
51
|
+
visualType: 'region',
|
|
52
|
+
category: 'structure',
|
|
53
|
+
sourceScheme: schemeLabel.toUpperCase(),
|
|
54
|
+
autoGenerated: true,
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Runs immunum numbering on a sequence column and returns a DataFrame matching
|
|
59
|
+
* the shape expected by `numbering-ui.ts` (same columns as the antpack Python
|
|
60
|
+
* script `number_antibody.py`):
|
|
61
|
+
* - `position_names` — comma-separated list of numbered position codes
|
|
62
|
+
* - `chain_type` — 'Heavy' | 'Light' | ''
|
|
63
|
+
* - `annotations_json` — JSON array of FR/CDR region definitions
|
|
64
|
+
* - `numbering_detail` — JSON array of {position, aa} per numbered residue
|
|
65
|
+
* - `numbering_map` — JSON object mapping position code → char index
|
|
66
|
+
*
|
|
67
|
+
* The numbering runs in a web worker (see `immunum.worker.ts`) so the main
|
|
68
|
+
* thread stays responsive even for large columns. */
|
|
69
|
+
export async function numberAntibodyColumn(
|
|
70
|
+
seqCol: DG.Column<string>,
|
|
71
|
+
scheme: string,
|
|
72
|
+
): Promise<DG.DataFrame> {
|
|
73
|
+
const n = seqCol.length;
|
|
74
|
+
const sequences: string[] = new Array(n);
|
|
75
|
+
for (let i = 0; i < n; i++)
|
|
76
|
+
sequences[i] = seqCol.get(i) ?? '';
|
|
77
|
+
|
|
78
|
+
const immunumScheme = toImmunumScheme(scheme);
|
|
79
|
+
const rows = await numberSequencesWithImmunum(sequences, immunumScheme);
|
|
80
|
+
|
|
81
|
+
const posCol = DG.Column.fromType(DG.COLUMN_TYPE.STRING, 'position_names', n);
|
|
82
|
+
const chainCol = DG.Column.fromType(DG.COLUMN_TYPE.STRING, 'chain_type', n);
|
|
83
|
+
const annotCol = DG.Column.fromType(DG.COLUMN_TYPE.STRING, 'annotations_json', n);
|
|
84
|
+
const detailCol = DG.Column.fromType(DG.COLUMN_TYPE.STRING, 'numbering_detail', n);
|
|
85
|
+
const mapCol = DG.Column.fromType(DG.COLUMN_TYPE.STRING, 'numbering_map', n);
|
|
86
|
+
|
|
87
|
+
for (let i = 0; i < n; i++) {
|
|
88
|
+
const r = rows[i];
|
|
89
|
+
if (!r || r.error || !r.positionNames) {
|
|
90
|
+
posCol.set(i, '');
|
|
91
|
+
chainCol.set(i, '');
|
|
92
|
+
annotCol.set(i, '[]');
|
|
93
|
+
detailCol.set(i, '');
|
|
94
|
+
mapCol.set(i, '');
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
posCol.set(i, r.positionNames);
|
|
98
|
+
chainCol.set(i, r.chainType);
|
|
99
|
+
annotCol.set(i, JSON.stringify(buildRegionAnnotations(scheme, r.chainType, r.chainCode)));
|
|
100
|
+
detailCol.set(i, JSON.stringify(r.numberingDetail));
|
|
101
|
+
mapCol.set(i, JSON.stringify(r.numberingMap));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return DG.DataFrame.fromColumns([posCol, chainCol, annotCol, detailCol, mapCol]);
|
|
105
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** Position → amino-acid entry in the numbering detail JSON. */
|
|
2
|
+
export interface ImmunumNumberingEntry {
|
|
3
|
+
/** Position code (e.g. "1", "27A"). */
|
|
4
|
+
position: string;
|
|
5
|
+
/** Single-letter residue at this position. */
|
|
6
|
+
aa: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Per-row numbering result produced by the immunum worker. */
|
|
10
|
+
export interface ImmunumNumberingRow {
|
|
11
|
+
/** Comma-separated position codes in scheme order. Empty string on failure. */
|
|
12
|
+
positionNames: string;
|
|
13
|
+
/** 'Heavy' / 'Light' / '' — UI-facing chain group. */
|
|
14
|
+
chainType: string;
|
|
15
|
+
/** Immunum chain code: H, K, L, A, B, G, D, or ''. */
|
|
16
|
+
chainCode: string;
|
|
17
|
+
/** Ordered list of {position, aa} entries for the aligned region. */
|
|
18
|
+
numberingDetail: ImmunumNumberingEntry[];
|
|
19
|
+
/** Map from position code → character index in the extracted (gap-free) sequence. */
|
|
20
|
+
numberingMap: Record<string, number>;
|
|
21
|
+
/** Alignment confidence in [0, 1]. */
|
|
22
|
+
confidence: number;
|
|
23
|
+
/** Error message, empty string on success. */
|
|
24
|
+
error: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type ImmunumWorkerRequest =
|
|
28
|
+
| {op: 'init'}
|
|
29
|
+
| {
|
|
30
|
+
op: 'number';
|
|
31
|
+
sequences: string[];
|
|
32
|
+
/** Immunum scheme: 'imgt' or 'kabat'. */
|
|
33
|
+
scheme: string;
|
|
34
|
+
/** Case-insensitive chain codes (e.g. ['H', 'K', 'L']). Defaults to H/K/L. */
|
|
35
|
+
chains?: string[];
|
|
36
|
+
/** Minimum confidence in [0, 1]; null/undefined → library default (0.5). */
|
|
37
|
+
minConfidence?: number | null;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type ImmunumWorkerResponse =
|
|
41
|
+
| {ok: true; rows?: ImmunumNumberingRow[]}
|
|
42
|
+
| {ok: false; error: string};
|
|
43
|
+
|
|
44
|
+
/** Supported schemes by immunum. Chothia/AHo are not supported — the UI engine
|
|
45
|
+
* dropdown lists all schemes globally so the immunum path falls back for the
|
|
46
|
+
* unsupported ones by returning empty rows with errors. */
|
|
47
|
+
export const IMMUNUM_SCHEMES = ['imgt', 'kabat'] as const;
|
|
48
|
+
export type ImmunumScheme = typeof IMMUNUM_SCHEMES[number];
|