@datagrok/peptides 1.15.0 → 1.15.2
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 +18 -1
- package/dist/436.js +2 -0
- package/dist/package-test.js +2 -2
- package/dist/package.js +2 -2
- package/files/tests/100k.d42 +0 -0
- package/files/tests/200k.d42 +0 -0
- package/files/tests/50k.d42 +0 -0
- package/files/tests/{aligned_5k.d42 → 5k.d42} +0 -0
- package/package.json +5 -5
- package/src/model.ts +52 -177
- package/src/package-test.ts +2 -1
- package/src/tests/benchmarks.ts +95 -0
- package/src/tests/core.ts +4 -60
- package/src/tests/{algorithms.ts → misc.ts} +3 -16
- package/src/tests/model.ts +0 -13
- package/src/tests/table-view.ts +1 -1
- package/src/utils/algorithms.ts +166 -16
- package/src/utils/cell-renderer.ts +1 -1
- package/src/utils/constants.ts +1 -0
- package/src/utils/parallel-mutation-cliffs.ts +106 -0
- package/src/utils/types.ts +0 -1
- package/src/viewers/logo-summary.ts +2 -3
- package/src/viewers/sar-viewer.ts +10 -10
- package/src/widgets/mutation-cliffs.ts +2 -2
- package/src/widgets/peptides.ts +0 -1
- package/src/widgets/settings.ts +3 -8
- package/src/workers/mutation-cliffs-worker.ts +77 -0
|
@@ -7,6 +7,7 @@ import {findMutations} from '../utils/algorithms';
|
|
|
7
7
|
import * as type from '../utils/types';
|
|
8
8
|
import {extractColInfo} from '../utils/misc';
|
|
9
9
|
|
|
10
|
+
|
|
10
11
|
category('Algorithms', () => {
|
|
11
12
|
let activityCol: type.RawData;
|
|
12
13
|
let monomerColumns: type.RawColumn[];
|
|
@@ -30,7 +31,7 @@ category('Algorithms', () => {
|
|
|
30
31
|
|
|
31
32
|
test('MutationCliffs', async () => {
|
|
32
33
|
// Check every pair
|
|
33
|
-
let mutationCliffsInfo: type.MutationCliffs = findMutations(activityCol, monomerColumns, settings);
|
|
34
|
+
let mutationCliffsInfo: type.MutationCliffs = await findMutations(activityCol, monomerColumns, settings);
|
|
34
35
|
expect(mutationCliffsInfo.has('C'), true, `MutationCliffsInfo should have key 'C'`);
|
|
35
36
|
expect(mutationCliffsInfo.has('D'), true, `MutationCliffsInfo should have key 'D'`);
|
|
36
37
|
expect(mutationCliffsInfo.has('A'), false, `MutationCliffsInfo should not have key 'A'`);
|
|
@@ -51,21 +52,7 @@ category('Algorithms', () => {
|
|
|
51
52
|
expect(d32[0], 1, `MutationCliffsInfo['D']['3'][2] should have value 1`);
|
|
52
53
|
|
|
53
54
|
// Check with target
|
|
54
|
-
mutationCliffsInfo = findMutations(activityCol, monomerColumns, settings, {targetCol, currentTarget: '1'});
|
|
55
|
+
mutationCliffsInfo = await findMutations(activityCol, monomerColumns, settings, {targetCol, currentTarget: '1'});
|
|
55
56
|
expect(mutationCliffsInfo.size, 0, `MutationCliffsInfo should be empty for target '1'`);
|
|
56
57
|
});
|
|
57
|
-
|
|
58
|
-
test('MutationCliffs - Benchmark 5k', async () => {
|
|
59
|
-
if (!DG.Test.isInBenchmark)
|
|
60
|
-
return;
|
|
61
|
-
|
|
62
|
-
const df = (await _package.files.readBinaryDataFrames('tests/aligned_5k.d42'))[0];
|
|
63
|
-
const activityCol: type.RawData = df.getCol('Activity').getRawData();
|
|
64
|
-
const monomerCols: type.RawColumn[] = [];
|
|
65
|
-
for (let i = 1; i < 16; ++i) {
|
|
66
|
-
const col = df.getCol(i.toString());
|
|
67
|
-
monomerCols.push({name: col.name, rawData: col.getRawData(), cat: col.categories});
|
|
68
|
-
}
|
|
69
|
-
DG.time('MutationCliffs', () => findMutations(activityCol, monomerCols));
|
|
70
|
-
}, {timeout: 5000});
|
|
71
58
|
});
|
package/src/tests/model.ts
CHANGED
|
@@ -74,19 +74,6 @@ category('Model: Settings', () => {
|
|
|
74
74
|
expectFloat(scaledActivityData[i], origActivityData[i], tolerance, getError(i, SCALING_METHODS.NONE));
|
|
75
75
|
});
|
|
76
76
|
|
|
77
|
-
test('Bidirectional analysis', async () => {
|
|
78
|
-
// Check that bidirectional analysis is disabled by default
|
|
79
|
-
expect(model.settings.isBidirectional, false, 'Bidirectional analysis is enabled by default');
|
|
80
|
-
|
|
81
|
-
// Check that bidirectional analysis can be enabled
|
|
82
|
-
model.settings = {isBidirectional: true};
|
|
83
|
-
expect(model.settings.isBidirectional, true, 'Bidirectional analysis is disabled after enabling');
|
|
84
|
-
|
|
85
|
-
// Check that bidirectional analysis can be disabled
|
|
86
|
-
model.settings = {isBidirectional: false};
|
|
87
|
-
expect(model.settings.isBidirectional, false, 'Bidirectional analysis is enabled after disabling');
|
|
88
|
-
});
|
|
89
|
-
|
|
90
77
|
test('Mutation Cliffs', async () => {
|
|
91
78
|
// Check default mutation cliffs parameters
|
|
92
79
|
expect(model.settings.maxMutations, mutationCliffsDefaultParams.maxMutations, `Max mutations mismatch: expected ` +
|
package/src/tests/table-view.ts
CHANGED
|
@@ -48,7 +48,7 @@ category('Table view', () => {
|
|
|
48
48
|
|
|
49
49
|
test('Visible columns', async () => {
|
|
50
50
|
const gridCols = model.analysisView.grid.columns;
|
|
51
|
-
const posCols = model.
|
|
51
|
+
const posCols = model.positionColumns.toArray().map((col) => col.name);
|
|
52
52
|
for (let colIdx = 1; colIdx < gridCols.length; colIdx++) {
|
|
53
53
|
const col = gridCols.byIndex(colIdx)!;
|
|
54
54
|
const tableColName = col.column!.name;
|
package/src/utils/algorithms.ts
CHANGED
|
@@ -1,22 +1,32 @@
|
|
|
1
|
+
import * as DG from 'datagrok-api/dg';
|
|
1
2
|
import * as C from './constants';
|
|
2
3
|
import * as type from './types';
|
|
3
|
-
import {
|
|
4
|
+
import {ParallelMutationCliffs} from './parallel-mutation-cliffs';
|
|
5
|
+
import {CLUSTER_TYPE, ClusterStats, ClusterTypeStats,
|
|
6
|
+
MonomerPositionStats, PositionStats, SummaryStats} from '../model';
|
|
7
|
+
import BitArray from '@datagrok-libraries/utils/src/bit-array';
|
|
8
|
+
import {Stats, getStats} from './statistics';
|
|
4
9
|
|
|
5
|
-
type MutationCliffInfo = {pos: string, seq1monomer: string, seq2monomer: string, seq1Idx: number, seq2Idx: number};
|
|
6
10
|
|
|
7
|
-
export function findMutations(activityArray: type.RawData, monomerInfoArray: type.RawColumn[],
|
|
11
|
+
export async function findMutations(activityArray: type.RawData, monomerInfoArray: type.RawColumn[],
|
|
8
12
|
settings: type.PeptidesSettings = {},
|
|
9
|
-
targetOptions: {targetCol?: type.RawColumn | null, currentTarget?: string | null} = {}
|
|
13
|
+
targetOptions: {targetCol?: type.RawColumn | null, currentTarget?: string | null} = {},
|
|
14
|
+
): Promise<type.MutationCliffs> {
|
|
10
15
|
const nCols = monomerInfoArray.length;
|
|
11
16
|
if (nCols === 0)
|
|
12
17
|
throw new Error(`PepAlgorithmError: Couldn't find any column of semType '${C.SEM_TYPES.MONOMER}'`);
|
|
13
18
|
|
|
14
19
|
settings.minActivityDelta ??= 0;
|
|
15
20
|
settings.maxMutations ??= 1;
|
|
16
|
-
const currentTargetIdx = targetOptions.targetCol?.cat!.indexOf(targetOptions.currentTarget!) ?? -1;
|
|
21
|
+
//const currentTargetIdx = targetOptions.targetCol?.cat!.indexOf(targetOptions.currentTarget!) ?? -1;
|
|
17
22
|
|
|
18
|
-
const substitutionsInfo: type.MutationCliffs = new Map();
|
|
19
|
-
const nRows = activityArray.length;
|
|
23
|
+
//const substitutionsInfo: type.MutationCliffs = new Map();
|
|
24
|
+
//const nRows = activityArray.length;
|
|
25
|
+
|
|
26
|
+
const substitutionsInfo =
|
|
27
|
+
await new ParallelMutationCliffs().calc(activityArray, monomerInfoArray, settings, targetOptions);
|
|
28
|
+
return substitutionsInfo;
|
|
29
|
+
/*
|
|
20
30
|
for (let seq1Idx = 0; seq1Idx < nRows - 1; seq1Idx++) {
|
|
21
31
|
if (currentTargetIdx !== -1 && targetOptions.targetCol?.rawData[seq1Idx] !== currentTargetIdx)
|
|
22
32
|
continue;
|
|
@@ -33,11 +43,12 @@ export function findMutations(activityArray: type.RawData, monomerInfoArray: typ
|
|
|
33
43
|
continue;
|
|
34
44
|
|
|
35
45
|
let substCounterFlag = false;
|
|
36
|
-
const tempData: MutationCliffInfo[] =
|
|
46
|
+
const tempData: MutationCliffInfo[] = new Array(monomerInfoArray.length);
|
|
47
|
+
let tempDataIdx = 0;
|
|
37
48
|
for (const monomerInfo of monomerInfoArray) {
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
if (
|
|
49
|
+
const seq1categoryIdx = monomerInfo.rawData[seq1Idx];
|
|
50
|
+
const seq2categoryIdx = monomerInfo.rawData[seq2Idx];
|
|
51
|
+
if (seq1categoryIdx === seq2categoryIdx)
|
|
41
52
|
continue;
|
|
42
53
|
|
|
43
54
|
substCounter++;
|
|
@@ -45,19 +56,22 @@ export function findMutations(activityArray: type.RawData, monomerInfoArray: typ
|
|
|
45
56
|
if (substCounterFlag)
|
|
46
57
|
break;
|
|
47
58
|
|
|
48
|
-
tempData
|
|
59
|
+
tempData[tempDataIdx++] ={
|
|
49
60
|
pos: monomerInfo.name,
|
|
50
|
-
seq1monomer: monomerInfo.cat![
|
|
51
|
-
seq2monomer: monomerInfo.cat![
|
|
61
|
+
seq1monomer: monomerInfo.cat![seq1categoryIdx],
|
|
62
|
+
seq2monomer: monomerInfo.cat![seq2categoryIdx],
|
|
52
63
|
seq1Idx: seq1Idx,
|
|
53
64
|
seq2Idx: seq2Idx,
|
|
54
|
-
}
|
|
65
|
+
};
|
|
55
66
|
}
|
|
56
67
|
|
|
57
68
|
if (substCounterFlag || substCounter === 0)
|
|
58
69
|
continue;
|
|
59
70
|
|
|
60
|
-
|
|
71
|
+
// Separate processing loop in case substCOunter is 0 or out of restricted range to
|
|
72
|
+
// prevent unnecessary computations
|
|
73
|
+
for (let i = 0; i < tempDataIdx; i++) {
|
|
74
|
+
const tempDataElement = tempData[i];
|
|
61
75
|
//Working with seq1monomer
|
|
62
76
|
const seq1monomer = tempDataElement.seq1monomer;
|
|
63
77
|
if (!substitutionsInfo.has(seq1monomer))
|
|
@@ -102,4 +116,140 @@ export function findMutations(activityArray: type.RawData, monomerInfoArray: typ
|
|
|
102
116
|
}
|
|
103
117
|
|
|
104
118
|
return substitutionsInfo;
|
|
119
|
+
*/
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function calculateMonomerPositionStatistics(df: DG.DataFrame, positionColumns: DG.Column<string>[],
|
|
123
|
+
options: {isFiltered?: boolean, columns?: string[]} = {}): MonomerPositionStats {
|
|
124
|
+
options.isFiltered ??= false;
|
|
125
|
+
const monomerPositionObject = {general: {}} as MonomerPositionStats & {general: SummaryStats};
|
|
126
|
+
let activityColData: Float64Array = df.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED).getRawData() as Float64Array;
|
|
127
|
+
let sourceDfLen = df.rowCount;
|
|
128
|
+
|
|
129
|
+
if (options.isFiltered) {
|
|
130
|
+
sourceDfLen = df.filter.trueCount;
|
|
131
|
+
const tempActivityData = new Float64Array(sourceDfLen);
|
|
132
|
+
const selectedIndexes = df.filter.getSelectedIndexes();
|
|
133
|
+
for (let i = 0; i < sourceDfLen; ++i)
|
|
134
|
+
tempActivityData[i] = activityColData[selectedIndexes[i]];
|
|
135
|
+
activityColData = tempActivityData;
|
|
136
|
+
positionColumns = DG.DataFrame.fromColumns(positionColumns).clone(df.filter).columns.toList();
|
|
137
|
+
}
|
|
138
|
+
options.columns ??= positionColumns.map((col) => col.name);
|
|
139
|
+
|
|
140
|
+
for (const posCol of positionColumns) {
|
|
141
|
+
if (!options.columns.includes(posCol.name))
|
|
142
|
+
continue;
|
|
143
|
+
const posColData = posCol.getRawData();
|
|
144
|
+
const posColCateogries = posCol.categories;
|
|
145
|
+
const currentPositionObject = {general: {}} as PositionStats & {general: SummaryStats};
|
|
146
|
+
|
|
147
|
+
for (let categoryIndex = 0; categoryIndex < posColCateogries.length; ++categoryIndex) {
|
|
148
|
+
const monomer = posColCateogries[categoryIndex];
|
|
149
|
+
if (monomer === '')
|
|
150
|
+
continue;
|
|
151
|
+
|
|
152
|
+
const boolArray: boolean[] = new Array(sourceDfLen).fill(false);
|
|
153
|
+
for (let i = 0; i < sourceDfLen; ++i) {
|
|
154
|
+
if (posColData[i] === categoryIndex)
|
|
155
|
+
boolArray[i] = true;
|
|
156
|
+
}
|
|
157
|
+
const bitArray = BitArray.fromValues(boolArray);
|
|
158
|
+
const stats = bitArray.allFalse || bitArray.allTrue ?
|
|
159
|
+
{count: sourceDfLen, meanDifference: 0, ratio: 1.0, pValue: null, mask: bitArray} :
|
|
160
|
+
getStats(activityColData, bitArray);
|
|
161
|
+
currentPositionObject[monomer] = stats;
|
|
162
|
+
getSummaryStats(currentPositionObject.general, stats);
|
|
163
|
+
}
|
|
164
|
+
monomerPositionObject[posCol.name] = currentPositionObject;
|
|
165
|
+
getSummaryStats(monomerPositionObject.general, null, currentPositionObject.general);
|
|
166
|
+
}
|
|
167
|
+
return monomerPositionObject;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function getSummaryStats(
|
|
171
|
+
genObj: SummaryStats, stats: Stats | null = null, summaryStats: SummaryStats | null = null,
|
|
172
|
+
): void {
|
|
173
|
+
if (stats === null && summaryStats === null)
|
|
174
|
+
throw new Error(`MonomerPositionStatsError: either stats or summaryStats must be present`);
|
|
175
|
+
|
|
176
|
+
const possibleMaxCount = stats?.count ?? summaryStats!.maxCount;
|
|
177
|
+
genObj.maxCount ??= possibleMaxCount;
|
|
178
|
+
if (genObj.maxCount < possibleMaxCount)
|
|
179
|
+
genObj.maxCount = possibleMaxCount;
|
|
180
|
+
|
|
181
|
+
const possibleMinCount = stats?.count ?? summaryStats!.minCount;
|
|
182
|
+
genObj.minCount ??= possibleMinCount;
|
|
183
|
+
if (genObj.minCount > possibleMinCount)
|
|
184
|
+
genObj.minCount = possibleMinCount;
|
|
185
|
+
|
|
186
|
+
const possibleMaxMeanDifference = stats?.meanDifference ?? summaryStats!.maxMeanDifference;
|
|
187
|
+
genObj.maxMeanDifference ??= possibleMaxMeanDifference;
|
|
188
|
+
if (genObj.maxMeanDifference < possibleMaxMeanDifference)
|
|
189
|
+
genObj.maxMeanDifference = possibleMaxMeanDifference;
|
|
190
|
+
|
|
191
|
+
const possibleMinMeanDifference = stats?.meanDifference ?? summaryStats!.minMeanDifference;
|
|
192
|
+
genObj.minMeanDifference ??= possibleMinMeanDifference;
|
|
193
|
+
if (genObj.minMeanDifference > possibleMinMeanDifference)
|
|
194
|
+
genObj.minMeanDifference = possibleMinMeanDifference;
|
|
195
|
+
|
|
196
|
+
if (!isNaN(stats?.pValue ?? NaN)) {
|
|
197
|
+
const possibleMaxPValue = stats?.pValue ?? summaryStats!.maxPValue;
|
|
198
|
+
genObj.maxPValue ??= possibleMaxPValue;
|
|
199
|
+
if (genObj.maxPValue < possibleMaxPValue)
|
|
200
|
+
genObj.maxPValue = possibleMaxPValue;
|
|
201
|
+
|
|
202
|
+
const possibleMinPValue = stats?.pValue ?? summaryStats!.minPValue;
|
|
203
|
+
genObj.minPValue ??= possibleMinPValue;
|
|
204
|
+
if (genObj.minPValue > possibleMinPValue)
|
|
205
|
+
genObj.minPValue = possibleMinPValue;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const possibleMaxRatio = stats?.ratio ?? summaryStats!.maxRatio;
|
|
209
|
+
genObj.maxRatio ??= possibleMaxRatio;
|
|
210
|
+
if (genObj.maxRatio < possibleMaxRatio)
|
|
211
|
+
genObj.maxRatio = possibleMaxRatio;
|
|
212
|
+
|
|
213
|
+
const possibleMinRatio = stats?.ratio ?? summaryStats!.minRatio;
|
|
214
|
+
genObj.minRatio ??= possibleMinRatio;
|
|
215
|
+
if (genObj.minRatio > possibleMinRatio)
|
|
216
|
+
genObj.minRatio = possibleMinRatio;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function calculateClusterStatistics(df: DG.DataFrame, clustersColumnName: string,
|
|
220
|
+
customClusters: DG.Column<boolean>[]): ClusterTypeStats {
|
|
221
|
+
const rowCount = df.rowCount;
|
|
222
|
+
const origClustCol = df.getCol(clustersColumnName);
|
|
223
|
+
const origClustColData = origClustCol.getRawData();
|
|
224
|
+
const origClustColCat = origClustCol.categories;
|
|
225
|
+
const origClustMasks: BitArray[] = Array.from({length: origClustColCat.length},
|
|
226
|
+
() => new BitArray(rowCount, false));
|
|
227
|
+
for (let rowIdx = 0; rowIdx < rowCount; ++rowIdx)
|
|
228
|
+
origClustMasks[origClustColData[rowIdx]].setTrue(rowIdx);
|
|
229
|
+
|
|
230
|
+
const customClustMasks = customClusters.map(
|
|
231
|
+
(v) => BitArray.fromUint32Array(rowCount, v.getRawData() as Uint32Array));
|
|
232
|
+
const customClustColNamesList = customClusters.map((v) => v.name);
|
|
233
|
+
|
|
234
|
+
const activityColData: type.RawData = df.getCol(C.COLUMNS_NAMES.ACTIVITY_SCALED).getRawData();
|
|
235
|
+
|
|
236
|
+
const origClustStats: ClusterStats = {};
|
|
237
|
+
const customClustStats: ClusterStats = {};
|
|
238
|
+
|
|
239
|
+
for (const clustType of Object.values(CLUSTER_TYPE)) {
|
|
240
|
+
const masks = clustType === CLUSTER_TYPE.ORIGINAL ? origClustMasks : customClustMasks;
|
|
241
|
+
const clustNames = clustType === CLUSTER_TYPE.ORIGINAL ? origClustColCat : customClustColNamesList;
|
|
242
|
+
const resultStats = clustType === CLUSTER_TYPE.ORIGINAL ? origClustStats : customClustStats;
|
|
243
|
+
for (let maskIdx = 0; maskIdx < masks.length; ++maskIdx) {
|
|
244
|
+
const mask = masks[maskIdx];
|
|
245
|
+
const stats = mask.allTrue || mask.allFalse ?
|
|
246
|
+
{count: mask.length, meanDifference: 0, ratio: 1.0, pValue: null, mask: mask} : getStats(activityColData, mask);
|
|
247
|
+
resultStats[clustNames[maskIdx]] = stats;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const resultStats = {} as ClusterTypeStats;
|
|
252
|
+
resultStats[CLUSTER_TYPE.ORIGINAL] = origClustStats;
|
|
253
|
+
resultStats[CLUSTER_TYPE.CUSTOM] = customClustStats;
|
|
254
|
+
return resultStats;
|
|
105
255
|
}
|
|
@@ -22,7 +22,7 @@ export function setMonomerRenderer(col: DG.Column, alphabet: string): void {
|
|
|
22
22
|
export function renderMutationCliffCell(canvasContext: CanvasRenderingContext2D, currentMonomer: string,
|
|
23
23
|
currentPosition: string, monomerPositionStats: MonomerPositionStats, bound: DG.Rect,
|
|
24
24
|
mutationCliffsSelection: types.Selection, substitutionsInfo: types.MutationCliffs | null = null,
|
|
25
|
-
|
|
25
|
+
renderNums: boolean = true): void {
|
|
26
26
|
const positionStats = monomerPositionStats[currentPosition];
|
|
27
27
|
const pVal = positionStats![currentMonomer]!.pValue;
|
|
28
28
|
const currentMeanDifference = positionStats![currentMonomer]!.meanDifference;
|
package/src/utils/constants.ts
CHANGED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
|
|
2
|
+
import * as type from './types';
|
|
3
|
+
|
|
4
|
+
export type ParallelMutationReturnType = {
|
|
5
|
+
// monomers1: string[],
|
|
6
|
+
// monomers2: string[],
|
|
7
|
+
pos: string[],
|
|
8
|
+
seq1Idxs: Uint32Array,
|
|
9
|
+
seq2Idxs: Uint32Array,
|
|
10
|
+
}
|
|
11
|
+
export class ParallelMutationCliffs {
|
|
12
|
+
private _workers: Worker[];
|
|
13
|
+
private _workerCount: number;
|
|
14
|
+
|
|
15
|
+
constructor() {
|
|
16
|
+
const threadCount = navigator.hardwareConcurrency;
|
|
17
|
+
this._workerCount = Math.max(threadCount - 2, 1);
|
|
18
|
+
this._workers = new Array(this._workerCount).fill(null)
|
|
19
|
+
.map(() => new Worker(new URL('../workers/mutation-cliffs-worker', import.meta.url)));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public async calc(activityArray: type.RawData, monomerInfoArray: type.RawColumn[],
|
|
23
|
+
settings: type.PeptidesSettings = {},
|
|
24
|
+
targetOptions: {targetCol?: type.RawColumn | null, currentTarget?: string | null} = {},
|
|
25
|
+
): Promise<type.MutationCliffs> {
|
|
26
|
+
const substitutionsInfo: type.MutationCliffs = new Map();
|
|
27
|
+
try {
|
|
28
|
+
const currentTargetIdx = targetOptions.targetCol?.cat!.indexOf(targetOptions.currentTarget!) ?? -1;
|
|
29
|
+
|
|
30
|
+
const len = activityArray.length;
|
|
31
|
+
const promises = new Array<Promise<ParallelMutationReturnType>>(this._workerCount);
|
|
32
|
+
const matSize = len * (len - 1) / 2; // size of reduced upper triangular matrix
|
|
33
|
+
this._workerCount = Math.min(this._workerCount, matSize);
|
|
34
|
+
const chunkSize = matSize / this._workerCount;
|
|
35
|
+
// monomerInfoArray[m].cat and targetCol can contain some function from datagrok-api,
|
|
36
|
+
//which the worker can't serialize and fails. so we need to remove it
|
|
37
|
+
monomerInfoArray.forEach((monomerInfo) => {
|
|
38
|
+
monomerInfo.cat = monomerInfo.cat?.slice();
|
|
39
|
+
});
|
|
40
|
+
targetOptions.targetCol?.cat && (targetOptions.targetCol.cat = targetOptions.targetCol.cat.slice());
|
|
41
|
+
for (let idx = 0; idx < this._workerCount; idx++) {
|
|
42
|
+
promises[idx] = new Promise((resolveWorker, rejectWorker) => {
|
|
43
|
+
const startIdx = Math.floor(idx * chunkSize);
|
|
44
|
+
const endIdx = idx === this._workerCount - 1 ? matSize : Math.floor((idx + 1) * chunkSize);
|
|
45
|
+
this._workers[idx].postMessage(
|
|
46
|
+
{startIdx, endIdx, activityArray, monomerInfoArray, settings, currentTargetIdx, targetOptions});
|
|
47
|
+
this._workers[idx].onmessage = ({data: {pos, seq1Idxs, seq2Idxs, error}}): void => {
|
|
48
|
+
if (error) {
|
|
49
|
+
this._workers[idx]?.terminate();
|
|
50
|
+
rejectWorker(error);
|
|
51
|
+
} else {
|
|
52
|
+
this._workers[idx].terminate();
|
|
53
|
+
resolveWorker({pos, seq1Idxs, seq2Idxs});
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const results = await Promise.all(promises);
|
|
60
|
+
const monomerPositionsMap = new Map<string, number>();
|
|
61
|
+
monomerInfoArray.forEach((monomerInfo, i) => {
|
|
62
|
+
monomerPositionsMap.set(monomerInfo.name, i);
|
|
63
|
+
});
|
|
64
|
+
results.filter(Boolean).forEach((result) => {
|
|
65
|
+
for (let i = 0; i< result.pos.length; i++) {
|
|
66
|
+
//getting monomers from monomerInfoArray by position
|
|
67
|
+
const monomerPos = monomerPositionsMap.get(result.pos[i])!;
|
|
68
|
+
const monomer1Cat = monomerInfoArray[monomerPos].rawData[result.seq1Idxs[i]];
|
|
69
|
+
const monomer1 = monomerInfoArray[monomerPos].cat![monomer1Cat];
|
|
70
|
+
const monomer2Cat = monomerInfoArray[monomerPos].rawData[result.seq2Idxs[i]];
|
|
71
|
+
const monomer2 = monomerInfoArray[monomerPos].cat![monomer2Cat];
|
|
72
|
+
|
|
73
|
+
// filling map
|
|
74
|
+
if (!substitutionsInfo.has(monomer1))
|
|
75
|
+
substitutionsInfo.set(monomer1, new Map());
|
|
76
|
+
if (!substitutionsInfo.has(monomer2))
|
|
77
|
+
substitutionsInfo.set(monomer2, new Map());
|
|
78
|
+
const position1Map = substitutionsInfo.get(monomer1)!;
|
|
79
|
+
const position2Map = substitutionsInfo.get(monomer2)!;
|
|
80
|
+
if (!position1Map.has(result.pos[i]))
|
|
81
|
+
position1Map.set(result.pos[i], new Map());
|
|
82
|
+
if (!position2Map.has(result.pos[i]))
|
|
83
|
+
position2Map.set(result.pos[i], new Map());
|
|
84
|
+
const indexes1Map = position1Map.get(result.pos[i])!;
|
|
85
|
+
const indexes2Map = position2Map.get(result.pos[i])!;
|
|
86
|
+
if (!indexes1Map.has(result.seq1Idxs[i]))
|
|
87
|
+
indexes1Map.set(result.seq1Idxs[i], []);
|
|
88
|
+
if (!indexes2Map.has(result.seq2Idxs[i]))
|
|
89
|
+
indexes2Map.set(result.seq2Idxs[i], []);
|
|
90
|
+
const indexes1 = indexes1Map.get(result.seq1Idxs[i])!;
|
|
91
|
+
const indexes2 = indexes2Map.get(result.seq2Idxs[i])!;
|
|
92
|
+
(indexes1 as number[]).push(result.seq2Idxs[i]);
|
|
93
|
+
(indexes2 as number[]).push(result.seq1Idxs[i]);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
} catch (error) {
|
|
97
|
+
this.terminate();
|
|
98
|
+
console.error(error);
|
|
99
|
+
}
|
|
100
|
+
return substitutionsInfo;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
public terminate(): void {
|
|
104
|
+
this._workers?.forEach((worker) => worker?.terminate());
|
|
105
|
+
}
|
|
106
|
+
}
|
package/src/utils/types.ts
CHANGED
|
@@ -214,7 +214,6 @@ export class LogoSummaryTable extends DG.JsViewer {
|
|
|
214
214
|
this.viewerGrid.sort([C.LST_COLUMN_NAMES.MEMBERS], [false]);
|
|
215
215
|
this.updateFilter();
|
|
216
216
|
const gridClustersCol = this.viewerGrid.col(C.LST_COLUMN_NAMES.CLUSTER)!;
|
|
217
|
-
// gridClustersCol.column!.name = C.LST_COLUMN_NAMES.CLUSTER;
|
|
218
217
|
gridClustersCol.visible = true;
|
|
219
218
|
this.viewerGrid.columns.setOrder([C.LST_COLUMN_NAMES.CLUSTER, C.LST_COLUMN_NAMES.MEMBERS,
|
|
220
219
|
C.LST_COLUMN_NAMES.WEB_LOGO, C.LST_COLUMN_NAMES.DISTRIBUTION, C.LST_COLUMN_NAMES.MEAN_DIFFERENCE,
|
|
@@ -224,7 +223,7 @@ export class LogoSummaryTable extends DG.JsViewer {
|
|
|
224
223
|
|
|
225
224
|
const webLogoCache = new DG.LruCache<number, DG.Viewer & IWebLogoViewer>();
|
|
226
225
|
const distCache = new DG.LruCache<number, DG.Viewer<DG.IHistogramLookSettings>>();
|
|
227
|
-
const maxSequenceLen = this.model.
|
|
226
|
+
const maxSequenceLen = this.model.positionColumns.toArray().length;
|
|
228
227
|
const webLogoGridCol = this.viewerGrid.columns.byName(C.LST_COLUMN_NAMES.WEB_LOGO)!;
|
|
229
228
|
webLogoGridCol.cellType = 'html';
|
|
230
229
|
webLogoGridCol.width = 350;
|
|
@@ -270,7 +269,7 @@ export class LogoSummaryTable extends DG.JsViewer {
|
|
|
270
269
|
const webLogoTable = this.createWebLogoDf(pepCol, clusterBitSet);
|
|
271
270
|
viewer = await webLogoTable.plot
|
|
272
271
|
.fromType('WebLogo', {positionHeight: this.webLogoMode, horizontalAlignment: HorizontalAlignments.LEFT,
|
|
273
|
-
maxHeight: 1000, minHeight: height, positionWidth: positionWidth});
|
|
272
|
+
maxHeight: 1000, minHeight: height, positionWidth: positionWidth, showPositionLabels: false});
|
|
274
273
|
webLogoCache.set(currentRowIdx, viewer);
|
|
275
274
|
}
|
|
276
275
|
gridCell.element = viewer.root;
|
|
@@ -27,7 +27,7 @@ export class MonomerPosition extends DG.JsViewer {
|
|
|
27
27
|
_titleHost = ui.divText(SELECTION_MODE.MUTATION_CLIFFS, {id: 'pep-viewer-title'});
|
|
28
28
|
_viewerGrid!: DG.Grid;
|
|
29
29
|
_model!: PeptidesModel;
|
|
30
|
-
|
|
30
|
+
color: string;
|
|
31
31
|
aggregation: string;
|
|
32
32
|
target: string;
|
|
33
33
|
keyPressed: boolean = false;
|
|
@@ -37,7 +37,7 @@ export class MonomerPosition extends DG.JsViewer {
|
|
|
37
37
|
super();
|
|
38
38
|
this.target = this.string(MONOMER_POSITION_PROPERTIES.TARGET, null,
|
|
39
39
|
{category: SELECTION_MODE.MUTATION_CLIFFS, choices: []});
|
|
40
|
-
this.
|
|
40
|
+
this.color = this.string(MONOMER_POSITION_PROPERTIES.COLOR_COLUMN_NAME, C.COLUMNS_NAMES.ACTIVITY_SCALED,
|
|
41
41
|
{category: SELECTION_MODE.INVARIANT_MAP,
|
|
42
42
|
choices: wu(grok.shell.t.columns.numerical).toArray().map((col) => col.name)});
|
|
43
43
|
this.aggregation = this.string(MONOMER_POSITION_PROPERTIES.AGGREGATION, DG.AGG.AVG,
|
|
@@ -82,14 +82,14 @@ export class MonomerPosition extends DG.JsViewer {
|
|
|
82
82
|
onPropertyChanged(property: DG.Property): void {
|
|
83
83
|
super.onPropertyChanged(property);
|
|
84
84
|
if (property.name === MONOMER_POSITION_PROPERTIES.TARGET)
|
|
85
|
-
this.model.updateMutationCliffs();
|
|
85
|
+
this.model.updateMutationCliffs().then(() => this.render(true));
|
|
86
86
|
|
|
87
|
-
this.render();
|
|
87
|
+
this.render(true);
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
createMonomerPositionDf(): DG.DataFrame {
|
|
91
91
|
const uniqueMonomers = new Set<string>();
|
|
92
|
-
const splitSeqCols = this.model.
|
|
92
|
+
const splitSeqCols = this.model.positionColumns.toArray();
|
|
93
93
|
for (const col of splitSeqCols) {
|
|
94
94
|
const colCat = col.categories;
|
|
95
95
|
for (const cat of colCat) {
|
|
@@ -112,11 +112,11 @@ export class MonomerPosition extends DG.JsViewer {
|
|
|
112
112
|
const monomerPositionDf = this.createMonomerPositionDf();
|
|
113
113
|
this.viewerGrid = monomerPositionDf.plot.grid();
|
|
114
114
|
this.viewerGrid.sort([C.COLUMNS_NAMES.MONOMER]);
|
|
115
|
-
this.viewerGrid.columns.setOrder([C.COLUMNS_NAMES.MONOMER, ...this.model.
|
|
115
|
+
this.viewerGrid.columns.setOrder([C.COLUMNS_NAMES.MONOMER, ...this.model.positionColumns.toArray().map((col) => col.name)]);
|
|
116
116
|
const monomerCol = monomerPositionDf.getCol(C.COLUMNS_NAMES.MONOMER);
|
|
117
117
|
CR.setMonomerRenderer(monomerCol, this.model.alphabet);
|
|
118
118
|
this.viewerGrid.onCellRender.subscribe((args: DG.GridCellRenderArgs) => renderCell(args, this.model,
|
|
119
|
-
this.mode === SELECTION_MODE.INVARIANT_MAP, this.dataFrame.getCol(this.
|
|
119
|
+
this.mode === SELECTION_MODE.INVARIANT_MAP, this.dataFrame.getCol(this.color), this.aggregation as DG.AGG));
|
|
120
120
|
|
|
121
121
|
this.viewerGrid.onCellTooltip((gridCell: DG.GridCell, x: number, y: number) => {
|
|
122
122
|
if (!gridCell.isTableCell) {
|
|
@@ -367,7 +367,7 @@ export class MostPotentResidues extends DG.JsViewer {
|
|
|
367
367
|
return;
|
|
368
368
|
const monomerPosition = this.getMonomerPosition(gridCell);
|
|
369
369
|
if (this.currentGridRowIdx !== null) {
|
|
370
|
-
const previousMonomerPosition = this.getMonomerPosition(this.viewerGrid.cell(
|
|
370
|
+
const previousMonomerPosition = this.getMonomerPosition(this.viewerGrid.cell('Diff', this.currentGridRowIdx));
|
|
371
371
|
this.model.modifyMutationCliffsSelection(previousMonomerPosition, {shiftPressed: true, ctrlPressed: true}, false);
|
|
372
372
|
}
|
|
373
373
|
if (this.model.mutationCliffs?.get(monomerPosition.monomerOrCluster)?.get(monomerPosition.positionOrClusterType)?.size)
|
|
@@ -420,7 +420,7 @@ export class MostPotentResidues extends DG.JsViewer {
|
|
|
420
420
|
|
|
421
421
|
function renderCell(args: DG.GridCellRenderArgs, model: PeptidesModel, isInvariantMap?: boolean,
|
|
422
422
|
colorCol?: DG.Column<number>, colorAgg?: DG.AGG, renderNums?: boolean): void {
|
|
423
|
-
const renderColNames = [...model.
|
|
423
|
+
const renderColNames = [...model.positionColumns.toArray().map((col) => col.name), C.COLUMNS_NAMES.MEAN_DIFFERENCE];
|
|
424
424
|
const canvasContext = args.g;
|
|
425
425
|
const bound = args.bounds;
|
|
426
426
|
|
|
@@ -478,7 +478,7 @@ function renderCell(args: DG.GridCellRenderArgs, model: PeptidesModel, isInvaria
|
|
|
478
478
|
canvasContext, currentMonomer, currentPosition, model.invariantMapSelection, value, bound, color);
|
|
479
479
|
} else {
|
|
480
480
|
CR.renderMutationCliffCell(canvasContext, currentMonomer, currentPosition, model.monomerPositionStats, bound,
|
|
481
|
-
model.mutationCliffsSelection, model.mutationCliffs,
|
|
481
|
+
model.mutationCliffsSelection, model.mutationCliffs, renderNums);
|
|
482
482
|
}
|
|
483
483
|
args.preventDefault();
|
|
484
484
|
canvasContext.restore();
|
|
@@ -147,11 +147,11 @@ export function mutationCliffsWidget(table: DG.DataFrame, model: PeptidesModel):
|
|
|
147
147
|
|
|
148
148
|
const gridCols = model.analysisView.grid.columns;
|
|
149
149
|
const originalGridColCount = gridCols.length;
|
|
150
|
-
const positionColumns = model.
|
|
150
|
+
const positionColumns = model.positionColumns.toArray().map((col) => col.name);
|
|
151
151
|
const columnNames: string[] = [];
|
|
152
152
|
for (let colIdx = 1; colIdx < originalGridColCount; colIdx++) {
|
|
153
153
|
const gridCol = gridCols.byIndex(colIdx);
|
|
154
|
-
if (gridCol?.name === model.settings.sequenceColumnName || (gridCol?.visible === true && !positionColumns.
|
|
154
|
+
if (gridCol?.name === model.settings.sequenceColumnName || (gridCol?.visible === true && !positionColumns.includes(gridCol.name)))
|
|
155
155
|
columnNames.push(gridCol!.name);
|
|
156
156
|
}
|
|
157
157
|
|
package/src/widgets/peptides.ts
CHANGED
|
@@ -180,7 +180,6 @@ export async function startAnalysis(activityColumn: DG.Column<number>, peptidesC
|
|
|
180
180
|
sequenceColumnName: peptidesCol.name,
|
|
181
181
|
activityColumnName: activityColumn.name,
|
|
182
182
|
scaling: scaling,
|
|
183
|
-
isBidirectional: false,
|
|
184
183
|
columns: {},
|
|
185
184
|
maxMutations: 1,
|
|
186
185
|
minActivityDelta: 0,
|
package/src/widgets/settings.ts
CHANGED
|
@@ -22,7 +22,6 @@ export enum SETTINGS_PANES {
|
|
|
22
22
|
export enum GENERAL_INPUTS {
|
|
23
23
|
ACTIVITY = 'Activity',
|
|
24
24
|
ACTIVITY_SCALING = 'Activity scaling',
|
|
25
|
-
BIDIRECTIONAL_ANALYSIS = 'Bidirectional analysis',
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
export enum VIEWERS_INPUTS {
|
|
@@ -51,7 +50,7 @@ export function getSettingsDialog(model: PeptidesModel): SettingsElements {
|
|
|
51
50
|
const accordion = ui.accordion();
|
|
52
51
|
const settings = model.settings;
|
|
53
52
|
const currentScaling = settings.scaling ?? C.SCALING_METHODS.NONE;
|
|
54
|
-
const currentBidirectional = settings.isBidirectional ?? false;
|
|
53
|
+
// const currentBidirectional = settings.isBidirectional ?? false;
|
|
55
54
|
const currentMaxMutations = settings.maxMutations ?? 1;
|
|
56
55
|
const currentMinActivityDelta = settings.minActivityDelta ?? 0;
|
|
57
56
|
const currentColumns = settings.columns ?? {};
|
|
@@ -69,13 +68,9 @@ export function getSettingsDialog(model: PeptidesModel): SettingsElements {
|
|
|
69
68
|
ui.choiceInput(GENERAL_INPUTS.ACTIVITY_SCALING, currentScaling, Object.values(C.SCALING_METHODS),
|
|
70
69
|
() => result.scaling = activityScaling.value as C.SCALING_METHODS) as DG.InputBase<C.SCALING_METHODS>;
|
|
71
70
|
activityScaling.setTooltip('Activity column transformation method');
|
|
72
|
-
const bidirectionalAnalysis = ui.boolInput(GENERAL_INPUTS.BIDIRECTIONAL_ANALYSIS, currentBidirectional,
|
|
73
|
-
() => result.isBidirectional = bidirectionalAnalysis.value) as DG.InputBase<boolean>;
|
|
74
|
-
bidirectionalAnalysis.setTooltip('Distinguish between positive and negative mean activity difference in ' +
|
|
75
|
-
'Monomer-Position and Most Potent Residues viewers');
|
|
76
71
|
|
|
77
|
-
accordion.addPane(SETTINGS_PANES.GENERAL, () => ui.inputs([activityCol, activityScaling
|
|
78
|
-
inputs[SETTINGS_PANES.GENERAL] = [activityCol, activityScaling
|
|
72
|
+
accordion.addPane(SETTINGS_PANES.GENERAL, () => ui.inputs([activityCol, activityScaling]), true);
|
|
73
|
+
inputs[SETTINGS_PANES.GENERAL] = [activityCol, activityScaling];
|
|
79
74
|
|
|
80
75
|
// Viewers pane options
|
|
81
76
|
/* FIXME: combinations of adding and deleting viewers are not working properly
|