@platforma-open/milaboratories.top-antibodies.workflow 1.7.0 → 1.8.1

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.
@@ -1,17 +1,21 @@
1
1
   WARN  Issue while reading "/home/runner/work/antibody-tcr-lead-selection/antibody-tcr-lead-selection/.npmrc". Failed to replace env in config: ${NPMJS_TOKEN}
2
2
 
3
- > @platforma-open/milaboratories.top-antibodies.workflow@1.7.0 build /home/runner/work/antibody-tcr-lead-selection/antibody-tcr-lead-selection/workflow
3
+ > @platforma-open/milaboratories.top-antibodies.workflow@1.8.1 build /home/runner/work/antibody-tcr-lead-selection/antibody-tcr-lead-selection/workflow
4
4
  > rm -rf dist && pl-tengo check && pl-tengo build
5
5
 
6
+ Processing "src/filter-and-sample.tpl.tengo"...
6
7
  Processing "src/main.tpl.tengo"...
7
8
  Processing "src/pf-spectratype-conv.lib.tengo"...
8
9
  Processing "src/pf-vj-usage-conv.lib.tengo"...
10
+ Processing "src/prerun.tpl.tengo"...
9
11
  Processing "src/sampled-cols-conv.lib.tengo"...
10
12
  No syntax errors found.
11
13
  info: Compiling 'dist'...
12
14
  info: - writing /home/runner/work/antibody-tcr-lead-selection/antibody-tcr-lead-selection/workflow/dist/tengo/lib/pf-spectratype-conv.lib.tengo
13
15
  info: - writing /home/runner/work/antibody-tcr-lead-selection/antibody-tcr-lead-selection/workflow/dist/tengo/lib/pf-vj-usage-conv.lib.tengo
14
16
  info: - writing /home/runner/work/antibody-tcr-lead-selection/antibody-tcr-lead-selection/workflow/dist/tengo/lib/sampled-cols-conv.lib.tengo
17
+ info: - writing /home/runner/work/antibody-tcr-lead-selection/antibody-tcr-lead-selection/workflow/dist/tengo/tpl/filter-and-sample.plj.gz
18
+ info: - writing /home/runner/work/antibody-tcr-lead-selection/antibody-tcr-lead-selection/workflow/dist/tengo/tpl/prerun.plj.gz
15
19
  info: - writing /home/runner/work/antibody-tcr-lead-selection/antibody-tcr-lead-selection/workflow/dist/tengo/tpl/main.plj.gz
16
20
  info: Template Pack build done.
17
21
  info: Template Pack build done.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @platforma-open/milaboratories.top-antibodies.workflow
2
2
 
3
+ ## 1.8.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 7397001: Remove typo
8
+
9
+ ## 1.8.0
10
+
11
+ ### Minor Changes
12
+
13
+ - a435169: Move filters to settings and add prerun
14
+
15
+ ### Patch Changes
16
+
17
+ - Updated dependencies [a435169]
18
+ - @platforma-open/milaboratories.top-antibodies.sample-clonotypes@1.1.0
19
+ - @platforma-open/milaboratories.top-antibodies.spectratype@1.4.0
20
+
3
21
  ## 1.7.0
4
22
 
5
23
  ### Minor Changes
package/dist/index.cjs CHANGED
@@ -1,3 +1,5 @@
1
1
  module.exports = { Templates: {
2
+ 'filter-and-sample': { type: 'from-file', path: require.resolve('./tengo/tpl/filter-and-sample.plj.gz') },
3
+ 'prerun': { type: 'from-file', path: require.resolve('./tengo/tpl/prerun.plj.gz') },
2
4
  'main': { type: 'from-file', path: require.resolve('./tengo/tpl/main.plj.gz') }
3
5
  }};
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  declare type TemplateFromFile = { readonly type: "from-file"; readonly path: string; };
2
- declare type TplName = "main";
2
+ declare type TplName = "filter-and-sample" | "prerun" | "main";
3
3
  declare const Templates: Record<TplName, TemplateFromFile>;
4
4
  export { Templates };
package/dist/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import { resolve } from 'node:path';
2
2
  export const Templates = {
3
+ 'filter-and-sample': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/filter-and-sample.plj.gz') },
4
+ 'prerun': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/prerun.plj.gz') },
3
5
  'main': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/main.plj.gz') }
4
6
  };
