@datagrok/bio 2.11.37 → 2.11.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 +2 -3
- package/CHANGELOG.md +14 -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/dockerfiles/Dockerfile +52 -1
- package/files/tests/100_3_clustests.csv +1 -1
- package/package.json +2 -2
- package/src/demo/bio01-similarity-diversity.ts +1 -1
- package/src/demo/bio01a-hierarchical-clustering-and-sequence-space.ts +1 -1
- package/src/demo/bio01b-hierarchical-clustering-and-activity-cliffs.ts +1 -1
- package/src/package.ts +2 -2
- package/src/tests/activity-cliffs-tests.ts +1 -1
- package/src/tests/detectors-tests.ts +2 -2
- package/src/tests/msa-tests.ts +1 -1
- package/src/tests/pepsea-tests.ts +53 -13
- package/src/tests/renderers-test.ts +3 -3
- package/src/tests/utils/test-logger.ts +29 -0
- package/src/tests/utils.ts +0 -8
- package/src/tests/viewers.ts +1 -1
- package/src/utils/docker.ts +39 -23
- package/src/utils/multiple-sequence-alignment-ui.ts +16 -5
- package/src/utils/pepsea.ts +59 -12
- package/files/samples/sample_FASTA.csv +0 -65
- package/files/samples/sample_HELM.csv +0 -541
- package/files/samples/sample_MSA.csv +0 -541
- /package/files/{data/sample_FASTA.fasta → samples/FASTA.fasta} +0 -0
- /package/files/{data/sample_FASTA_DNA.csv → samples/FASTA_DNA.csv} +0 -0
- /package/files/{data/sample_FASTA_PT.csv → samples/FASTA_PT.csv} +0 -0
- /package/files/{data/sample_FASTA_PT_activity.csv → samples/FASTA_PT_activity.csv} +0 -0
- /package/files/{data/sample_FASTA_RNA.csv → samples/FASTA_RNA.csv} +0 -0
- /package/files/{data/sample_HELM_50.csv → samples/HELM_50.csv} +0 -0
- /package/files/{data/sample_HELM_empty_unkn.csv → samples/HELM_empty_unkn.csv} +0 -0
- /package/files/{data/sample_HELM_empty_vals.csv → samples/HELM_empty_vals.csv} +0 -0
- /package/files/{data/sample_SEPARATOR_PT.csv → samples/SEPARATOR_PT.csv} +0 -0
package/dockerfiles/Dockerfile
CHANGED
|
@@ -55,11 +55,62 @@ def distout():\n\
|
|
|
55
55
|
if distout_output is None:\n\
|
|
56
56
|
raise FileNotFoundError("distance matrix is not found")\n\
|
|
57
57
|
\n\
|
|
58
|
-
return distout_output\n
|
|
58
|
+
return distout_output\n\
|
|
59
|
+
\n\
|
|
60
|
+
from fastapi import Request, HTTPException\n\
|
|
61
|
+
import sys\n\
|
|
62
|
+
import ujson as json\n\
|
|
63
|
+
from io import StringIO\n\
|
|
64
|
+
from subprocess import CalledProcessError\n\
|
|
65
|
+
\n\
|
|
66
|
+
@api.middleware("http")\n\
|
|
67
|
+
async def error_to_json_middleware(request: Request, call_next):\n\
|
|
68
|
+
sys.stderr = temp_stderr = StringIO()\n\
|
|
69
|
+
response = None\n\
|
|
70
|
+
try:\n\
|
|
71
|
+
response = await call_next(request)\n\
|
|
72
|
+
finally:\n\
|
|
73
|
+
sys.stderr = sys.__stderr__\n\
|
|
74
|
+
stderr_msg = temp_stderr.getvalue()\n\
|
|
75
|
+
if len(stderr_msg) > 0 and response and response.headers["content-type"] == "application/json":\n\
|
|
76
|
+
response_body = [chunk async for chunk in response.body_iterator]\n\
|
|
77
|
+
response_str = "".join(((s.decode() if isinstance(s, bytes) else str(s)) for s in response_body))\n\
|
|
78
|
+
response_obj = json.loads(response_str)\n\
|
|
79
|
+
response_obj.update({"pepsea-stderr": stderr_msg})\n\
|
|
80
|
+
headers = {}\n\
|
|
81
|
+
for header_name in response.headers:\n\
|
|
82
|
+
if header_name != "content-length" and header_name != "content-type":\n\
|
|
83
|
+
headers[header_name] = response.headers[header_name]\n\
|
|
84
|
+
json_response = JSONResponse(status_code=response.status_code, headers=headers, content=response_obj)\n\
|
|
85
|
+
return json_response\n\
|
|
86
|
+
return response\n\
|
|
87
|
+
\n\
|
|
88
|
+
\n\
|
|
89
|
+
def pepsea_json_response(err_msg: str):\n\
|
|
90
|
+
return JSONResponse(status_code=500, content={\n\
|
|
91
|
+
"pepsea-error": err_msg,\n\
|
|
92
|
+
})\n\
|
|
93
|
+
\n\
|
|
94
|
+
\n\
|
|
95
|
+
@api.exception_handler(CalledProcessError)\n\
|
|
96
|
+
async def called_process_error_handler(request: Request, ex: CalledProcessError):\n\
|
|
97
|
+
s = ex.stderr\n\
|
|
98
|
+
err_msg = s.decode() if isinstance(s, bytes) else str(ex.stderr)\n\
|
|
99
|
+
sys.__stderr__.write("called_process_error_handler() {0}: {1}\\n".format(type(ex).__name__, err_msg))\n\
|
|
100
|
+
return pepsea_json_response(err_msg)\n\
|
|
101
|
+
\n\
|
|
102
|
+
\n\
|
|
103
|
+
@api.exception_handler(Exception)\n\
|
|
104
|
+
async def base_exception_handler(request: Request, ex: Exception):\n\
|
|
105
|
+
err_msg = str(ex)\n\
|
|
106
|
+
sys.__stderr__.write("base_exception_handler() {0}: {1}\\n".format(type(ex).__name__, err_msg))\n\
|
|
107
|
+
return pepsea_json_response(err_msg)\n\
|
|
108
|
+
\n' >> /opt/PepSeA-main/alignment/api.py;
|
|
59
109
|
|
|
60
110
|
# It is important to run docker container as user and not as root
|
|
61
111
|
USER grok:grok
|
|
62
112
|
|
|
113
|
+
|
|
63
114
|
# Command source does not work for Docker, cause it will apply only to one layer
|
|
64
115
|
# The PATH works better for Docker
|
|
65
116
|
ARG VIRTUAL_ENV=${HOME_DIR}/venv
|
|
@@ -97,4 +97,4 @@ cluster,sequence_id,sequence,activity,is_cliff
|
|
|
97
97
|
2,c2_seq1052,NQYRREPRGCRSPVWK,-0.18,False
|
|
98
98
|
2,c2_seq1053,GMGWYQHFIDCMPVME, 0.39,False
|
|
99
99
|
2,c2_seq1054,HHEDYSSHGDRHPVAE, 0.34,False
|
|
100
|
-
2,c2_seq1055,WFYIRFWNGCMWPVDH, 0.63,False
|
|
100
|
+
2,c2_seq1055,WFYIRFWNGCMWPVDH, 0.63,False
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"name": "Leonid Stolbov",
|
|
6
6
|
"email": "lstolbov@datagrok.ai"
|
|
7
7
|
},
|
|
8
|
-
"version": "2.11.
|
|
8
|
+
"version": "2.11.39",
|
|
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",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@biowasm/aioli": "^3.1.0",
|
|
37
|
-
"@datagrok-libraries/bio": "5.39.
|
|
37
|
+
"@datagrok-libraries/bio": "5.39.29",
|
|
38
38
|
"@datagrok-libraries/chem-meta": "^1.2.1",
|
|
39
39
|
"@datagrok-libraries/ml": "^6.4.10",
|
|
40
40
|
"@datagrok-libraries/tutorials": "^1.3.11",
|
|
@@ -8,7 +8,7 @@ import {handleError} from './utils';
|
|
|
8
8
|
import {SequenceDiversityViewer} from '../analysis/sequence-diversity-viewer';
|
|
9
9
|
import {SequenceSimilarityViewer} from '../analysis/sequence-similarity-viewer';
|
|
10
10
|
|
|
11
|
-
const dataFn: string = '
|
|
11
|
+
const dataFn: string = 'samples/FASTA_PT_activity.csv';
|
|
12
12
|
|
|
13
13
|
export async function demoBio01UI() {
|
|
14
14
|
let view: DG.TableView;
|
|
@@ -9,7 +9,7 @@ import {demoSequenceSpace, handleError} from './utils';
|
|
|
9
9
|
import {DemoScript} from '@datagrok-libraries/tutorials/src/demo-script';
|
|
10
10
|
import {getClusterMatrixWorker} from '@datagrok-libraries/math';
|
|
11
11
|
|
|
12
|
-
const dataFn = '
|
|
12
|
+
const dataFn = 'samples/FASTA_PT_activity.csv';
|
|
13
13
|
const seqColName = 'sequence';
|
|
14
14
|
|
|
15
15
|
export async function demoBio01aUI() {
|
|
@@ -14,7 +14,7 @@ import {MmDistanceFunctionsNames} from '@datagrok-libraries/ml/src/macromolecule
|
|
|
14
14
|
import {getClusterMatrixWorker} from '@datagrok-libraries/math';
|
|
15
15
|
import {DimReductionMethods} from '@datagrok-libraries/ml/src/multi-column-dimensionality-reduction/types';
|
|
16
16
|
|
|
17
|
-
const dataFn: string = '
|
|
17
|
+
const dataFn: string = 'samples/FASTA_PT_activity.csv';
|
|
18
18
|
|
|
19
19
|
export async function demoBio01bUI() {
|
|
20
20
|
let treeHelper: ITreeHelper;
|
package/src/package.ts
CHANGED
|
@@ -912,7 +912,7 @@ export async function webLogoAggApp(): Promise<void> {
|
|
|
912
912
|
try {
|
|
913
913
|
const urlParams = new URLSearchParams(window.location.search);
|
|
914
914
|
const app = new WebLogoApp(urlParams, 'webLogoAggApp');
|
|
915
|
-
const df: DG.DataFrame = await _package.files.readCsv('
|
|
915
|
+
const df: DG.DataFrame = await _package.files.readCsv('samples/FASTA_PT_activity.csv');
|
|
916
916
|
await grok.data.detectSemanticTypes(df);
|
|
917
917
|
await app.init(df);
|
|
918
918
|
} finally {
|
|
@@ -937,7 +937,7 @@ export async function getRegionHelmApp(): Promise<void> {
|
|
|
937
937
|
const pi = DG.TaskBarProgressIndicator.create('getRegion ...');
|
|
938
938
|
try {
|
|
939
939
|
const urlParams = new URLSearchParams(window.location.search);
|
|
940
|
-
const df = await _package.files.readCsv('
|
|
940
|
+
const df = await _package.files.readCsv('samples/HELM_empty_vals.csv');
|
|
941
941
|
const app = new GetRegionApp(urlParams, 'getRegionHelmApp');
|
|
942
942
|
await app.init({df: df, colName: 'HELM'});
|
|
943
943
|
} finally {
|
|
@@ -72,7 +72,7 @@ category('activityCliffs', async () => {
|
|
|
72
72
|
});
|
|
73
73
|
|
|
74
74
|
test('Helm', async () => {
|
|
75
|
-
const df = await _package.files.readCsv('
|
|
75
|
+
const df = await _package.files.readCsv('samples/HELM_50.csv');
|
|
76
76
|
const _view = grok.shell.addTableView(df);
|
|
77
77
|
|
|
78
78
|
await _testActivityCliffsOpen(df, DimReductionMethods.UMAP,
|
|
@@ -155,8 +155,8 @@ MWRSWY-CKHPMWRSWY-CKHP`;
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
const samples: { [key: string]: string } = {
|
|
158
|
-
[Samples.fastaFasta]: 'System:AppData/Bio/
|
|
159
|
-
[Samples.fastaPtCsv]: 'System:AppData/Bio/
|
|
158
|
+
[Samples.fastaFasta]: 'System:AppData/Bio/samples/FASTA.fasta',
|
|
159
|
+
[Samples.fastaPtCsv]: 'System:AppData/Bio/samples/FASTA_PT.csv',
|
|
160
160
|
[Samples.msaComplex]: 'System:AppData/Bio/samples/MSA.csv',
|
|
161
161
|
[Samples.fastaCsv]: 'System:AppData/Bio/samples/FASTA.csv',
|
|
162
162
|
[Samples.helmCsv]: 'System:AppData/Bio/samples/HELM.csv',
|
package/src/tests/msa-tests.ts
CHANGED
|
@@ -6,7 +6,7 @@ import {category, expect, expectArray, test} from '@datagrok-libraries/utils/src
|
|
|
6
6
|
import {ALIGNMENT, ALPHABET, NOTATION, TAGS as bioTAGS} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
7
7
|
import {runKalign} from '../utils/multiple-sequence-alignment';
|
|
8
8
|
import {multipleSequenceAlignmentUI} from '../utils/multiple-sequence-alignment-ui';
|
|
9
|
-
import {awaitContainerStart} from '
|
|
9
|
+
import {awaitContainerStart} from '../utils/docker';
|
|
10
10
|
|
|
11
11
|
category('MSA', async () => {
|
|
12
12
|
//table = await grok.data.files.openTable('Demo:Files/bio/peptides.csv');
|
|
@@ -1,27 +1,67 @@
|
|
|
1
1
|
import * as DG from 'datagrok-api/dg';
|
|
2
2
|
|
|
3
|
-
import {before, category, expect, test} from '@datagrok-libraries/utils/src/test';
|
|
3
|
+
import {before, category, expect, expectArray, test} from '@datagrok-libraries/utils/src/test';
|
|
4
4
|
import {runPepsea} from '../utils/pepsea';
|
|
5
|
-
import {
|
|
5
|
+
import {TestLogger} from './utils/test-logger';
|
|
6
|
+
import {errInfo} from '@datagrok-libraries/bio/src/utils/err-info';
|
|
7
|
+
import {awaitContainerStart} from '../utils/docker';
|
|
6
8
|
|
|
7
9
|
category('PepSeA', () => {
|
|
8
10
|
const testCsv = `HELM,MSA
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
"PEPTIDE1{F.L.R.G.W.[MeF].Y.S.N.N.C}$$$$","F.L.R.G.W.MeF.Y..S.N.N.C"
|
|
12
|
+
"PEPTIDE1{F.L.R.G.Y.[MeF].Y.W.N.C}$$$$","F.L.R.G.Y.MeF.Y.W...N.C"
|
|
13
|
+
"PEPTIDE1{F.G.Y.[MeF].Y.W.S.D.N.C}$$$$","F...G.Y.MeF.Y.W.S.D.N.C"
|
|
14
|
+
"PEPTIDE1{F.L.R.G.Y.[MeF].Y.W.S.N.D.C}$$$$","F.L.R.G.Y.MeF.Y.W.S.N.D.C"
|
|
15
|
+
"PEPTIDE1{F.V.R.G.Y.[MeF].Y.W.S.N.C}$$$$","F.V.R.G.Y.MeF.Y.W.S..N.C"
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
const pepseaStderrCsv: string = `HELM,MSA
|
|
19
|
+
"PEPTIDE1{F.L.Mis.G.W.[MeF].Y.S.N.N.C}$$$$","F.L.Mis.G.W.MeF.Y..S.N.N.C"
|
|
20
|
+
"PEPTIDE1{F.L.Mis.G.Y.[MeF].Y.W.N.C}$$$$","F.L.Mis.G.Y.MeF.Y...W.N.C"
|
|
21
|
+
"PEPTIDE1{F.G.Y.[MeF].Y.W.S.D.N.C}$$$$","F...G.Y.MeF.Y.W.S.D.N.C"
|
|
22
|
+
`;
|
|
23
|
+
const pepseaStderrWarningList: string = 'Mis not found in Monomer Map\nMeF not found in Monomer Map\n';
|
|
24
|
+
|
|
25
|
+
const pepseaErrorCsv: string = `HELM
|
|
26
|
+
"PEPTIDE1{[NH2].*.A.Q.T.T.Y.K.N.Y.R.R.N.L.L.*.[COOH]}$$$$"
|
|
27
|
+
"PEPTIDE1{[NH2].M.A.N.T.T.Y.K.N.Y.R.N.N.L.L.*.[COOH]}$$$$"
|
|
28
|
+
"PEPTIDE1{[NH2].*.A.N.T.T.Y.K.C.Y.R.R.N.L.L.*.[COOH]}$$$$"
|
|
29
|
+
"PEPTIDE1{[NH2].*.A.N.T.T.Y.K.F.Y.R.R.N.L.L.*.[COOH]}$$$$"
|
|
30
|
+
`;
|
|
31
|
+
const pepseaErrorError: string = 'PepSeA error: The pair (*,M) couldn\'t be found in the substitution matrix';
|
|
14
32
|
|
|
15
33
|
before(async () => {
|
|
16
34
|
await awaitContainerStart();
|
|
17
35
|
});
|
|
18
36
|
|
|
19
37
|
test('Basic alignment', async () => {
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
38
|
+
const df = DG.DataFrame.fromCsv(testCsv);
|
|
39
|
+
const resMsaCol = await runPepsea(df.getCol('HELM'), 'msa(HELM)');
|
|
40
|
+
const tgtMsaCol = df.getCol('MSA');
|
|
41
|
+
for (let i = 0; i < resMsaCol!.length; ++i)
|
|
42
|
+
expect(resMsaCol!.get(i) == tgtMsaCol.get(i), true);
|
|
43
|
+
}, {timeout: 60000 /* docker */});
|
|
44
|
+
|
|
45
|
+
test('stderr', async () => {
|
|
46
|
+
const logger = new TestLogger();
|
|
47
|
+
const df = DG.DataFrame.fromCsv(pepseaStderrCsv);
|
|
48
|
+
const resMsaCol = await runPepsea(df.getCol('HELM'), 'msa(HELM)',
|
|
49
|
+
undefined, undefined, undefined, undefined, logger);
|
|
50
|
+
const tgtMsaCol = df.getCol('MSA');
|
|
51
|
+
expectArray(resMsaCol!.toList(), tgtMsaCol.toList());
|
|
52
|
+
expect(logger.warningList[0].message, pepseaStderrWarningList);
|
|
26
53
|
}, {timeout: 60000 /* docker */});
|
|
54
|
+
|
|
55
|
+
test('error', async () => {
|
|
56
|
+
const logger = new TestLogger();
|
|
57
|
+
try {
|
|
58
|
+
const df = DG.DataFrame.fromCsv(pepseaErrorCsv);
|
|
59
|
+
const _resMsaCol = await runPepsea(df.getCol('HELM'), 'msa(HELM)',
|
|
60
|
+
undefined, undefined, undefined, undefined, logger);
|
|
61
|
+
} catch (err: any) {
|
|
62
|
+
const [errMsg, errStack] = errInfo(err);
|
|
63
|
+
logger.error(errMsg, undefined, errStack);
|
|
64
|
+
}
|
|
65
|
+
expect(logger.errorList[0].message, pepseaErrorError);
|
|
66
|
+
});
|
|
27
67
|
});
|
|
@@ -71,7 +71,7 @@ category('renderers', () => {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
async function _rendererMacromoleculeSeparator() {
|
|
74
|
-
const csv: string = await grok.dapi.files.readAsText('System:AppData/Bio/
|
|
74
|
+
const csv: string = await grok.dapi.files.readAsText('System:AppData/Bio/samples/SEPARATOR_PT.csv');
|
|
75
75
|
const df: DG.DataFrame = DG.DataFrame.fromCsv(csv);
|
|
76
76
|
|
|
77
77
|
const seqCol = df.getCol('sequence');
|
|
@@ -113,7 +113,7 @@ category('renderers', () => {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
async function _testAfterMsa() {
|
|
116
|
-
const fastaTxt: string = await grok.dapi.files.readAsText('System:AppData/Bio/
|
|
116
|
+
const fastaTxt: string = await grok.dapi.files.readAsText('System:AppData/Bio/samples/FASTA.fasta');
|
|
117
117
|
const df: DG.DataFrame = importFasta(fastaTxt)[0];
|
|
118
118
|
|
|
119
119
|
const srcSeqCol: DG.Column = df.getCol('sequence');
|
|
@@ -153,7 +153,7 @@ category('renderers', () => {
|
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
async function _testAfterConvert() {
|
|
156
|
-
const csv: string = await grok.dapi.files.readAsText('System:AppData/Bio/
|
|
156
|
+
const csv: string = await grok.dapi.files.readAsText('System:AppData/Bio/samples/FASTA_PT.csv');
|
|
157
157
|
const df: DG.DataFrame = DG.DataFrame.fromCsv(csv);
|
|
158
158
|
|
|
159
159
|
const srcCol: DG.Column = df.col('sequence')!;
|
|
@@ -0,0 +1,29 @@
|
|
|
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 {ILogger} from '@datagrok-libraries/bio/src/utils/logger';
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export class TestLogger implements ILogger {
|
|
9
|
+
errorList: { message: any, params?: object, stackTrace?: string }[] = [];
|
|
10
|
+
warningList: { message: string, params?: object }[] = [];
|
|
11
|
+
infoList: { message: string, params?: object }[] = [];
|
|
12
|
+
debugList: { message: string, params?: object }[] = [];
|
|
13
|
+
|
|
14
|
+
error(message: any, params?: object, stackTrace?: string): void {
|
|
15
|
+
this.errorList.push({message, params, stackTrace});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
warning(message: string, params?: object): void {
|
|
19
|
+
this.warningList.push({message, params});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
info(message: string, params?: object): void {
|
|
23
|
+
this.infoList.push({message, params});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
debug(message: string, params?: object): void {
|
|
27
|
+
this.debugList.push({message, params});
|
|
28
|
+
}
|
|
29
|
+
}
|
package/src/tests/utils.ts
CHANGED
|
@@ -34,14 +34,6 @@ export function _testTableIsNotEmpty(table: DG.DataFrame): void {
|
|
|
34
34
|
expect(table.columns.length > 0 && table.rowCount > 0, true);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
/** Waits if container is not started
|
|
38
|
-
* @param {number} ms - time to wait in milliseconds */
|
|
39
|
-
export async function awaitContainerStart(ms: number = 30000): Promise<void> {
|
|
40
|
-
const dc = await grok.dapi.docker.dockerContainers.filter('bio').first();
|
|
41
|
-
const dcId = dc.id;
|
|
42
|
-
await startDockerContainer(dcId, ms);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
37
|
export async function awaitGrid(grid: DG.Grid, timeout: number = 5000): Promise<void> {
|
|
46
38
|
await delay(0);
|
|
47
39
|
await testEvent(grid.onAfterDrawContent, () => {},
|
package/src/tests/viewers.ts
CHANGED
|
@@ -10,7 +10,7 @@ category('viewers', () => {
|
|
|
10
10
|
const viewers = DG.Func.find({package: 'Bio', tags: ['viewer']}).map((f) => f.friendlyName);
|
|
11
11
|
for (const v of viewers) {
|
|
12
12
|
test(v, async () => {
|
|
13
|
-
const df = await readDataframe('
|
|
13
|
+
const df = await readDataframe('samples/FASTA_DNA.csv');
|
|
14
14
|
await testViewer(v, df, {detectSemanticTypes: true});
|
|
15
15
|
}, {
|
|
16
16
|
skipReason: {
|
package/src/utils/docker.ts
CHANGED
|
@@ -1,36 +1,52 @@
|
|
|
1
1
|
import * as grok from 'datagrok-api/grok';
|
|
2
2
|
import * as ui from 'datagrok-api/ui';
|
|
3
3
|
import * as DG from 'datagrok-api/dg';
|
|
4
|
+
|
|
4
5
|
import {delay} from '@datagrok-libraries/utils/src/test';
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
import {Pepsea} from './pepsea';
|
|
8
|
+
|
|
9
|
+
/** Waits if container is not started
|
|
10
|
+
* @param {number} ms - time to wait in milliseconds */
|
|
11
|
+
export async function awaitContainerStart(ms: number = 60000): Promise<void> {
|
|
12
|
+
const dc = await Pepsea.getDockerContainer();
|
|
13
|
+
await startDockerContainer(dc, ms);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function startDockerContainer(argDc: DG.DockerContainer, timeout: number = 60000): Promise<void> {
|
|
17
|
+
// argDc contains current container status
|
|
18
|
+
let dc: DG.DockerContainer | null = argDc;
|
|
9
19
|
let end: boolean = false;
|
|
10
20
|
for (let i = 0; i < timeout / 200; ++i) {
|
|
11
|
-
|
|
21
|
+
if (dc === null) dc = await grok.dapi.docker.dockerContainers.find(argDc.id);
|
|
22
|
+
|
|
23
|
+
if (isStarted(dc)) {
|
|
24
|
+
end = true;
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
|
|
12
28
|
switch (dc.status) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
break;
|
|
26
|
-
}
|
|
27
|
-
case 'error': {
|
|
28
|
-
throw new Error('Docker container error state.');
|
|
29
|
-
}
|
|
29
|
+
case 'stopped': {
|
|
30
|
+
// TODO: Use the new dockerContainers API
|
|
31
|
+
await grok.dapi.docker.dockerContainers.run(dc.id);
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
case 'pending change':
|
|
35
|
+
case 'changing': {
|
|
36
|
+
// skip to wait
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
case 'error': {
|
|
40
|
+
throw new Error('Docker container error state.');
|
|
30
41
|
}
|
|
31
|
-
|
|
42
|
+
}
|
|
43
|
+
dc = null;
|
|
32
44
|
await delay(200);
|
|
33
45
|
}
|
|
34
|
-
if (!end) throw new Error('Docker container
|
|
46
|
+
if (!end) throw new Error('Docker container start timeout.');
|
|
35
47
|
// this.dc = await grok.dapi.docker.dockerContainers.find(dcId);
|
|
36
48
|
}
|
|
49
|
+
|
|
50
|
+
export function isStarted(dc: DG.DockerContainer): boolean {
|
|
51
|
+
return dc.status === 'checking' || dc.status === 'started';
|
|
52
|
+
}
|
|
@@ -11,6 +11,7 @@ import {pepseaMethods, runPepsea} from './pepsea';
|
|
|
11
11
|
import {checkInputColumnUI} from './check-input-column';
|
|
12
12
|
import {multipleSequenceAlginmentUIOptions} from './types';
|
|
13
13
|
import {kalignVersion, msaDefaultOptions} from './constants';
|
|
14
|
+
import {awaitContainerStart} from './docker';
|
|
14
15
|
|
|
15
16
|
import {_package} from '../package';
|
|
16
17
|
|
|
@@ -58,6 +59,10 @@ export async function multipleSequenceAlignmentUI(
|
|
|
58
59
|
const msaParamsDiv = ui.inputs([gapOpenInput, gapExtendInput, terminalGapInput]);
|
|
59
60
|
const msaParamsButton = ui.button('Alignment parameters', () => {
|
|
60
61
|
msaParamsDiv.hidden = !msaParamsDiv.hidden;
|
|
62
|
+
[gapOpenInput, gapExtendInput, terminalGapInput].forEach((input) => {
|
|
63
|
+
input.root.style.removeProperty('max-width');
|
|
64
|
+
input.captionLabel.style.removeProperty('max-width');
|
|
65
|
+
});
|
|
61
66
|
}, 'Adjust alignment parameters such as penalties for opening and extending gaps');
|
|
62
67
|
msaParamsButton.classList.add('msa-params-button');
|
|
63
68
|
msaParamsDiv.hidden = true;
|
|
@@ -68,7 +73,7 @@ export async function multipleSequenceAlignmentUI(
|
|
|
68
73
|
let performAlignment: (() => Promise<DG.Column<string> | null>) | undefined;
|
|
69
74
|
|
|
70
75
|
//TODO: remove when the new version of datagrok-api is available
|
|
71
|
-
//TODO: allow only macromolecule
|
|
76
|
+
//TODO: allow only macromolecule columns to be chosen
|
|
72
77
|
const colInput = ui.columnInput('Sequence', table, seqCol, async () => {
|
|
73
78
|
performAlignment = await onColInputChange(
|
|
74
79
|
colInput.value, table, pepseaInputRootStyles, kalignInputRootStyles,
|
|
@@ -166,8 +171,11 @@ async function onColInputChange(
|
|
|
166
171
|
gapOpenInput.value ??= msaDefaultOptions.pepsea.gapOpen;
|
|
167
172
|
gapExtendInput.value ??= msaDefaultOptions.pepsea.gapExtend;
|
|
168
173
|
|
|
169
|
-
return async () =>
|
|
170
|
-
|
|
174
|
+
return async () => {
|
|
175
|
+
await awaitContainerStart();
|
|
176
|
+
return runPepsea(col, unusedName, methodInput.value!,
|
|
177
|
+
gapOpenInput.value!, gapExtendInput.value!, clustersColInput.value);
|
|
178
|
+
};
|
|
171
179
|
} else if (checkInputColumnUI(col, col.name, [NOTATION.SEPARATOR], [ALPHABET.UN], false)) {
|
|
172
180
|
//if the column is separator with unknown alphabet, it might be helm. check if it can be converted to helm
|
|
173
181
|
const potentialColUH = UnitsHandler.getOrCreate(col);
|
|
@@ -177,8 +185,11 @@ async function onColInputChange(
|
|
|
177
185
|
gapExtendInput.value ??= msaDefaultOptions.pepsea.gapExtend;
|
|
178
186
|
// convert to helm and assign alignment function to PepSea
|
|
179
187
|
|
|
180
|
-
return async () =>
|
|
181
|
-
|
|
188
|
+
return async () => {
|
|
189
|
+
await awaitContainerStart();
|
|
190
|
+
return runPepsea(helmCol, unusedName, methodInput.value!,
|
|
191
|
+
gapOpenInput.value!, gapExtendInput.value!, clustersColInput.value);
|
|
192
|
+
};
|
|
182
193
|
} else {
|
|
183
194
|
gapOpenInput.value = null;
|
|
184
195
|
gapExtendInput.value = null;
|
package/src/utils/pepsea.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {Subject} from 'rxjs';
|
|
|
6
6
|
|
|
7
7
|
import {testEvent} from '@datagrok-libraries/utils/src/test';
|
|
8
8
|
import {NOTATION, TAGS as bioTAGS, ALIGNMENT, ALPHABET} from '@datagrok-libraries/bio/src/utils/macromolecule';
|
|
9
|
+
import {ILogger} from '@datagrok-libraries/bio/src/utils/logger';
|
|
9
10
|
|
|
10
11
|
import * as C from './constants';
|
|
11
12
|
|
|
@@ -38,17 +39,15 @@ type PepseaBodyUnit = { ID: string, HELM: string };
|
|
|
38
39
|
* @param {number} gapOpen - The gap open penalty.
|
|
39
40
|
* @param {number} gapExtend - The gap extension penalty.
|
|
40
41
|
* @param {DG.Column} clustersCol - The column containing the clusters of the sequences.
|
|
42
|
+
* @param logger {ILogger} Logger
|
|
41
43
|
*/
|
|
42
44
|
export async function runPepsea(srcCol: DG.Column<string>, unUsedName: string,
|
|
43
45
|
method: typeof pepseaMethods[number] = 'ginsi', gapOpen: number = 1.53, gapExtend: number = 0.0,
|
|
44
|
-
clustersCol: DG.Column<string | number> | null = null,
|
|
45
|
-
): Promise<DG.Column<string
|
|
46
|
+
clustersCol: DG.Column<string | number> | null = null, logger?: ILogger
|
|
47
|
+
): Promise<DG.Column<string>> {
|
|
46
48
|
const pepseaContainer = await Pepsea.getDockerContainer();
|
|
47
|
-
if (pepseaContainer.status !== 'started' && pepseaContainer.status !== 'checking')
|
|
48
|
-
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
|
|
49
|
+
if (pepseaContainer.status !== 'started' && pepseaContainer.status !== 'checking')
|
|
50
|
+
throw new Error('PepSeA container has not started yet');
|
|
52
51
|
|
|
53
52
|
const peptideCount = srcCol.length;
|
|
54
53
|
clustersCol ??= DG.Column.int('Clusters', peptideCount).init(0);
|
|
@@ -72,7 +71,7 @@ export async function runPepsea(srcCol: DG.Column<string>, unUsedName: string,
|
|
|
72
71
|
|
|
73
72
|
const alignedSequences: string[] = new Array(peptideCount);
|
|
74
73
|
for (const body of bodies) { // getting aligned sequences for each cluster
|
|
75
|
-
const alignedObject = await requestAlignedObjects(pepseaContainer.id, body, method, gapOpen, gapExtend);
|
|
74
|
+
const alignedObject = await requestAlignedObjects(pepseaContainer.id, body, method, gapOpen, gapExtend, logger);
|
|
76
75
|
const alignments = alignedObject.Alignment;
|
|
77
76
|
|
|
78
77
|
for (const alignment of alignments) { // filling alignedSequencesCol
|
|
@@ -94,14 +93,62 @@ export async function runPepsea(srcCol: DG.Column<string>, unUsedName: string,
|
|
|
94
93
|
return alignedSequencesCol;
|
|
95
94
|
}
|
|
96
95
|
|
|
97
|
-
async function requestAlignedObjects(
|
|
98
|
-
|
|
96
|
+
async function requestAlignedObjects(
|
|
97
|
+
dockerfileId: string, body: PepseaBodyUnit[], method: string, gapOpen: number, gapExtend: number, logger?: ILogger
|
|
98
|
+
): Promise<PepseaResponse> {
|
|
99
99
|
const params = {
|
|
100
100
|
method: 'POST',
|
|
101
101
|
headers: {'Accept': 'application/json', 'Content-Type': 'application/json'},
|
|
102
102
|
body: JSON.stringify(body),
|
|
103
103
|
};
|
|
104
104
|
const path = `/align?method=${method}&gap_open=${gapOpen}&gap_extend=${gapExtend}`;
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
let responseObj: any;
|
|
106
|
+
if ('fetchProxy' in grok.dapi.docker.dockerContainers) {
|
|
107
|
+
// new dockerContainers API
|
|
108
|
+
const t1: number = window.performance.now();
|
|
109
|
+
// @ts-ignore
|
|
110
|
+
const response: Response = await grok.dapi.docker.dockerContainers.fetchProxy(dockerfileId, path, params);
|
|
111
|
+
const t2: number = window.performance.now();
|
|
112
|
+
_package.logger.debug(`Bio: requestAlignedObjects() dockerContainers.fetchProxy(), ET: ${(t2 - t1)} ms`);
|
|
113
|
+
const responseContentType = response.headers.get('content-type');
|
|
114
|
+
const isJson: boolean = responseContentType === 'application/json';
|
|
115
|
+
if (!response.ok && isJson) {
|
|
116
|
+
const responseJson = await response.json();
|
|
117
|
+
const pepseaErrorMsg = responseJson['pepsea-error'];
|
|
118
|
+
if (!!pepseaErrorMsg)
|
|
119
|
+
throw new Error(`PepSeA error: ${pepseaErrorMsg}`);
|
|
120
|
+
|
|
121
|
+
const datagrokErrorMsg = responseJson['datagrok-error'];
|
|
122
|
+
if (!!datagrokErrorMsg)
|
|
123
|
+
throw new Error(`Datagrok error: ${datagrokErrorMsg}`);
|
|
124
|
+
|
|
125
|
+
throw new Error(response.statusText);
|
|
126
|
+
} else if (!response.ok && !isJson) {
|
|
127
|
+
const responseStr = await response.text();
|
|
128
|
+
throw new Error(`Error: ${responseStr}`);
|
|
129
|
+
} else if (!isJson) {
|
|
130
|
+
const responseStr = await response.text();
|
|
131
|
+
throw new Error(`Error: PepSeA expected JSON response, got '${responseStr}'.`);
|
|
132
|
+
}
|
|
133
|
+
responseObj = await response.json();
|
|
134
|
+
} else {
|
|
135
|
+
const responseStr = await grok.dapi.docker.dockerContainers.request(dockerfileId, path, params)!;
|
|
136
|
+
if (!responseStr)
|
|
137
|
+
throw new Error('Empty response');
|
|
138
|
+
responseObj = JSON.parse(responseStr);
|
|
139
|
+
|
|
140
|
+
const pepseaErrorMsg = responseObj['pepsea-error'];
|
|
141
|
+
if (!!pepseaErrorMsg)
|
|
142
|
+
throw new Error(`PepSeA error: ${pepseaErrorMsg}`);
|
|
143
|
+
|
|
144
|
+
const datagrokErrorMsg = responseObj['datagrok-error'];
|
|
145
|
+
if (!!datagrokErrorMsg)
|
|
146
|
+
throw new Error(`Datagrok error: ${datagrokErrorMsg}`);
|
|
147
|
+
}
|
|
148
|
+
// Check for pepsea stderr output
|
|
149
|
+
if ('pepsea-stderr' in responseObj) {
|
|
150
|
+
const pepseaStdErr: string = responseObj['pepsea-stderr'] as string;
|
|
151
|
+
logger?.warning(pepseaStdErr);
|
|
152
|
+
}
|
|
153
|
+
return responseObj as PepseaResponse;
|
|
107
154
|
}
|