Skip to content

Commit 49d18da

Browse files
committed
Initial support for Nimble/Append and TCR/Append to Seurat objects
1 parent 8b045ae commit 49d18da

File tree

6 files changed

+314
-0
lines changed

6 files changed

+314
-0
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
if (!file.exists('/homeDir/.netrc')) {
2+
print(list.files('/homeDir'))
3+
stop('Unable to find file: /homeDir/.netrc')
4+
}
5+
6+
invisible(Rlabkey::labkey.setCurlOptions(NETRC_FILE = '/homeDir/.netrc'))
7+
Rdiscvr::SetLabKeyDefaults(baseUrl = serverBaseUrl, defaultFolder = defaultLabKeyFolder)
8+
9+
for (datasetId in names(seuratObjects)) {
10+
printName(datasetId)
11+
seuratObj <- readRDS(seuratObjects[[datasetId]])
12+
13+
seuratObj <- Rdiscvr::DownloadAndAppendTcrClonotypes(seuratObj, allowMissing = allowMissing)
14+
15+
saveData(seuratObj, datasetId)
16+
17+
# Cleanup
18+
rm(seuratObj)
19+
gc()
20+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
if (!file.exists('/homeDir/.netrc')) {
2+
print(list.files('/homeDir'))
3+
stop('Unable to find file: /homeDir/.netrc')
4+
}
5+
6+
invisible(Rlabkey::labkey.setCurlOptions(NETRC_FILE = '/homeDir/.netrc'))
7+
Rdiscvr::SetLabKeyDefaults(baseUrl = serverBaseUrl, defaultFolder = defaultLabKeyFolder)
8+
9+
for (datasetId in names(seuratObjects)) {
10+
printName(datasetId)
11+
seuratObj <- readRDS(seuratObjects[[datasetId]])
12+
13+
for (genomeId in names(nimbleGenomes)) {
14+
#TODO: dropAmbiguousFeatures
15+
seuratObj <- Rdiscvr::DownloadAndAppendNimble(seuratObject = seuratObj, allowableGenomes = genomeId, targetAssayName = nimbleGenomes[[genomeId]], enforceUniqueFeatureNames = TRUE)
16+
}
17+
18+
saveData(seuratObj, datasetId)
19+
20+
# Cleanup
21+
rm(seuratObj)
22+
gc()
23+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
Ext4.define('SingleCell.panel.NimbleAppendPanel', {
2+
extend: 'Ext.form.FieldSet',
3+
alias: 'widget.singlecell-nimbleappendpanel',
4+
title: 'Nimble Genomes',
5+
6+
genomeField: Ext4.widget({
7+
xtype: 'sequenceanalysis-genomefield',
8+
allowBlank: false
9+
}),
10+
initComponent: function(){
11+
Ext4.apply(this, {
12+
style: 'padding: 10px;margins: 5px;',
13+
minWidth: 650,
14+
border: true,
15+
items: [{
16+
html: 'This step will first run cellranger using the primary genome (selected above). The resulting BAM will be passed to nimble, which will align using each of the genomes selected below, creating supplemental feature counts. By default, the original cellranger output is discarded.',
17+
maxWidth: 600,
18+
border: false,
19+
style: 'padding-bottom: 10px;'
20+
},{
21+
xtype: 'ldk-gridpanel',
22+
clicksToEdit: 1,
23+
width: 600,
24+
tbar: [{
25+
text: 'Add',
26+
handler: function(btn){
27+
var grid = btn.up('grid');
28+
var store = grid.store;
29+
var recs = store.add(store.createModel({
30+
genomeId: null,
31+
targetAssay: 'Nimble'
32+
}));
33+
34+
var idx = store.indexOf(recs[0]);
35+
var cellEditing = grid.getPlugin(grid.editingPluginId);
36+
cellEditing.completeEdit();
37+
var jexlIdx = this.showFilterName ? 1 : 0;
38+
cellEditing.startEditByPosition({row: idx, column: jexlIdx});
39+
}
40+
},LABKEY.ext4.GRIDBUTTONS.DELETERECORD()],
41+
store: {
42+
type: 'array',
43+
fields: ['genomeId', 'targetAssay']
44+
},
45+
columns: [{
46+
dataIndex: 'genomeId',
47+
width: 200,
48+
header: 'Genome',
49+
editor: this.genomeField,
50+
renderer: function(val){
51+
const store = this.up('singlecell-nimblealignpanel').genomeField.store
52+
if (val && store) {
53+
const recIdx = store.find('rowid', val);
54+
return store.getAt(recIdx).get('name');
55+
}
56+
else if (val) {
57+
return '[' + val + ']';
58+
}
59+
else {
60+
return '[None]';
61+
}
62+
}
63+
},{
64+
dataIndex: 'targetAssay',
65+
width: 200,
66+
header: 'Target Assay',
67+
editor: {
68+
xtype: 'textfield',
69+
allowBlank: false
70+
}
71+
}]
72+
}]
73+
});
74+
75+
this.callParent(arguments);
76+
},
77+
78+
getValue: function(){
79+
var ret = [];
80+
this.down('ldk-gridpanel').store.each(function(r, i) {
81+
ret.push([r.data.genomeId, r.data.targetAssay]);
82+
}, this);
83+
84+
return Ext4.isEmpty(ret) ? null : JSON.stringify(ret);
85+
},
86+
87+
getErrors: function(){
88+
var msgs = [];
89+
this.down('ldk-gridpanel').store.each(function(r, i){
90+
if (!r.data.genomeId){
91+
msgs.push('Missing genome for one or more rows');
92+
}
93+
94+
if (!r.data.targetAssay){
95+
msgs.push('Missing target assay for one or more rows');
96+
}
97+
}, this);
98+
99+
if (!this.down('ldk-gridpanel').store.getCount()){
100+
msgs.push('Must provide at least one genome for nimble');
101+
}
102+
103+
return msgs;
104+
},
105+
106+
setValue: function(val){
107+
var grid = this.down('ldk-gridpanel');
108+
if (val){
109+
if (!Ext4.isArray(val)){
110+
val = Ext4.JSON.decode(val);
111+
}
112+
113+
Ext4.Array.forEach(val, function(row){
114+
var rec = grid.store.createModel({
115+
genomeId: row[0],
116+
targetAssay: row[1]
117+
});
118+
grid.store.add(rec);
119+
}, this);
120+
}
121+
else {
122+
grid.store.removeAll();
123+
}
124+
}
125+
});

singlecell/src/org/labkey/singlecell/SingleCellModule.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,8 @@ public static void registerPipelineSteps()
188188
SequencePipelineService.get().registerPipelineStep(new CheckExpectations.Provider());
189189
SequencePipelineService.get().registerPipelineStep(new CommonFilters.Provider());
190190
SequencePipelineService.get().registerPipelineStep(new RunVision.Provider());
191+
SequencePipelineService.get().registerPipelineStep(new NimbleAppend.Provider());
192+
SequencePipelineService.get().registerPipelineStep(new AppendTcr.Provider());
191193

192194
SequenceAnalysisService.get().registerFileHandler(new NimbleHandler());
193195
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package org.labkey.singlecell.pipeline.singlecell;
2+
3+
import org.json.JSONObject;
4+
import org.labkey.api.pipeline.PipelineJobException;
5+
import org.labkey.api.sequenceanalysis.pipeline.AbstractPipelineStepProvider;
6+
import org.labkey.api.sequenceanalysis.pipeline.PipelineContext;
7+
import org.labkey.api.sequenceanalysis.pipeline.SequenceOutputHandler;
8+
import org.labkey.api.singlecell.pipeline.SeuratToolParameter;
9+
import org.labkey.api.singlecell.pipeline.SingleCellStep;
10+
11+
import java.util.Arrays;
12+
import java.util.List;
13+
14+
public class AppendTcr extends AbstractRDiscvrStep
15+
{
16+
public AppendTcr(PipelineContext ctx, AppendTcr.Provider provider)
17+
{
18+
super(provider, ctx);
19+
}
20+
21+
public static class Provider extends AbstractPipelineStepProvider<SingleCellStep>
22+
{
23+
public Provider()
24+
{
25+
super("AppendTcr", "Append TCR Data", "RDiscvr", "This uses Rdiscvr::DownloadAndAppendTcrClonotypes to append TCR data.", Arrays.asList(
26+
SeuratToolParameter.create("allowMissing", "Allow Missing Data", "If checked, an error will be thrown if any sample lacks TCR data", "checkbox", new JSONObject(){{
27+
put("checked", true);
28+
}}, true)
29+
), null, null);
30+
}
31+
32+
33+
@Override
34+
public AppendTcr create(PipelineContext ctx)
35+
{
36+
return new AppendTcr(ctx, this);
37+
}
38+
}
39+
40+
@Override
41+
public String getFileSuffix()
42+
{
43+
return "tcr";
44+
}
45+
46+
@Override
47+
protected Chunk createParamChunk(SequenceOutputHandler.JobContext ctx, List<SeuratObjectWrapper> inputObjects, String outputPrefix) throws PipelineJobException
48+
{
49+
Chunk ret = super.createParamChunk(ctx, inputObjects, outputPrefix);
50+
51+
ret.bodyLines.add("serverBaseUrl <- '" + getPipelineCtx().getJob().getParameters().get("serverBaseUrl") + "'");
52+
ret.bodyLines.add("defaultLabKeyFolder <- '" + getPipelineCtx().getJob().getParameters().get("labkeyFolderPath") + "'");
53+
54+
return ret;
55+
}
56+
}
57+
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package org.labkey.singlecell.pipeline.singlecell;
2+
3+
import org.json.JSONArray;
4+
import org.json.JSONObject;
5+
import org.labkey.api.pipeline.PipelineJobException;
6+
import org.labkey.api.sequenceanalysis.pipeline.AbstractPipelineStepProvider;
7+
import org.labkey.api.sequenceanalysis.pipeline.PipelineContext;
8+
import org.labkey.api.sequenceanalysis.pipeline.SequenceOutputHandler;
9+
import org.labkey.api.sequenceanalysis.pipeline.ToolParameterDescriptor;
10+
import org.labkey.api.singlecell.pipeline.SingleCellStep;
11+
12+
import java.util.Arrays;
13+
import java.util.Collection;
14+
import java.util.HashSet;
15+
import java.util.List;
16+
import java.util.Set;
17+
18+
public class NimbleAppend extends AbstractRDiscvrStep
19+
{
20+
public NimbleAppend(PipelineContext ctx, NimbleAppend.Provider provider)
21+
{
22+
super(provider, ctx);
23+
}
24+
25+
public static class Provider extends AbstractPipelineStepProvider<SingleCellStep>
26+
{
27+
public Provider()
28+
{
29+
super("AppendNimble", "Append Nimble Data", "Nimble/Rdiscvr", "The seurat object will be subset based on the expression below, which is passed directly to Seurat's subset(subset = X).", Arrays.asList(
30+
ToolParameterDescriptor.create("nimbleGenomes", "Genomes", "Genomes to include", "singlecell-nimbleappendpanel", new JSONObject(){{
31+
put("allowBlank", false);
32+
}}, null)
33+
), Arrays.asList("sequenceanalysis/field/GenomeField.js", "/singlecell/panel/NimbleAppendPanel.js"), null);
34+
}
35+
36+
37+
@Override
38+
public NimbleAppend create(PipelineContext ctx)
39+
{
40+
return new NimbleAppend(ctx, this);
41+
}
42+
}
43+
44+
@Override
45+
public Collection<String> getRLibraries()
46+
{
47+
Set<String> ret = new HashSet<>();
48+
ret.add("Seurat");
49+
ret.addAll(super.getRLibraries());
50+
51+
return ret;
52+
}
53+
54+
@Override
55+
protected Chunk createParamChunk(SequenceOutputHandler.JobContext ctx, List<SeuratObjectWrapper> inputObjects, String outputPrefix) throws PipelineJobException
56+
{
57+
Chunk ret = super.createParamChunk(ctx, inputObjects, outputPrefix);
58+
59+
ret.bodyLines.add("serverBaseUrl <- '" + getPipelineCtx().getJob().getParameters().get("serverBaseUrl") + "'");
60+
ret.bodyLines.add("defaultLabKeyFolder <- '" + getPipelineCtx().getJob().getParameters().get("labkeyFolderPath") + "'");
61+
62+
ret.bodyLines.add("nimbleGenomes <- list(");
63+
String genomeStr = getPipelineCtx().getJob().getParameters().get("nimbleGenomes");
64+
JSONArray json = new JSONArray(genomeStr);
65+
for (int i = 0; i < json.length(); i++)
66+
{
67+
JSONArray arr = json.getJSONArray(i);
68+
if (arr.length() != 2)
69+
{
70+
throw new PipelineJobException("Unexpected value: " + json.getString(i));
71+
}
72+
73+
int genomeId = arr.getInt(0);
74+
String targetAssay = arr.getString(1);
75+
ret.bodyLines.add("\t" + genomeId + " = " + targetAssay);
76+
}
77+
ret.bodyLines.add(")");
78+
79+
return ret;
80+
}
81+
82+
@Override
83+
public String getFileSuffix()
84+
{
85+
return "nimble";
86+
}
87+
}

0 commit comments

Comments
 (0)