Binary file
Binary file
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@platforma-open/milaboratories.top-antibodies.workflow",
3
- "version": "1.7.0",
3
+ "version": "1.8.1",
4
4
  "type": "module",
5
5
  "description": "Block Workflow",
6
6
  "dependencies": {
7
- "@platforma-sdk/workflow-tengo": "^4.8.0",
8
- "@platforma-open/milaboratories.top-antibodies.sample-clonotypes": "1.0.3",
9
- "@platforma-open/milaboratories.top-antibodies.spectratype": "1.3.2",
7
+ "@platforma-sdk/workflow-tengo": "^4.9.2",
8
+ "@platforma-open/milaboratories.top-antibodies.sample-clonotypes": "1.1.0",
9
+ "@platforma-open/milaboratories.top-antibodies.spectratype": "1.4.0",
10
10
  "@platforma-open/milaboratories.top-antibodies.umap": "1.0.3"
11
11
  },
12
12
  "devDependencies": {
13
- "@platforma-sdk/tengo-builder": "^2.1.10",
14
- "@platforma-sdk/test": "1.34.8",
13
+ "@platforma-sdk/tengo-builder": "^2.1.12",
14
+ "@platforma-sdk/test": "^1.38.0",
15
15
  "vitest": "^2.1.8"
16
16
  },
