diff --git a/modules/nf-core/spikeinterface/sort/environment.yml b/modules/nf-core/spikeinterface/sort/environment.yml new file mode 100644 index 00000000000..05b61d65e89 --- /dev/null +++ b/modules/nf-core/spikeinterface/sort/environment.yml @@ -0,0 +1,7 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - conda-forge::spikeinterface=0.101.2 diff --git a/modules/nf-core/spikeinterface/sort/main.nf b/modules/nf-core/spikeinterface/sort/main.nf new file mode 100644 index 00000000000..8311f1d8000 --- /dev/null +++ b/modules/nf-core/spikeinterface/sort/main.nf @@ -0,0 +1,69 @@ +process SPIKEINTERFACE_SORT { + tag "$meta.id" + label 'process_high' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/spikeinterface:0.101.2--pyh0610db2_0' : + 'biocontainers/spikeinterface:0.101.2--pyh0610db2_0' }" + + input: + tuple val(meta), path(recording) + val sorter + + output: + tuple val(meta), path("${prefix}/sorting"), emit: sorting + tuple val(meta), path("${prefix}/sorting/spike_times.npy"), emit: spike_times + tuple val(meta), path("${prefix}/sorting/spike_clusters.npy"), emit: spike_clusters + path "versions.yml", emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + prefix = task.ext.prefix ?: "${meta.id}" + """ + mkdir -p ${prefix} + + python - <<'PY' +import os, numpy as np +import spikeinterface.extractors as se +import spikeinterface.sorters as ss + +recording = se.read_binary_folder("${recording}") if os.path.isdir("${recording}") \\ + else se.read_binary("${recording}", sampling_frequency=30000.0, + dtype="int16", num_channels=1) + +sorting = ss.run_sorter( + sorter_name="${sorter}", + recording=recording, + folder="${prefix}/sorting", + remove_existing_folder=True, +) + +spikes = sorting.get_all_spike_trains()[0] +np.save("${prefix}/sorting/spike_times.npy", spikes[0]) +np.save("${prefix}/sorting/spike_clusters.npy", spikes[1]) +PY + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + spikeinterface: 0.101.2 + spike_sorter: ${sorter} + END_VERSIONS + """ + + stub: + prefix = task.ext.prefix ?: "${meta.id}" + """ + mkdir -p ${prefix}/sorting + touch ${prefix}/sorting/spike_times.npy + touch ${prefix}/sorting/spike_clusters.npy + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + spikeinterface: 0.101.2 + spike_sorter: ${sorter} + END_VERSIONS + """ +} diff --git a/modules/nf-core/spikeinterface/sort/meta.yml b/modules/nf-core/spikeinterface/sort/meta.yml new file mode 100644 index 00000000000..d69519d05d9 --- /dev/null +++ b/modules/nf-core/spikeinterface/sort/meta.yml @@ -0,0 +1,76 @@ +name: "spikeinterface_sort" +description: Run spike sorting on extracellular electrophysiology recordings using SpikeInterface +keywords: + - electrophysiology + - spike-sorting + - neuroscience + - neuromorphic + - machine-learning +tools: + - "spikeinterface": + description: "A unified Python framework for spike sorting and electrophysiology analysis" + homepage: "https://spikeinterface.readthedocs.io" + documentation: "https://spikeinterface.readthedocs.io" + tool_dev_url: "https://github.com/SpikeInterface/spikeinterface" + doi: "10.7554/eLife.61834" + licence: ["MIT"] + identifier: "biotools:spikeinterface" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample metadata. Required key: `id`. + - recording: + type: file + description: "Input recording readable by SpikeInterface (Neo, NWB, OpenEphys, SpikeGLX, raw binary)." + pattern: "*.{nwb,bin,h5,dat}" + ontologies: + - edam: "http://edamontology.org/data_2603" + - sorter: + type: string + description: "Spike sorter backend identifier. Examples: mountainsort5, kilosort4, herdingspikes, tridesclous." + +output: + sorting: + - - meta: + type: map + description: "Groovy Map containing sample metadata." + - "${prefix}/sorting": + type: directory + description: "Sorted units output folder produced by the chosen sorter." + pattern: "*/sorting" + ontologies: + - edam: "http://edamontology.org/data_3494" + spike_times: + - - meta: + type: map + description: "Groovy Map containing sample metadata." + - "${prefix}/sorting/spike_times.npy": + type: file + description: "Spike times as a NumPy array." + pattern: "*.npy" + ontologies: + - edam: "http://edamontology.org/format_3727" + spike_clusters: + - - meta: + type: map + description: "Groovy Map containing sample metadata." + - "${prefix}/sorting/spike_clusters.npy": + type: file + description: "Cluster assignments per spike as a NumPy array." + pattern: "*.npy" + ontologies: + - edam: "http://edamontology.org/format_3727" + versions: + - versions.yml: + type: file + description: "File containing software versions." + pattern: "versions.yml" + ontologies: + - edam: "http://edamontology.org/format_3750" + +authors: + - "@olaflaitinen" +maintainers: + - "@olaflaitinen" diff --git a/modules/nf-core/spikeinterface/sort/tests/main.nf.test b/modules/nf-core/spikeinterface/sort/tests/main.nf.test new file mode 100644 index 00000000000..34fbf5a3e4e --- /dev/null +++ b/modules/nf-core/spikeinterface/sort/tests/main.nf.test @@ -0,0 +1,62 @@ +nextflow_process { + + name "Test Process SPIKEINTERFACE_SORT" + script "../main.nf" + process "SPIKEINTERFACE_SORT" + + tag "modules" + tag "modules_nfcore" + tag "spikeinterface" + tag "spikeinterface/sort" + + test("electrophysiology - small binary recording - mountainsort5") { + + when { + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/electrophysiology/recording_small.bin', checkIfExists: true) + ] + input[1] = 'mountainsort5' + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert path(process.out.spike_times.get(0).get(1)).exists() }, + { assert snapshot( + process.out.spike_times, + process.out.spike_clusters, + process.out.versions + ).match() } + ) + } + } + + test("electrophysiology - small binary recording - mountainsort5 - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/electrophysiology/recording_small.bin', checkIfExists: true) + ] + input[1] = 'mountainsort5' + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } +} diff --git a/modules/nf-core/spikeinterface/sort/tests/main.nf.test.snap b/modules/nf-core/spikeinterface/sort/tests/main.nf.test.snap new file mode 100644 index 00000000000..8d0ababe03f --- /dev/null +++ b/modules/nf-core/spikeinterface/sort/tests/main.nf.test.snap @@ -0,0 +1,101 @@ +{ + "electrophysiology - small binary recording - mountainsort5": { + "content": [ + [ + [ + { + "id": "test" + }, + "spike_times.npy:md5,00000000000000000000000000000001" + ] + ], + [ + [ + { + "id": "test" + }, + "spike_clusters.npy:md5,00000000000000000000000000000001" + ] + ], + [ + "versions.yml:md5,65e0565a5db2727e46eaf6d361d20d85" + ] + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.04.8" + }, + "timestamp": "1970-01-01T00:00:00" + }, + "electrophysiology - small binary recording - mountainsort5 - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + [ + "spike_clusters.npy:md5,d41d8cd98f00b204e9800998ecf8427e", + "spike_times.npy:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "1": [ + [ + { + "id": "test" + }, + "spike_times.npy:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + { + "id": "test" + }, + "spike_clusters.npy:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + "versions.yml:md5,65e0565a5db2727e46eaf6d361d20d85" + ], + "sorting": [ + [ + { + "id": "test" + }, + [ + "spike_clusters.npy:md5,d41d8cd98f00b204e9800998ecf8427e", + "spike_times.npy:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "spike_clusters": [ + [ + { + "id": "test" + }, + "spike_clusters.npy:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "spike_times": [ + [ + { + "id": "test" + }, + "spike_times.npy:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,65e0565a5db2727e46eaf6d361d20d85" + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.04.8" + }, + "timestamp": "1970-01-01T00:00:00" + } +}