Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable tool selection for seqinspector #23

Closed
wants to merge 9 commits into from
25 changes: 25 additions & 0 deletions lib/SeqinspectorDataClasses.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// enum for the supported run modes

public static enum RunMode {
FASTQ,
RUNFOLDER,
}

// enum for the available tools

public static enum Tool {
FASTQSCREEN,
FASTQC,
MULTIQC,
}

// ToolProfile class to define and intersect selections of tools and handle extra arguments and settings

public class ToolProfile {
Set<Tool> enable
Set<Tool> disable
Map<Tool, Map<String,String>> tool_arguments = [:]
}



79 changes: 79 additions & 0 deletions modules/local/utils_seqinspector_profiles/main.nf
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
new GroovyShell().evaluate(new File("$projectDir/lib/SeqinspectorDataClasses.groovy"))

/*
========================================================================================
FUNCTIONS
========================================================================================
*/

// Function to get a valid profile from a ToolProfiles map
/*
String profileStr,
Map<String, ToolProfile> toolProfilesMap
<logger> log
*/

def getProfileFromToolProfiles(profileStr, toolProfilesMap, log) {

if (!toolProfilesMap) {
toolProfilesMap = SeqinspectorDataClasses.ToolProfiles
}

if (!toolProfilesMap.containsKey(profileStr.toUpperCase())) {
def keys = toolProfilesMap.keySet().toLowerCase().join('\n - ')
log.error "Invalid profile specified: '${profileStr}'. Valid options are:\n - ${keys}"
nextflow.Nextflow.exit(1)
}
return toolProfilesMap[profileStr]
}


// Function to combine two profiles
/*
String profileStr,
Map<String, ToolProfile> toolProfilesMap
<logger> log
*/

def combine_profiles(firstProfile, otherProfile, log) {

// Create a new ToolProfile instance to store the combined results
def combinedProfile = new SeqinspectorDataClasses.ToolProfile(
enable: (firstProfile.enable + otherProfile.enable),
disable: (firstProfile.disable + otherProfile.disable),
tool_arguments: [:]
)

// remove possibly disabled tools
combinedProfile.enable.removeAll(combinedProfile.disable)


// Combine tool_arguments maps
def allArgs = firstProfile.tool_arguments.keySet() + otherProfile.tool_arguments.keySet()
allArgs.each { tool ->
def firstArgs = firstProfile.tool_arguments[tool] ?: [:]
def otherArgs = otherProfile.tool_arguments[tool] ?: [:]

// Check for common arguments specified in both profiles for a tool
def commonArgs = firstArgs.keySet().intersect(otherArgs.keySet())
// if a common setting for a tool is detected, compare the values
if (commonArgs) {
def incompatibleArgs = false
commonArgs.each { arg ->
if(firstArgs[arg] != otherArgs[arg]) {
log.error "Conflicting settings of argument '${arg}' for tool '${tool}'"
incompatibleArgs = true
}
}
if(incompatibleArgs) {
nextflow.Nextflow.exit(1)
}
}
combinedProfile.tool_arguments[tool] = firstArgs + otherArgs
}

return combinedProfile


}

14 changes: 14 additions & 0 deletions modules/local/utils_seqinspector_tools/main.nf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

include { getEnumNames } from '../utils_seqinspector_validation/main.nf'

new GroovyShell().evaluate(new File("$projectDir/lib/SeqinspectorDataClasses.groovy"))

/*
========================================================================================
FUNCTIONS
========================================================================================
*/

def getAllToolNames() {
getEnumNames(SeqinspectorDataClasses.Tool)
}
97 changes: 97 additions & 0 deletions modules/local/utils_seqinspector_validation/main.nf
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@

new GroovyShell().evaluate(new File("$projectDir/lib/SeqinspectorDataClasses.groovy"))

/*
========================================================================================
FUNCTIONS
========================================================================================
*/


// Function to convert a string to an enum variant (enums give you a way of saying a value is one of a possible set of values)
/*
String s
Enum e
*/


def getEnumFromString(s, e) {
try {
return e.valueOf(s.toUpperCase())
} catch(java.lang.IllegalArgumentException err) {
return null
}
}

// Function to return all possible names of an enum
/*
Enum e
*/

def getEnumNames(e) {
e
.values()
*.name()
*.toLowerCase()
}



// Function to convert a comma-seperated string of tools to a list of Tool enums
/*
String tool_str
*/

def validateToolList(tool_str, log) {
if (!tool_str) {
return []
}
return tool_str
.tokenize(',')
.collect { token ->
try {
return SeqinspectorDataClasses.Tool.valueOf(token.toUpperCase())
} catch(java.lang.IllegalArgumentException e) {
def all_tools = getEnumNames(SeqinspectorDataClasses.Tool).join('\n - ')
log.error "Recieved invalid tool specification: '${token}'. Valid options are:\n - ${all_tools}"
nextflow.Nextflow.exit(1)
}
}
.unique()
}

// Function to check if the include and exclude lists have any common elements
/*
List<Enum> include_list
List<Enum> exclude_list
*/

def checkIncludeExcludeList(include_list, exclude_list, log) {
def common_tools = include_list + exclude_list
.countBy { it }
.findAll { k, v -> v > 1 }
.keySet()

if (common_tools) {
def common_tools_str = common_tools.values().join('\n - ')
def message_base = 'The following tools were found in the include and the exclude lists!'
log.error "${message_base}:\n - ${common_tools_str}"
nextflow.Nextflow.exit(1)
}
}