17
17
  "scripts": {
@@ -0,0 +1,80 @@
1
+ // Template for clonotype filtering
2
+ self := import("@platforma-sdk/workflow-tengo:tpl")
3
+ exec := import("@platforma-sdk/workflow-tengo:exec")
4
+ assets := import("@platforma-sdk/workflow-tengo:assets")
5
+ pframes := import("@platforma-sdk/workflow-tengo:pframes")
6
+ xsv := import("@platforma-sdk/workflow-tengo:pframes.xsv")
7
+ render := import("@platforma-sdk/workflow-tengo:render")
8
+ sampledColsConv := import(":sampled-cols-conv")
9
+ json := import("json")
10
+
11
+ self.defineOutputs("sampledRows", "finalClonotypesCsv")
12
+
13
+ self.body(func(inputs) {
14
+
15
+ cloneTable := inputs.cloneTable
16
+ datasetSpec := inputs.datasetSpec
17
+ filterMap := inputs.filterMap
18
+ topClonotypes := inputs.topClonotypes
19
+
20
+ outputs := {}
21
+ finalClonotypesCsv := undefined
22
+
23
+ // Run filtering script
24
+ filterResult := exec.builder().
25
+ software(assets.importSoftware("@platforma-open/milaboratories.top-antibodies.sample-clonotypes:filter")).
26
+ mem("16GiB").
27
+ cpu(1).
28
+ addFile("clonotypes.csv", cloneTable).
29
+ arg("--csv").arg("clonotypes.csv").
30
+ arg("--out").arg("filteredClonotypes.csv").
31
+ arg("--filter-map").arg(string(json.encode(filterMap))).
32
+ saveFile("filteredClonotypes.csv").
33
+ printErrStreamToStdout().
34
+ saveStdoutContent().
35
+ cache(24 * 60 * 60 * 1000).
36
+ run()
37
+
38
+ // Save filtered CSV file
39
+ filteredClonotypesCsv := filterResult.getFile("filteredClonotypes.csv")
40
+
41
+ // Store outputs
42
+ sampledColsParams := sampledColsConv.getColumns(datasetSpec)
43
+ filteredClonotypesPf := xsv.importFile(filteredClonotypesCsv, "csv", sampledColsParams,
44
+ {cpu: 1, mem: "16GiB"})
45
+
46
+ // Prepare outputs in case there is no top ranking
47
+ outputs["sampledRows"] = pframes.exportFrame(filteredClonotypesPf)
48
+ finalClonotypesCsv = filteredClonotypesCsv
49
+
50
+ if topClonotypes != undefined {
51
+
52
+ ////////// Top Clonotypes Sampling //////////
53
+ // Run sampling script on filtered data
54
+ sampleClones := exec.builder().
55
+ software(assets.importSoftware("@platforma-open/milaboratories.top-antibodies.sample-clonotypes:main")).
56
+ mem("16GiB").
57
+ cpu(1).
58
+ addFile("filteredClonotypes.csv", filteredClonotypesCsv).
59
+ arg("--csv").arg("filteredClonotypes.csv").
60
+ arg("--n").arg(string(topClonotypes)).
61
+ arg("--out").arg("sampledClonotypes_top.csv").
62
+ saveFile("sampledClonotypes_top.csv").
63
+ printErrStreamToStdout().
64
+ saveStdoutContent().
65
+ cache(24 * 60 * 60 * 1000).
66
+ run()
67
+
68
+ // Save top clonotypes CSV file
69
+ finalClonotypesCsv = sampleClones.getFile("sampledClonotypes_top.csv")
70
+
71
+ // Store outputs
72
+ sampledColumnsPf := xsv.importFile(finalClonotypesCsv, "csv", sampledColsParams,
73
+ {cpu: 1, mem: "16GiB"})
74
+ outputs["sampledRows"] = pframes.exportFrame(sampledColumnsPf)
75
+ }
76
+
77
+ outputs["finalClonotypesCsv"] = finalClonotypesCsv
78
+
79
+ return outputs
80
+ })
@@ -8,46 +8,49 @@ sampledColsConv := import(":sampled-cols-conv")
8
8
  spectratypeConv := import(":pf-spectratype-conv")
9
9
  vjUsageConv := import(":pf-vj-usage-conv")
10
10
  slices := import("@platforma-sdk/workflow-tengo:slices")
11
+ render := import("@platforma-sdk/workflow-tengo:render")
11
12
 
12
- wf.prepare(func(args){
13
- // We need a table with cluster ID (optional) | clonotype id | selected ranking columns
14
-
15
- bundleBuilder := wf.createPBundleBuilder()
16
- bundleBuilder.ignoreMissingDomains() // to make query work for both bulk and single cell data
17
- bundleBuilder.addAnchor("main", args.inputAnchor)
18
-
19
- if len(args.rankingOrder) > 0 {
20
- for col in args.rankingOrder {
21
- bundleBuilder.addAnchor(col.value.anchorName, col.value.anchorRef)
22
- bundleBuilder.addSingle(col.value.column)
23
- }
24
- } else {
25
- bundleBuilder.addAnchor(args.rankingOrderDefault.value.anchorName,
26
- args.rankingOrderDefault.value.anchorRef)
27
- bundleBuilder.addSingle(args.rankingOrderDefault.value.column)
28
- }
29
-
13
+ filterAndSampleTpl := assets.importTemplate(":filter-and-sample")
30
14
 
31
- // Add linker column
32
- bundleBuilder.addMulti({
33
- axes: [{ anchor: "main", idx: 1 }], // this will do partial axes match (unlike in the model)
34
- annotations: { "pl7.app/isLinkerColumn": "true" },
35
- partialAxesMatch: true
36
- }, "linkers")
37
-
38
- // Add full aa sequence column
39
- bundleBuilder.addMulti({
40
- axes: [{ anchor: "main", idx: 1 }],
41
- annotations: {
42
- "pl7.app/vdj/isAssemblingFeature": "true",
43
- "pl7.app/vdj/isMainSequence": "true"
44
- },
45
- domain: {
46
- "pl7.app/alphabet": "aminoacid"
47
- }
48
- }, "aaSequence")
15
+ // Set prerun template for clonotype filtering
16
+ wf.setPreRun(assets.importTemplate(":prerun"))
49
17
 
50
- // Add CDR3 sequences
18
+ wf.prepare(func(args){
19
+ // We need a table with cluster ID (optional) | clonotype id | selected ranking columns
20
+ bundleBuilder := wf.createPBundleBuilder()
21
+ bundleBuilder.ignoreMissingDomains() // to make query work for both bulk and single cell data
22
+ bundleBuilder.addAnchor("main", args.inputAnchor)
23
+
24
+ if len(args.rankingOrder) > 0 {
25
+ for col in args.rankingOrder {
26
+ bundleBuilder.addAnchor(col.value.anchorName, col.value.anchorRef)
27
+ bundleBuilder.addSingle(col.value.column)
28
+ }
29
+ } else {
30
+ bundleBuilder.addAnchor(args.rankingOrderDefault.value.anchorName,
31
+ args.rankingOrderDefault.value.anchorRef)
32
+ bundleBuilder.addSingle(args.rankingOrderDefault.value.column)
33
+ }
34
+
35
+ // Load filter columns
36
+ if len(args.filters) > 0 {
37
+ for filter in args.filters {
38
+ if filter.value != undefined {
39
+ bundleBuilder.addAnchor(filter.value.anchorName, filter.value.anchorRef)
40
+ bundleBuilder.addSingle(filter.value.column)
41
+ }
42
+ }
43
+ }
44
+
45
+
46
+ // Add linker column
47
+ bundleBuilder.addMulti({
48
+ axes: [{ anchor: "main", idx: 1 }], // this will do partial axes match (unlike in the model)
49
+ annotations: { "pl7.app/isLinkerColumn": "true" },
50
+ partialAxesMatch: true
51
+ }, "linkers")
52
+
53
+ // Add CDR3 sequences
51
54
  bundleBuilder.addMulti({
52
55
  axes: [{ anchor: "main", idx: 1 }], // Clonotype axis
53
56
  name: "pl7.app/vdj/sequence",
@@ -74,10 +77,10 @@ wf.prepare(func(args){
74
77
  "pl7.app/vdj/reference": "JGene"
75
78
  }
76
79
  }, "JGenes")
77
-
78
- return {
79
- columns: bundleBuilder.build()
80
- }
80
+
81
+ return {
82
+ columns: bundleBuilder.build()
83
+ }
81
84
  })
82
85
 
83
86
  wf.body(func(args) {
@@ -94,90 +97,105 @@ wf.body(func(args) {
94
97
  // output containers
95
98
  outputs := {}
96
99
 
97
- ////////// Clonotype Filtering //////////
98
-
99
- topClonotypesCsv := undefined
100
-
101
- if topClonotypes != undefined {
102
- // Build clonotype table
103
- cloneTable := pframes.csvFileBuilder()
104
- cloneTable.setAxisHeader(datasetSpec.axesSpec[1].name, "clonotypeKey")
105
-
106
- if len(args.rankingOrder) > 0 {
107
- for i, col in args.rankingOrder {
108
- cloneTable.add(columns.getColumn(col.value.column), {header: "Col" + string(i)})
100
+ // Get filtered clonotypes from prerun
101
+ // Build clonotype table
102
+ cloneTable := pframes.csvFileBuilder()
103
+ cloneTable.setAxisHeader(datasetSpec.axesSpec[1].name, "clonotypeKey")
104
+
105
+ // Add Filters to table
106
+ addedAxes := []
107
+ filterMap := {}
108
+ if len(args.filters) > 0 {
109
+ for i, filter in args.filters {
110
+ if filter.value != undefined {
111
+ // Columns added here might also be in ranking list, so we add default IDs
112
+ cloneTable.add(columns.getColumn(filter.value.column),
113
+ {header: "Filter_" + string(i), id: "filter_" + string(i)})
114
+ // Store reference value and filter type associated to this column
115
+ filterMap["Filter_" + string(i)] = filter.filter
109
116
 
110
117
  // If column does not have main anchor axis we have to include theirs
111
- colsSpec := columns.getSpec(col.value.column)
118
+ colsSpec := columns.getSpec(filter.value.column)
112
119
  axesNames := slices.map(colsSpec.axesSpec, func (a) { return a.name})
113
120
  if !slices.hasElement(axesNames, datasetSpec.axesSpec[1].name) {
114
121
  for na, ax in colsSpec.axesSpec {
115
122
  if ax.name != datasetSpec.axesSpec[1].name {
116
123
  cloneTable.setAxisHeader(ax.name, "cluster_" + string(i) + string(na))
124
+ addedAxes = slices.append(addedAxes, ax.name)
117
125
  }
118
126
  }
119
127
  }
120
128
  }
121
- } else {
122
- i := 0
123
- cloneTable.add(columns.getColumn(args.rankingOrderDefault.value.column), {header: "Col" + string(i)})
129
+ }
130
+ }
131
+
132
+ // Add ranking columns to table
133
+ if len(args.rankingOrder) > 0 {
134
+ for i, col in args.rankingOrder {
135
+ cloneTable.add(columns.getColumn(col.value.column), {header: "Col" + string(i)})
124
136
 
125
137
  // If column does not have main anchor axis we have to include theirs
126
- colsSpec := columns.getSpec(args.rankingOrderDefault.value.column)
138
+ colsSpec := columns.getSpec(col.value.column)
127
139
  axesNames := slices.map(colsSpec.axesSpec, func (a) { return a.name})
128
140
  if !slices.hasElement(axesNames, datasetSpec.axesSpec[1].name) {
129
141
  for na, ax in colsSpec.axesSpec {
130
- if ax.name != datasetSpec.axesSpec[1].name {
142
+ if ax.name != datasetSpec.axesSpec[1].name && !slices.hasElement(addedAxes, ax.name) {
131
143
  cloneTable.setAxisHeader(ax.name, "cluster_" + string(i) + string(na))
132
144
  }
133
145
  }
134
146
  }
135
147
  }
136
-
137
- // Columns gotten by query require .key
138
- linkerAxisSpec := {}
139
- if len(columns.getColumns("linkers")) > 0 {
140
- for i, col in columns.getColumns("linkers") {
141
- if datasetSpec.axesSpec[1].name == col.spec.axesSpec[1].name {
142
- cloneTable.add(col, {header: "linker." + string(i)})
143
- cloneTable.setAxisHeader(col.spec.axesSpec[0].name, "cluster_" + string(i))
144
- linkerAxisSpec["cluster_" + string(i)] = col.spec.axesSpec[0]
145
- } else if datasetSpec.axesSpec[1].name == col.spec.axesSpec[0].name {
146
- cloneTable.add(col, {header: "linker." + string(i)})
147
- cloneTable.setAxisHeader(col.spec.axesSpec[1].name, "cluster_" + string(i))
148
- linkerAxisSpec["cluster_" + string(i)] = col.spec.axesSpec[1]
148
+ } else {
149
+ i := 0
150
+ cloneTable.add(columns.getColumn(args.rankingOrderDefault.value.column), {header: "Col" + string(i)})
151
+
152
+ // If column does not have main anchor axis we have to include theirs
153
+ colsSpec := columns.getSpec(args.rankingOrderDefault.value.column)
154
+ axesNames := slices.map(colsSpec.axesSpec, func (a) { return a.name})
155
+ if !slices.hasElement(axesNames, datasetSpec.axesSpec[1].name) {
156
+ for na, ax in colsSpec.axesSpec {
157
+ if ax.name != datasetSpec.axesSpec[1].name {
158
+ cloneTable.setAxisHeader(ax.name, "cluster_" + string(i) + string(na))
149
159
  }
150
160
  }
151
- }
152
- cloneTable.mem("16GiB")
153
- cloneTable.cpu(1)
154
- cloneTable = cloneTable.build()
155
-
156
- // Run sampling script
157
- sampleClones := exec.builder().
158
- software(assets.importSoftware("@platforma-open/milaboratories.top-antibodies.sample-clonotypes:main")).
159
- mem("16GiB").
160
- cpu(1).
161
- addFile("filteredClonotypes.csv", cloneTable).
162
- arg("--csv").arg("filteredClonotypes.csv").
163
- arg("--n").arg(string(topClonotypes)).
164
- arg("--out").arg("sampledClonotypes.csv").
165
- saveFile("sampledClonotypes_top.csv").
166
- printErrStreamToStdout().
167
- saveStdoutContent().
168
- cache(24 * 60 * 60 * 1000).
169
- run()
170
-
171
- // Save top clonotypes CSV file
172
- topClonotypesCsv = sampleClones.getFile("sampledClonotypes_top.csv")
173
-
174
- // Store outputs
175
- sampledColsParams := sampledColsConv.getColumns(datasetSpec)
176
- sampledColumnsPf := xsv.importFile(topClonotypesCsv, "csv", sampledColsParams,
177
- {cpu: 1, mem: "16GiB"})
178
- outputs["sampledRows"] = pframes.exportFrame(sampledColumnsPf)
161
+ }
179
162
  }
180
163
 
164
+ // Get linker columns if needed
165
+ linkerAxisSpec := {}
166
+ if len(columns.getColumns("linkers")) > 0 {
167
+ for i, col in columns.getColumns("linkers") {
168
+ if datasetSpec.axesSpec[1].name == col.spec.axesSpec[1].name {
169
+ cloneTable.add(col, {header: "linker." + string(i)})
170
+ cloneTable.setAxisHeader(col.spec.axesSpec[0].name, "cluster_" + string(i))
171
+ linkerAxisSpec["cluster_" + string(i)] = col.spec.axesSpec[0]
172
+ } else if datasetSpec.axesSpec[1].name == col.spec.axesSpec[0].name {
173
+ cloneTable.add(col, {header: "linker." + string(i)})
174
+ cloneTable.setAxisHeader(col.spec.axesSpec[1].name, "cluster_" + string(i))
175
+ linkerAxisSpec["cluster_" + string(i)] = col.spec.axesSpec[1]
176
+ }
177
+ }
178
+ }
179
+ cloneTable.mem("16GiB")
180
+ cloneTable.cpu(1)
181
+ cloneTable = cloneTable.build()
182
+
183
+ // Use ender.create to call the filter-clonotypes template
184
+ filterResult := render.create(filterAndSampleTpl, {
185
+ inputAnchor: args.inputAnchor,
186
+ cloneTable: cloneTable,
187
+ rankingOrder: args.rankingOrder,
188
+ rankingOrderDefault: args.rankingOrderDefault,
189
+ filters: args.filters,
190
+ filterMap: filterMap,
191
+ datasetSpec: datasetSpec,
192
+ topClonotypes: args.topClonotypes
193
+ })
194
+
195
+ // Get the filtered clonotypes P-frame and CSV from the template result
196
+ finalClonotypesCsv := filterResult.output("finalClonotypesCsv", 24 * 60 * 60 * 1000)
197
+ // outputs["sampledRows"] = filterResult.output("sampledRows", 24 * 60 * 60 * 1000)
198
+
181
199
  ////////// CDR3 Length Calculation //////////
182
200
 
183
201
  cdr3SeqTable := pframes.tsvFileBuilder()
@@ -247,10 +265,10 @@ wf.body(func(args) {
247
265
  arg("--vj_usage_tsv").arg("vj_usage.tsv") // no dot here
248
266
 
249
267
  // Add top clonotypes argument and file to the builder if provided
250
- if topClonotypes != undefined {
268
+ if finalClonotypesCsv != undefined {
251
269
  cdr3VspectratypeCmd = cdr3VspectratypeCmd.
252
- arg("--top_clonotypes_csv").arg("topClonotypes.csv").
253
- addFile("topClonotypes.csv", topClonotypesCsv)
270
+ arg("--final_clonotypes_csv").arg("finalClonotypes.csv").
271
+ addFile("finalClonotypes.csv", finalClonotypesCsv)
254
272
  }
255
273
 
256
274
  cdr3VspectratypeCmd = cdr3VspectratypeCmd. // continue building the command
@@ -0,0 +1,204 @@
1
+ // Prerun template for clonotype filtering
2
+ wf := import("@platforma-sdk/workflow-tengo:workflow")
3
+ render := import("@platforma-sdk/workflow-tengo:render")
4
+ ll := import("@platforma-sdk/workflow-tengo:ll")
5
+ assets := import("@platforma-sdk/workflow-tengo:assets")
6
+ pframes := import("@platforma-sdk/workflow-tengo:pframes")
7
+ slices := import("@platforma-sdk/workflow-tengo:slices")
8
+
9
+ filterAndSampleTpl := assets.importTemplate(":filter-and-sample")
10
+
11
+ wf.prepare(func(args){
12
+ // We need a table with cluster ID (optional) | clonotype id | selected ranking columns
13
+ bundleBuilder := wf.createPBundleBuilder()
14
+ bundleBuilder.ignoreMissingDomains() // to make query work for both bulk and single cell data
15
+ bundleBuilder.addAnchor("main", args.inputAnchor)
16
+
17
+ if len(args.rankingOrder) > 0 {
18
+ for col in args.rankingOrder {
19
+ // For cases where the user is selecting the table to filter
20
+ if col.value != undefined {
21
+ bundleBuilder.addAnchor(col.value.anchorName, col.value.anchorRef)
22
+ bundleBuilder.addSingle(col.value.column)
23
+ }
24
+ }
25
+ } else {
26
+ // @TODO: this is a temporal patch for issue where rankingOrderDefault
27
+ // are not defined by the time prerun works
28
+ // prerun sometimes runs before this variable is ready
29
+ if args.rankingOrderDefault.value != undefined {
30
+ bundleBuilder.addAnchor(args.rankingOrderDefault.value.anchorName,
31
+ args.rankingOrderDefault.value.anchorRef)
32
+ bundleBuilder.addSingle(args.rankingOrderDefault.value.column)
33
+ }
34
+ }
35
+
36
+ // Load filter columns
37
+ if len(args.filters) > 0 {
38
+ for filter in args.filters {
39
+ if filter.value != undefined {
40
+ bundleBuilder.addAnchor(filter.value.anchorName, filter.value.anchorRef)
41
+ bundleBuilder.addSingle(filter.value.column)
42
+ }
43
+ }
44
+ }
45
+
46
+
47
+ // Add linker column
48
+ bundleBuilder.addMulti({
49
+ axes: [{ anchor: "main", idx: 1 }], // this will do partial axes match (unlike in the model)
50
+ annotations: { "pl7.app/isLinkerColumn": "true" },
51
+ partialAxesMatch: true
52
+ }, "linkers")
53
+
54
+ // Add CDR3 sequences
55
+ bundleBuilder.addMulti({
56
+ axes: [{ anchor: "main", idx: 1 }], // Clonotype axis
57
+ name: "pl7.app/vdj/sequence",
58
+ domain: {
59
+ "pl7.app/alphabet": "aminoacid",
60
+ "pl7.app/vdj/feature": "CDR3" // Specify CDR3 feature
61
+ }
62
+ }, "cdr3Sequences") // New collection name for CDR3 sequences
63
+
64
+ // Add V gene
65
+ bundleBuilder.addMulti({
66
+ axes: [{ anchor: "main", idx: 1 }], // Clonotype axis
67
+ name: "pl7.app/vdj/geneHit",
68
+ domain: {
69
+ "pl7.app/vdj/reference": "VGene"
70
+ }
71
+ }, "VGenes")
72
+
73
+ // Add J gene
74
+ bundleBuilder.addMulti({
75
+ axes: [{ anchor: "main", idx: 1 }], // Clonotype axis
76
+ name: "pl7.app/vdj/geneHit",
77
+ domain: {
78
+ "pl7.app/vdj/reference": "JGene"
79
+ }
80
+ }, "JGenes")
81
+
82
+ return {
83
+ columns: bundleBuilder.build()
84
+ }
85
+ })
86
+
87
+ wf.body(func(args) {
88
+ // output containers
89
+ outputs := {}
90
+
91
+ if !is_undefined(args.inputAnchor) {
92
+ columns := args.columns
93
+ datasetSpec := columns.getSpec(args.inputAnchor)
94
+
95
+ ////////// Clonotype Filtering //////////
96
+ // Build clonotype table
97
+ cloneTable := pframes.csvFileBuilder()
98
+ cloneTable.setAxisHeader(datasetSpec.axesSpec[1].name, "clonotypeKey")
99
+
100
+ // Add Filters to table
101
+ addedAxes := []
102
+ filterMap := {}
103
+ if len(args.filters) > 0 {
104
+ for i, filter in args.filters {
105
+ if filter.value != undefined {
106
+ // Columns added here might also be in ranking list, so we add default IDs
107
+ cloneTable.add(columns.getColumn(filter.value.column),
108
+ {header: "Filter_" + string(i), id: "filter_" + string(i)})
109
+ // Store reference value and filter type associated to this column
110
+ filterMap["Filter_" + string(i)] = filter.filter
111
+
112
+ // If column does not have main anchor axis we have to include theirs
113
+ colsSpec := columns.getSpec(filter.value.column)
114
+ axesNames := slices.map(colsSpec.axesSpec, func (a) { return a.name})
115
+ if !slices.hasElement(axesNames, datasetSpec.axesSpec[1].name) {
116
+ for na, ax in colsSpec.axesSpec {
117
+ if ax.name != datasetSpec.axesSpec[1].name {
118
+ cloneTable.setAxisHeader(ax.name, "cluster_" + string(i) + string(na))
119
+ addedAxes = append(addedAxes, ax.name)
120
+ }
121
+ }
122
+ }
123
+ }
124
+ }
125
+ }
126
+
127
+ // Add ranking columns to table
128
+ if len(args.rankingOrder) > 0 {
129
+ for i, col in args.rankingOrder {
130
+ if col.value != undefined {
131
+ cloneTable.add(columns.getColumn(col.value.column), {header: "Col" + string(i)})
132
+
133
+ // If column does not have main anchor axis we have to include theirs
134
+ colsSpec := columns.getSpec(col.value.column)
135
+ axesNames := slices.map(colsSpec.axesSpec, func (a) { return a.name})
136
+ if !slices.hasElement(axesNames, datasetSpec.axesSpec[1].name) {
137
+ for na, ax in colsSpec.axesSpec {
138
+ if ax.name != datasetSpec.axesSpec[1].name && !slices.hasElement(addedAxes, ax.name) {
139
+ cloneTable.setAxisHeader(ax.name, "cluster_" + string(i) + string(na))
140
+ }
141
+ }
142
+ }
143
+ }
144
+ }
145
+ } else {
146
+ // @TODO: this is a temporal patch for issue where rankingOrderDefault
147
+ // are not defined by the time prerun works
148
+ if args.rankingOrderDefault.value != undefined {
149
+ i := 0
150
+ cloneTable.add(columns.getColumn(args.rankingOrderDefault.value.column), {header: "Col" + string(i)})
151
+
152
+ // If column does not have main anchor axis we have to include theirs
153
+ colsSpec := columns.getSpec(args.rankingOrderDefault.value.column)
154
+ axesNames := slices.map(colsSpec.axesSpec, func (a) { return a.name})
155
+ if !slices.hasElement(axesNames, datasetSpec.axesSpec[1].name) {
156
+ for na, ax in colsSpec.axesSpec {
157
+ if ax.name != datasetSpec.axesSpec[1].name {
158
+ cloneTable.setAxisHeader(ax.name, "cluster_" + string(i) + string(na))
159
+ }
160
+ }
161
+ }
162
+ }
163
+ }
164
+
165
+ // Get linker columns if needed
166
+ linkerAxisSpec := {}
167
+ if len(columns.getColumns("linkers")) > 0 {
168
+ for i, col in columns.getColumns("linkers") {
169
+ if datasetSpec.axesSpec[1].name == col.spec.axesSpec[1].name {
170
+ cloneTable.add(col, {header: "linker." + string(i)})
171
+ cloneTable.setAxisHeader(col.spec.axesSpec[0].name, "cluster_" + string(i))
172
+ linkerAxisSpec["cluster_" + string(i)] = col.spec.axesSpec[0]
173
+ } else if datasetSpec.axesSpec[1].name == col.spec.axesSpec[0].name {
174
+ cloneTable.add(col, {header: "linker." + string(i)})
175
+ cloneTable.setAxisHeader(col.spec.axesSpec[1].name, "cluster_" + string(i))
176
+ linkerAxisSpec["cluster_" + string(i)] = col.spec.axesSpec[1]
177
+ }
178
+ }
179
+ }
180
+ cloneTable.mem("16GiB")
181
+ cloneTable.cpu(1)
182
+ cloneTable = cloneTable.build()
183
+
184
+ // Use ender.create to call the filter-clonotypes template
185
+ filterResult := render.create(filterAndSampleTpl, {
186
+ inputAnchor: args.inputAnchor,
187
+ cloneTable: cloneTable,
188
+ topClonotypes: args.topClonotypes,
189
+ rankingOrder: args.rankingOrder,
190
+ rankingOrderDefault: args.rankingOrderDefault,
191
+ filters: args.filters,
192
+ filterMap: filterMap,
193
+ datasetSpec: datasetSpec
194
+ })
195
+
196
+ // Get the filtered clonotypes from the template result
197
+ outputs["sampledRows"] = filterResult.output("sampledRows", 24 * 60 * 60 * 1000)
198
+ }
199
+
200
+ return {
201
+ outputs: outputs,
202
+ exports: {}
203
+ }
204
+ })