Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Chore

- Clear all `nextflow lint` warnings (use `channel` factory API, explicit closure parameters, unused-parameter prefixes). ([#542](https://github.com/nf-core/scrnaseq/pull/542))
- Migrate local subworkflows to directory-based layout with `main.nf`, matching the new nf-core standard structure for modules and subworkflows ([#553](https://github.com/nf-core/scrnaseq/pull/553))
- Template update for nf-core/tools v3.5.1 ([#509](https://github.com/nf-core/scrnaseq/pull/509))
- Template update for nf-core/tools v4.0.2 ([#541](https://github.com/nf-core/scrnaseq/pull/541))
Expand Down
2 changes: 1 addition & 1 deletion conf/modules.config
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ process {
publishDir = [
path: { "${params.outdir}/${params.aligner}/${meta.id}" },
mode: params.publish_dir_mode,
saveAs: { (!it.endsWith('.bam') || params.save_align_intermeds) ? it : null }
saveAs: { filename -> (!filename.endsWith('.bam') || params.save_align_intermeds) ? filename : null }
]
}

Expand Down
8 changes: 4 additions & 4 deletions subworkflows/local/align_cellranger/main.nf
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,17 @@ workflow CELLRANGER_ALIGN {
ch_matrices_raw =
CELLRANGER_COUNT.out.outs.map { meta, mtx_files ->
def desired_files = []
mtx_files.each{
if ( it.toString().contains("raw_feature_bc_matrix") ) { desired_files.add( it ) }
mtx_files.each{ path ->
if ( path.toString().contains("raw_feature_bc_matrix") ) { desired_files.add( path ) }
}
[ meta + [input_type: 'raw'], desired_files ]
}

ch_matrices_filtered =
CELLRANGER_COUNT.out.outs.map { meta, mtx_files ->
def desired_files = []
mtx_files.each{
if ( it.toString().contains("filtered_feature_bc_matrix") ) { desired_files.add( it ) }
mtx_files.each{ path ->
if ( path.toString().contains("filtered_feature_bc_matrix") ) { desired_files.add( path ) }
}
[ meta + [input_type: 'filtered'], desired_files ]
}
Expand Down
2 changes: 1 addition & 1 deletion subworkflows/local/align_cellrangerarc/main.nf
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def parse_demultiplexed_output_channels(in_ch, pattern) {
meta_clone.input_type = pattern.contains('raw_') ? 'raw' : 'filtered'
// Iterate over the matrix files and add the ones matching the pattern to the desired files list
def desired_files = []
mtx_files.each{ if ( it.toString().contains("${pattern}") ) { desired_files.add( it ) } }
mtx_files.each{ path -> if ( path.toString().contains("${pattern}") ) { desired_files.add( path ) } }
[ meta_clone, desired_files ]
}

Expand Down
26 changes: 13 additions & 13 deletions subworkflows/local/align_cellrangermulti/main.nf
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ workflow CELLRANGER_MULTI_ALIGN {
.flatten()
.map{ meta ->
def meta_clone = meta.clone()
def data_dict = meta_clone.find{ it.key == "${meta_clone.feature_type}" }
def data_dict = meta_clone.find{ entry -> entry.key == "${meta_clone.feature_type}" }
def fastqs = data_dict?.value
meta_clone.remove( data_dict?.key )
[ meta_clone, fastqs ]
Expand Down Expand Up @@ -90,26 +90,26 @@ workflow CELLRANGER_MULTI_ALIGN {

// CMO
ch_grouped_fastq.gex
.map{ [it[0].id] }
.concat( PARSE_CELLRANGERMULTI_SAMPLESHEET.out.cmo.flatten().map { [ "${it.baseName}" - "_cmo", it ] } )
.map{ pair -> [pair[0].id] }
.concat( PARSE_CELLRANGERMULTI_SAMPLESHEET.out.cmo.flatten().map { csv -> [ "${csv.baseName}" - "_cmo", csv ] } )
.groupTuple()
.map { if ( it.size() == 2 ) { it[1] } else { [] } } // a correct tuple from snippet will have: [ sample, cmo.csv ]
.map { grp -> if ( grp.size() == 2 ) { grp[1] } else { [] } } // a correct tuple from snippet will have: [ sample, cmo.csv ]
.set { ch_cmo_barcode_csv }

// OCM
ch_grouped_fastq.gex
.map{ [it[0].id] }
.concat( PARSE_CELLRANGERMULTI_SAMPLESHEET.out.ocm.flatten().map { [ "${it.baseName}" - "_ocm", it ] } )
.map{ pair -> [pair[0].id] }
.concat( PARSE_CELLRANGERMULTI_SAMPLESHEET.out.ocm.flatten().map { csv -> [ "${csv.baseName}" - "_ocm", csv ] } )
.groupTuple()
.map { if ( it.size() == 2 ) { it[1] } else { [] } } // a correct tuple from snippet will have: [ sample, ocm.csv ]
.map { grp -> if ( grp.size() == 2 ) { grp[1] } else { [] } } // a correct tuple from snippet will have: [ sample, ocm.csv ]
.set { ch_ocm_barcode_csv }

// FRNA
ch_grouped_fastq.gex
.map{ [it[0].id] }
.concat( PARSE_CELLRANGERMULTI_SAMPLESHEET.out.frna.flatten().map { [ "${it.baseName}" - "_frna", it ] } )
.map{ pair -> [pair[0].id] }
.concat( PARSE_CELLRANGERMULTI_SAMPLESHEET.out.frna.flatten().map { csv -> [ "${csv.baseName}" - "_frna", csv ] } )
.groupTuple()
.map { if ( it.size() == 2 ) { it[1] } else { [] } } // a correct tuple from snippet will have: [ sample, frna.csv ]
.map { grp -> if ( grp.size() == 2 ) { grp[1] } else { [] } } // a correct tuple from snippet will have: [ sample, frna.csv ]
.set { ch_frna_sample_csv }

} else {
Expand Down Expand Up @@ -197,7 +197,7 @@ workflow CELLRANGER_MULTI_ALIGN {
// MODULE: cellranger multi
//
CELLRANGER_MULTI(
ch_grouped_fastq.gex.map{ it[0] },
ch_grouped_fastq.gex.map{ pair -> pair[0] },
ch_grouped_fastq.gex,
ch_grouped_fastq.vdj,
ch_grouped_fastq.ab,
Expand Down Expand Up @@ -244,7 +244,7 @@ def parse_demultiplexed_output_channels(in_ch, pattern) {
def out_ch = in_ch
.map { meta, mtx_files ->
def desired_files = []
mtx_files.each{ if ( it.toString().contains("${pattern}") ) { desired_files.add( it ) } }
mtx_files.each{ path -> if ( path.toString().contains("${pattern}") ) { desired_files.add( path ) } }
[ meta, desired_files ]
} // separate only desired files
.transpose() // transpose for handling one meta/file pair at a time
Expand All @@ -260,7 +260,7 @@ def parse_demultiplexed_output_channels(in_ch, pattern) {
}
[ meta_clone, mtx_files ]
} // check if output is from demultiplexed sample, if yes, correct meta.id for proper conversion naming
.filter{ it != null } // remove nulls from previous step
.filter{ item -> item != null } // remove nulls from previous step
.groupTuple( by: 0 ) // group it back as one file collection per sample

return out_ch
Expand Down
2 changes: 1 addition & 1 deletion subworkflows/local/fastqc/main.nf
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ workflow FASTQC_CHECK {
.map { it -> [ it[1] ] }
.set { fastqc_html_only }

fastqc_multiqc = Channel.empty()
fastqc_multiqc = channel.empty()
fastqc_multiqc = fastqc_multiqc.mix( fastqc_zip_only, fastqc_html_only )

emit:
Expand Down
2 changes: 1 addition & 1 deletion subworkflows/local/kallisto_bustools/main.nf
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ workflow KALLISTO_BUSTOOLS {
ch_fastq

main:
ch_versions = Channel.empty()
ch_versions = channel.empty()

assert (txp2gene && kallisto_index) || (genome_fasta && gtf):
"Must provide a genome fasta file ('--fasta') and a gtf file ('--gtf') if no index is given!"
Expand Down
12 changes: 6 additions & 6 deletions subworkflows/local/simpleaf/main.nf
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ workflow SIMPLEAF {
map_dir

main:
ch_versions = Channel.empty()
ch_versions = channel.empty()

/*
* Build simpleaf index if needed
Expand Down Expand Up @@ -60,20 +60,20 @@ workflow SIMPLEAF {
if (!txp2gene) {
txp2gene = SIMPLEAF_INDEX.out.t2g.collect().map { _meta, it -> it }
} else {
txp2gene = Channel.of( txp2gene )
txp2gene = channel.of( txp2gene )
}
} else {
// we have a map dir, so we do not need to build the index
simpleaf_index = Channel.of( [ [:], [] ] )
simpleaf_index = channel.of( [ [:], [] ] )
}
} else {
// we have a simpleaf index, we use it directly
// ensure simpleaf index and txp2gene are Channels
simpleaf_index = Channel.of( [ [ id: simpleaf_index.getName() ], simpleaf_index ] )
simpleaf_index = channel.of( [ [ id: simpleaf_index.getName() ], simpleaf_index ] )

// channel or null
if (txp2gene) {
txp2gene = Channel.of( txp2gene )
txp2gene = channel.of( txp2gene )
}
}

Expand Down Expand Up @@ -119,7 +119,7 @@ workflow SIMPLEAF {
/*
* Run qcatch QC (optional)
*/
ch_qcatch_report = Channel.empty()
ch_qcatch_report = channel.empty()
if ( !skip_qcatch ) {
// Map quant channel to include chemistry for qcatch: tuple(meta, chemistry, quant_dir)
ch_qcatch_input = ch_af_quant.map { meta, quant_dir -> [meta, qcatch_chemistry, quant_dir] }
Expand Down
2 changes: 1 addition & 1 deletion subworkflows/local/starsolo/main.nf
Original file line number Diff line number Diff line change
Expand Up @@ -69,5 +69,5 @@ workflow STARSOLO {
star_counts = STAR_ALIGN.out.counts
raw_counts = raw_counts
filtered_counts = filtered_counts
for_multiqc = STAR_ALIGN.out.log_final.map{ meta, it -> it }
for_multiqc = STAR_ALIGN.out.log_final.map{ _meta, logFinal -> logFinal }
}
30 changes: 15 additions & 15 deletions subworkflows/local/utils_nfcore_scrnaseq_pipeline/main.nf
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ workflow PIPELINE_INITIALISATION {
monochrome_logs // boolean: Do not use coloured log outputs
nextflow_cli_args // array: List of positional nextflow CLI args
outdir // string: The output directory where the results will be saved
input // string: Path to input samplesheet
_input // string: Path to input samplesheet
help // boolean: Display help message and exit
help_full // boolean: Show the full help message
show_hidden // boolean: Show hidden parameters in the help message
Expand Down Expand Up @@ -107,7 +107,7 @@ workflow PIPELINE_INITIALISATION {
// Create channel from input file provided through params.input
//
if (params.aligner == 'cellrangermulti') { // the cellrangermulti sub-workflow logic needs that channels have reads separated by feature_type. Cannot merge all.
Channel
channel
.fromList(samplesheetToList(params.input, "${projectDir}/assets/schema_input.json"))
.map {
meta, fastq_1, fastq_2 ->
Expand All @@ -118,17 +118,17 @@ workflow PIPELINE_INITIALISATION {
}
}
.groupTuple( by: [0,1] )
.map{ id, type, meta, reads -> [ id, meta, reads ] }
.map {
validateInputSamplesheet(it)
.map{ id, _type, meta, reads -> [ id, meta, reads ] }
.map { sheet_row ->
validateInputSamplesheet(sheet_row)
}
.map {
meta, fastqs ->
return [ meta, fastqs.flatten() ]
}
.set { ch_samplesheet }
} else if (params.aligner == 'cellrangerarc') { // the cellrangerarc sub-workflow logic needs that channels have a meta, type, subsample, fastqs structure.
Channel
channel
.fromList(samplesheetToList(params.input, "${projectDir}/assets/schema_input.json"))
.map { meta, fastq_1, fastq_2 ->
if (!fastq_2 || (meta.sample_type == "atac" && !meta.fastq_barcode)) {
Expand All @@ -141,12 +141,12 @@ workflow PIPELINE_INITIALISATION {
}
}
.groupTuple()
.map {
cellrangerarcStructure(it)
.map { structure_input ->
cellrangerarcStructure(structure_input)
}
.set { ch_samplesheet }
} else {
Channel
channel
.fromList(samplesheetToList(params.input, "${projectDir}/assets/schema_input.json"))
.map {
meta, fastq_1, fastq_2 ->
Expand All @@ -157,8 +157,8 @@ workflow PIPELINE_INITIALISATION {
}
}
.groupTuple()
.map {
validateInputSamplesheet(it)
.map { sheet_row ->
validateInputSamplesheet(sheet_row)
}
.map {
meta, fastqs ->
Expand Down Expand Up @@ -241,7 +241,7 @@ def validateCellrangerMultiBarcodes() {
def cellranger_multi_barcodes = file(params.cellranger_multi_barcodes).splitCsv(header: true)

// Get unique samples from input samplesheet for cross-validation
def inputSamples = file(params.input).splitCsv(header: true).collect { it.sample }.toSet()
def inputSamples = file(params.input).splitCsv(header: true).collect { row -> row.sample }.toSet()

// Check that at least one barcode column is provided for each row
// and that each sample uses only one type of barcode
Expand All @@ -268,14 +268,14 @@ def validateCellrangerMultiBarcodes() {

// Validate that at least one barcode identifier is populated in each row
if (rowsWithoutBarcodes) {
def errorDetails = rowsWithoutBarcodes.collect { "row ${it.row} (${it.multiplexed_sample_id})" }.join(', ')
def errorDetails = rowsWithoutBarcodes.collect { missing -> "row ${missing.row} (${missing.multiplexed_sample_id})" }.join(', ')
error("Please check cellranger_multi_barcodes samplesheet -> " +
"The following rows have no barcode identifiers: ${errorDetails}. " +
"Each row must have exactly one of: 'probe_barcode_ids', 'cmo_ids', or 'ocm_ids'.")
}

// Validate that no more than one barcode identifier is populated in each row
def samplesWithMixedBarcodes = sampleBarcodeTypes.findAll { multiplexed_sample_id, info -> info.types.size() > 1 }
def samplesWithMixedBarcodes = sampleBarcodeTypes.findAll { _multiplexed_sample_id, info -> info.types.size() > 1 }
if (samplesWithMixedBarcodes) {
def errorMsg = samplesWithMixedBarcodes.collect { multiplexed_sample_id, info ->
"'${multiplexed_sample_id}' (row ${info.row}) uses multiple barcode types: ${info.types.join(', ')}"
Expand Down Expand Up @@ -321,7 +321,7 @@ def cellrangerarcStructure(input) {

// Validate that the property "sample_type" is present and has valid values
def valid_sample_types = ["gex", "atac"]
def sample_type_ok = metas.collect { meta -> meta.sample_type }.unique().every { it in valid_sample_types }
def sample_type_ok = metas.collect { meta -> meta.sample_type }.unique().every { st -> st in valid_sample_types }
if (!sample_type_ok) {
error("Please check input samplesheet -> The property 'sample_type' is required and can only be 'gex' or 'atac'.")
}
Expand Down
18 changes: 9 additions & 9 deletions workflows/scrnaseq.nf
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ workflow SCRNASEQ {
//
if (fasta) {
if (fasta.endsWith('.gz')) {
ch_genome_fasta = GUNZIP_FASTA ( [ [:], ch_genome_fasta ] ).gunzip.map { it[1] }
ch_genome_fasta = GUNZIP_FASTA ( [ [:], ch_genome_fasta ] ).gunzip.map { tuple -> tuple[1] }
} else {
ch_genome_fasta = channel.value( ch_genome_fasta )
}
Expand All @@ -120,7 +120,7 @@ workflow SCRNASEQ {
//
if (gtf) {
if (gtf.endsWith('.gz')) {
ch_gtf = GUNZIP_GTF ( [ [:], ch_gtf ] ).gunzip.map { it[1] }
ch_gtf = GUNZIP_GTF ( [ [:], ch_gtf ] ).gunzip.map { tuple -> tuple[1] }
} else {
ch_gtf = channel.value( ch_gtf )
}
Expand Down Expand Up @@ -205,7 +205,7 @@ workflow SCRNASEQ {
)
ch_mtx_matrices = ch_mtx_matrices.mix( CELLRANGER_ALIGN.out.cellranger_matrices_raw, CELLRANGER_ALIGN.out.cellranger_matrices_filtered )
ch_multiqc_files = ch_multiqc_files.mix(CELLRANGER_ALIGN.out.cellranger_out.map {
meta, outs -> outs.findAll{ it -> it.name == "web_summary.html"}
_meta, outs -> outs.findAll{ summary -> summary.name == "web_summary.html"}
})
}

Expand Down Expand Up @@ -263,12 +263,12 @@ workflow SCRNASEQ {
// needs to have a collected map like that, so every sample from the samplesheet is analysed one at a time,
// allowing to have multiple samples in the sheet, having all the data-type tuples initialized,
// either empty or populated. It will be branched inside the subworkflow.
if (!map_collection_clone.any{ it.feature_type == 'gex' }) { map_collection_clone.add( [id: sample_id, feature_type: 'gex' , gex: empty_file, options:[:] ] ) }
if (!map_collection_clone.any{ it.feature_type == 'vdj' }) { map_collection_clone.add( [id: sample_id, feature_type: 'vdj' , vdj: empty_file, options:[:] ] ) }
if (!map_collection_clone.any{ it.feature_type == 'ab' }) { map_collection_clone.add( [id: sample_id, feature_type: 'ab' , ab: empty_file, options:[:] ] ) }
if (!map_collection_clone.any{ it.feature_type == 'beam' }) { map_collection_clone.add( [id: sample_id, feature_type: 'beam' , beam: empty_file, options:[:] ] ) } // currently not implemented, the input samplesheet checking will not allow it.
if (!map_collection_clone.any{ it.feature_type == 'crispr' }) { map_collection_clone.add( [id: sample_id, feature_type: 'crispr', crispr: empty_file, options:[:] ] ) }
if (!map_collection_clone.any{ it.feature_type == 'cmo' }) { map_collection_clone.add( [id: sample_id, feature_type: 'cmo' , cmo: empty_file, options:[:] ] ) }
if (!map_collection_clone.any{ m -> m.feature_type == 'gex' }) { map_collection_clone.add( [id: sample_id, feature_type: 'gex' , gex: empty_file, options:[:] ] ) }
if (!map_collection_clone.any{ m -> m.feature_type == 'vdj' }) { map_collection_clone.add( [id: sample_id, feature_type: 'vdj' , vdj: empty_file, options:[:] ] ) }
if (!map_collection_clone.any{ m -> m.feature_type == 'ab' }) { map_collection_clone.add( [id: sample_id, feature_type: 'ab' , ab: empty_file, options:[:] ] ) }
if (!map_collection_clone.any{ m -> m.feature_type == 'beam' }) { map_collection_clone.add( [id: sample_id, feature_type: 'beam' , beam: empty_file, options:[:] ] ) } // currently not implemented, the input samplesheet checking will not allow it.
if (!map_collection_clone.any{ m -> m.feature_type == 'crispr' }) { map_collection_clone.add( [id: sample_id, feature_type: 'crispr', crispr: empty_file, options:[:] ] ) }
if (!map_collection_clone.any{ m -> m.feature_type == 'cmo' }) { map_collection_clone.add( [id: sample_id, feature_type: 'cmo' , cmo: empty_file, options:[:] ] ) }

// return final map
map_collection_clone
Expand Down
Loading