@platforma-open/milaboratories.mixcr-clonotyping-2.workflow 2.2.2 → 2.3.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.
@@ -79,6 +79,12 @@ exportSpecOpsFromPreset := func(presetSpecForBack) {
79
79
  }
80
80
  }
81
81
 
82
+ addSpec := func(columns, additionalSpec) {
83
+ return slices.map(columns, func(columnSpec) {
84
+ return maps.deepMerge(columnSpec, additionalSpec)
85
+ })
86
+ }
87
+
82
88
  calculateExportSpecs := func(presetSpecForBack, blockId) {
83
89
  ops := exportSpecOpsFromPreset(presetSpecForBack)
84
90
 
@@ -87,19 +93,30 @@ calculateExportSpecs := func(presetSpecForBack, blockId) {
87
93
  hasUmi := ops.hasUmi
88
94
  splitByC := ops.splitByC
89
95
 
96
+ isSingleCell := !is_undefined(cellTags) && len(cellTags) > 0
97
+
90
98
  assemblingFeatureInfo := assemblingFeatureInfo(assemblingFeature)
91
99
  productiveFeature := assemblingFeatureInfo.productiveFeature
92
100
  coreGeneFeatures := assemblingFeatureInfo.coreGeneFeatures
93
101
 
94
102
  clonotypeKeyColumns := undefined
95
- if !is_undefined(assemblingFeature) && (is_undefined(cellTags) || len(cellTags) == 0) {
103
+ clonotypeKeyArgs := undefined
104
+ if !is_undefined(assemblingFeature) {
96
105
  clonotypeKeyColumns = ["nSeq" + assemblingFeature, "bestVGene", "bestJGene"]
106
+ clonotypeKeyArgs = [
107
+ [ "-nFeature", assemblingFeature ],
108
+ [ "-vGene" ],
109
+ [ "-jGene" ]
110
+ ]
97
111
  if splitByC {
98
112
  clonotypeKeyColumns += ["bestCGene"]
113
+ clonotypeKeyArgs += [ [ "-cGene" ] ]
99
114
  }
115
+
100
116
  }
101
117
 
102
118
  columnsSpecPerSample := []
119
+ columnsSpecPerSampleSc := undefined
103
120
  columnsSpecPerClonotype := []
104
121
 
105
122
  // array of array of arg groups
@@ -191,6 +208,52 @@ calculateExportSpecs := func(presetSpecForBack, blockId) {
191
208
  mainAbundanceColumn = "uniqueMoleculeFraction"
192
209
  }
193
210
 
211
+ if isSingleCell {
212
+ // copying reads and umi counts and fraction removing isPrimary and isAnchor
213
+ // addSpec(columnsSpecPerSample, {
214
+ // spec: {
215
+ // annotations: {
216
+ // "pl7.app/abundance/isPrimary": undefined,
217
+ // "pl7.app/isAnchor": undefined
218
+ // }
219
+ // }
220
+ // }) +
221
+ columnsSpecPerSampleSc = [ {
222
+ column: "uniqueCellCount",
223
+ id: "cell-count",
224
+ allowNA: false,
225
+ spec: {
226
+ name: "pl7.app/vdj/uniqueCellCount",
227
+ valueType: "Long",
228
+ annotations: a(88000, true, {
229
+ "pl7.app/min": "1",
230
+ "pl7.app/isAbundance": "true",
231
+ "pl7.app/abundance/unit": "cells",
232
+ "pl7.app/abundance/normalized": "false",
233
+ "pl7.app/abundance/isPrimary": "true",
234
+ "pl7.app/isAnchor": "true",
235
+ "pl7.app/label": "Number of Cells"
236
+ })
237
+ }
238
+ }, {
239
+ column: "uniqueCellFraction",
240
+ id: "cell-fraction",
241
+ allowNA: false,
242
+ spec: {
243
+ name: "pl7.app/vdj/uniqueCellFraction",
244
+ valueType: "Double",
245
+ annotations: a(88000, true, {
246
+ "pl7.app/min": "0",
247
+ "pl7.app/isAbundance": "true",
248
+ "pl7.app/abundance/unit": "cells",
249
+ "pl7.app/abundance/normalized": "true",
250
+ "pl7.app/abundance/isPrimary": "true",
251
+ "pl7.app/label": "Fraction of Cells"
252
+ })
253
+ }
254
+ } ]
255
+ }
256
+
194
257
  // VDJC Hits
195
258
 
196
259
  orderP := 80000
@@ -310,21 +373,21 @@ calculateExportSpecs := func(presetSpecForBack, blockId) {
310
373
  orderP = 10000
311
374
 
312
375
  mutationColumnVariants := [ {
313
- name: "Mutations",
314
- valueType: "String",
315
- labelPart: " mutations in ",
316
- idPart: "-mutations-"
317
- }, {
318
- name: "MutationsCount",
319
- valueType: "Int",
320
- labelPart: " mutations count in ",
321
- idPart: "-mutations-count-"
322
- }, {
323
- name: "MutationsRate",
324
- valueType: "Double",
325
- labelPart: " mutations rate in ",
326
- idPart: "-mutations-rate-"
327
- } ]
376
+ name: "Mutations",
377
+ valueType: "String",
378
+ labelPart: " mutations in ",
379
+ idPart: "-mutations-"
380
+ }, {
381
+ name: "MutationsCount",
382
+ valueType: "Int",
383
+ labelPart: " mutations count in ",
384
+ idPart: "-mutations-count-"
385
+ }, {
386
+ name: "MutationsRate",
387
+ valueType: "Double",
388
+ labelPart: " mutations rate in ",
389
+ idPart: "-mutations-rate-"
390
+ } ]
328
391
 
329
392
  for isAminoAcid in [false, true] {
330
393
  alphabetShort := isAminoAcid ? "AA" : "Nt"
@@ -465,12 +528,6 @@ calculateExportSpecs := func(presetSpecForBack, blockId) {
465
528
 
466
529
  // Adding block and segment annotations
467
530
 
468
- addSpec := func(columns, additionalSpec) {
469
- return slices.map(columns, func(columnSpec) {
470
- return maps.deepMerge(columnSpec, additionalSpec)
471
- })
472
- }
473
-
474
531
  columnsSpecPerSample = addSpec(columnsSpecPerSample, { spec: { domain: {
475
532
  "pl7.app/vdj/clonotypingRunId": blockId
476
533
  } } })
@@ -500,10 +557,10 @@ calculateExportSpecs := func(presetSpecForBack, blockId) {
500
557
  // Axes
501
558
 
502
559
  axesByClonotypeKey := undefined
560
+ axesByScClonotypeKey := undefined
561
+ cellTagColumns := undefined
503
562
 
504
563
  if !is_undefined(clonotypeKeyColumns) {
505
- ll.assert(is_undefined(cellTags) || len(cellTags) == 0, "cellTags and clonotypeKeyAxes cannot both be defined")
506
-
507
564
  // checking that corresponding columns exist in export
508
565
  keyStrincture := []
509
566
  for keyColumn in clonotypeKeyColumns {
@@ -515,93 +572,70 @@ calculateExportSpecs := func(presetSpecForBack, blockId) {
515
572
  }
516
573
 
517
574
  axesByClonotypeKey = [ {
518
- column: "clonotypeKey",
575
+ column: "clonotypeKey",
576
+ naRegex: "",
577
+ spec: {
578
+ name: "pl7.app/vdj/clonotypeKey",
579
+ type: "String",
580
+ domain: {
581
+ "pl7.app/vdj/clonotypeKey/structure": string(json.encode(keyStrincture))
582
+ },
583
+ annotations: {
584
+ "pl7.app/label": "Clonotype key",
585
+ "pl7.app/table/visibility": "optional",
586
+ "pl7.app/table/orderPriority": "110000"
587
+ }
588
+ }
589
+ } ]
590
+
591
+ if isSingleCell {
592
+ cellTagColumns = slices.map(cellTags, func(cellTag) {
593
+ return "tagValue" + cellTag
594
+ })
595
+
596
+ axesByScClonotypeKey = [ {
597
+ column: "scClonotypeKey",
519
598
  naRegex: "",
520
599
  spec: {
521
- name: "pl7.app/vdj/clonotypeKey",
600
+ name: "pl7.app/vdj/scClonotypeKey",
522
601
  type: "String",
523
602
  domain: {
524
- "pl7.app/vdj/clonotypeKey/structure": string(json.encode(keyStrincture))
603
+ "pl7.app/vdj/scClonotypeKey/structure": string(json.encode(keyStrincture))
525
604
  },
526
605
  annotations: {
527
- "pl7.app/label": "Clonotype key",
606
+ "pl7.app/label": "SC Clonotype key",
528
607
  "pl7.app/table/visibility": "optional",
529
608
  "pl7.app/table/orderPriority": "110000"
530
609
  }
531
610
  }
532
611
  } ]
533
- }
534
612
 
535
- axesByClonotypeId := [ {
536
- column: "cloneId",
537
- spec: {
538
- name: "pl7.app/vdj/cloneId",
539
- type: "Long",
540
- domain: {
541
- "pl7.app/blockId": blockId
542
- },
543
- annotations: {
544
- "pl7.app/min": "0",
545
- "pl7.app/label": "Clone id",
546
- "pl7.app/table/visibility": "optional",
547
- "pl7.app/table/orderPriority": "90000"
548
- }
549
- }
550
- } ]
551
- exportArgs += [ [ "-cloneId" ] ]
552
-
553
- orderP = 100000
554
- if !is_undefined(cellTags) && len(cellTags) > 0 {
555
- for tag in cellTags {
556
- label := undefined
557
- if tag == "CELL" {
558
- label = "Cell tag"
559
- } else {
560
- label = text.to_title(tag[:4]) + " " + text.to_lower(tag[4:])
561
- }
562
- axesByClonotypeId += [ {
563
- column: "tagValue" + tag,
564
- naRegex: "",
565
- spec: {
566
- name: "pl7.app/vdj/cellTag",
567
- type: "String",
568
- domain: {
569
- "pl7.app/vdj/cellTagId": tag,
570
- "pl7.app/vdj/clonotypingRunId": blockId
571
- },
572
- annotations: a(orderP, true, {
573
- "pl7.app/label": label
574
- })
575
- }
576
- } ]
577
- orderP -= 1
613
+ // exportArgs += [ [ "-tags", "Cell" ] ]
614
+ // axesByClonotypeKeyAndCellTag = axesByClonotypeKey + [ {
615
+ // column: "cellTag",
616
+ // naRegex: "",
617
+ // spec: {
618
+ // name: "pl7.app/vdj/cellTag",
619
+ // type: "String",
620
+ // annotations: a(orderP, true, {
621
+ // "pl7.app/label": "Cell tag"
622
+ // })
623
+ // }
624
+ // } ]
578
625
  }
579
- exportArgs += [ [ "-tags", "Cell" ] ]
580
-
581
- columnsSpec += [ {
582
- column: "cellGroup",
583
- id: "cell-group",
584
- naRegex: "undefined|contamination",
585
- allowNA: true,
586
- spec: {
587
- name: "pl7.app/vdj/cellGroup",
588
- valueType: "Long",
589
- annotations: a(24000, true, {
590
- "pl7.app/min": "0",
591
- "pl7.app/label": "Cell group number"
592
- } )
593
- }
594
- } ]
595
- exportArgs += [ [ "-cellGroup" ] ]
596
626
  }
597
627
 
598
628
  return {
599
629
  clonotypeKeyColumns: clonotypeKeyColumns,
630
+ clonotypeKeyArgs: clonotypeKeyArgs,
631
+
632
+ cellTagColumns: cellTagColumns,
600
633
 
601
- axesByClonotypeId: axesByClonotypeId,
602
634
  axesByClonotypeKey: axesByClonotypeKey,
635
+ axesByScClonotypeKey: axesByScClonotypeKey,
603
636
 
604
637
  columnsSpecPerSample: columnsSpecPerSample,
638
+ columnsSpecPerSampleSc: columnsSpecPerSampleSc,
605
639
  columnsSpecPerClonotype: columnsSpecPerClonotype,
606
640
 
607
641
  columnsSpec: columnsSpec,
@@ -93,9 +93,11 @@ wf.body(func(args) {
93
93
  presetSpecForBack := presetInfoResult.output("presetSpecForBack", 24 * 60 * 60 * 1000)
94
94
 
95
95
  // calculating chains
96
- chains := ["IGHeavy", "IGLight", "TRAlpha", "TRBeta", "TRGamma", "TRDelta"]
96
+ // chains := ["IGHeavy", "IGLight", "TRAlpha", "TRBeta", "TRGamma", "TRDelta"]
97
+ //receptorsOrChains := ["IG", "TCRAB", "TCRGD"]
98
+ receptorsOrChains := ["IG"]
97
99
  if !is_undefined(args.chains) {
98
- chains = args.chains
100
+ receptorsOrChains = args.chains
99
101
  }
100
102
 
101
103
  runMixcr := render.createEphemeral(processTpl, {
@@ -110,7 +112,7 @@ wf.body(func(args) {
110
112
 
111
113
  params: smart.createJsonResource({
112
114
  species: species,
113
- chains: chains,
115
+ receptorsOrChains: receptorsOrChains,
114
116
  limitInput: limitInput,
115
117
  blockId: blockId,
116
118
  presetCommonName: args.presetCommonName,
@@ -127,7 +129,7 @@ wf.body(func(args) {
127
129
  spec: runMixcr.output("reports.spec"),
128
130
  data: runMixcr.output("reports.data")
129
131
  },
130
- clones: runMixcr.output("clonotypes"),
132
+ clonotypes: runMixcr.output("clonotypes"),
131
133
  clns: {
132
134
  spec: runMixcr.output("clns.spec"),
133
135
  data: runMixcr.output("clns.data")
@@ -37,7 +37,7 @@ self.body(func(inputs) {
37
37
 
38
38
  presetContent := inputs.presetContent.getDataAsJson()
39
39
 
40
- if !is_map(presetContent) {
40
+ if !is_map(presetContent) || !is_array(presetContent.pipeline) {
41
41
  ll.panic("malformed presetContent %v", presetContent)
42
42
  }
43
43
 
@@ -45,7 +45,7 @@ self.body(func(inputs) {
45
45
 
46
46
  hasAssembleContigs := false
47
47
  hasAssembleCells := false
48
- for stage in presetContent.analysisStages {
48
+ for stage in presetContent.pipeline {
49
49
  if stage == "assembleContigs" {
50
50
  hasAssembleContigs = true
51
51
  } else if stage == "assembleCells" {
@@ -1,12 +1,13 @@
1
1
  ll := import("@platforma-sdk/workflow-tengo:ll")
2
2
  self := import("@platforma-sdk/workflow-tengo:tpl")
3
3
  pConstants := import("@platforma-sdk/workflow-tengo:pframes.constants")
4
+ smart := import("@platforma-sdk/workflow-tengo:smart")
4
5
  assets := import("@platforma-sdk/workflow-tengo:assets")
5
6
  exec := import("@platforma-sdk/workflow-tengo:exec")
6
7
 
7
8
  json := import("json")
8
9
 
9
- self.defineOutputs("tsv")
10
+ self.defineOutputs("tsv", "tsvForSingleCell")
10
11
 
11
12
  mixcrSw := assets.importSoftware("@platforma-open/milaboratories.software-mixcr:low-memory")
12
13
  ptransformSw := assets.importSoftware("@platforma-open/milaboratories.software-ptransform:main")
@@ -19,37 +20,49 @@ self.body(func(inputs) {
19
20
  exportArgs := params.exportArgs
20
21
 
21
22
  clonotypeKeyColumns := params.clonotypeKeyColumns
23
+ clonotypeKeyArgs := params.clonotypeKeyArgs
24
+ cellTagColumns := params.cellTagColumns
22
25
 
23
26
  // Exporting clones from clns file
24
27
 
25
- mixcrCmdBuilder := exec.builder().
26
- inMediumQueue().
27
- printErrStreamToStdout().
28
- software(mixcrSw).
29
- secret("MI_LICENSE", "MI_LICENSE").
30
- arg("exportClones").
31
- arg("--dont-split-files").
32
- arg("--chains").arg(chains)
33
-
34
- for argGrp in exportArgs {
35
- for arg in argGrp {
36
- mixcrCmdBuilder.arg(arg)
37
- }
28
+ createExport := func(additionalAction) {
29
+ mixcrCmdBuilder := exec.builder().
30
+ inMediumQueue().
31
+ printErrStreamToStdout().
32
+ software(mixcrSw).
33
+ secret("MI_LICENSE", "MI_LICENSE").
34
+ arg("exportClones").
35
+ arg("--dont-split-files").
36
+ arg("--drop-default-fields").
37
+ arg("--reset-export-clone-table-splitting").
38
+ arg("--chains").arg(chains)
39
+
40
+ additionalAction(mixcrCmdBuilder)
41
+
42
+ return mixcrCmdBuilder.
43
+ arg("clones.clns").
44
+ addFile("clones.clns", clnsFile).
45
+ arg("clones.tsv").
46
+ saveFile("clones.tsv").
47
+ run()
38
48
  }
39
49
 
40
- mixcrCmd := mixcrCmdBuilder.
41
- arg("clones.clns").
42
- addFile("clones.clns", clnsFile).
43
- arg("clones.tsv").
44
- saveFile("clones.tsv").
45
- run()
50
+ mixcrCmd := createExport(func(mixcrCmdBuilder) {
51
+ for argGrp in exportArgs {
52
+ for arg in argGrp {
53
+ mixcrCmdBuilder.arg(arg)
54
+ }
55
+ }
56
+ })
46
57
 
47
58
  unprocessedTsv := mixcrCmd.getFile("clones.tsv")
48
59
 
60
+ result := {
61
+ tsvForSingleCell: smart.createNullResource()
62
+ }
63
+
49
64
  if is_undefined(clonotypeKeyColumns) {
50
- return {
51
- tsv: unprocessedTsv
52
- }
65
+ result.tsv = unprocessedTsv
53
66
  } else {
54
67
  // Adding clonotypeKey column
55
68
  pWorkflow := {
@@ -71,8 +84,53 @@ self.body(func(inputs) {
71
84
 
72
85
  processedTsv := aggregateCmd.getFile("output.tsv")
73
86
 
74
- return {
75
- tsv: processedTsv
87
+ result.tsv = processedTsv
88
+ }
89
+
90
+ if !is_undefined(cellTagColumns) {
91
+ mixcrForSingleCell := createExport(func(mixcrCmdBuilder) {
92
+ mixcrCmdBuilder.
93
+ arg("--split-by-tags").arg("Cell").
94
+ arg("-tags").arg("Cell").
95
+ arg("-readCount").
96
+ arg("-isProductive").arg("CDR3")
97
+
98
+ for argGrp in clonotypeKeyArgs {
99
+ for arg in argGrp {
100
+ mixcrCmdBuilder.arg(arg)
101
+ }
102
+ }
103
+ })
104
+
105
+ if is_undefined(clonotypeKeyColumns) {
106
+ ll.panic("clonotypeKeyColumns is undefined")
76
107
  }
108
+
109
+ unprocessedTsvForSingleCell := mixcrForSingleCell.getFile("clones.tsv")
110
+
111
+ pWorkflow := {
112
+ steps: [ {
113
+ type: "combine_columns_as_json",
114
+ src: clonotypeKeyColumns,
115
+ dst: "clonotypeKey"
116
+ }, {
117
+ type: "combine_columns_as_json",
118
+ src: cellTagColumns,
119
+ dst: "cellTag"
120
+ } ]
121
+ }
122
+
123
+ aggregateCmd := exec.builder().
124
+ printErrStreamToStdout().
125
+ software(ptransformSw).
126
+ arg("--workflow").arg("wf.json").
127
+ writeFile("wf.json", json.encode(pWorkflow)).
128
+ arg("input.tsv").addFile("input.tsv", unprocessedTsvForSingleCell).
129
+ arg("output.tsv").saveFile("output.tsv").
130
+ run()
131
+
132
+ result.tsvForSingleCell = aggregateCmd.getFile("output.tsv")
77
133
  }
134
+
135
+ return result
78
136
  })
@@ -0,0 +1,108 @@
1
+ ll := import("@platforma-sdk/workflow-tengo:ll")
2
+ self := import("@platforma-sdk/workflow-tengo:tpl")
3
+ pConstants := import("@platforma-sdk/workflow-tengo:pframes.constants")
4
+ slices := import("@platforma-sdk/workflow-tengo:slices")
5
+ assets := import("@platforma-sdk/workflow-tengo:assets")
6
+ exec := import("@platforma-sdk/workflow-tengo:exec")
7
+
8
+ json := import("json")
9
+
10
+ self.defineOutputs("abundanceTsv", "propertiesAPrimaryTsv", "propertiesASecondaryTsv", "propertiesBPrimaryTsv", "propertiesBSecondaryTsv")
11
+
12
+ scGroupBuilderSw := assets.importSoftware("@platforma-open/milaboratories.mixcr-clonotyping-2.single-cell-scripts:sc-group-builder")
13
+ scPreprocessingSw := assets.importSoftware("@platforma-open/milaboratories.mixcr-clonotyping-2.single-cell-scripts:preprocessing")
14
+ scOutputProcessingSw := assets.importSoftware("@platforma-open/milaboratories.mixcr-clonotyping-2.single-cell-scripts:output-processing")
15
+
16
+ self.body(func(inputs) {
17
+ byCellTagA := inputs[pConstants.VALUE_FIELD_NAME]
18
+ inputDataMeta := byCellTagA.getDataAsJson()
19
+ ll.assert(inputDataMeta.keyLength == 1, "unexpected number of aggregation axes")
20
+
21
+ byCellTagB := inputs.byCellTagB
22
+ propertiesA := inputs.propertiesA
23
+ propertiesB := inputs.propertiesB
24
+
25
+ // Creating files map
26
+ filesMap := {}
27
+ for k, v in byCellTagA.inputs() {
28
+ key := json.decode(k)
29
+ fileName := "by_cell_a_" + key[0] + ".tsv"
30
+ filesMap[fileName] = v
31
+ }
32
+
33
+ for k, v in byCellTagB.inputs() {
34
+ key := json.decode(k)
35
+ fileName := "by_cell_b_" + key[0] + ".tsv"
36
+ filesMap[fileName] = v
37
+ }
38
+
39
+ scPreprocessingCmd := exec.builder().
40
+ printErrStreamToStdout().
41
+ software(scPreprocessingSw).
42
+ addFiles(filesMap)
43
+
44
+ for name, f in filesMap {
45
+ scPreprocessingCmd = scPreprocessingCmd.arg(name)
46
+ }
47
+
48
+ // Data preprocessing
49
+ scPreprocessingCmd = scPreprocessingCmd.arg("--output_chainA").arg("chain_a_output.tsv").
50
+ arg("--output_chainB").arg("chain_b_output.tsv").
51
+ saveFile("chain_a_output.tsv").
52
+ saveFile("chain_b_output.tsv")
53
+ run := scPreprocessingCmd.run()
54
+ chainAoutput := run.getFile("chain_a_output.tsv")
55
+ chainBoutput := run.getFile("chain_b_output.tsv")
56
+
57
+
58
+ // Generating scClonotypeKey and abundanceTable
59
+ scClonotypeBuilderCmd := exec.builder().
60
+ printErrStreamToStdout().
61
+ software(scGroupBuilderSw).
62
+ addFile("chain_a_output.tsv", chainAoutput).
63
+ addFile("chain_b_output.tsv", chainBoutput).
64
+ arg("--chainA").arg("chain_a_output.tsv").
65
+ arg("--chainB").arg("chain_b_output.tsv").
66
+ arg("--output_clonotype").arg("clonotype.tsv").
67
+ arg("--output_cell").arg("abundance.tsv").
68
+ saveFile("clonotype.tsv").
69
+ saveFile("abundance.tsv")
70
+ scClonotypeCmd := scClonotypeBuilderCmd.run()
71
+
72
+ clonotypeTsv := scClonotypeCmd.getFile("clonotype.tsv")
73
+ abundanceTsv := scClonotypeCmd.getFile("abundance.tsv")
74
+
75
+ propertiesAFile := propertiesA.inputs()["[]"]
76
+ propertiesBFile := propertiesB.inputs()["[]"]
77
+
78
+ // Propagate scClonotypeKey to properties tables
79
+ scOutputProcessingBuilderCmd := exec.builder().
80
+ printErrStreamToStdout().
81
+ software(scOutputProcessingSw).
82
+ addFile("clonotype.tsv", clonotypeTsv).
83
+ arg("--main_table").arg("clonotype.tsv").
84
+ addFile("properties_a.tsv", propertiesAFile).
85
+ addFile("properties_b.tsv", propertiesBFile).
86
+ arg("--properties_a").arg("properties_a.tsv").
87
+ arg("--properties_b").arg("properties_b.tsv").
88
+ arg("--output_A1").arg("properties_a_primary.tsv").
89
+ arg("--output_A2").arg("properties_a_secondary.tsv").
90
+ arg("--output_B1").arg("properties_b_primary.tsv").
91
+ arg("--output_B2").arg("properties_b_secondary.tsv").
92
+ saveFile("properties_a_primary.tsv").
93
+ saveFile("properties_a_secondary.tsv").
94
+ saveFile("properties_b_primary.tsv").
95
+ saveFile("properties_b_secondary.tsv")
96
+ scOutputCmd := scOutputProcessingBuilderCmd.run()
97
+
98
+ return {
99
+ // must have sampleId and scClonotypeKey columns
100
+ abundanceTsv: abundanceTsv,
101
+
102
+ // must have scClonotypeKey columns
103
+ propertiesAPrimaryTsv: scOutputCmd.getFile("properties_a_primary.tsv"),
104
+ propertiesASecondaryTsv: scOutputCmd.getFile("properties_a_secondary.tsv"),
105
+ propertiesBPrimaryTsv: scOutputCmd.getFile("properties_b_primary.tsv"),
106
+ propertiesBSecondaryTsv: scOutputCmd.getFile("properties_b_secondary.tsv")
107
+ }
108
+ })