@datagrok/bio 2.4.31 → 2.4.39
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/.eslintrc.json +6 -8
- package/README.md +22 -7
- package/detectors.js +21 -12
- package/dist/1.js +2 -0
- package/dist/1.js.map +1 -0
- package/dist/18.js +2 -0
- package/dist/18.js.map +1 -0
- package/dist/190.js +2 -0
- package/dist/190.js.map +1 -0
- package/dist/452.js +2 -0
- package/dist/452.js.map +1 -0
- package/dist/729.js +2 -0
- package/dist/729.js.map +1 -0
- package/dist/package-test.js +1 -1
- package/dist/package-test.js.map +1 -1
- package/dist/package.js +1 -1
- package/dist/package.js.map +1 -1
- package/files/libraries/broken-lib.sdf +136 -0
- package/files/libraries/group1/mock-lib-3.json +74 -0
- package/files/libraries/mock-lib-2.json +48 -0
- package/files/tests/100_3_clustests.csv +100 -0
- package/files/tests/100_3_clustests_empty_vals.csv +100 -0
- package/files/tests/peptides_motif-with-random_10000.csv +9998 -0
- package/package.json +4 -4
- package/scripts/sequence_generator.py +164 -48
- package/src/analysis/sequence-activity-cliffs.ts +7 -9
- package/src/analysis/sequence-diversity-viewer.ts +8 -3
- package/src/analysis/sequence-search-base-viewer.ts +4 -3
- package/src/analysis/sequence-similarity-viewer.ts +13 -7
- package/src/analysis/sequence-space.ts +15 -12
- package/src/analysis/workers/mm-distance-array-service.ts +48 -0
- package/src/analysis/workers/mm-distance-array-worker.ts +29 -0
- package/src/analysis/workers/mm-distance-worker-creator.ts +6 -9
- package/src/apps/web-logo-app.ts +34 -0
- package/src/calculations/monomerLevelMols.ts +10 -12
- package/src/demo/bio01-similarity-diversity.ts +4 -5
- package/src/demo/bio01a-hierarchical-clustering-and-sequence-space.ts +6 -7
- package/src/demo/bio01b-hierarchical-clustering-and-activity-cliffs.ts +7 -8
- package/src/demo/bio03-atomic-level.ts +1 -4
- package/src/demo/bio05-helm-msa-sequence-space.ts +6 -4
- package/src/demo/utils.ts +3 -4
- package/src/package-test.ts +1 -2
- package/src/package.ts +135 -82
- package/src/seq_align.ts +482 -483
- package/src/substructure-search/substructure-search.ts +3 -3
- package/src/tests/Palettes-test.ts +1 -1
- package/src/tests/WebLogo-positions-test.ts +12 -35
- package/src/tests/_first-tests.ts +1 -1
- package/src/tests/activity-cliffs-tests.ts +10 -7
- package/src/tests/activity-cliffs-utils.ts +6 -5
- package/src/tests/bio-tests.ts +20 -25
- package/src/tests/checkInputColumn-tests.ts +5 -11
- package/src/tests/converters-test.ts +19 -37
- package/src/tests/detectors-benchmark-tests.ts +35 -37
- package/src/tests/detectors-tests.ts +29 -34
- package/src/tests/detectors-weak-and-likely-tests.ts +11 -21
- package/src/tests/fasta-export-tests.ts +3 -3
- package/src/tests/fasta-handler-test.ts +2 -3
- package/src/tests/lib-tests.ts +2 -4
- package/src/tests/mm-distance-tests.ts +25 -17
- package/src/tests/monomer-libraries-tests.ts +1 -1
- package/src/tests/msa-tests.ts +12 -9
- package/src/tests/pepsea-tests.ts +6 -3
- package/src/tests/renderers-test.ts +13 -11
- package/src/tests/sequence-space-test.ts +10 -8
- package/src/tests/sequence-space-utils.ts +6 -4
- package/src/tests/similarity-diversity-tests.ts +47 -61
- package/src/tests/splitters-test.ts +14 -20
- package/src/tests/to-atomic-level-tests.ts +9 -17
- package/src/tests/units-handler-splitted-tests.ts +106 -0
- package/src/tests/units-handler-tests.ts +22 -26
- package/src/tests/utils/sequences-generators.ts +6 -2
- package/src/tests/utils.ts +10 -4
- package/src/tests/viewers.ts +1 -1
- package/src/utils/atomic-works.ts +49 -57
- package/src/utils/cell-renderer.ts +25 -8
- package/src/utils/check-input-column.ts +19 -4
- package/src/utils/constants.ts +3 -3
- package/src/utils/convert.ts +56 -23
- package/src/utils/monomer-lib.ts +83 -64
- package/src/utils/multiple-sequence-alignment-ui.ts +24 -21
- package/src/utils/multiple-sequence-alignment.ts +2 -2
- package/src/utils/pepsea.ts +17 -7
- package/src/utils/save-as-fasta.ts +11 -4
- package/src/utils/ui-utils.ts +1 -1
- package/src/viewers/vd-regions-viewer.ts +21 -22
- package/src/viewers/web-logo-viewer.ts +189 -154
- package/src/widgets/bio-substructure-filter.ts +9 -6
- package/src/widgets/representations.ts +11 -12
- package/tsconfig.json +1 -1
- package/dist/258.js +0 -2
- package/dist/258.js.map +0 -1
- package/dist/457.js +0 -2
- package/dist/457.js.map +0 -1
- package/dist/562.js +0 -2
- package/dist/562.js.map +0 -1
- package/dist/925.js +0 -2
- package/dist/925.js.map +0 -1
- package/src/analysis/workers/mm-distance-worker.ts +0 -16
package/src/utils/monomer-lib.ts
CHANGED
|
@@ -6,10 +6,17 @@ import {Observable, Subject} from 'rxjs';
|
|
|
6
6
|
import {IMonomerLib, Monomer} from '@datagrok-libraries/bio/src/types/index';
|
|
7
7
|
import {
|
|
8
8
|
createJsonMonomerLibFromSdf,
|
|
9
|
-
|
|
10
|
-
IMonomerLibHelper
|
|
9
|
+
IMonomerLibHelper,
|
|
11
10
|
} from '@datagrok-libraries/bio/src/monomer-works/monomer-utils';
|
|
11
|
+
import {HELM_REQUIRED_FIELDS as REQ, HELM_OPTIONAL_FIELDS as OPT} from '@datagrok-libraries/bio/src/utils/const';
|
|
12
|
+
import {_package} from '../package';
|
|
12
13
|
|
|
14
|
+
const _HELM_REQUIRED_FIELDS_ARRAY = [
|
|
15
|
+
REQ.SYMBOL, REQ.NAME, REQ.MOLFILE, REQ.AUTHOR, REQ.ID,
|
|
16
|
+
REQ.RGROUPS, REQ.SMILES, REQ.POLYMER_TYPE, REQ.MONOMER_TYPE, REQ.CREATE_DATE,
|
|
17
|
+
] as const;
|
|
18
|
+
|
|
19
|
+
const _HELM_OPTIONAL_FIELDS_ARRAY = [OPT.NATURAL_ANALOG, OPT.META] as const;
|
|
13
20
|
// -- Monomer libraries --
|
|
14
21
|
export const LIB_STORAGE_NAME = 'Libraries';
|
|
15
22
|
export const LIB_PATH = 'System:AppData/Bio/libraries/';
|
|
@@ -21,8 +28,12 @@ export type LibSettings = {
|
|
|
21
28
|
}
|
|
22
29
|
|
|
23
30
|
export async function getLibFileNameList(): Promise<string[]> {
|
|
24
|
-
|
|
25
|
-
|
|
31
|
+
// list files recursively because permissions are available for folders only
|
|
32
|
+
const res: string[] = await Promise.all((await grok.dapi.files.list(LIB_PATH, true, ''))
|
|
33
|
+
.map(async (it) => {
|
|
34
|
+
// Get relative path (to LIB_PATH)
|
|
35
|
+
return it.fullPath.substring(LIB_PATH.length);
|
|
36
|
+
}));
|
|
26
37
|
return res;
|
|
27
38
|
}
|
|
28
39
|
|
|
@@ -41,36 +52,39 @@ export async function setUserLibSetting(value: LibSettings): Promise<void> {
|
|
|
41
52
|
}
|
|
42
53
|
|
|
43
54
|
export class MonomerLib implements IMonomerLib {
|
|
44
|
-
|
|
55
|
+
public readonly error: string | undefined;
|
|
56
|
+
|
|
57
|
+
private _monomers: { [polymerType: string]: { [monomerSymbol: string]: Monomer } } = {};
|
|
45
58
|
private _onChanged = new Subject<any>();
|
|
46
59
|
|
|
47
|
-
constructor(monomers: { [
|
|
60
|
+
constructor(monomers: { [polymerType: string]: { [monomerSymbol: string]: Monomer } }, error?: string) {
|
|
48
61
|
this._monomers = monomers;
|
|
62
|
+
this.error = error;
|
|
49
63
|
}
|
|
50
64
|
|
|
51
|
-
getMonomer(
|
|
52
|
-
if (
|
|
53
|
-
return this._monomers![
|
|
65
|
+
getMonomer(polymerType: string, monomerSymbol: string): Monomer | null {
|
|
66
|
+
if (polymerType in this._monomers! && monomerSymbol in this._monomers![polymerType])
|
|
67
|
+
return this._monomers![polymerType][monomerSymbol];
|
|
54
68
|
else
|
|
55
69
|
return null;
|
|
56
70
|
}
|
|
57
71
|
|
|
58
|
-
|
|
72
|
+
getPolymerTypes(): string[] {
|
|
59
73
|
return Object.keys(this._monomers);
|
|
60
74
|
}
|
|
61
75
|
|
|
62
|
-
|
|
63
|
-
const res: { [
|
|
76
|
+
getMonomerMolsByPolymerType(polymerType: string): { [monomerSymbol: string]: string } {
|
|
77
|
+
const res: { [monomerSymbol: string]: string } = {};
|
|
64
78
|
|
|
65
|
-
Object.keys(this._monomers[
|
|
66
|
-
res[monomerSymbol] = this._monomers[
|
|
79
|
+
Object.keys(this._monomers[polymerType]).forEach((monomerSymbol) => {
|
|
80
|
+
res[monomerSymbol] = this._monomers[polymerType][monomerSymbol].molfile;
|
|
67
81
|
});
|
|
68
82
|
|
|
69
83
|
return res;
|
|
70
84
|
}
|
|
71
85
|
|
|
72
|
-
|
|
73
|
-
return Object.keys(this._monomers[
|
|
86
|
+
getMonomerSymbolsByType(polymerType: string): string[] {
|
|
87
|
+
return Object.keys(this._monomers[polymerType]);
|
|
74
88
|
}
|
|
75
89
|
|
|
76
90
|
get onChanged(): Observable<any> {
|
|
@@ -78,8 +92,8 @@ export class MonomerLib implements IMonomerLib {
|
|
|
78
92
|
}
|
|
79
93
|
|
|
80
94
|
private _updateInt(lib: IMonomerLib): void {
|
|
81
|
-
const typesNew = lib.
|
|
82
|
-
const types = this.
|
|
95
|
+
const typesNew = lib.getPolymerTypes();
|
|
96
|
+
const types = this.getPolymerTypes();
|
|
83
97
|
|
|
84
98
|
typesNew.forEach((type) => {
|
|
85
99
|
//could possibly rewrite -> TODO: check duplicated monomer symbol
|
|
@@ -87,9 +101,9 @@ export class MonomerLib implements IMonomerLib {
|
|
|
87
101
|
if (!types.includes(type))
|
|
88
102
|
this._monomers![type] = {};
|
|
89
103
|
|
|
90
|
-
const monomers = lib.
|
|
91
|
-
monomers.forEach((
|
|
92
|
-
this._monomers[type][
|
|
104
|
+
const monomers = lib.getMonomerSymbolsByType(type);
|
|
105
|
+
monomers.forEach((monomerSymbol) => {
|
|
106
|
+
this._monomers[type][monomerSymbol] = lib.getMonomer(type, monomerSymbol)!;
|
|
93
107
|
});
|
|
94
108
|
});
|
|
95
109
|
}
|
|
@@ -101,7 +115,7 @@ export class MonomerLib implements IMonomerLib {
|
|
|
101
115
|
|
|
102
116
|
public updateLibs(libList: IMonomerLib[], reload: boolean = false): void {
|
|
103
117
|
if (reload) this._monomers = {};
|
|
104
|
-
for (const lib of libList) this._updateInt(lib);
|
|
118
|
+
for (const lib of libList) if (!lib.error) this._updateInt(lib);
|
|
105
119
|
this._onChanged.next();
|
|
106
120
|
}
|
|
107
121
|
|
|
@@ -117,35 +131,58 @@ export class MonomerLibHelper implements IMonomerLibHelper {
|
|
|
117
131
|
/** Protect constructor to prevent multiple instantiation. */
|
|
118
132
|
protected constructor() {}
|
|
119
133
|
|
|
120
|
-
/** Singleton monomer library
|
|
134
|
+
/** Singleton monomer library
|
|
135
|
+
* @return {MonomerLibHelper} MonomerLibHelper instance
|
|
136
|
+
*/
|
|
121
137
|
getBioLib(): IMonomerLib {
|
|
122
138
|
return this._monomerLib;
|
|
123
139
|
}
|
|
124
140
|
|
|
125
|
-
|
|
141
|
+
/** Allows syncing with managing settings/loading libraries */
|
|
142
|
+
public loadLibrariesPromise: Promise<void> = Promise.resolve();
|
|
126
143
|
|
|
127
144
|
/** Loads libraries based on settings in user storage {@link LIB_STORAGE_NAME}
|
|
128
145
|
* @param {boolean} reload Clean {@link monomerLib} before load libraries [false]
|
|
129
146
|
*/
|
|
130
147
|
async loadLibraries(reload: boolean = false): Promise<void> {
|
|
131
148
|
return this.loadLibrariesPromise = this.loadLibrariesPromise.then(async () => {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
149
|
+
// This function is not allowed to throw any exception,
|
|
150
|
+
// because it will prevent further handling monomer library settings
|
|
151
|
+
// through blocking this.loadLibrariesPromise
|
|
152
|
+
try {
|
|
153
|
+
const [libFileNameList, settings]: [string[], LibSettings] = await Promise.all([
|
|
154
|
+
getLibFileNameList(),
|
|
155
|
+
getUserLibSettings(),
|
|
156
|
+
]);
|
|
157
|
+
const libs: IMonomerLib[] = await Promise.all(libFileNameList
|
|
158
|
+
.filter((libFileName) => !settings.exclude.includes(libFileName))
|
|
159
|
+
.map((libFileName) => {
|
|
160
|
+
//TODO handle whether files are in place
|
|
161
|
+
return this.readLibrary(LIB_PATH, libFileName).catch((err: any) => {
|
|
162
|
+
const errMsg: string = `Loading monomers from '${libFileName}' error: ` +
|
|
163
|
+
`${err instanceof Error ? err.message : err.toString()}`;
|
|
164
|
+
return new MonomerLib({}, errMsg);
|
|
165
|
+
});
|
|
166
|
+
}));
|
|
167
|
+
this._monomerLib.updateLibs(libs, reload);
|
|
168
|
+
} catch (err: any) {
|
|
169
|
+
const errMsg: string = 'Loading monomer libraries error: ' +
|
|
170
|
+
`${err instanceof Error ? err.message : err.toString()}`;
|
|
171
|
+
grok.shell.warning(errMsg);
|
|
172
|
+
|
|
173
|
+
const errStack = err instanceof Error ? err.stack : undefined;
|
|
174
|
+
_package.logger.error(errMsg, undefined, errStack);
|
|
175
|
+
}
|
|
143
176
|
});
|
|
144
177
|
}
|
|
145
178
|
|
|
146
|
-
/** Reads library from file shares, handles .json and .sdf
|
|
179
|
+
/** Reads library from file shares, handles .json and .sdf
|
|
180
|
+
* @param {string} path Path to library file
|
|
181
|
+
* @param {string} fileName Name of library file
|
|
182
|
+
* @return {Promise<IMonomerLib>} Promise of IMonomerLib
|
|
183
|
+
*/
|
|
147
184
|
async readLibrary(path: string, fileName: string): Promise<IMonomerLib> {
|
|
148
|
-
let
|
|
185
|
+
let rawLibData: any[] = [];
|
|
149
186
|
let file;
|
|
150
187
|
let dfSdf;
|
|
151
188
|
const fileSource = new DG.FileSource(path);
|
|
@@ -154,41 +191,23 @@ export class MonomerLibHelper implements IMonomerLibHelper {
|
|
|
154
191
|
if (funcList.length === 1) {
|
|
155
192
|
file = await fileSource.readAsBytes(fileName);
|
|
156
193
|
dfSdf = await grok.functions.call('Chem:importSdf', {bytes: file});
|
|
157
|
-
|
|
194
|
+
rawLibData = createJsonMonomerLibFromSdf(dfSdf[0]);
|
|
158
195
|
} else {
|
|
159
196
|
grok.shell.warning('Chem package is not installed');
|
|
160
197
|
}
|
|
161
198
|
} else {
|
|
162
199
|
const file = await fileSource.readAsText(fileName);
|
|
163
|
-
|
|
200
|
+
rawLibData = JSON.parse(file);
|
|
164
201
|
}
|
|
165
202
|
|
|
166
|
-
const monomers: { [
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
'name': monomer['name'],
|
|
173
|
-
'naturalAnalog': monomer['naturalAnalog'],
|
|
174
|
-
'molfile': monomer['molfile'],
|
|
175
|
-
'rgroups': monomer['rgroups'],
|
|
176
|
-
'polymerType': monomer['polymerType'],
|
|
177
|
-
'monomerType': monomer['monomerType'],
|
|
178
|
-
'data': {}
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
Object.keys(monomer).forEach((prop) => {
|
|
182
|
-
if (!expectedMonomerData.includes(prop))
|
|
183
|
-
monomerAdd.data[prop] = monomer[prop];
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
if (!types.includes(monomer['polymerType'])) {
|
|
187
|
-
monomers[monomer['polymerType']] = {};
|
|
188
|
-
types.push(monomer['polymerType']);
|
|
203
|
+
const monomers: { [polymerType: string]: { [monomerSymbol: string]: Monomer } } = {};
|
|
204
|
+
const polymerTypes: string[] = [];
|
|
205
|
+
rawLibData.forEach((monomer) => {
|
|
206
|
+
if (!polymerTypes.includes(monomer[REQ.POLYMER_TYPE])) {
|
|
207
|
+
monomers[monomer[REQ.POLYMER_TYPE]] = {};
|
|
208
|
+
polymerTypes.push(monomer[REQ.POLYMER_TYPE]);
|
|
189
209
|
}
|
|
190
|
-
|
|
191
|
-
monomers[monomer['polymerType']][monomer['symbol']] = monomerAdd;
|
|
210
|
+
monomers[monomer[REQ.POLYMER_TYPE]][monomer[REQ.SYMBOL]] = monomer as Monomer;
|
|
192
211
|
});
|
|
193
212
|
|
|
194
213
|
return new MonomerLib(monomers);
|
|
@@ -11,6 +11,7 @@ import {_package} from '../package';
|
|
|
11
11
|
import {multipleSequenceAlginmentUIOptions} from './types';
|
|
12
12
|
import {kalignVersion, msaDefaultOptions} from './constants';
|
|
13
13
|
import '../../css/msa.css';
|
|
14
|
+
import { ColumnInputOptions } from '@datagrok-libraries/utils/src/type-declarations';
|
|
14
15
|
export class MsaWarning extends Error {
|
|
15
16
|
constructor(message: string, options?: ErrorOptions) {
|
|
16
17
|
super(message, options);
|
|
@@ -18,7 +19,7 @@ export class MsaWarning extends Error {
|
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
export async function multipleSequenceAlignmentUI(
|
|
21
|
-
options: multipleSequenceAlginmentUIOptions = {}
|
|
22
|
+
options: multipleSequenceAlginmentUIOptions = {},
|
|
22
23
|
): Promise<DG.Column> {
|
|
23
24
|
return new Promise(async (resolve, reject) => {
|
|
24
25
|
options.clustersCol ??= null;
|
|
@@ -40,7 +41,7 @@ export async function multipleSequenceAlignmentUI(
|
|
|
40
41
|
methodInput.setTooltip('Alignment method');
|
|
41
42
|
|
|
42
43
|
// UI for Kalign alignment
|
|
43
|
-
const terminalGapInput = ui.floatInput('Terminal gap', options?.kalign?.terminalGap ??
|
|
44
|
+
const terminalGapInput = ui.floatInput('Terminal gap', options?.kalign?.terminalGap ?? msaDefaultOptions.kalign.terminalGap);
|
|
44
45
|
terminalGapInput.setTooltip('Penalty for opening a gap at the beginning or end of the sequence');
|
|
45
46
|
const kalignVersionDiv = ui.p(`Kalign version: ${kalignVersion}`, 'kalign-version');
|
|
46
47
|
|
|
@@ -53,15 +54,17 @@ export async function multipleSequenceAlignmentUI(
|
|
|
53
54
|
const pepseaInputRootStyles: CSSStyleDeclaration[] = [methodInput.root.style];
|
|
54
55
|
const kalignInputRootStyles: CSSStyleDeclaration[] = [terminalGapInput.root.style, kalignVersionDiv.style];
|
|
55
56
|
|
|
56
|
-
let performAlignment: (() => Promise<DG.Column<string
|
|
57
|
+
let performAlignment: (() => Promise<DG.Column<string> | null>) | undefined;
|
|
57
58
|
|
|
58
|
-
//
|
|
59
|
+
//TODO: remove when the new version of datagrok-api is available
|
|
60
|
+
//TODO: allow only macromolecule colums to be chosen
|
|
59
61
|
const colInput = ui.columnInput('Sequence', table, seqCol, async () => {
|
|
60
62
|
performAlignment = await onColInputChange(
|
|
61
63
|
colInput.value, table, pepseaInputRootStyles, kalignInputRootStyles,
|
|
62
|
-
methodInput, clustersColInput, gapOpenInput, gapExtendInput, terminalGapInput
|
|
64
|
+
methodInput, clustersColInput, gapOpenInput, gapExtendInput, terminalGapInput,
|
|
63
65
|
);
|
|
64
|
-
|
|
66
|
+
//@ts-ignore
|
|
67
|
+
}, {filter: (col: DG.Column) => col.semType === DG.SEMTYPE.MACROMOLECULE} as ColumnInputOptions
|
|
65
68
|
) as DG.InputBase<DG.Column<string>>;
|
|
66
69
|
colInput.setTooltip('Sequences column to use for alignment');
|
|
67
70
|
const clustersColInput = ui.columnInput('Clusters', table, options.clustersCol);
|
|
@@ -71,13 +74,13 @@ export async function multipleSequenceAlignmentUI(
|
|
|
71
74
|
if (options.col) {
|
|
72
75
|
performAlignment = await onColInputChange(
|
|
73
76
|
options.col, table, pepseaInputRootStyles, kalignInputRootStyles,
|
|
74
|
-
methodInput, clustersColInput, gapOpenInput, gapExtendInput, terminalGapInput
|
|
77
|
+
methodInput, clustersColInput, gapOpenInput, gapExtendInput, terminalGapInput,
|
|
75
78
|
);
|
|
76
79
|
|
|
77
80
|
await onDialogOk(colInput, table, performAlignment, resolve, reject);
|
|
78
81
|
return;
|
|
79
82
|
}
|
|
80
|
-
const
|
|
83
|
+
const _dlg = ui.dialog('MSA')
|
|
81
84
|
.add(colInput)
|
|
82
85
|
.add(clustersColInput)
|
|
83
86
|
.add(methodInput)
|
|
@@ -93,9 +96,9 @@ export async function multipleSequenceAlignmentUI(
|
|
|
93
96
|
async function onDialogOk(
|
|
94
97
|
colInput: DG.InputBase< DG.Column<any>>,
|
|
95
98
|
table: DG.DataFrame,
|
|
96
|
-
performAlignment: (() => Promise<DG.Column<string
|
|
99
|
+
performAlignment: (() => Promise<DG.Column<string> | null>) | undefined,
|
|
97
100
|
resolve: (value: DG.Column<any>) => void,
|
|
98
|
-
reject: (reason: any) => void
|
|
101
|
+
reject: (reason: any) => void,
|
|
99
102
|
): Promise<void> {
|
|
100
103
|
let msaCol: DG.Column<string> | null = null;
|
|
101
104
|
const pi = DG.TaskBarProgressIndicator.create('Analyze for MSA ...');
|
|
@@ -107,7 +110,7 @@ async function onDialogOk(
|
|
|
107
110
|
throw new Error('Invalid column format');
|
|
108
111
|
msaCol = await performAlignment(); // progress
|
|
109
112
|
if (msaCol == null)
|
|
110
|
-
return
|
|
113
|
+
return reject('PepSeA container has not started');
|
|
111
114
|
|
|
112
115
|
table.columns.add(msaCol);
|
|
113
116
|
await grok.data.detectSemanticTypes(table);
|
|
@@ -128,8 +131,8 @@ async function onColInputChange(
|
|
|
128
131
|
pepseaInputRootStyles: CSSStyleDeclaration[], kalignInputRootStyles: CSSStyleDeclaration[],
|
|
129
132
|
methodInput: DG.InputBase<string | null>, clustersColInput: DG.InputBase<DG.Column<any> | null>,
|
|
130
133
|
gapOpenInput: DG.InputBase<number | null>, gapExtendInput: DG.InputBase<number | null>,
|
|
131
|
-
terminalGapInput: DG.InputBase<number | null
|
|
132
|
-
): Promise<(() => Promise<DG.Column<string
|
|
134
|
+
terminalGapInput: DG.InputBase<number | null>,
|
|
135
|
+
): Promise<(() => Promise<DG.Column<string> | null>) | undefined> {
|
|
133
136
|
try {
|
|
134
137
|
if (col.semType !== DG.SEMTYPE.MACROMOLECULE)
|
|
135
138
|
return;
|
|
@@ -139,9 +142,9 @@ async function onColInputChange(
|
|
|
139
142
|
[NOTATION.FASTA, NOTATION.SEPARATOR], [ALPHABET.DNA, ALPHABET.RNA, ALPHABET.PT], false)
|
|
140
143
|
) { // Kalign - natural alphabets. if the notation is separator, convert to fasta and then run kalign
|
|
141
144
|
switchDialog(pepseaInputRootStyles, kalignInputRootStyles, 'kalign');
|
|
142
|
-
gapOpenInput.value
|
|
143
|
-
gapExtendInput.value
|
|
144
|
-
terminalGapInput.value
|
|
145
|
+
gapOpenInput.value ??= msaDefaultOptions.kalign.gapOpen;
|
|
146
|
+
gapExtendInput.value ??= msaDefaultOptions.kalign.gapExtend;
|
|
147
|
+
terminalGapInput.value ??= msaDefaultOptions.kalign.terminalGap;
|
|
145
148
|
const potentialColNC = new NotationConverter(col);
|
|
146
149
|
const performCol: DG.Column<string> = potentialColNC.isFasta() ? col :
|
|
147
150
|
potentialColNC.convert(NOTATION.FASTA);
|
|
@@ -150,8 +153,8 @@ async function onColInputChange(
|
|
|
150
153
|
[NOTATION.HELM], [], false)
|
|
151
154
|
) { // PepSeA branch - Helm notation or separator notation with unknown alphabets
|
|
152
155
|
switchDialog(pepseaInputRootStyles, kalignInputRootStyles, 'pepsea');
|
|
153
|
-
gapOpenInput.value
|
|
154
|
-
gapExtendInput.value
|
|
156
|
+
gapOpenInput.value ??= msaDefaultOptions.pepsea.gapOpen;
|
|
157
|
+
gapExtendInput.value ??= msaDefaultOptions.pepsea.gapExtend;
|
|
155
158
|
|
|
156
159
|
return async () => await runPepsea(col, unusedName, methodInput.value!,
|
|
157
160
|
gapOpenInput.value!, gapExtendInput.value!, clustersColInput.value);
|
|
@@ -162,8 +165,8 @@ async function onColInputChange(
|
|
|
162
165
|
return;
|
|
163
166
|
const helmCol = potentialColNC.convert(NOTATION.HELM);
|
|
164
167
|
switchDialog(pepseaInputRootStyles, kalignInputRootStyles, 'pepsea');
|
|
165
|
-
gapOpenInput.value
|
|
166
|
-
gapExtendInput.value
|
|
168
|
+
gapOpenInput.value ??= msaDefaultOptions.pepsea.gapOpen;
|
|
169
|
+
gapExtendInput.value ??= msaDefaultOptions.pepsea.gapExtend;
|
|
167
170
|
// convert to helm and assign alignment function to PepSea
|
|
168
171
|
|
|
169
172
|
return async () => await runPepsea(helmCol, unusedName, methodInput.value!,
|
|
@@ -182,7 +185,7 @@ async function onColInputChange(
|
|
|
182
185
|
type MSADialogType = 'kalign' | 'pepsea';
|
|
183
186
|
|
|
184
187
|
function switchDialog(
|
|
185
|
-
pepseaInputRootStyles: CSSStyleDeclaration[], kalignInputRootStyles: CSSStyleDeclaration[], dialogType: MSADialogType
|
|
188
|
+
pepseaInputRootStyles: CSSStyleDeclaration[], kalignInputRootStyles: CSSStyleDeclaration[], dialogType: MSADialogType,
|
|
186
189
|
) {
|
|
187
190
|
if (dialogType === 'kalign') {
|
|
188
191
|
for (const inputRootStyle of pepseaInputRootStyles)
|
|
@@ -34,7 +34,7 @@ function _stringsToFasta(sequences: string[]): string {
|
|
|
34
34
|
* @return {Promise<DG.Column>} Aligned sequences.
|
|
35
35
|
*/
|
|
36
36
|
export async function runKalign(srcCol: DG.Column<string>, isAligned: boolean = false, unUsedName: string = '',
|
|
37
|
-
clustersCol: DG.Column | null = null, gapOpen?: number, gapExtend?: number, terminalGap?: number
|
|
37
|
+
clustersCol: DG.Column | null = null, gapOpen?: number, gapExtend?: number, terminalGap?: number,
|
|
38
38
|
): Promise<DG.Column> {
|
|
39
39
|
let sequences: string[] = srcCol.toList();
|
|
40
40
|
|
|
@@ -60,7 +60,7 @@ export async function runKalign(srcCol: DG.Column<string>, isAligned: boolean =
|
|
|
60
60
|
|
|
61
61
|
const CLI = await new Aioli([
|
|
62
62
|
'base/1.0.0',
|
|
63
|
-
{tool: 'kalign', version: kalignVersion, reinit: true}
|
|
63
|
+
{tool: 'kalign', version: kalignVersion, reinit: true},
|
|
64
64
|
]);
|
|
65
65
|
const tgtCol = DG.Column.string(unUsedName, sequencesLength);
|
|
66
66
|
|
package/src/utils/pepsea.ts
CHANGED
|
@@ -16,11 +16,25 @@ type PepseaBodyUnit = { ID: string, HELM: string };
|
|
|
16
16
|
|
|
17
17
|
/** Gets the column containing MSA sequences produced by the 'PepSeA' tool from the {@link srcCol} column.
|
|
18
18
|
* Does not add the result column to the dataframe of {@link srcCol}.
|
|
19
|
+
* @async
|
|
20
|
+
* @param {DG.Column} srcCol - The column containing the sequences to be aligned.
|
|
21
|
+
* @param {string} unUsedName - The name of the result column.
|
|
22
|
+
* @param {string} method - The method used for alignment.
|
|
23
|
+
* @param {number} gapOpen - The gap open penalty.
|
|
24
|
+
* @param {number} gapExtend - The gap extension penalty.
|
|
25
|
+
* @param {DG.Column} clustersCol - The column containing the clusters of the sequences.
|
|
19
26
|
*/
|
|
20
27
|
export async function runPepsea(srcCol: DG.Column<string>, unUsedName: string,
|
|
21
28
|
method: typeof pepseaMethods[number] = 'ginsi', gapOpen: number = 1.53, gapExtend: number = 0.0,
|
|
22
29
|
clustersCol: DG.Column<string | number> | null = null,
|
|
23
|
-
): Promise<DG.Column<string
|
|
30
|
+
): Promise<DG.Column<string> | null> {
|
|
31
|
+
const pepseaContainer = await grok.dapi.docker.dockerContainers.filter('bio').first();
|
|
32
|
+
if (pepseaContainer.status !== 'started' && pepseaContainer.status !== 'checking') {
|
|
33
|
+
grok.log.warning('PepSeA container has not started yet');
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
24
38
|
const peptideCount = srcCol.length;
|
|
25
39
|
clustersCol ??= DG.Column.int('Clusters', peptideCount).init(0);
|
|
26
40
|
if (clustersCol.type != DG.COLUMN_TYPE.STRING)
|
|
@@ -41,14 +55,12 @@ export async function runPepsea(srcCol: DG.Column<string>, unUsedName: string,
|
|
|
41
55
|
(bodies[clusterId] ??= []).push({ID: rowIndex.toString(), HELM: helmSeq});
|
|
42
56
|
}
|
|
43
57
|
|
|
44
|
-
//@ts-ignore: this is a temporary workaround for the issue with docker containers. This will be fixed in 1.14.0
|
|
45
|
-
const pepseaContainer = await (grok.dapi.docker !== undefined ? grok.dapi.docker.dockerContainers : grok.dapi.dockerfiles).filter('bio').first();
|
|
46
58
|
const alignedSequences: string[] = new Array(peptideCount);
|
|
47
59
|
for (const body of bodies) { // getting aligned sequences for each cluster
|
|
48
60
|
const alignedObject = await requestAlignedObjects(pepseaContainer.id, body, method, gapOpen, gapExtend);
|
|
49
61
|
const alignments = alignedObject.Alignment;
|
|
50
62
|
|
|
51
|
-
for (const alignment of alignments) {
|
|
63
|
+
for (const alignment of alignments) { // filling alignedSequencesCol
|
|
52
64
|
alignedSequences[parseInt(alignment.ID)] = Object.entries(alignment)
|
|
53
65
|
.filter((v) => !alignmentObjectMetaKeys.includes(v[0]))
|
|
54
66
|
.map((v) => v[1] !== '-' ? v[1] : '')
|
|
@@ -75,8 +87,6 @@ async function requestAlignedObjects(dockerfileId: string, body: PepseaBodyUnit[
|
|
|
75
87
|
body: JSON.stringify(body),
|
|
76
88
|
};
|
|
77
89
|
const path = `/align?method=${method}&gap_open=${gapOpen}&gap_extend=${gapExtend}`;
|
|
78
|
-
|
|
79
|
-
const response = await (grok.dapi.docker !== undefined ? grok.dapi.docker.dockerContainers : grok.dapi.dockerfiles)
|
|
80
|
-
.request(dockerfileId, path, params);
|
|
90
|
+
const response = await grok.dapi.docker.dockerContainers.request(dockerfileId, path, params);
|
|
81
91
|
return JSON.parse(response ?? '{}');
|
|
82
92
|
}
|
|
@@ -28,7 +28,7 @@ export function saveAsFastaUI() {
|
|
|
28
28
|
.filter((gc: DG.GridColumn) => {
|
|
29
29
|
const col: DG.Column | null = gc.column;
|
|
30
30
|
if (col && col.semType === DG.SEMTYPE.MACROMOLECULE) {
|
|
31
|
-
const uh =
|
|
31
|
+
const uh = UnitsHandler.getOrCreate(col);
|
|
32
32
|
return uh.isFasta();
|
|
33
33
|
}
|
|
34
34
|
return false;
|
|
@@ -44,7 +44,7 @@ export function saveAsFastaUI() {
|
|
|
44
44
|
.add(ui.inputs([
|
|
45
45
|
idGColListInput,
|
|
46
46
|
seqColInput,
|
|
47
|
-
lineWidthInput
|
|
47
|
+
lineWidthInput,
|
|
48
48
|
]))
|
|
49
49
|
.onOK(() => {
|
|
50
50
|
const valueIdColList: DG.Column[] = idGColListInput.value ?
|
|
@@ -66,9 +66,16 @@ export function saveAsFastaUI() {
|
|
|
66
66
|
.show();
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
/**
|
|
69
|
+
/**
|
|
70
|
+
* Builds FASTA content from id columns list and seq column
|
|
71
|
+
* @param {DG.Column[]} idColList - list of columns with identifiers
|
|
72
|
+
* @param {DG.Column} seqCol - column with sequence
|
|
73
|
+
* @param {number} lineWidth - FASTA line width
|
|
74
|
+
* @param {string} lineSeparator - FASTA line separator
|
|
75
|
+
* @return {string} FASTA content
|
|
76
|
+
*/
|
|
70
77
|
export function saveAsFastaDo(
|
|
71
|
-
idColList: DG.Column[], seqCol: DG.Column, lineWidth: number = FASTA_LINE_WIDTH, lineSeparator: string = '\n'
|
|
78
|
+
idColList: DG.Column[], seqCol: DG.Column, lineWidth: number = FASTA_LINE_WIDTH, lineSeparator: string = '\n',
|
|
72
79
|
): string {
|
|
73
80
|
const splitter: SplitterFunc = splitterAsFasta;
|
|
74
81
|
|
package/src/utils/ui-utils.ts
CHANGED
|
@@ -3,9 +3,8 @@ import * as DG from 'datagrok-api/dg';
|
|
|
3
3
|
|
|
4
4
|
import * as rxjs from 'rxjs';
|
|
5
5
|
import {WebLogoViewer, PROPS as wlPROPS} from '../viewers/web-logo-viewer';
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
VdRegion, VdRegionType
|
|
6
|
+
import {IVdRegionsViewer,
|
|
7
|
+
VdRegion, VdRegionType,
|
|
9
8
|
} from '@datagrok-libraries/bio/src/viewers/vd-regions';
|
|
10
9
|
import {FilterSources, PositionHeight} from '@datagrok-libraries/bio/src/viewers/web-logo';
|
|
11
10
|
import {Unsubscribable} from 'rxjs';
|
|
@@ -128,12 +127,12 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
128
127
|
|
|
129
128
|
if (property) {
|
|
130
129
|
switch (property.name) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
130
|
+
case 'regionTypes':
|
|
131
|
+
break;
|
|
132
|
+
case 'chains':
|
|
133
|
+
break;
|
|
134
|
+
case 'sequenceColumnNamePostfix':
|
|
135
|
+
break;
|
|
137
136
|
// for (let orderI = 0; orderI < this.logos.length; orderI++) {
|
|
138
137
|
// for (let chainI = 0; chainI < this.chains.length; chainI++) {
|
|
139
138
|
// const chain: string = this.chains[chainI];
|
|
@@ -145,11 +144,11 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
145
144
|
}
|
|
146
145
|
|
|
147
146
|
switch (property.name) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
147
|
+
case 'skipEmptyPositions':
|
|
148
|
+
case 'positionWidth':
|
|
149
|
+
case 'positionHeight':
|
|
150
|
+
this.setData(this.dataFrame, this.regions); // onPropertyChanged
|
|
151
|
+
break;
|
|
153
152
|
}
|
|
154
153
|
}
|
|
155
154
|
|
|
@@ -259,7 +258,7 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
259
258
|
width: '16px',
|
|
260
259
|
marginTop: '24px',
|
|
261
260
|
marginLeft: '6px',
|
|
262
|
-
}
|
|
261
|
+
},
|
|
263
262
|
})] : []),
|
|
264
263
|
// List with controls for regions
|
|
265
264
|
...[...Array(orderList.length).keys()].map((orderI) => {
|
|
@@ -271,7 +270,7 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
271
270
|
// height: '100%',
|
|
272
271
|
marginTop: '4px',
|
|
273
272
|
marginBottom: '4px',
|
|
274
|
-
}
|
|
273
|
+
},
|
|
275
274
|
});
|
|
276
275
|
|
|
277
276
|
return resDiv;
|
|
@@ -280,8 +279,8 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
280
279
|
},
|
|
281
280
|
['', ...[...Array(orderList.length).keys()].map(
|
|
282
281
|
(orderI: number) => regionsFiltered.find(
|
|
283
|
-
(r: VdRegion) => r.order == orderList[orderI] && r.chain == this.chains[0]
|
|
284
|
-
)!.name || 'Name')]
|
|
282
|
+
(r: VdRegion) => r.order == orderList[orderI] && r.chain == this.chains[0],
|
|
283
|
+
)!.name || 'Name')],
|
|
285
284
|
);
|
|
286
285
|
this.mainLayout.className = 'mlb-vd-regions-viewer-table2';
|
|
287
286
|
// this.mainLayout.style.background = '#EEEEFF';
|
|
@@ -294,7 +293,7 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
294
293
|
this.filterSourceInput.root.style.top = '-3px';
|
|
295
294
|
ui.tooltip.bind(this.filterSourceInput.root, 'Check to filter sequences for selected VRs');
|
|
296
295
|
|
|
297
|
-
const
|
|
296
|
+
const _color: string = `#ffbb${Math.ceil(Math.random() * 255).toString(16)}`;
|
|
298
297
|
this.host = ui.div([this.mainLayout, this.filterSourceInput!.root],
|
|
299
298
|
{/*style: {backgroundColor: color}*/});
|
|
300
299
|
this.root.appendChild(this.host);
|
|
@@ -310,7 +309,7 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
310
309
|
|
|
311
310
|
const maxHeight: number = Math.min(logoHeight,
|
|
312
311
|
Math.max(...this.logos.map((wlDict) =>
|
|
313
|
-
Math.max(...Object.values(wlDict).map((wl) => wl.maxHeight))))
|
|
312
|
+
Math.max(...Object.values(wlDict).map((wl) => wl.maxHeight)))),
|
|
314
313
|
);
|
|
315
314
|
|
|
316
315
|
for (let orderI = 0; orderI < this.logos.length; orderI++) {
|
|
@@ -323,11 +322,11 @@ export class VdRegionsViewer extends DG.JsViewer implements IVdRegionsViewer {
|
|
|
323
322
|
|
|
324
323
|
// -- Handle events --
|
|
325
324
|
|
|
326
|
-
private rootOnSizeChanged(
|
|
325
|
+
private rootOnSizeChanged(_args: any): void {
|
|
327
326
|
this.calcSize();
|
|
328
327
|
}
|
|
329
328
|
|
|
330
|
-
private rootOnMouseMove(
|
|
329
|
+
private rootOnMouseMove(_e: MouseEvent) {
|
|
331
330
|
// ui.tooltip.show('text', e.x + 8, e.y + 8,);
|
|
332
331
|
// console.log(`onMouseMoveRoot.( x: ${e.x}, y: ${e.y} )`);
|
|
333
332
|
}
|