@datagrok/bio 2.27.2 → 2.27.4
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/agents/package-knowledge.yaml +53 -0
- package/dist/455.js +1 -1
- package/dist/455.js.map +1 -1
- 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 +4 -2
- package/src/demo/bio01b-hierarchical-clustering-and-activity-cliffs.ts +24 -11
- 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/tests/detectors-tests.ts +5 -1
- package/src/tests/splitters-test.ts +8 -4
- package/src/tests/to-atomic-level-tests.ts +144 -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/src/utils/seq-helper/seq-handler.ts +25 -9
- package/test-console-output-1.log +582 -485
- 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
|
@@ -7,99 +7,48 @@ import {TAGS as bioTAGS, ALIGNMENT, ALPHABET, NOTATION} from '@datagrok-librarie
|
|
|
7
7
|
import {
|
|
8
8
|
SeqAnnotation, SeqAnnotationHit, AnnotationCategory,
|
|
9
9
|
} from '@datagrok-libraries/bio/src/utils/macromolecule/annotations';
|
|
10
|
-
import {NumberingScheme} from '@datagrok-libraries/bio/src/utils/macromolecule/numbering-schemes';
|
|
11
10
|
import {
|
|
12
11
|
setColumnAnnotations, getColumnAnnotations,
|
|
13
12
|
getOrCreateAnnotationColumn, getRowAnnotations, setRowAnnotations, mergeRowHits,
|
|
14
13
|
} from './annotation-manager';
|
|
15
|
-
|
|
16
|
-
import type {NumberingResult, Scheme} from '../antibody-numbering (WIP)';
|
|
17
|
-
import {VdRegionsViewer} from '../../viewers/vd-regions-viewer';
|
|
18
|
-
import { VdRegion, VdRegionType } from '@datagrok-libraries/bio/src/viewers/vd-regions';
|
|
19
|
-
|
|
20
|
-
const BUILTIN_ENGINE_KEY = '__builtin__';
|
|
21
|
-
const BUILTIN_ENGINE_LABEL = 'Built-in (TypeScript)';
|
|
22
|
-
|
|
23
|
-
/** An engine entry: either a dynamically discovered DG.Func or the built-in TS engine. */
|
|
14
|
+
/** An engine entry — a dynamically discovered DG.Func with meta.role: 'antibodyNumbering'. */
|
|
24
15
|
interface NumberingEngine {
|
|
25
|
-
/** Display label for the dropdown */
|
|
26
16
|
label: string;
|
|
27
|
-
/** Unique key —
|
|
17
|
+
/** Unique key — `${package}:${name}`. */
|
|
28
18
|
key: string;
|
|
29
|
-
|
|
30
|
-
func: DG.Func | null;
|
|
19
|
+
func: DG.Func;
|
|
31
20
|
}
|
|
32
21
|
|
|
33
|
-
/** Discovers all registered antibody numbering engines
|
|
34
|
-
*
|
|
22
|
+
/** Discovers all registered antibody numbering engines (functions with
|
|
23
|
+
* meta.role = 'antibodyNumbering'). Built-in engines: Bio package ships an
|
|
24
|
+
* immunum-WASM-based engine; other packages (Proteomics etc.) can register
|
|
25
|
+
* AntPack/ANARCI/etc. by adding their own meta.role function. */
|
|
35
26
|
function discoverEngines(): NumberingEngine[] {
|
|
36
27
|
const engines: NumberingEngine[] = [];
|
|
37
|
-
|
|
38
28
|
const funcs = DG.Func.find({meta: {role: 'antibodyNumbering'}});
|
|
39
29
|
if (funcs.length === 0) {
|
|
40
|
-
grok.shell.error('No antibody numbering engines found. Make sure
|
|
41
|
-
throw new Error('No
|
|
30
|
+
grok.shell.error('No antibody numbering engines found. Make sure the Bio package is up to date.');
|
|
31
|
+
throw new Error('No antibody numbering engines found.');
|
|
42
32
|
}
|
|
43
33
|
for (const f of funcs) {
|
|
44
34
|
const pkgName = f.package?.name ?? '';
|
|
45
|
-
const label = f.friendlyName || f.name;
|
|
46
35
|
engines.push({
|
|
47
|
-
label:
|
|
36
|
+
label: f.friendlyName || f.name,
|
|
48
37
|
key: pkgName ? `${pkgName}:${f.name}` : f.name,
|
|
49
38
|
func: f,
|
|
50
39
|
});
|
|
51
40
|
}
|
|
52
|
-
|
|
53
|
-
// Built-in TS engine is always last
|
|
54
|
-
engines.push({label: BUILTIN_ENGINE_LABEL, key: BUILTIN_ENGINE_KEY, func: null});
|
|
55
41
|
return engines;
|
|
56
42
|
}
|
|
57
43
|
|
|
58
|
-
/**
|
|
59
|
-
*
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
const numMap = DG.Column.fromType(DG.COLUMN_TYPE.STRING, 'numbering_map', n);
|
|
67
|
-
|
|
68
|
-
for (let i = 0; i < n; i++) {
|
|
69
|
-
const r = results[i];
|
|
70
|
-
if (r.error && r.percentIdentity < 0.3) {
|
|
71
|
-
posNames.set(i, '');
|
|
72
|
-
chainTypes.set(i, '');
|
|
73
|
-
annotJson.set(i, '[]');
|
|
74
|
-
numDetail.set(i, '');
|
|
75
|
-
numMap.set(i, '');
|
|
76
|
-
} else {
|
|
77
|
-
posNames.set(i, r.positionNames);
|
|
78
|
-
chainTypes.set(i, r.chainType);
|
|
79
|
-
annotJson.set(i, JSON.stringify(r.annotations));
|
|
80
|
-
numDetail.set(i, JSON.stringify(r.numberingDetail));
|
|
81
|
-
numMap.set(i, JSON.stringify(r.numberingMap));
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return DG.DataFrame.fromColumns([posNames, chainTypes, annotJson, numDetail, numMap]);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/** Runs the built-in TS numbering engine on all rows of a sequence column. */
|
|
89
|
-
async function runBuiltinNumbering(
|
|
90
|
-
seqCol: DG.Column<string>, schemeName: string,
|
|
91
|
-
): Promise<DG.DataFrame> {
|
|
92
|
-
const {numberSequences, extractSequence} = await import('../antibody-numbering (WIP)');
|
|
93
|
-
const scheme = schemeName.toLowerCase() as Scheme;
|
|
94
|
-
|
|
95
|
-
const sequences: string[] = [];
|
|
96
|
-
for (let i = 0; i < seqCol.length; i++) {
|
|
97
|
-
const raw = seqCol.get(i);
|
|
98
|
-
sequences.push(extractSequence(raw ?? ''));
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const results = numberSequences(sequences, scheme);
|
|
102
|
-
return numberingResultsToDataFrame(results);
|
|
44
|
+
/** Reads the `scheme` parameter's declared choices off an engine function.
|
|
45
|
+
* Engines advertise which schemes they support via the `choices` option of
|
|
46
|
+
* their `scheme: string` parameter; if the function has none we fall back to
|
|
47
|
+
* a single generic IMGT option so the dropdown is never empty. */
|
|
48
|
+
function getEngineSchemes(engine: NumberingEngine): string[] {
|
|
49
|
+
const schemeInput = engine.func.inputs.find((p) => p.name.toLowerCase() === 'scheme');
|
|
50
|
+
const choices = schemeInput?.choices ?? [];
|
|
51
|
+
return choices.length > 0 ? choices.slice() : ['imgt'];
|
|
103
52
|
}
|
|
104
53
|
|
|
105
54
|
export function showNumberingSchemeDialog(): void {
|
|
@@ -117,45 +66,40 @@ export function showNumberingSchemeDialog(): void {
|
|
|
117
66
|
|
|
118
67
|
const engines = discoverEngines();
|
|
119
68
|
const engineLabels = engines.map((e) => e.label);
|
|
120
|
-
const schemeChoices = Object.values(NumberingScheme);
|
|
121
69
|
|
|
122
70
|
const tableInput = ui.input.table('Table', {value: df});
|
|
123
71
|
const seqInput = ui.input.column('Sequence', {
|
|
124
72
|
table: df, value: seqCols[0],
|
|
125
73
|
filter: (col: DG.Column) => col.semType === DG.SEMTYPE.MACROMOLECULE,
|
|
126
74
|
});
|
|
127
|
-
const schemeInput = ui.input.choice('Scheme', {value: NumberingScheme.IMGT, items: schemeChoices});
|
|
128
75
|
const engineInput = ui.input.choice('Engine', {
|
|
129
76
|
value: engineLabels[0], items: engineLabels,
|
|
130
77
|
});
|
|
131
|
-
|
|
132
|
-
|
|
78
|
+
const initialSchemes = getEngineSchemes(engines[0]);
|
|
79
|
+
const schemeInput = ui.input.choice('Scheme', {value: initialSchemes[0], items: initialSchemes});
|
|
80
|
+
|
|
81
|
+
// Switch the scheme list when the user picks a different engine — each engine
|
|
82
|
+
// advertises its own set via the `scheme` parameter's `choices`.
|
|
83
|
+
engineInput.onChanged.subscribe(() => {
|
|
84
|
+
const selected = engines.find((e) => e.label === engineInput.value);
|
|
85
|
+
if (!selected) return;
|
|
86
|
+
const schemes = getEngineSchemes(selected);
|
|
87
|
+
const prev = schemeInput.value;
|
|
88
|
+
schemeInput.items = schemes;
|
|
89
|
+
schemeInput.value = schemes.includes(prev ?? '') ? prev : schemes[0];
|
|
90
|
+
});
|
|
133
91
|
|
|
134
92
|
const dialog = ui.dialog({title: 'Apply Antibody Numbering'})
|
|
135
|
-
.add(ui.inputs([tableInput, seqInput,
|
|
93
|
+
.add(ui.inputs([tableInput, seqInput, engineInput, schemeInput]))
|
|
136
94
|
.onOK(async () => {
|
|
137
95
|
const seqCol = seqInput.value!;
|
|
138
96
|
const schemeName = schemeInput.value!;
|
|
139
97
|
const selectedLabel = engineInput.value!;
|
|
140
|
-
const engine = engines.find((e) => e.label === selectedLabel) ?? engines[
|
|
98
|
+
const engine = engines.find((e) => e.label === selectedLabel) ?? engines[0];
|
|
141
99
|
const pi = DG.TaskBarProgressIndicator.create(`Applying ${schemeName} numbering...`);
|
|
142
100
|
try {
|
|
143
|
-
|
|
144
|
-
if (engine.func)
|
|
145
|
-
result = await engine.func.apply({df: df, seqCol: seqCol, scheme: schemeName.toLowerCase()});
|
|
146
|
-
else
|
|
147
|
-
result = await runBuiltinNumbering(seqCol, schemeName);
|
|
148
|
-
|
|
101
|
+
const result: DG.DataFrame = await engine.func.apply({df: df, seqCol: seqCol, scheme: schemeName});
|
|
149
102
|
applyNumberingResults(df, seqCol, result, schemeName, true, engine.label);
|
|
150
|
-
|
|
151
|
-
// // Open VD Regions viewer
|
|
152
|
-
// if (openVdRegions.value && grok.shell.tv) {
|
|
153
|
-
// try {
|
|
154
|
-
// await grok.shell.tv.dataFrame.plot.fromType('VdRegions', {});
|
|
155
|
-
// } catch (err) {
|
|
156
|
-
// console.warn('Could not open VD Regions viewer:', err);
|
|
157
|
-
// }
|
|
158
|
-
// }
|
|
159
103
|
} catch (err: any) {
|
|
160
104
|
grok.shell.error(`Numbering failed: ${err.message ?? err}`);
|
|
161
105
|
console.error(err);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ImmunumNumberingRow, ImmunumWorkerRequest, ImmunumWorkerResponse,
|
|
3
|
+
} from './types';
|
|
4
|
+
|
|
5
|
+
/** Short-lived worker: we spin it up per call and tear it down immediately
|
|
6
|
+
* after. Numbering is a one-shot batch operation — keeping the worker alive
|
|
7
|
+
* would pin the immunum WASM instance (≈700 KB) in memory indefinitely. */
|
|
8
|
+
function spawnWorker(): Worker {
|
|
9
|
+
return new Worker(new URL('./immunum.worker', import.meta.url));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function callOnce(worker: Worker, req: ImmunumWorkerRequest): Promise<ImmunumWorkerResponse> {
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
const ch = new MessageChannel();
|
|
15
|
+
ch.port1.onmessage = ({data}) => {
|
|
16
|
+
ch.port1.close();
|
|
17
|
+
resolve(data as ImmunumWorkerResponse);
|
|
18
|
+
};
|
|
19
|
+
ch.port1.onmessageerror = (err) => {
|
|
20
|
+
ch.port1.close();
|
|
21
|
+
reject(err);
|
|
22
|
+
};
|
|
23
|
+
worker.postMessage({req}, [ch.port2]);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Runs immunum numbering on a batch of sequences inside a web worker. Spawns a
|
|
28
|
+
* fresh worker for this call and terminates it before returning so the WASM
|
|
29
|
+
* instance is freed. Throws on WASM/init errors; individual per-row errors
|
|
30
|
+
* are attached to each row's `error` field. */
|
|
31
|
+
export async function numberSequencesWithImmunum(
|
|
32
|
+
sequences: string[],
|
|
33
|
+
scheme: string,
|
|
34
|
+
chains?: string[],
|
|
35
|
+
minConfidence?: number | null,
|
|
36
|
+
): Promise<ImmunumNumberingRow[]> {
|
|
37
|
+
const worker = spawnWorker();
|
|
38
|
+
try {
|
|
39
|
+
const resp = await callOnce(worker, {op: 'number', sequences, scheme, chains, minConfidence});
|
|
40
|
+
if (!resp.ok) throw new Error(resp.error);
|
|
41
|
+
return resp.rows ?? [];
|
|
42
|
+
} finally {
|
|
43
|
+
worker.terminate();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -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
|
+
});
|