@platforma-open/milaboratories.tcrdisco-enrichment.workflow 1.1.0

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.
@@ -0,0 +1,579 @@
1
+ wf := import("@platforma-sdk/workflow-tengo:workflow")
2
+ assets := import("@platforma-sdk/workflow-tengo:assets")
3
+ pframes := import("@platforma-sdk/workflow-tengo:pframes")
4
+ render := import("@platforma-sdk/workflow-tengo:render")
5
+ text := import("text")
6
+ xsv := import("@platforma-sdk/workflow-tengo:pframes.xsv")
7
+ ll := import("@platforma-sdk/workflow-tengo:ll")
8
+ smart := import("@platforma-sdk/workflow-tengo:smart")
9
+ pSpec := import("@platforma-sdk/workflow-tengo:pframes.spec")
10
+ json := import("json")
11
+
12
+ tcrAnalysisTpl := assets.importTemplate(":tcr-analysis")
13
+ tcrAbPairsTpl := assets.importTemplate(":tcr-ab-pairs")
14
+
15
+ topTablePfconvParamsLib := import(":libs.top_table_pfconv_params")
16
+ tcrPairsParamsLib := import(":libs.tcr_pairs_params")
17
+ topFrequenciesLib := import(":libs.top_frequencies")
18
+ cdSubsetParamsLib := import(":libs.cd_subset_params")
19
+
20
+ mapToPValueData := func(map) {
21
+ data := {}
22
+ for key, value in map {
23
+ data[json.encode([key])] = value
24
+ }
25
+ result := {
26
+ keyLength: 1,
27
+ data: data
28
+ }
29
+ return result
30
+ }
31
+ createJsonPColumnData := func(data) {
32
+ return smart.createValueResource({ Name: "PColumnData/Json", Version: "1" }, data)
33
+ }
34
+
35
+ // Helper function to process results for one chain type
36
+ processChainResults := func(mainSpec, inputType, log2FcThreshold, pAdjThreshold, trace,
37
+ blockId, denominators, topTableFile, daFile, defaultConvMem, defaultConvCpu,
38
+ topDegBuilder, cdSubsetCol) {
39
+ topTableImportParams := topTablePfconvParamsLib.getColumns(copy(mainSpec), inputType,
40
+ log2FcThreshold,
41
+ pAdjThreshold,
42
+ cdSubsetCol,
43
+ denominators)
44
+ topTablePf := xsv.importFile(topTableFile, "csv", topTableImportParams,
45
+ // This changes output format from default to per column
46
+ // So key will be column name and values spec and data
47
+ {splitDataAndSpec: true, mem: defaultConvMem, cpu: defaultConvCpu})
48
+
49
+ for columnName, value in topTablePf {
50
+ columnData := value.data
51
+ topDegBuilder.add(
52
+ columnName,
53
+ trace.inject(value.spec),
54
+ columnData
55
+ )
56
+ }
57
+ }
58
+
59
+ wf.prepare(func(args) {
60
+ bundleBuilder := wf.createPBundleBuilder()
61
+
62
+ // Load mainRef and its associated sequences
63
+ bundleBuilder.addAnchor("mainAlpha", args.mainRef)
64
+ bundleBuilder.addSingle({
65
+ annotations: {
66
+ "pl7.app/isAbundance": "true",
67
+ "pl7.app/abundance/normalized": "true",
68
+ "pl7.app/abundance/isPrimary": "true"
69
+ },
70
+ axes: [{anchor: "mainAlpha", idx: 0},
71
+ {anchor: "mainAlpha", idx: 1}]
72
+ }, "mainAlphaFraction");
73
+ bundleBuilder.addMulti({
74
+ name: "pl7.app/vdj/sequence",
75
+ domain: {
76
+ "pl7.app/vdj/feature": "CDR3"
77
+ },
78
+ axes: [{anchor: "mainAlpha", idx: 1}]
79
+ }, "mainCdr3s");
80
+ bundleBuilder.addMulti({
81
+ name: "pl7.app/vdj/geneHit",
82
+ axes: [{anchor: "mainAlpha", idx: 1}]
83
+ }, "mainVDJGenes");
84
+
85
+ // Load other chain counts
86
+ // @TODO: Once override option is available in axis update code to load TCRBeta chains, we can remove this part only
87
+ bundleBuilder.addMulti({
88
+ annotations: {
89
+ "pl7.app/isAbundance": "true",
90
+ "pl7.app/abundance/normalized": "false",
91
+ "pl7.app/abundance/isPrimary": "true"
92
+ },
93
+ axes: [{anchor: "mainAlpha", idx: 0}],
94
+ partialAxesMatch: true
95
+ }, "mainCounts");
96
+ bundleBuilder.addMulti({
97
+ annotations: {
98
+ "pl7.app/isAbundance": "true",
99
+ "pl7.app/abundance/normalized": "true",
100
+ "pl7.app/abundance/isPrimary": "true"
101
+ },
102
+ axes: [{anchor: "mainAlpha", idx: 0}]
103
+ }, "mainFractions");
104
+
105
+ // Load all sequence and CDR3 columns
106
+ bundleBuilder.addMulti({
107
+ name: "pl7.app/vdj/sequence",
108
+ domain: {
109
+ "pl7.app/vdj/feature": "CDR3"
110
+ }
111
+ }, "otherCdr3s");
112
+ bundleBuilder.addMulti({
113
+ name: "pl7.app/vdj/geneHit"
114
+ }, "otherVDJGenes");
115
+
116
+ // Load all metadata columns
117
+ bundleBuilder.addMulti({
118
+ name: "pl7.app/metadata",
119
+ axes: [{anchor: "mainAlpha", idx: 0}]
120
+ }, "allMetadata");
121
+
122
+
123
+ // If cdRef is provided, load it and its metadata
124
+ if args.cdRef != undefined {
125
+ // Get the CD4/8 dataset and its associated sequences
126
+ bundleBuilder.addAnchor("cdAlpha", args.cdRef)
127
+ bundleBuilder.addSingle({
128
+ axes: [{anchor: "cdAlpha", idx: 0},
129
+ {anchor: "cdAlpha", idx: 1}],
130
+ annotations: {
131
+ "pl7.app/isAbundance": "true",
132
+ "pl7.app/abundance/normalized": "true",
133
+ "pl7.app/abundance/isPrimary": "true"
134
+ }
135
+ }, "cdAlphaFraction");
136
+ bundleBuilder.addSingle({
137
+ name: "pl7.app/vdj/sequence",
138
+ domain: {
139
+ "pl7.app/alphabet": "aminoacid",
140
+ "pl7.app/vdj/feature": "CDR3"
141
+ },
142
+ axes: [{anchor: "cdAlpha", idx: 1}]
143
+ }, "cdCdr3");
144
+ bundleBuilder.addSingle({
145
+ name: "pl7.app/vdj/geneHit",
146
+ domain: {
147
+ "pl7.app/vdj/reference": "VGene"
148
+ },
149
+ axes: [{anchor: "cdAlpha", idx: 1}]
150
+ }, "cdVGene");
151
+ }
152
+
153
+ metaRefs := {}
154
+ i := 0
155
+ for metaRef in args.covariateRefs {
156
+ metaRefs["metaRef" + i ] = wf.resolve(metaRef, { errIfMissing: true })
157
+ i = i + 1
158
+ }
159
+
160
+ return {
161
+ columns: bundleBuilder.build(),
162
+ resolvedContrastFactor: wf.resolve(args.contrastFactor),
163
+ resolvedCdSubsetCol: wf.resolve(args.cdSubsetCol),
164
+ metaRefs: metaRefs
165
+ }
166
+ })
167
+
168
+ wf.body(func(args) {
169
+ blockId := wf.blockId().getDataAsJson()
170
+ columns := args.columns
171
+ denominators := args.denominators
172
+ denominatorString := text.join(denominators, "-")
173
+ cdSubsetCol := undefined
174
+ // This is needed for cases in which user selects a value and then removes the CD4/8 dataset
175
+ if args.cdRef != undefined {
176
+ cdSubsetCol = string(args.resolvedCdSubsetCol.spec.annotations["pl7.app/label"])
177
+ }
178
+ thresholdCounts := args.thresholdCounts
179
+ thresholdSamples := args.thresholdSamples
180
+ log2FcThreshold := args.log2FcThreshold
181
+ pAdjThreshold := args.pAdjThreshold
182
+ contrastColLabel := args.resolvedContrastFactor.spec.annotations["pl7.app/label"]
183
+ pairingMetadataCol := args.pairingMetadataCol
184
+ // Barcode ID is the default pairing metadata column when we have demultiplexed data
185
+ if pairingMetadataCol == undefined {
186
+ pairingMetadataCol = "Barcode ID"
187
+ }
188
+ outputs := {}
189
+
190
+ // Set default conversion memory and CPU
191
+ defaultConvMem := "16GiB"
192
+ defaultConvCpu := 1
193
+
194
+ covariates := []
195
+ for _, v in args.metaRefs {
196
+ covariates = append(covariates, v)
197
+ }
198
+ // convert PColumns to csv
199
+ covariatesTsv := xsv.exportFrame(covariates, "tsv", {mem: defaultConvMem, cpu: defaultConvCpu})
200
+
201
+ //////////////// Load input data ////////////////
202
+ // Get the main dataset
203
+ mainAlpha := columns.getColumn(args.mainRef)
204
+ mainAlphaSpec := mainAlpha.spec
205
+ mainAlphaClonoStructure := mainAlphaSpec.axesSpec[1].domain["pl7.app/vdj/clonotypeKey/structure"]
206
+ // Get counts block id from mainAlpha key
207
+ countsBlockId := mainAlphaSpec.axesSpec[1].domain["pl7.app/vdj/clonotypingRunId"]
208
+
209
+ // Build TSV file for main dataset (Alpha)
210
+ mainAlphaTable := pframes.tsvFileBuilder()
211
+ mainAlphaTable.setAxisHeader("pl7.app/sampleId", "internalSampleId")
212
+ mainAlphaTable.setAxisHeader(mainAlphaSpec.axesSpec[1].name, "clonotypeKey")
213
+ mainAlphaTable.add(mainAlpha, {header: "count"})
214
+ mainAlphaTable.add(columns.getColumn("mainAlphaFraction"), {header: "fraction"})
215
+ for cdr3Col in columns.getColumns("mainCdr3s") {
216
+ if cdr3Col.spec.domain["pl7.app/alphabet"] == "aminoacid" {
217
+ mainAlphaTable.add(cdr3Col, {header: "CDR3aa"})
218
+ } else if cdr3Col.spec.domain["pl7.app/alphabet"] == "nucleotide" {
219
+ mainAlphaTable.add(cdr3Col, {header: "CDR3nt"})
220
+ }
221
+ }
222
+ for geneCol in columns.getColumns("mainVDJGenes") {
223
+ if geneCol.spec.domain["pl7.app/vdj/reference"] == "VGene" {
224
+ mainAlphaTable.add(geneCol, {header: "VGene"})
225
+ } else if geneCol.spec.domain["pl7.app/vdj/reference"] == "JGene" {
226
+ mainAlphaTable.add(geneCol, {header: "JGene"})
227
+ }
228
+ }
229
+ mainAlphaTable.mem(defaultConvMem)
230
+ mainAlphaTable.cpu(defaultConvCpu)
231
+ mainAlphaTsv := mainAlphaTable.build()
232
+
233
+ // Build TSV file for main dataset (Beta)
234
+ // Load beta chain using subtemplate
235
+ mainBetaTable := pframes.tsvFileBuilder()
236
+ mainBetaTable.setAxisHeader("pl7.app/sampleId", "internalSampleId")
237
+ mainBetaTable.setAxisHeader(mainAlphaSpec.axesSpec[1].name, "clonotypeKey")
238
+ mainBetaSpec := undefined
239
+ for countCol in columns.getColumns("mainCounts") {
240
+ colChain := countCol.spec.axesSpec[1].domain["pl7.app/vdj/chain"]
241
+ colBlockId := countCol.spec.axesSpec[1].domain["pl7.app/vdj/clonotypingRunId"]
242
+ clonoStructure := countCol.spec.axesSpec[1].domain["pl7.app/vdj/clonotypeKey/structure"]
243
+ if colChain == "TCRBeta" && colBlockId == countsBlockId && clonoStructure == mainAlphaClonoStructure {
244
+ mainBetaTable.add(countCol, {header: "count"})
245
+ mainBetaSpec = countCol.spec
246
+ }
247
+ }
248
+ for fractionCol in columns.getColumns("mainFractions") {
249
+ colChain := fractionCol.spec.axesSpec[1].domain["pl7.app/vdj/chain"]
250
+ colBlockId := fractionCol.spec.axesSpec[1].domain["pl7.app/vdj/clonotypingRunId"]
251
+ clonoStructure := fractionCol.spec.axesSpec[1].domain["pl7.app/vdj/clonotypeKey/structure"]
252
+ if colChain == "TCRBeta" && colBlockId == countsBlockId && clonoStructure == mainAlphaClonoStructure {
253
+ mainBetaTable.add(fractionCol, {header: "fraction"})
254
+ }
255
+ }
256
+ for cdr3Col in columns.getColumns("otherCdr3s") {
257
+ colChain := cdr3Col.spec.axesSpec[0].domain["pl7.app/vdj/chain"]
258
+ colBlockId := cdr3Col.spec.axesSpec[0].domain["pl7.app/vdj/clonotypingRunId"]
259
+ clonoStructure := cdr3Col.spec.axesSpec[0].domain["pl7.app/vdj/clonotypeKey/structure"]
260
+ if colChain == "TCRBeta" && colBlockId == countsBlockId && clonoStructure == mainAlphaClonoStructure {
261
+ if cdr3Col.spec.domain["pl7.app/alphabet"] == "aminoacid" {
262
+ mainBetaTable.add(cdr3Col, {header: "CDR3aa"})
263
+ } else if cdr3Col.spec.domain["pl7.app/alphabet"] == "nucleotide" {
264
+ mainBetaTable.add(cdr3Col, {header: "CDR3nt"})
265
+ }
266
+ }
267
+ }
268
+ for vGeneCol in columns.getColumns("otherVDJGenes") {
269
+ colChain := vGeneCol.spec.axesSpec[0].domain["pl7.app/vdj/chain"]
270
+ colBlockId := vGeneCol.spec.axesSpec[0].domain["pl7.app/vdj/clonotypingRunId"]
271
+ clonoStructure := vGeneCol.spec.axesSpec[0].domain["pl7.app/vdj/clonotypeKey/structure"]
272
+ if colChain == "TCRBeta" && colBlockId == countsBlockId && clonoStructure == mainAlphaClonoStructure {
273
+ if vGeneCol.spec.domain["pl7.app/vdj/reference"] == "VGene" {
274
+ mainBetaTable.add(vGeneCol, {header: "VGene"})
275
+ } else if vGeneCol.spec.domain["pl7.app/vdj/reference"] == "JGene" {
276
+ mainBetaTable.add(vGeneCol, {header: "JGene"})
277
+ }
278
+ }
279
+ }
280
+ mainBetaTable.mem(defaultConvMem)
281
+ mainBetaTable.cpu(defaultConvCpu)
282
+ mainBetaTsv := mainBetaTable.build()
283
+
284
+ // Load main metadata
285
+ metadataTable := pframes.tsvFileBuilder()
286
+ metadataTable.setAxisHeader("pl7.app/sampleId", "internalSampleId")
287
+ for metadataCol in columns.getColumns("allMetadata") {
288
+ metadataTable.add(metadataCol, {header: metadataCol.spec.annotations["pl7.app/label"]})
289
+ }
290
+ metadataTable.mem(defaultConvMem)
291
+ metadataTable.cpu(defaultConvCpu)
292
+ metadataTsv := metadataTable.build()
293
+
294
+ // Get CD info and its beta alternative (if available)
295
+ cdAlphaTsv := undefined
296
+ cdBetaTsv := undefined
297
+ if args.cdRef != undefined {
298
+ cdAlpha := columns.getColumn(args.cdRef)
299
+ cdAlphaSpec := cdAlpha.spec
300
+ cdAlphaClonoStructure := cdAlphaSpec.axesSpec[1].domain["pl7.app/vdj/clonotypeKey/structure"]
301
+ // Get counts block id from mainAlpha key
302
+ cdCountsBlockId := cdAlphaSpec.axesSpec[1].domain["pl7.app/vdj/clonotypingRunId"]
303
+
304
+ // Build TSV file for CD dataset (Alpha)
305
+ cdAlphaTable := pframes.tsvFileBuilder()
306
+ cdAlphaTable.setAxisHeader("pl7.app/sampleId", "internalSampleId")
307
+ cdAlphaTable.setAxisHeader(mainAlphaSpec.axesSpec[1].name, "clonotypeKey")
308
+ cdAlphaTable.add(cdAlpha, {header: "count"})
309
+ cdAlphaTable.add(columns.getColumn("cdAlphaFraction"), {header: "fraction"})
310
+ cdAlphaTable.add(columns.getColumn("cdCdr3"), {header: "CDR3aa"})
311
+ cdAlphaTable.add(columns.getColumn("cdVGene"), {header: "VGene"})
312
+ cdAlphaTable.mem(defaultConvMem)
313
+ cdAlphaTable.cpu(defaultConvCpu)
314
+ cdAlphaTsv = cdAlphaTable.build()
315
+
316
+ // Build TSV file for CD dataset (Beta)
317
+ cdBetaTable := pframes.tsvFileBuilder()
318
+ cdBetaTable.setAxisHeader("pl7.app/sampleId", "internalSampleId")
319
+ cdBetaTable.setAxisHeader(mainAlphaSpec.axesSpec[1].name, "clonotypeKey")
320
+ for countCol in columns.getColumns("mainCounts") {
321
+ colChain := countCol.spec.axesSpec[1].domain["pl7.app/vdj/chain"]
322
+ colBlockId := countCol.spec.axesSpec[1].domain["pl7.app/vdj/clonotypingRunId"]
323
+ clonoStructure := countCol.spec.axesSpec[1].domain["pl7.app/vdj/clonotypeKey/structure"]
324
+ if colChain == "TCRBeta" && colBlockId == cdCountsBlockId && clonoStructure == cdAlphaClonoStructure {
325
+ cdBetaTable.add(countCol, {header: "count"})
326
+ }
327
+ }
328
+ for fractionCol in columns.getColumns("mainFractions") {
329
+ colChain := fractionCol.spec.axesSpec[1].domain["pl7.app/vdj/chain"]
330
+ colBlockId := fractionCol.spec.axesSpec[1].domain["pl7.app/vdj/clonotypingRunId"]
331
+ clonoStructure := fractionCol.spec.axesSpec[1].domain["pl7.app/vdj/clonotypeKey/structure"]
332
+ if colChain == "TCRBeta" && colBlockId == cdCountsBlockId && clonoStructure == cdAlphaClonoStructure {
333
+ cdBetaTable.add(fractionCol, {header: "fraction"})
334
+ }
335
+ }
336
+ for cdr3Col in columns.getColumns("otherCdr3s") {
337
+ colChain := cdr3Col.spec.axesSpec[0].domain["pl7.app/vdj/chain"]
338
+ colBlockId := cdr3Col.spec.axesSpec[0].domain["pl7.app/vdj/clonotypingRunId"]
339
+ clonoStructure := cdr3Col.spec.axesSpec[0].domain["pl7.app/vdj/clonotypeKey/structure"]
340
+ if colChain == "TCRBeta" && colBlockId == cdCountsBlockId && clonoStructure == cdAlphaClonoStructure {
341
+ if cdr3Col.spec.domain["pl7.app/alphabet"] == "aminoacid" {
342
+ cdBetaTable.add(cdr3Col, {header: "CDR3aa"})
343
+ }
344
+ }
345
+ }
346
+ for vGeneCol in columns.getColumns("otherVDJGenes") {
347
+ colChain := vGeneCol.spec.axesSpec[0].domain["pl7.app/vdj/chain"]
348
+ colBlockId := vGeneCol.spec.axesSpec[0].domain["pl7.app/vdj/clonotypingRunId"]
349
+ clonoStructure := vGeneCol.spec.axesSpec[0].domain["pl7.app/vdj/clonotypeKey/structure"]
350
+ if colChain == "TCRBeta" && colBlockId == cdCountsBlockId && clonoStructure == cdAlphaClonoStructure {
351
+ if vGeneCol.spec.domain["pl7.app/vdj/reference"] == "VGene" {
352
+ cdBetaTable.add(vGeneCol, {header: "VGene"})
353
+ }
354
+ }
355
+ }
356
+ cdBetaTable.mem(defaultConvMem)
357
+ cdBetaTable.cpu(defaultConvCpu)
358
+ cdBetaTsv = cdBetaTable.build()
359
+ }
360
+
361
+ //////////////// Run TCR Disco ////////////////
362
+
363
+ // Get type of input
364
+ inputType := undefined
365
+ if (mainAlphaSpec.axesSpec[1].name == "pl7.app/vdj/clonotypeKey" || mainAlphaSpec.axesSpec[1].name == "pl7.app/vdj/scClonotypeKey") {
366
+ inputType = "Clonotype"
367
+ // } else if (mainAlphaSpec.axesSpec[1].name == "pl7.app/vdj/clusterId") {
368
+ // inputType = "Cluster"
369
+ } else {
370
+ ll.panic("\n\nNot yet supported input data. Please contact Milaboratories to include it:\n%v\n", mainAlphaSpec)
371
+ }
372
+
373
+ tcrAnalysis := render.create(tcrAnalysisTpl, {
374
+ numerators: args.numerators,
375
+ denominators: denominators,
376
+ mainAlphaTsv: mainAlphaTsv,
377
+ mainBetaTsv: mainBetaTsv,
378
+ metadataTsv: metadataTsv,
379
+ cdAlphaTsv: cdAlphaTsv,
380
+ cdBetaTsv: cdBetaTsv,
381
+ cdSubsetCol: cdSubsetCol,
382
+ covariatesTsv: covariatesTsv,
383
+ contrastColLabel: contrastColLabel,
384
+ thresholdCounts: thresholdCounts,
385
+ thresholdSamples: thresholdSamples,
386
+ log2FcThreshold: log2FcThreshold,
387
+ pAdjThreshold: pAdjThreshold,
388
+ mainAlphaSpec: mainAlphaSpec,
389
+ mainBetaSpec: mainBetaSpec,
390
+ blockId: blockId,
391
+ defaultConvMem: defaultConvMem,
392
+ defaultConvCpu: defaultConvCpu
393
+ })
394
+
395
+ topTableFileAlpha := tcrAnalysis.output("topTableFileAlpha")
396
+ topTableFileBeta := tcrAnalysis.output("topTableFileBeta")
397
+ daFileAlpha := tcrAnalysis.output("daFileAlpha")
398
+ daFileBeta := tcrAnalysis.output("daFileBeta")
399
+ mainAlphaFrequenciesTsv := tcrAnalysis.output("mainAlphaFrequenciesTsv")
400
+ mainBetaFrequenciesTsv := tcrAnalysis.output("mainBetaFrequenciesTsv")
401
+ clonotypeToSubsetAlpha := tcrAnalysis.output("clonotypeToSubsetAlpha")
402
+ clonotypeToSubsetBeta := tcrAnalysis.output("clonotypeToSubsetBeta")
403
+ robustEnrichmentPFAlpha_clonotype := tcrAnalysis.output("robustEnrichmentPFAlpha_clonotype")
404
+ robustEnrichmentPFBeta_clonotype := tcrAnalysis.output("robustEnrichmentPFBeta_clonotype")
405
+
406
+ ////////////////// Spec preparation ////////////////
407
+ topDegPframeBuilderAlpha := pframes.pFrameBuilder()
408
+ topDegPframeBuilderBeta := pframes.pFrameBuilder()
409
+
410
+
411
+ trace := pSpec.makeTrace(mainAlphaSpec,
412
+ {type: "milaboratories.tcr-disco-enrichment", importance: 30,
413
+ label: "DA - Denominator: " + denominatorString + " (log2FC: " + log2FcThreshold + ", pAdj: " + pAdjThreshold + ", threshold counts: " + thresholdCounts + ", threshold samples: " + thresholdSamples + ")"}
414
+ )
415
+
416
+
417
+ // Run TCR AB pairing if stated
418
+ pairsTsvFile := undefined
419
+ pairsPF := undefined
420
+ if args.findTcrAbPairs && mainBetaSpec != undefined {
421
+ tcrAbPairs := render.create(tcrAbPairsTpl, {
422
+ mainAlphaTsv: mainAlphaTsv,
423
+ mainBetaTsv: mainBetaTsv,
424
+ daFileAlphaCsv: daFileAlpha,
425
+ daFileBetaCsv: daFileBeta,
426
+ metadataTsv: metadataTsv,
427
+ pairingMetadataCol: pairingMetadataCol,
428
+ contrastColLabel: contrastColLabel,
429
+ log2FcThreshold: log2FcThreshold,
430
+ pAdjThreshold: pAdjThreshold
431
+ })
432
+ pairsTsvFile = tcrAbPairs.output("pairsTsvFile")
433
+
434
+ // Convert pairs TSV to PFrame
435
+ pairsImportParams := tcrPairsParamsLib.getColumns(copy(mainAlphaSpec), copy(mainBetaSpec))
436
+ pairsPf := xsv.importFile(pairsTsvFile, "tsv", pairsImportParams,
437
+ {splitDataAndSpec: true, mem: defaultConvMem, cpu: defaultConvCpu})
438
+
439
+ // Build PFrame for pairs
440
+ pairsPframeBuilder := pframes.pFrameBuilder()
441
+ pairsTrace := pSpec.makeTrace(mainAlphaSpec,
442
+ {type: "milaboratories.tcr-disco-enrichment", importance: 30,
443
+ label: "TCR AB Pairs - Denominator: " + denominatorString + " (log2FC: " + log2FcThreshold + ", pAdj: " + pAdjThreshold + ")"}
444
+ )
445
+
446
+ for columnName, value in pairsPf {
447
+ columnData := value.data
448
+ pairsPframeBuilder.add(
449
+ columnName,
450
+ pairsTrace.inject(value.spec),
451
+ columnData
452
+ )
453
+ }
454
+ pairsPF = pairsPframeBuilder.build()
455
+ }
456
+
457
+ // Process alpha chain results
458
+ processChainResults(mainAlphaSpec, inputType, log2FcThreshold, pAdjThreshold,
459
+ trace, blockId, denominators, topTableFileAlpha, daFileAlpha, defaultConvMem, defaultConvCpu,
460
+ topDegPframeBuilderAlpha, cdSubsetCol)
461
+
462
+ // Process beta chain results (only if beta data exists)
463
+ if mainBetaSpec != undefined {
464
+ processChainResults(mainBetaSpec, inputType, log2FcThreshold, pAdjThreshold,
465
+ trace, blockId, denominators, topTableFileBeta, daFileBeta, defaultConvMem, defaultConvCpu,
466
+ topDegPframeBuilderBeta, cdSubsetCol)
467
+ }
468
+
469
+
470
+ // Create contrast label Pcolumn
471
+ contrastExport := {
472
+ spec: {
473
+ kind: "PColumn",
474
+ name: "pl7.app/label",
475
+ valueType: "String",
476
+ annotations: {
477
+ "pl7.app/label": "Contrast",
478
+ "pl7.app/isLabel": "true"
479
+ },
480
+ axesSpec: [{
481
+ name: "pl7.app/" + "differentialAbundance" + "/contrastGroup",
482
+ type: "String",
483
+ domain: {
484
+ "pl7.app/blockId": blockId
485
+ },
486
+ annotations: {
487
+ "pl7.app/label": "Contrast"
488
+ }
489
+ }]
490
+ },
491
+ data: createJsonPColumnData(json.encode(mapToPValueData(tcrAnalysis.output("contrastMap"))))
492
+ }
493
+
494
+ // Build separate PFrames for alpha and beta
495
+ topDegPFAlpha := topDegPframeBuilderAlpha.build()
496
+ topDegPFBeta := undefined
497
+ if mainBetaSpec != undefined {
498
+ topDegPFBeta = topDegPframeBuilderBeta.build()
499
+ }
500
+
501
+
502
+ // Convert frequency TSV files to PFrames
503
+ frequenciesTrace := pSpec.makeTrace(mainAlphaSpec,
504
+ {type: "milaboratories.tcr-disco-enrichment", importance: 30,
505
+ label: "Frequencies - Denominator: " + denominatorString}
506
+ )
507
+
508
+ // Convert alpha frequencies
509
+ alphaFrequenciesImportParams := topFrequenciesLib.getColumns(copy(mainAlphaSpec))
510
+ alphaFrequenciesPf := xsv.importFile(mainAlphaFrequenciesTsv, "tsv", alphaFrequenciesImportParams,
511
+ {splitDataAndSpec: true, mem: defaultConvMem, cpu: defaultConvCpu})
512
+
513
+ alphaFrequenciesBuilder := pframes.pFrameBuilder()
514
+ for columnName, value in alphaFrequenciesPf {
515
+ columnData := value.data
516
+ alphaFrequenciesBuilder.add(
517
+ columnName,
518
+ frequenciesTrace.inject(value.spec),
519
+ columnData
520
+ )
521
+ }
522
+ mainAlphaFrequenciesPF := alphaFrequenciesBuilder.build()
523
+
524
+ // Convert beta frequencies (only if beta data exists)
525
+ mainBetaFrequenciesPF := undefined
526
+ if mainBetaSpec != undefined {
527
+ betaFrequenciesImportParams := topFrequenciesLib.getColumns(copy(mainBetaSpec))
528
+ betaFrequenciesPf := xsv.importFile(mainBetaFrequenciesTsv, "tsv", betaFrequenciesImportParams,
529
+ {splitDataAndSpec: true, mem: defaultConvMem, cpu: defaultConvCpu})
530
+
531
+ betaFrequenciesBuilder := pframes.pFrameBuilder()
532
+ for columnName, value in betaFrequenciesPf {
533
+ columnData := value.data
534
+ betaFrequenciesBuilder.add(
535
+ columnName,
536
+ frequenciesTrace.inject(value.spec),
537
+ columnData
538
+ )
539
+ }
540
+ mainBetaFrequenciesPF = betaFrequenciesBuilder.build()
541
+ }
542
+
543
+ // Export subset pframes without trace
544
+ alphaSubsetParams := cdSubsetParamsLib.getColumns(copy(mainAlphaSpec))
545
+ clonotypeToSubsetAlphaPf := xsv.importFile(clonotypeToSubsetAlpha, "tsv", alphaSubsetParams,
546
+ {mem: defaultConvMem, cpu: defaultConvCpu})
547
+
548
+ clonotypeToSubsetBetaPf := undefined
549
+ if mainBetaSpec != undefined {
550
+ betaSubsetParams := cdSubsetParamsLib.getColumns(copy(mainBetaSpec))
551
+ clonotypeToSubsetBetaPf = xsv.importFile(clonotypeToSubsetBeta, "tsv", betaSubsetParams,
552
+ {mem: defaultConvMem, cpu: defaultConvCpu})
553
+ }
554
+
555
+ // Store all generated PFrames in outputs only if they where defined
556
+ outputs.topDegPFAlpha = pframes.exportFrame(topDegPFAlpha)
557
+ outputs.mainAlphaFrequenciesPF = pframes.exportFrame(mainAlphaFrequenciesPF)
558
+ if mainBetaSpec != undefined {
559
+ outputs.topDegPFBeta = pframes.exportFrame(topDegPFBeta)
560
+ outputs.mainBetaFrequenciesPF = pframes.exportFrame(mainBetaFrequenciesPF)
561
+ if pairsPF != undefined {
562
+ outputs.pairsPF = pframes.exportFrame(pairsPF)
563
+ }
564
+ if !is_undefined(cdAlphaTsv) && !is_undefined(cdBetaTsv) {
565
+ outputs.clonotypeToSubsetAlpha = pframes.exportFrame(clonotypeToSubsetAlphaPf)
566
+ outputs.clonotypeToSubsetBeta = pframes.exportFrame(clonotypeToSubsetBetaPf)
567
+ }
568
+ }
569
+
570
+ return {
571
+ outputs: outputs,
572
+ exports: {
573
+ robustEnrichmentAlpha: robustEnrichmentPFAlpha_clonotype,
574
+ robustEnrichmentBeta: robustEnrichmentPFBeta_clonotype,
575
+ contrastExport: contrastExport
576
+ }
577
+ }
578
+ })
579
+
@@ -0,0 +1,60 @@
1
+ self := import("@platforma-sdk/workflow-tengo:tpl")
2
+ exec := import("@platforma-sdk/workflow-tengo:exec")
3
+ assets := import("@platforma-sdk/workflow-tengo:assets")
4
+ pt := import("@platforma-sdk/workflow-tengo:pt")
5
+ ll := import("@platforma-sdk/workflow-tengo:ll")
6
+
7
+ tcrAbPairsSoftware := assets.importSoftware("@platforma-open/milaboratories.run-tcrdisco-enrichment.software:tcr-ab-pairs")
8
+
9
+ self.validateInputs({
10
+ "__options__,closed": "",
11
+ "mainAlphaTsv": "any",
12
+ "mainBetaTsv": "any",
13
+ "daFileAlphaCsv": "any",
14
+ "daFileBetaCsv": "any",
15
+ "metadataTsv": "any",
16
+ "pairingMetadataCol": "string",
17
+ "contrastColLabel": "string",
18
+ "log2FcThreshold": "number",
19
+ "pAdjThreshold": "number"
20
+ })
21
+
22
+ self.defineOutputs("pairsTsvFile")
23
+
24
+ self.body(func(inputs) {
25
+
26
+ mainAlphaTsv := inputs.mainAlphaTsv
27
+ mainBetaTsv := inputs.mainBetaTsv
28
+ daFileAlphaCsv := inputs.daFileAlphaCsv
29
+ daFileBetaCsv := inputs.daFileBetaCsv
30
+ metadataTsv := inputs.metadataTsv
31
+ pairingMetadataCol := inputs.pairingMetadataCol
32
+ contrastColLabel := inputs.contrastColLabel
33
+ log2FcThreshold := inputs.log2FcThreshold
34
+ pAdjThreshold := inputs.pAdjThreshold
35
+
36
+ pairExec := exec.builder().
37
+ software(tcrAbPairsSoftware).
38
+ addFile("mainAlpha.tsv", mainAlphaTsv).
39
+ addFile("daAlpha.csv", daFileAlphaCsv).
40
+ addFile("mainBeta.tsv", mainBetaTsv).
41
+ addFile("daBeta.csv", daFileBetaCsv).
42
+ addFile("metadata.tsv", metadataTsv).
43
+ arg("--main_alpha").arg("mainAlpha.tsv").
44
+ arg("--main_beta").arg("mainBeta.tsv").
45
+ arg("--da_alpha").arg("daAlpha.csv").
46
+ arg("--da_beta").arg("daBeta.csv").
47
+ arg("--metadata").arg("metadata.tsv").
48
+ arg("--contrast_col").arg(contrastColLabel).
49
+ arg("--sample_id_col").arg(pairingMetadataCol).
50
+ arg("-f").arg(string(log2FcThreshold)).
51
+ arg("-p").arg(string(pAdjThreshold)).
52
+ arg("-o").arg(".").
53
+ saveFile("ab_pairs.tsv").
54
+ run()
55
+
56
+ return {
57
+ pairsTsvFile: pairExec.getFile("ab_pairs.tsv")
58
+ }
59
+ })
60
+