@datagrok/bio 2.26.0 → 2.26.1
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 +4 -0
- package/dist/282.js +1 -1
- package/dist/282.js.map +1 -1
- package/dist/422.js +1 -1
- package/dist/422.js.map +1 -1
- 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 +2 -2
- package/src/package-api.ts +7 -0
- package/src/package.g.ts +9 -0
- package/src/package.ts +10 -0
- package/src/tests/substructure-filters-tests.ts +1 -0
- package/src/utils/annotations/liability-scanner.ts +1 -1
- package/src/utils/annotations/numbering-ui.ts +23 -1
- package/src/utils/get-region.ts +103 -17
- package/src/utils/sequence-column-input.ts +57 -0
- package/src/viewers/vd-regions-viewer.ts +2 -0
- package/test-console-output-1.log +568 -518
- package/test-record-1.mp4 +0 -0
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"name": "Davit Rizhinashvili",
|
|
6
6
|
"email": "drizhinashvili@datagrok.ai"
|
|
7
7
|
},
|
|
8
|
-
"version": "2.26.
|
|
8
|
+
"version": "2.26.1",
|
|
9
9
|
"description": "Bioinformatics support (import/export of sequences, conversion, visualization, analysis). [See more](https://github.com/datagrok-ai/public/blob/master/packages/Bio/README.md) for details.",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
],
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@biowasm/aioli": "^3.1.0",
|
|
47
|
-
"@datagrok-libraries/bio": "^5.63.
|
|
47
|
+
"@datagrok-libraries/bio": "^5.63.2",
|
|
48
48
|
"@datagrok-libraries/chem-meta": "^1.2.9",
|
|
49
49
|
"@datagrok-libraries/math": "^1.2.6",
|
|
50
50
|
"@datagrok-libraries/ml": "^6.10.9",
|
package/src/package-api.ts
CHANGED
|
@@ -178,6 +178,13 @@ export namespace funcs {
|
|
|
178
178
|
return await grok.functions.call('Bio:ManageAnnotations', {});
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
/**
|
|
182
|
+
Creates a new input for sequence columns with ability to extract a region
|
|
183
|
+
*/
|
|
184
|
+
export async function sequenceColumnInput(name: string , options: any ): Promise<any> {
|
|
185
|
+
return await grok.functions.call('Bio:SequenceColumnInput', { name, options });
|
|
186
|
+
}
|
|
187
|
+
|
|
181
188
|
/**
|
|
182
189
|
Detects pairs of molecules with similar structure and significant difference in any given property
|
|
183
190
|
*/
|
package/src/package.g.ts
CHANGED
|
@@ -244,6 +244,15 @@ export function manageAnnotations() : void {
|
|
|
244
244
|
PackageFunctions.manageAnnotations();
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
+
//name: Sequence Column Input
|
|
248
|
+
//description: Creates a new input for sequence columns with ability to extract a region
|
|
249
|
+
//input: string name
|
|
250
|
+
//input: dynamic options
|
|
251
|
+
//output: dynamic result
|
|
252
|
+
export function sequenceColumnInput(name: string, options: any) : any {
|
|
253
|
+
return PackageFunctions.sequenceColumnInput(name, options);
|
|
254
|
+
}
|
|
255
|
+
|
|
247
256
|
//name: Sequence Activity Cliffs
|
|
248
257
|
//description: Detects pairs of molecules with similar structure and significant difference in any given property
|
|
249
258
|
//input: dataframe table { description: Input data table }
|
package/src/package.ts
CHANGED
|
@@ -79,6 +79,8 @@ import {handleSequenceHeaderRendering} from './widgets/sequence-scrolling-widget
|
|
|
79
79
|
import {PolymerType} from '@datagrok-libraries/js-draw-lite/src/types/org';
|
|
80
80
|
import {BilnNotationProvider} from './utils/biln';
|
|
81
81
|
import {showMonomerCollectionsView} from './utils/monomer-lib/monomer-collections-view';
|
|
82
|
+
import {ISequenceColumnInput} from '@datagrok-libraries/bio/src/utils/sequence-column-input';
|
|
83
|
+
import {SequenceColumnInput} from './utils/sequence-column-input';
|
|
82
84
|
|
|
83
85
|
import * as api from './package-api';
|
|
84
86
|
export const _package = new BioPackage(/*{debug: true}/**/);
|
|
@@ -488,6 +490,14 @@ export class PackageFunctions {
|
|
|
488
490
|
import('./utils/annotations/annotation-manager-ui').then((m) => m.showAnnotationManagerDialog());
|
|
489
491
|
}
|
|
490
492
|
|
|
493
|
+
@grok.decorators.func({
|
|
494
|
+
name: 'Sequence Column Input',
|
|
495
|
+
description: 'Creates a new input for sequence columns with ability to extract a region',
|
|
496
|
+
})
|
|
497
|
+
static sequenceColumnInput(name: string, options: any): ISequenceColumnInput {
|
|
498
|
+
return SequenceColumnInput.create(name, options);
|
|
499
|
+
}
|
|
500
|
+
|
|
491
501
|
@grok.decorators.func({
|
|
492
502
|
name: 'Sequence Activity Cliffs',
|
|
493
503
|
description: 'Detects pairs of molecules with similar structure and significant difference in any given property',
|
|
@@ -428,6 +428,7 @@ category('bio-substructure-filters', async () => {
|
|
|
428
428
|
await awaitGrid(view.grid);
|
|
429
429
|
|
|
430
430
|
const seqFilter = fg.filters[0] as BioSubstructureFilter;
|
|
431
|
+
await awaitCheck(() => seqFilter.bioFilter !== null, 'FastaBioFilter hasn\'t been created', 1000);
|
|
431
432
|
const seqBf = seqFilter.bioFilter as FastaBioFilter;
|
|
432
433
|
await testEvent(df.onRowsFiltered, () => {}, () => {
|
|
433
434
|
seqBf.props = new BioFilterProps(fSubStr, undefined, _package.logger);
|
|
@@ -45,7 +45,7 @@ function getCanonicalString(sh: ISeqHandler, rowIdx: number): string {
|
|
|
45
45
|
const splitted = sh.getSplitted(rowIdx);
|
|
46
46
|
const chars: string[] = new Array(splitted.length);
|
|
47
47
|
for (let i = 0; i < splitted.length; i++)
|
|
48
|
-
chars[i] = splitted.
|
|
48
|
+
chars[i] = splitted.getOriginal(i);
|
|
49
49
|
return chars.join('');
|
|
50
50
|
}
|
|
51
51
|
|
|
@@ -14,6 +14,8 @@ import {
|
|
|
14
14
|
} from './annotation-manager';
|
|
15
15
|
import {_package} from '../../package';
|
|
16
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';
|
|
17
19
|
|
|
18
20
|
const BUILTIN_ENGINE_KEY = '__builtin__';
|
|
19
21
|
const BUILTIN_ENGINE_LABEL = 'Built-in (TypeScript)';
|
|
@@ -34,8 +36,10 @@ function discoverEngines(): NumberingEngine[] {
|
|
|
34
36
|
const engines: NumberingEngine[] = [];
|
|
35
37
|
|
|
36
38
|
const funcs = DG.Func.find({meta: {role: 'antibodyNumbering'}});
|
|
37
|
-
if (funcs.length === 0)
|
|
39
|
+
if (funcs.length === 0) {
|
|
40
|
+
grok.shell.error('No antibody numbering engines found. Make sure that Proteomics plugin is installed and up to date.');
|
|
38
41
|
throw new Error('No external antibody numbering engines found. Make sure that Proteomics plugin is installed and up to date.');
|
|
42
|
+
}
|
|
39
43
|
for (const f of funcs) {
|
|
40
44
|
const pkgName = f.package?.name ?? '';
|
|
41
45
|
const label = f.friendlyName || f.name;
|
|
@@ -439,6 +443,23 @@ function applyNumberingResults(
|
|
|
439
443
|
const regionAnnotations: SeqAnnotation[] = JSON.parse(annotationsJson);
|
|
440
444
|
const existing = getColumnAnnotations(alignment.alignedCol).filter((a) => a.category !== AnnotationCategory.Structure);
|
|
441
445
|
setColumnAnnotations(alignment.alignedCol, [...existing, ...regionAnnotations]);
|
|
446
|
+
// chunk for vd regions viewer if that becomes a desired feature in the future
|
|
447
|
+
// if (grok.shell.tv?.dataFrame === df) {
|
|
448
|
+
// (async () => {
|
|
449
|
+
// const vdRegionsViewer: VdRegionsViewer = await grok.shell.tv.dataFrame.plot.fromType('VdRegions',) as VdRegionsViewer;
|
|
450
|
+
// vdRegionsViewer.chains = [chainType];
|
|
451
|
+
// vdRegionsViewer.setData(regionAnnotations.map((a, i) => ({
|
|
452
|
+
// type: a.name?.toLowerCase().startsWith('cdr') ? VdRegionType.CDR : VdRegionType.FR,
|
|
453
|
+
// name: a.name,
|
|
454
|
+
// chain: chainType,
|
|
455
|
+
// order: i,
|
|
456
|
+
// sequenceColumnName: alignment.alignedCol.name,
|
|
457
|
+
// positionStartName: a.start ?? '',
|
|
458
|
+
// positionEndName: a.end ?? '',
|
|
459
|
+
// } satisfies VdRegion)));
|
|
460
|
+
// grok.shell.tv.addViewer(vdRegionsViewer);
|
|
461
|
+
// })();
|
|
462
|
+
// }
|
|
442
463
|
} catch (err) {
|
|
443
464
|
console.warn('Failed to set column-level annotations on aligned column:', err);
|
|
444
465
|
}
|
|
@@ -446,5 +467,6 @@ function applyNumberingResults(
|
|
|
446
467
|
}
|
|
447
468
|
|
|
448
469
|
df.fireValuesChanged();
|
|
470
|
+
|
|
449
471
|
grok.shell.info(`Numbering applied: ${schemeName}, chain type: ${chainType}`);
|
|
450
472
|
}
|
package/src/utils/get-region.ts
CHANGED
|
@@ -10,33 +10,119 @@ import {getAnnotationColumnName, cacheAllRowAnnotations} from './annotations/ann
|
|
|
10
10
|
import {_package} from '../package';
|
|
11
11
|
|
|
12
12
|
export function getRegionUI(col: DG.Column<string>): void {
|
|
13
|
+
showGetRegionDialog(col);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Shows the Get Region dialog for the given column.
|
|
17
|
+
* When the user confirms, the region column is extracted, added to the dataframe, and
|
|
18
|
+
* {@link onRegionCreated} is called with it (if provided).
|
|
19
|
+
* Returns the dialog instance for further control. */
|
|
20
|
+
export function showGetRegionDialog(
|
|
21
|
+
col: DG.Column<string>,
|
|
22
|
+
onRegionCreated?: (regCol: DG.Column<string>) => void,
|
|
23
|
+
): DG.Dialog {
|
|
13
24
|
const sh = _package.seqHelper.getSeqHandler(col);
|
|
14
25
|
|
|
15
26
|
const nameInput = ui.input.string('Name', {value: ''});
|
|
16
27
|
const startPositionInput = ui.input.choice('Start Position', {value: sh.posList[0], items: sh.posList,
|
|
17
|
-
onValueChanged: () =>
|
|
18
|
-
const endPositionInput = ui.input.choice('End Position', {value: sh.posList[sh.posList.length], items: sh.posList,
|
|
19
|
-
onValueChanged: () =>
|
|
28
|
+
onValueChanged: () => updateNamePlaceholder()});
|
|
29
|
+
const endPositionInput = ui.input.choice('End Position', {value: sh.posList[sh.posList.length - 1], items: sh.posList,
|
|
30
|
+
onValueChanged: () => updateNamePlaceholder()});
|
|
31
|
+
|
|
32
|
+
let selectedRegionName: string | null = null;
|
|
20
33
|
|
|
21
34
|
const getDefaultName = (): string => {
|
|
22
|
-
return
|
|
35
|
+
return selectedRegionName
|
|
36
|
+
? `${col.name}(${selectedRegionName}): ${startPositionInput.value}-${endPositionInput.value}`
|
|
37
|
+
: `${col.name}:${startPositionInput.value}-${endPositionInput.value}`;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const updateNamePlaceholder = (): void => {
|
|
41
|
+
if (!nameInput.value)
|
|
42
|
+
nameInput.input.setAttribute('placeholder', getDefaultName());
|
|
23
43
|
};
|
|
44
|
+
updateNamePlaceholder();
|
|
45
|
+
|
|
46
|
+
// Build region presets from annotations (new system) or .regions tag (legacy)
|
|
47
|
+
const regionInput = _buildRegionPresetsInput(col, startPositionInput, endPositionInput, (regionName) => {
|
|
48
|
+
selectedRegionName = regionName;
|
|
49
|
+
updateNamePlaceholder();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const inputsList: DG.InputBase[] = [];
|
|
53
|
+
if (regionInput) inputsList.push(regionInput);
|
|
54
|
+
inputsList.push(nameInput, startPositionInput, endPositionInput);
|
|
55
|
+
|
|
56
|
+
const dlg = ui.dialog({title: 'Get Region'}).add(ui.inputs(inputsList))
|
|
57
|
+
.onOK(() => {
|
|
58
|
+
const pi = DG.TaskBarProgressIndicator.create('Getting region...');
|
|
59
|
+
try {
|
|
60
|
+
const name: string = nameInput.value || getDefaultName();
|
|
61
|
+
const regCol = getRegionDo(col, startPositionInput.value, endPositionInput.value, name);
|
|
62
|
+
col.dataFrame.columns.add(regCol);
|
|
63
|
+
regCol.setTag(DG.TAGS.CELL_RENDERER, 'sequence');
|
|
64
|
+
onRegionCreated?.(regCol);
|
|
65
|
+
} catch (err: any) {
|
|
66
|
+
grok.shell.error(err.toString());
|
|
67
|
+
} finally { pi.close(); }
|
|
68
|
+
});
|
|
69
|
+
dlg.show();
|
|
70
|
+
return dlg;
|
|
71
|
+
}
|
|
24
72
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
73
|
+
/** Builds a Region preset dropdown from column annotations / legacy .regions tag.
|
|
74
|
+
* Returns null if the column has no annotated regions. */
|
|
75
|
+
function _buildRegionPresetsInput(
|
|
76
|
+
col: DG.Column<string>,
|
|
77
|
+
startInput: DG.InputBase<string | null>,
|
|
78
|
+
endInput: DG.InputBase<string | null>,
|
|
79
|
+
onRegionSelected?: (regionName: string | null) => void,
|
|
80
|
+
): DG.InputBase | null {
|
|
81
|
+
type RegionPreset = { name: string, start: string, end: string };
|
|
82
|
+
let regionList: RegionPreset[] | null = null;
|
|
83
|
+
|
|
84
|
+
// New annotation system
|
|
85
|
+
const annotationsTag: string | null = col.getTag(bioTAGS.annotations);
|
|
86
|
+
if (annotationsTag) {
|
|
31
87
|
try {
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
88
|
+
const annotations = JSON.parse(annotationsTag);
|
|
89
|
+
const structAnnots = annotations.filter(
|
|
90
|
+
(a: any) => a.category === AnnotationCategory.Structure && a.start && a.end);
|
|
91
|
+
if (structAnnots.length > 0) {
|
|
92
|
+
regionList = structAnnots.map((a: any) => ({
|
|
93
|
+
name: a.name, start: a.start, end: a.end,
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
96
|
+
} catch { /* ignore parse errors */ }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Legacy .regions tag
|
|
100
|
+
if (!regionList) {
|
|
101
|
+
const regionsTagTxt: string | null = col.getTag(bioTAGS.regions);
|
|
102
|
+
if (regionsTagTxt) {
|
|
103
|
+
try { regionList = JSON.parse(regionsTagTxt); } catch { /* ignore */ }
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!regionList || regionList.length === 0) return null;
|
|
108
|
+
|
|
109
|
+
const items = ['', ...regionList.map((r) => `${r.name}: ${r.start}-${r.end}`)];
|
|
110
|
+
const regionInput = ui.input.choice('Region', {
|
|
111
|
+
value: '', items: items,
|
|
112
|
+
onValueChanged: (value: string) => {
|
|
113
|
+
if (!value) {
|
|
114
|
+
onRegionSelected?.(null);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const preset = regionList!.find((r) => `${r.name}: ${r.start}-${r.end}` === value);
|
|
118
|
+
if (preset) {
|
|
119
|
+
startInput.value = preset.start;
|
|
120
|
+
endInput.value = preset.end;
|
|
121
|
+
onRegionSelected?.(preset.name);
|
|
122
|
+
}
|
|
123
|
+
},
|
|
39
124
|
});
|
|
125
|
+
return regionInput;
|
|
40
126
|
}
|
|
41
127
|
|
|
42
128
|
/** {@link startPosName} and {@link endPosName} are according positionNames tag (or default ['1', '2',...]) */
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as grok from 'datagrok-api/grok';
|
|
2
|
+
import * as ui from 'datagrok-api/ui';
|
|
3
|
+
import * as DG from 'datagrok-api/dg';
|
|
4
|
+
|
|
5
|
+
import {showGetRegionDialog} from './get-region';
|
|
6
|
+
import {ISequenceColumnInput} from '@datagrok-libraries/bio/src/utils/sequence-column-input';
|
|
7
|
+
|
|
8
|
+
/** A column input that filters to macromolecule columns and provides a
|
|
9
|
+
* "get region" button so users can extract a sub-region and use it instead. */
|
|
10
|
+
export class SequenceColumnInput implements ISequenceColumnInput {
|
|
11
|
+
private readonly colInput: DG.InputBase<DG.Column | null>;
|
|
12
|
+
|
|
13
|
+
private constructor(
|
|
14
|
+
name: string,
|
|
15
|
+
options: ui.input.IColumnInputInitOptions<DG.Column>,
|
|
16
|
+
) {
|
|
17
|
+
const filter = options.filter;
|
|
18
|
+
this.colInput = ui.input.column(name, {
|
|
19
|
+
...options,
|
|
20
|
+
filter: (col: DG.Column) => {
|
|
21
|
+
if (col.semType !== DG.SEMTYPE.MACROMOLECULE) return false;
|
|
22
|
+
return filter ? filter(col) : true;
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const regionIcon = ui.iconFA('cut', () => this.onRegionIconClick(), 'Extract a region from the sequence');
|
|
27
|
+
this.colInput.addOptions(regionIcon);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Creates a new SequenceColumnInput.
|
|
31
|
+
* @param name - Caption for the input.
|
|
32
|
+
* @param options - Same options as {@link ui.input.column}, table is required.
|
|
33
|
+
* The `filter` option is extended to always require semType === Macromolecule. */
|
|
34
|
+
static create(
|
|
35
|
+
name: string,
|
|
36
|
+
options: ui.input.IColumnInputInitOptions<DG.Column>,
|
|
37
|
+
): SequenceColumnInput {
|
|
38
|
+
return new SequenceColumnInput(name, options);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get root(): HTMLElement { return this.colInput.root; }
|
|
42
|
+
get value(): DG.Column | null { return this.colInput.value; }
|
|
43
|
+
set value(col: DG.Column | null) { this.colInput.value = col; }
|
|
44
|
+
get inputBase(): DG.InputBase<DG.Column | null> { return this.colInput; }
|
|
45
|
+
|
|
46
|
+
private onRegionIconClick(): void {
|
|
47
|
+
const col = this.colInput.value;
|
|
48
|
+
if (!col) {
|
|
49
|
+
grok.shell.warning('Select a macromolecule column first.');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
showGetRegionDialog(col, (regCol) => {
|
|
54
|
+
this.colInput.value = regCol;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -322,6 +322,8 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
322
322
|
for (const chain of this.chains) {
|
|
323
323
|
const region: VdRegion | undefined = regionsFiltered
|
|
324
324
|
.find((r) => r.order == orderList[orderI] && r.chain == chain);
|
|
325
|
+
if (!region)
|
|
326
|
+
continue;
|
|
325
327
|
logoPromiseList.push((async (): Promise<[number, string, WebLogoViewer]> => {
|
|
326
328
|
const wl: WebLogoViewer = await this.dataFrame.plot.fromType('WebLogo', {
|
|
327
329
|
sequenceColumnName: region!.sequenceColumnName,
|