// Function to validate the run mode by comparing it to the enumerated values of a constant
/*
String run_mode
*/

def validateRunMode(run_mode, log) {
def run_mode_enum = getEnumFromString(run_mode, SeqinspectorDataClasses.RunMode)
if (!run_mode_enum) {
def run_modes_str = getEnumNames(SeqinspectorDataClasses.RunMode).join('\n - ')
log.error "Invalid run mode selected: '${run_mode}'. Valid options are:\n - ${run_modes_str}"
nextflow.Nextflow.exit(1)
}
return run_mode_enum
}
6 changes: 6 additions & 0 deletions nextflow.config
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ params {
max_multiqc_email_size = '25.MB'
multiqc_methods_description = null

// Tool selection options
tool_profile = 'default'
tool_profile_custom_path = null
tools_include = null
tools_exclude = null

// Boilerplate options
outdir = null
publish_dir_mode = 'copy'
Expand Down
39 changes: 38 additions & 1 deletion nextflow_schema.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"$schema": "https://json-schema.org/draft-07/schema",
"$id": "https://raw.githubusercontent.com/nf-core/seqinspector/master/nextflow_schema.json",
"title": "nf-core/seqinspector pipeline parameters",
"description": "Pipeline to QC your sequences",
Expand Down Expand Up @@ -43,6 +43,40 @@
}
}
},
"tool_selection_options": {
"title": "Tool selection options",
"type": "object",
"description": "Select the quality checks and tools you wish to run.",
"default": "",
"properties": {
"tool_profile": {
"type": "string",
"default": "default",
"description": "Pick and apply preconfigured profiles to select tools.",
"fa_icon": "fab fa-blackberry",
"pattern": "^\\w+(\\s(AND|OR|XOR|IAND)\\s\\w+)*$"
},
"tool_profile_custom_path": {
"type": "string",
"description": "Path to a custom profile used instead of the preconfigured ones.",
"format": "file-path",
"mimetype": "text/vnd.yaml",
"fa_icon": "fas fa-clipboard-list"
},
"tools_include": {
"type": "string",
"fa_icon": "fas fa-search-plus",
"description": "Comma-seperated list of tools that you wish to run. The selection will be applied after the profiles and added to the final list.",
"pattern": "^(\\w+(,\\s\\w+)*)?$"
},
"tools_exclude": {
"type": "string",
"fa_icon": "fas fa-search-minus",
"description": "Comma-seperated list of tools that must not run. The selection will be subtracted from the evaluated profiles.",
"pattern": "^(\\w+(,\\s\\w+)*)?$"
}
}
},
"reference_genome_options": {
"title": "Reference genome options",
"type": "object",
Expand Down Expand Up @@ -280,6 +314,9 @@
{
"$ref": "#/definitions/input_output_options"
},
{
"$ref": "#/definitions/tool_selection_options"
},
{
"$ref": "#/definitions/reference_genome_options"
},
Expand Down
32 changes: 32 additions & 0 deletions subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,38 @@ workflow PIPELINE_INITIALISATION {
//
validateInputParameters() // Runs additional validation that is not done by $projectDir/nextflow_schema.json

//
// Process the tool selection parameters to generate the final list of tools (processes) to run
//
def seqinspector_tools = new SeqinspectorDataClasses.ToolProfile() // Start with no tools (except MultiQC)


if(path.tool_profile_custom_path){

} else {
// Activate the tools that are selected through the applied profiles (typically `default`)
if (params.tool_selection) {
def evaluated_profiles = Utilities.parseAndApplyBooleanOperation(params.tool_selection, Constants.ToolProfiles, log)
seqinspector_tools = seqinspector_tools.orOperation(evaluated_profiles)
}}

if (params.tools_include || params.tools_exclude) {
def tool_include_list = Utilities.getToolList(params.tools_include, log)
def tool_exclude_list = Utilities.getToolList(params.tools_exclude, log)

// Check for overlapping include and exclude list entries
Utilities.checkIncludeExcludeList(tool_include_list, tool_exclude_list, log)

// Convert the specified custom tools into a profile
def custom_tools = Utilities.buildToolTracker(tool_include_list, tool_exclude_list)

// Apply the custom tools to the existing tool profile: orOperation to activate the include_tools
seqinspector_tools = seqinspector_tools.orOperation(custom_tools)

// Apply the custom tools to the existing tool profile: iAndOperation to deactivate the exclude_tools
seqinspector_tools = seqinspector_tools.iAndOperation(custom_tools)
}

//
// Create channel from input file provided through params.input
//
Expand Down
3 changes: 3 additions & 0 deletions workflows/seqinspector.nf
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ workflow SEQINSPECTOR {
ch_multiqc_extra_files = Channel.empty()
ch_multiqc_reports = Channel.empty()


if (seqinspector_tools['FASTQC']){
//
// MODULE: Run FastQC
//
Expand All @@ -40,6 +42,7 @@ workflow SEQINSPECTOR {
)
ch_multiqc_files = ch_multiqc_files.mix(FASTQC.out.zip)
ch_versions = ch_versions.mix(FASTQC.out.versions.first())
}

//
// Collate and save software versions
Expand Down
Loading