From ca57e14408cf354bb66d1c298bade4e87323b118 Mon Sep 17 00:00:00 2001 From: RobJY Date: Wed, 24 Jul 2024 16:31:22 -0400 Subject: [PATCH 01/22] seems to be working but needs more tests and clean-up --- assets/markers_1_sp.csv | 10 +- assets/schema_marker.json | 9 + nextflow_schema.json | 4 + .../utils_nfcore_mcmicro_pipeline/main.nf | 2 +- tests/main.nf.test | 187 ++++++++++++------ tests/main.nf.test.snap | 17 ++ tests/workflows/mcmicro.nf.test | 8 +- workflows/mcmicro.nf | 17 +- 8 files changed, 174 insertions(+), 80 deletions(-) diff --git a/assets/markers_1_sp.csv b/assets/markers_1_sp.csv index 69e33d7..93b7c14 100644 --- a/assets/markers_1_sp.csv +++ b/assets/markers_1_sp.csv @@ -1,5 +1,5 @@ -channel_number,cycle_number,marker_name,filter,excitation_wavelength,emission_wavelength,background -21,1,DNA_6,DAPI,395,431,21 -22,1,ELA NE,FITC,485,525,21 -23,1,CD57,Sy tox,555,590,21 -24,1,CD45,Cy5,640,690,21 +channel_number,cycle_number,marker_name +21,1,DNA_6 +22,1,ELA NE +23,1,CD57 +24,1,CD45 diff --git a/assets/schema_marker.json b/assets/schema_marker.json index 3387752..255ec03 100644 --- a/assets/schema_marker.json +++ b/assets/schema_marker.json @@ -34,6 +34,15 @@ "emission_wavelength": { "type": "integer", "errorMessage": "" + }, + "exposure": { + "type": "integer" + }, + "background": { + "type": "string" + }, + "remove": { + "type": "boolean" } }, "required": ["channel_number", "cycle_number", "marker_name"] diff --git a/nextflow_schema.json b/nextflow_schema.json index c0209e5..d4c4cf7 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -74,6 +74,10 @@ "cellpose_model": { "type": "string", "description": "optional model file for cellpose segmentation" + }, + "backsub": { + "type": "boolean", + "description": "boolean to flag whether or not to apply background subtraction" } } }, diff --git a/subworkflows/local/utils_nfcore_mcmicro_pipeline/main.nf b/subworkflows/local/utils_nfcore_mcmicro_pipeline/main.nf index 6e14870..0c3213e 100644 --- a/subworkflows/local/utils_nfcore_mcmicro_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_mcmicro_pipeline/main.nf @@ -226,7 +226,7 @@ def validateInputMarkersheet( markersheet_data ) { def validateInputSamplesheetMarkersheet ( samples, markers ) { def sample_cycles = samples.collect{ meta, image_tiles, dfp, ffp -> meta.cycle_number } - def marker_cycles = markers.collect{ channel_number, cycle_number, marker_name, filter, excitation_wavelength, emission_wavelength -> cycle_number } + def marker_cycles = markers.collect{ channel_number, cycle_number, marker_name, _1, _2, _3, _4, _5, _6 -> cycle_number } if (marker_cycles.unique(false) != sample_cycles.unique(false) ) { error("cycle_number values must match between sample and marker sheets") diff --git a/tests/main.nf.test b/tests/main.nf.test index 2baff99..0787919 100644 --- a/tests/main.nf.test +++ b/tests/main.nf.test @@ -24,10 +24,10 @@ nextflow_workflow { ) input[1] = Channel.of( [ - [1,1,'DNA_6',[],[],[]], - [2,1,'ELANE',[],[],[]], - [3,1,'CD57',[],[],[]], - [4,1,'CD45',[],[],[]], + [1,1,'DNA_6',[],[],[],[],[],[]], + [2,1,'ELANE',[],[],[],[],[],[]], + [3,1,'CD57',[],[],[],[],[],[]], + [4,1,'CD45',[],[],[],[],[],[]], ], ) """ @@ -68,10 +68,10 @@ nextflow_workflow { ) input[1] = Channel.of( [ - [1,1,'DNA_6',[],[],[]], - [2,1,'ELANE',[],[],[]], - [3,1,'CD57',[],[],[]], - [4,1,'CD45',[],[],[]], + [1,1,'DNA_6',[],[],[],[],[],[]], + [2,1,'ELANE',[],[],[],[],[],[]], + [3,1,'CD57',[],[],[],[],[],[]], + [4,1,'CD45',[],[],[],[],[],[]], ], ) """ @@ -113,10 +113,10 @@ nextflow_workflow { ) input[1] = Channel.of( [ - [1,1,'DNA_6',[],[],[]], - [2,1,'ELANE',[],[],[]], - [3,1,'CD57',[],[],[]], - [4,1,'CD45',[],[],[]], + [1,1,'DNA_6',[],[],[],[],[],[]], + [2,1,'ELANE',[],[],[],[],[],[]], + [3,1,'CD57',[],[],[],[],[],[]], + [4,1,'CD45',[],[],[],[],[],[]], ], ) """ @@ -167,18 +167,18 @@ nextflow_workflow { ) input[1] = Channel.of( [ - [1,1,'DNA_6',[],[],[]], - [2,1,'ELANE',[],[],[]], - [3,1,'CD57',[],[],[]], - [4,1,'CD45',[],[],[]], - [5,1,'DNA_7',[],[],[]], - [6,1,'ELANE7',[],[],[]], - [7,1,'CD577',[],[],[]], - [8,1,'CD457',[],[],[]], - [9,1,'DNA_8',[],[],[]], - [10,1,'ELANE8',[],[],[]], - [11,1,'CD578',[],[],[]], - [12,1,'CD458',[],[],[]], + [1,1,'DNA_6',[],[],[],[],[],[]], + [2,1,'ELANE',[],[],[],[],[],[]], + [3,1,'CD57',[],[],[],[],[],[]], + [4,1,'CD45',[],[],[],[],[],[]], + [5,1,'DNA_7',[],[],[],[],[],[]], + [6,1,'ELANE7',[],[],[],[],[],[]], + [7,1,'CD577',[],[],[],[],[],[]], + [8,1,'CD457',[],[],[],[],[],[]], + [9,1,'DNA_8',[],[],[],[],[],[]], + [10,1,'ELANE8',[],[],[],[],[],[]], + [11,1,'CD578',[],[],[],[],[],[]], + [12,1,'CD458',[],[],[],[],[],[]], ], ) """ @@ -235,14 +235,14 @@ nextflow_workflow { ) input[1] = Channel.of( [ - [1,1,'DNA_6',[],[],[]], - [2,1,'ELANE',[],[],[]], - [3,1,'CD57',[],[],[]], - [4,1,'CD45',[],[],[]], - [5,1,'DNA_7',[],[],[]], - [6,1,'ELANE7',[],[],[]], - [7,1,'CD577',[],[],[]], - [8,1,'CD457',[],[],[]], + [1,1,'DNA_6',[],[],[],[],[],[]], + [2,1,'ELANE',[],[],[],[],[],[]], + [3,1,'CD57',[],[],[],[],[],[]], + [4,1,'CD45',[],[],[],[],[],[]], + [5,1,'DNA_7',[],[],[],[],[],[]], + [6,1,'ELANE7',[],[],[],[],[],[]], + [7,1,'CD577',[],[],[],[],[],[]], + [8,1,'CD457',[],[],[],[],[],[]], ], ) """ @@ -302,14 +302,14 @@ nextflow_workflow { ) input[1] = Channel.of( [ - [1,1,'DNA_6',[],[],[]], - [2,1,'ELANE',[],[],[]], - [3,1,'CD57',[],[],[]], - [4,1,'CD45',[],[],[]], - [5,1,'DNA_7',[],[],[]], - [6,1,'ELANE7',[],[],[]], - [7,1,'CD577',[],[],[]], - [8,1,'CD457',[],[],[]], + [1,1,'DNA_6',[],[],[],[],[],[]], + [2,1,'ELANE',[],[],[],[],[],[]], + [3,1,'CD57',[],[],[],[],[],[]], + [4,1,'CD45',[],[],[],[],[],[]], + [5,1,'DNA_7',[],[],[],[],[],[]], + [6,1,'ELANE7',[],[],[],[],[],[]], + [7,1,'CD577',[],[],[],[],[],[]], + [8,1,'CD457',[],[],[],[],[],[]], ], ) """ @@ -368,18 +368,18 @@ nextflow_workflow { ) input[1] = Channel.of( [ - [1,1,'DNA_6',[],[],[]], - [2,1,'ELANE',[],[],[]], - [3,1,'CD57',[],[],[]], - [4,1,'CD45',[],[],[]], - [5,1,'DNA_7',[],[],[]], - [6,1,'ELANE7',[],[],[]], - [7,1,'CD577',[],[],[]], - [8,1,'CD457',[],[],[]], - [9,1,'DNA_8',[],[],[]], - [10,1,'ELANE8',[],[],[]], - [11,1,'CD578',[],[],[]], - [12,1,'CD458',[],[],[]], + [1,1,'DNA_6',[],[],[],[],[],[]], + [2,1,'ELANE',[],[],[],[],[],[]], + [3,1,'CD57',[],[],[],[],[],[]], + [4,1,'CD45',[],[],[],[],[],[]], + [5,1,'DNA_7',[],[],[],[],[],[]], + [6,1,'ELANE7',[],[],[],[],[],[]], + [7,1,'CD577',[],[],[],[],[],[]], + [8,1,'CD457',[],[],[],[],[],[]], + [9,1,'DNA_8',[],[],[],[],[],[]], + [10,1,'ELANE8',[],[],[],[],[],[]], + [11,1,'CD578',[],[],[],[],[],[]], + [12,1,'CD458',[],[],[],[],[],[]], ], ) """ @@ -443,14 +443,14 @@ nextflow_workflow { ) input[1] = Channel.of( [ - [1,1,'DNA_6',[],[],[]], - [2,1,'ELANE',[],[],[]], - [3,1,'CD57',[],[],[]], - [4,1,'CD45',[],[],[]], - [5,1,'DNA_7',[],[],[]], - [6,1,'ELANE7',[],[],[]], - [7,1,'CD577',[],[],[]], - [8,1,'CD457',[],[],[]], + [1,1,'DNA_6',[],[],[],[],[],[]], + [2,1,'ELANE',[],[],[],[],[],[]], + [3,1,'CD57',[],[],[],[],[],[]], + [4,1,'CD45',[],[],[],[],[],[]], + [5,1,'DNA_7',[],[],[],[],[],[]], + [6,1,'ELANE7',[],[],[],[],[],[]], + [7,1,'CD577',[],[],[],[],[],[]], + [8,1,'CD457',[],[],[],[],[],[]], ], ) """ @@ -498,10 +498,10 @@ nextflow_workflow { ) input[1] = Channel.of( [ - [1,1,'DNA_6',[],[],[]], - [2,1,'ELANE',[],[],[]], - [3,1,'CD57',[],[],[]], - [4,1,'CD45',[],[],[]], + [1,1,'DNA_6',[],[],[],[],[],[]], + [2,1,'ELANE',[],[],[],[],[],[]], + [3,1,'CD57',[],[],[],[],[],[]], + [4,1,'CD45',[],[],[],[],[],[]], ], ) """ @@ -523,5 +523,64 @@ nextflow_workflow { }, + test("cycle: no illumination correction, backsub") { + + when { + params { + segmentation = "mesmer" + backsub = true + } + workflow { + """ + input[0] = Channel.of( + [ + [id:"TEST1", cycle_number:1, channel_count:4], + "https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/imaging/background_subtraction/cycif_tonsil_registered.ome.tif", + [], + [], + ], + [ + [id:"TEST2", cycle_number:2, channel_count:4], + "https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/imaging/background_subtraction/cycif_tonsil_registered.ome.tif", + [], + [], + ], + ) + input[1] = Channel.of( + [ + [1,1,'DNA 1',[],[],[],100,[],[]], + [2,1,'Na/K ATPase',[],[],[],100,[],[]], + [3,1,'CD3',[],[],[],100,[],[]], + [4,1,'CD45RO',[],[],[],100,[],[]], + [5,2,'DNA 2',[],[],[],100,[],'TRUE'], + [6,2,'Antigen Ki67',[],[],[],100,'Na/K ATPase',[]], + [7,2,'Pan-cytokeratin',[],[],[],100,'CD3',[]], + [8,2,'Aortic smooth muscle actin',[],[],[],100,'CD45RO',[]] + ], + ) + """ + } + } + + then { + assertAll ( + { + assert snapshot ( + path("$outputDir/registration/ashlar/TEST1.ome.tif"), + path("$outputDir/registration/ashlar/TEST2.ome.tif"), + path("$outputDir/segmentation/deepcell_mesmer/mask_TEST1.tif"), + path("$outputDir/segmentation/deepcell_mesmer/mask_TEST2.tif"), + CsvUtils.roundAndHashCsv("$outputDir/quantification/mcquant/mesmer/TEST1_mask_TEST1.csv"), + CsvUtils.roundAndHashCsv("$outputDir/quantification/mcquant/mesmer/TEST2_mask_TEST2.csv"), + path("$outputDir/backsub/TEST1.backsub.ome.tif"), + path("$outputDir/backsub/TEST2.backsub.ome.tif"), + ).match() + }, + { assert workflow.success } + ) + } + + }, + ] } diff --git a/tests/main.nf.test.snap b/tests/main.nf.test.snap index 28fcf73..cd0b1e1 100644 --- a/tests/main.nf.test.snap +++ b/tests/main.nf.test.snap @@ -87,6 +87,23 @@ }, "timestamp": "2024-04-23T11:52:51.734421588" }, + "cycle: no illumination correction, backsub": { + "content": [ + "TEST1.ome.tif:md5,9d4fca1d7ec97be3f6343eb712af9dea", + "TEST2.ome.tif:md5,9d4fca1d7ec97be3f6343eb712af9dea", + "mask_TEST1.tif:md5,c1075eb025f558fcadedc1f0d903ec15", + "mask_TEST2.tif:md5,c1075eb025f558fcadedc1f0d903ec15", + "TEST1_mask_TEST1.csv:rounded:md5,af6d1aae676ad32b008876b8be737fa7", + "TEST2_mask_TEST2.csv:rounded:md5,af6d1aae676ad32b008876b8be737fa7", + "TEST1.backsub.ome.tif:md5,469bc15d980a437f4a06128cc39d40c3", + "TEST2.backsub.ome.tif:md5,469bc15d980a437f4a06128cc39d40c3" + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-07-23T14:57:11.807495676" + }, "cycle: multiple file ashlar input with multiple samples no correction, multiple segmentation": { "content": [ "cycif-tonsil.ome.tif:md5,9d4fca1d7ec97be3f6343eb712af9dea", diff --git a/tests/workflows/mcmicro.nf.test b/tests/workflows/mcmicro.nf.test index bb92fda..dabfdee 100644 --- a/tests/workflows/mcmicro.nf.test +++ b/tests/workflows/mcmicro.nf.test @@ -22,10 +22,10 @@ nextflow_workflow { ) input[1] = Channel.of( [ - [1,1,'DNA 1',[],[],[]], - [2,1,'Na/K ATPase',[],[],[]], - [3,1,'CD3',[],[],[]], - [4,1,'CD45RO',[],[],[]], + [1,1,'DNA 1',[],[],[],[],[],[]], + [2,1,'Na/K ATPase',[],[],[],[],[],[]], + [3,1,'CD3',[],[],[],[],[],[]], + [4,1,'CD45RO',[],[],[],[],[],[]], ], ) """ diff --git a/workflows/mcmicro.nf b/workflows/mcmicro.nf index 1d6744d..d3ee2fa 100644 --- a/workflows/mcmicro.nf +++ b/workflows/mcmicro.nf @@ -76,17 +76,22 @@ workflow MCMICRO { // // Run Background Correction // BACKSUB(ASHLAR.out.tif, ch_markers) //BACKSUB(ASHLAR.out.tif, [[id: "backsub"], params.marker_sheet]) - /* if (params.backsub) { - BACKSUB(ASHLAR.out.tif, [[id:"$ASHLAR.out.tif[0]['id']"], params.marker_sheet]) + ch_backsub_markers = ch_markersheet + .map { + it.collect{ channel_number, cycle_number, marker_name, filter, excitation, emission, exposure, background, remove -> channel_number + "," + cycle_number + "," + marker_name + "," + exposure + "," + background + "," + remove } + } + .map { ['channel_number,cycle_number,marker_name,exposure,background,remove', *it] } + .flatten() + .map { it.replace('[]', '') } + .collectFile(name: 'markers_backsub.csv', sort: false, newLine: true, storeDir: '/tmp') + //BACKSUB(ASHLAR.out.tif, [[id:"$ASHLAR.out.tif[0]['id']"], params.marker_sheet]) + BACKSUB(ASHLAR.out.tif, [[id:"$ASHLAR.out.tif[0]['id']"], "/tmp/markers_backsub.csv"]) ch_segmentation_input = BACKSUB.out.backsub_tif ch_versions = ch_versions.mix(BACKSUB.out.versions) } else { - */ ch_segmentation_input = ASHLAR.out.tif - /* } - */ // Run Segmentation @@ -116,7 +121,7 @@ workflow MCMICRO { ch_mcquant_markers = ch_markersheet .flatMap{ ['marker_name'] + - it.collect{ _1, _2, marker_name, _4, _5, _6 -> '"' + marker_name + '"' } + it.collect{ _1, _2, marker_name, _4, _5, _6, _7, _8, _9 -> '"' + marker_name + '"' } } .collectFile(name: 'markers.csv', sort: false, newLine: true) From aabcf52835cbfe9a4389110c550ebc6838bed82a Mon Sep 17 00:00:00 2001 From: RobJY Date: Wed, 24 Jul 2024 16:44:11 -0400 Subject: [PATCH 02/22] clean-up --- workflows/mcmicro.nf | 3 --- 1 file changed, 3 deletions(-) diff --git a/workflows/mcmicro.nf b/workflows/mcmicro.nf index d3ee2fa..54d7299 100644 --- a/workflows/mcmicro.nf +++ b/workflows/mcmicro.nf @@ -74,8 +74,6 @@ workflow MCMICRO { ch_versions = ch_versions.mix(ASHLAR.out.versions) // // Run Background Correction - // BACKSUB(ASHLAR.out.tif, ch_markers) - //BACKSUB(ASHLAR.out.tif, [[id: "backsub"], params.marker_sheet]) if (params.backsub) { ch_backsub_markers = ch_markersheet .map { @@ -85,7 +83,6 @@ workflow MCMICRO { .flatten() .map { it.replace('[]', '') } .collectFile(name: 'markers_backsub.csv', sort: false, newLine: true, storeDir: '/tmp') - //BACKSUB(ASHLAR.out.tif, [[id:"$ASHLAR.out.tif[0]['id']"], params.marker_sheet]) BACKSUB(ASHLAR.out.tif, [[id:"$ASHLAR.out.tif[0]['id']"], "/tmp/markers_backsub.csv"]) ch_segmentation_input = BACKSUB.out.backsub_tif ch_versions = ch_versions.mix(BACKSUB.out.versions) From fc9dc82e58cd2a012a1337383e043434c28875e8 Mon Sep 17 00:00:00 2001 From: RobJY Date: Wed, 24 Jul 2024 17:06:29 -0400 Subject: [PATCH 03/22] clean-up --- workflows/mcmicro.nf | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/workflows/mcmicro.nf b/workflows/mcmicro.nf index 54d7299..17c63c4 100644 --- a/workflows/mcmicro.nf +++ b/workflows/mcmicro.nf @@ -76,10 +76,7 @@ workflow MCMICRO { // // Run Background Correction if (params.backsub) { ch_backsub_markers = ch_markersheet - .map { - it.collect{ channel_number, cycle_number, marker_name, filter, excitation, emission, exposure, background, remove -> channel_number + "," + cycle_number + "," + marker_name + "," + exposure + "," + background + "," + remove } - } - .map { ['channel_number,cycle_number,marker_name,exposure,background,remove', *it] } + .map { ['channel_number,cycle_number,marker_name,exposure,background,remove', it.collect{ channel_number, cycle_number, marker_name, _1, _2, _3, exposure, background, remove -> channel_number + "," + cycle_number + "," + marker_name + "," + exposure + "," + background + "," + remove}] } .flatten() .map { it.replace('[]', '') } .collectFile(name: 'markers_backsub.csv', sort: false, newLine: true, storeDir: '/tmp') From 8e54128a902ae308ed8005c98ad004bf2ff17cb6 Mon Sep 17 00:00:00 2001 From: RobJY Date: Wed, 24 Jul 2024 17:24:31 -0400 Subject: [PATCH 04/22] changed storeDir to the workDir --- workflows/mcmicro.nf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/workflows/mcmicro.nf b/workflows/mcmicro.nf index 17c63c4..acd2017 100644 --- a/workflows/mcmicro.nf +++ b/workflows/mcmicro.nf @@ -79,8 +79,8 @@ workflow MCMICRO { .map { ['channel_number,cycle_number,marker_name,exposure,background,remove', it.collect{ channel_number, cycle_number, marker_name, _1, _2, _3, exposure, background, remove -> channel_number + "," + cycle_number + "," + marker_name + "," + exposure + "," + background + "," + remove}] } .flatten() .map { it.replace('[]', '') } - .collectFile(name: 'markers_backsub.csv', sort: false, newLine: true, storeDir: '/tmp') - BACKSUB(ASHLAR.out.tif, [[id:"$ASHLAR.out.tif[0]['id']"], "/tmp/markers_backsub.csv"]) + .collectFile(name: 'markers_backsub.csv', sort: false, newLine: true, storeDir: "${workDir}") + BACKSUB(ASHLAR.out.tif, [[id:"$ASHLAR.out.tif[0]['id']"], "${workDir}/markers_backsub.csv"]) ch_segmentation_input = BACKSUB.out.backsub_tif ch_versions = ch_versions.mix(BACKSUB.out.versions) } else { From 6f2ebfcb811a57d0341ce1abc77cb56b3d8c79fe Mon Sep 17 00:00:00 2001 From: RobJY Date: Thu, 25 Jul 2024 10:50:16 -0400 Subject: [PATCH 05/22] clean-up --- workflows/mcmicro.nf | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/workflows/mcmicro.nf b/workflows/mcmicro.nf index acd2017..2e984cb 100644 --- a/workflows/mcmicro.nf +++ b/workflows/mcmicro.nf @@ -76,7 +76,9 @@ workflow MCMICRO { // // Run Background Correction if (params.backsub) { ch_backsub_markers = ch_markersheet - .map { ['channel_number,cycle_number,marker_name,exposure,background,remove', it.collect{ channel_number, cycle_number, marker_name, _1, _2, _3, exposure, background, remove -> channel_number + "," + cycle_number + "," + marker_name + "," + exposure + "," + background + "," + remove}] } + .map { ['channel_number,cycle_number,marker_name,exposure,background,remove', + it.collect{ channel_number, cycle_number, marker_name, _1, _2, _3, exposure, background, remove -> + channel_number + "," + cycle_number + "," + marker_name + "," + exposure + "," + background + "," + remove}] } .flatten() .map { it.replace('[]', '') } .collectFile(name: 'markers_backsub.csv', sort: false, newLine: true, storeDir: "${workDir}") From 0ffa4d6987bc481b6af4bdc73e5c33fef41eb977 Mon Sep 17 00:00:00 2001 From: RobJY Date: Thu, 25 Jul 2024 15:22:37 -0400 Subject: [PATCH 06/22] added backsub to config --- nextflow.config | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nextflow.config b/nextflow.config index 2f9f598..00437e5 100644 --- a/nextflow.config +++ b/nextflow.config @@ -19,6 +19,9 @@ params { // Illumination correction illumination = null + // Background subtraction + backsub = false + // MultiQC options multiqc_config = null multiqc_title = null From 3056e07f04466b55d9ba369651e68a98b96ccbb9 Mon Sep 17 00:00:00 2001 From: RobJY Date: Mon, 16 Sep 2024 12:18:36 -0400 Subject: [PATCH 07/22] fixed backsub command with suggestions from PR --- .../utils_nfcore_mcmicro_pipeline/main.nf | 23 +++++++++++++++++++ workflows/mcmicro.nf | 16 ++++++++++--- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/subworkflows/local/utils_nfcore_mcmicro_pipeline/main.nf b/subworkflows/local/utils_nfcore_mcmicro_pipeline/main.nf index 0c3213e..0cf3757 100644 --- a/subworkflows/local/utils_nfcore_mcmicro_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_mcmicro_pipeline/main.nf @@ -221,6 +221,29 @@ def validateInputMarkersheet( markersheet_data ) { error("Duplicate [channel, cycle] pairs: ${dups}") } + // validate backsub columns if present + def exposure_list = markersheet_data.findResults{ _1, _2, _3, _4, _5, _6, exposure, _8, _9 -> exposure != [] ? exposure : null } + def background_list = markersheet_data.findResults{ _1, _2, _3, _4, _5, _6, _7, background, _9 -> background != [] ? background: null } + def remove_list = markersheet_data.findResults{ _1, _2, _3, _4, _5, _6, _7, _8, remove -> remove != [] ? remove : null } + + if (background_list.size() == 0 && (exposure_list.size() > 0 || remove_list.size() > 0)) { + error("No values in background column, but values in either exposure or remove columns. Must have background column values to perform background subtraction.") + } else if (background_list.size() > 0) { + inter_list = marker_name_list.intersect(background_list) + if (inter_list.size() != background_list.size()) { + outliers_list = background_list - inter_list + error('background column values must exist in the marker_name column. The following background column values do not exist in the marker_name column: ' + outliers_list) + } + + if (exposure_list.size() == 0) { + error('You must have at least one value in the exposure column to perform background subtraction') + } + + if (remove_list.size() == 0) { + error ('You must have at least one value in the remove column to perform background subtraction') + } + } + return markersheet_data } diff --git a/workflows/mcmicro.nf b/workflows/mcmicro.nf index 2e984cb..38fbb7e 100644 --- a/workflows/mcmicro.nf +++ b/workflows/mcmicro.nf @@ -73,7 +73,7 @@ workflow MCMICRO { | ASHLAR ch_versions = ch_versions.mix(ASHLAR.out.versions) - // // Run Background Correction + // Run Background Correction if (params.backsub) { ch_backsub_markers = ch_markersheet .map { ['channel_number,cycle_number,marker_name,exposure,background,remove', @@ -81,14 +81,24 @@ workflow MCMICRO { channel_number + "," + cycle_number + "," + marker_name + "," + exposure + "," + background + "," + remove}] } .flatten() .map { it.replace('[]', '') } - .collectFile(name: 'markers_backsub.csv', sort: false, newLine: true, storeDir: "${workDir}") - BACKSUB(ASHLAR.out.tif, [[id:"$ASHLAR.out.tif[0]['id']"], "${workDir}/markers_backsub.csv"]) + .collectFile(name: 'markers_backsub.csv', sort: false, newLine: true) + + ASHLAR.out.tif + .combine(ch_backsub_markers) + .dump(tag: 'BACKSUB IN') + .multiMap{ meta, image, marker -> + image: [meta, image] + markers: [meta, marker] + } + | BACKSUB + ch_segmentation_input = BACKSUB.out.backsub_tif ch_versions = ch_versions.mix(BACKSUB.out.versions) } else { ch_segmentation_input = ASHLAR.out.tif } + // Run Segmentation ch_masks = Channel.empty() From cc37008157b24048255bf549af90bb1765a9d810 Mon Sep 17 00:00:00 2001 From: RobJY Date: Wed, 18 Sep 2024 16:25:01 -0400 Subject: [PATCH 08/22] fixed ch_segmentation_input and cleaned-up some --- workflows/mcmicro.nf | 47 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/workflows/mcmicro.nf b/workflows/mcmicro.nf index 38fbb7e..c7bbad4 100644 --- a/workflows/mcmicro.nf +++ b/workflows/mcmicro.nf @@ -74,6 +74,7 @@ workflow MCMICRO { ch_versions = ch_versions.mix(ASHLAR.out.versions) // Run Background Correction + /* if (params.backsub) { ch_backsub_markers = ch_markersheet .map { ['channel_number,cycle_number,marker_name,exposure,background,remove', @@ -97,6 +98,51 @@ workflow MCMICRO { } else { ch_segmentation_input = ASHLAR.out.tif } + */ + + ASHLAR_OUT_BACKSUB = ch_markersheet + .map { it.collect{ _1, _2, _3, _4, _5, _6, exposure, background, remove -> [exposure, background, remove]} } + .flatten() + .filter { it != [] } + .toList() + .combine(ASHLAR.out.tif) + .map { it.size() == 2 ? [it[0] + [backsub: false], [it[1]]] : [it[-2] + [backsub: true], it[-1]] } + + ch_backsub_markers = ch_markersheet + .map { ['channel_number,cycle_number,marker_name,exposure,background,remove', + it.collect{ channel_number, cycle_number, marker_name, _1, _2, _3, exposure, background, remove -> + channel_number + "," + cycle_number + "," + marker_name + "," + exposure + "," + background + "," + remove}] } + .flatten() + .map { it.replace('[]', '') } + .collectFile(name: 'markers_backsub.csv', sort: false, newLine: true) + + BACKSUB_INPUT = ASHLAR_OUT_BACKSUB + .combine(ch_backsub_markers) + .dump(tag: 'BACKSUB IN') + .multiMap{ meta, image, marker -> + image: [meta, image] + markers: [meta, marker] + } + + BACKSUB_INPUT_IMAGE = BACKSUB_INPUT.image + .branch{ meta, image -> + exists: meta.backsub + return [meta, image] + } + + BACKSUB_INPUT_MARKERS = BACKSUB_INPUT.markers + .branch{ meta, markers -> + exists: meta.backsub + return [meta, markers] + } + + BACKSUB(BACKSUB_INPUT_IMAGE.exists, BACKSUB_INPUT_MARKERS.exists) + + ch_segmentation_input = ASHLAR_OUT_BACKSUB + .concat(BACKSUB.out.backsub_tif) + .map { [it[-2], it[-1]] } + ch_versions = ch_versions.mix(BACKSUB.out.versions) + // Run Segmentation @@ -129,6 +175,7 @@ workflow MCMICRO { ['marker_name'] + it.collect{ _1, _2, marker_name, _4, _5, _6, _7, _8, _9 -> '"' + marker_name + '"' } } + .dump(tag: "MARKERS") .collectFile(name: 'markers.csv', sort: false, newLine: true) ASHLAR.out.tif From dc67b4d1ba390c0aca791cb6fe612f71809f9c2c Mon Sep 17 00:00:00 2001 From: RobJY Date: Wed, 18 Sep 2024 16:38:38 -0400 Subject: [PATCH 09/22] removed params.backsub --- nextflow.config | 3 --- nextflow_schema.json | 4 ---- tests/main.nf.test | 1 - workflows/mcmicro.nf | 26 -------------------------- 4 files changed, 34 deletions(-) diff --git a/nextflow.config b/nextflow.config index 00437e5..2f9f598 100644 --- a/nextflow.config +++ b/nextflow.config @@ -19,9 +19,6 @@ params { // Illumination correction illumination = null - // Background subtraction - backsub = false - // MultiQC options multiqc_config = null multiqc_title = null diff --git a/nextflow_schema.json b/nextflow_schema.json index d4c4cf7..c0209e5 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -74,10 +74,6 @@ "cellpose_model": { "type": "string", "description": "optional model file for cellpose segmentation" - }, - "backsub": { - "type": "boolean", - "description": "boolean to flag whether or not to apply background subtraction" } } }, diff --git a/tests/main.nf.test b/tests/main.nf.test index 0787919..61a7137 100644 --- a/tests/main.nf.test +++ b/tests/main.nf.test @@ -528,7 +528,6 @@ nextflow_workflow { when { params { segmentation = "mesmer" - backsub = true } workflow { """ diff --git a/workflows/mcmicro.nf b/workflows/mcmicro.nf index c7bbad4..ab89af5 100644 --- a/workflows/mcmicro.nf +++ b/workflows/mcmicro.nf @@ -74,32 +74,6 @@ workflow MCMICRO { ch_versions = ch_versions.mix(ASHLAR.out.versions) // Run Background Correction - /* - if (params.backsub) { - ch_backsub_markers = ch_markersheet - .map { ['channel_number,cycle_number,marker_name,exposure,background,remove', - it.collect{ channel_number, cycle_number, marker_name, _1, _2, _3, exposure, background, remove -> - channel_number + "," + cycle_number + "," + marker_name + "," + exposure + "," + background + "," + remove}] } - .flatten() - .map { it.replace('[]', '') } - .collectFile(name: 'markers_backsub.csv', sort: false, newLine: true) - - ASHLAR.out.tif - .combine(ch_backsub_markers) - .dump(tag: 'BACKSUB IN') - .multiMap{ meta, image, marker -> - image: [meta, image] - markers: [meta, marker] - } - | BACKSUB - - ch_segmentation_input = BACKSUB.out.backsub_tif - ch_versions = ch_versions.mix(BACKSUB.out.versions) - } else { - ch_segmentation_input = ASHLAR.out.tif - } - */ - ASHLAR_OUT_BACKSUB = ch_markersheet .map { it.collect{ _1, _2, _3, _4, _5, _6, exposure, background, remove -> [exposure, background, remove]} } .flatten() From c430f145e43b31198d61267eab510d8c9a7a8d66 Mon Sep 17 00:00:00 2001 From: RobJY Date: Wed, 18 Sep 2024 17:34:22 -0400 Subject: [PATCH 10/22] added commented out dev code --- workflows/mcmicro.nf | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/workflows/mcmicro.nf b/workflows/mcmicro.nf index ab89af5..672f73e 100644 --- a/workflows/mcmicro.nf +++ b/workflows/mcmicro.nf @@ -109,9 +109,22 @@ workflow MCMICRO { exists: meta.backsub return [meta, markers] } - + BACKSUB(BACKSUB_INPUT_IMAGE.exists, BACKSUB_INPUT_MARKERS.exists) + /* + BACKSUB_INPUT_TEST = ASHLAR_OUT_BACKSUB + .combine(ch_backsub_markers) + .view() + .branch{ meta, image, markers -> + exists: meta.backsub + return [[meta, image], [meta, markers]] + } + BACKSUB_INPUT_TEST.exists.view { "TEST : " + it } + + BACKSUB(BACKSUB_INPUT_TEST.exists) + */ + ch_segmentation_input = ASHLAR_OUT_BACKSUB .concat(BACKSUB.out.backsub_tif) .map { [it[-2], it[-1]] } From 3f0363c13880d1e00c970cb48d21e312c0d2bd15 Mon Sep 17 00:00:00 2001 From: RobJY Date: Thu, 19 Sep 2024 13:37:57 -0400 Subject: [PATCH 11/22] added commented out attempts to make code more compact for discussion --- workflows/mcmicro.nf | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/workflows/mcmicro.nf b/workflows/mcmicro.nf index 672f73e..c602601 100644 --- a/workflows/mcmicro.nf +++ b/workflows/mcmicro.nf @@ -109,20 +109,36 @@ workflow MCMICRO { exists: meta.backsub return [meta, markers] } - BACKSUB(BACKSUB_INPUT_IMAGE.exists, BACKSUB_INPUT_MARKERS.exists) + - /* + /* seems like this should replace the blocks (BACKSUB_INPUT, BACKSUB_INPUT_IMAGE and BACKSUB_INPUT_MARKERS) above. + from https://github.com/nextflow-io/nextflow/issues/1208 it seems like this should work, + but as described using null fails + ASHLAR_OUT_BACKSUB + .combine(ch_backsub_markers) + .dump(tag: 'BACKSUB IN') + .multiMap{ meta, image, marker -> + // image: meta.backsub ? [meta, image] : Channel.empty() + // markers: meta.backsub ? [meta, marker] : Channel.empty() + image: meta.backsub ? [meta, image] : null + markers: meta.backsub ? [meta, marker] : null + // image: meta.backsub ? [meta, image] : [] + // markers: meta.backsub ? [meta, marker] : [] + } + | BACKSUB + */ + + /* another idea to replace the blocks (BACKSUB_INPUT, BACKSUB_INPUT_IMAGE and BACKSUB_INPUT_MARKERS) above. + would like to have a single branch condition return 2 channel outputs, + but I don't see how to do it. Is something like this possible? BACKSUB_INPUT_TEST = ASHLAR_OUT_BACKSUB .combine(ch_backsub_markers) - .view() .branch{ meta, image, markers -> exists: meta.backsub - return [[meta, image], [meta, markers]] + return [[meta1, image], [meta, markers]] } - BACKSUB_INPUT_TEST.exists.view { "TEST : " + it } - - BACKSUB(BACKSUB_INPUT_TEST.exists) + BACKSUB(BACKSUB_INPUT_TEST.exists) */ ch_segmentation_input = ASHLAR_OUT_BACKSUB From 9539963cac92dc7031be4001dea118a359dbddc6 Mon Sep 17 00:00:00 2001 From: RobJY Date: Thu, 26 Sep 2024 11:04:34 -0400 Subject: [PATCH 12/22] reverted to backsub input parameter --- nextflow.config | 3 ++ nextflow_schema.json | 4 ++ tests/main.nf.test | 1 + workflows/mcmicro.nf | 96 +++++++++++--------------------------------- 4 files changed, 31 insertions(+), 73 deletions(-) diff --git a/nextflow.config b/nextflow.config index 2f9f598..00437e5 100644 --- a/nextflow.config +++ b/nextflow.config @@ -19,6 +19,9 @@ params { // Illumination correction illumination = null + // Background subtraction + backsub = false + // MultiQC options multiqc_config = null multiqc_title = null diff --git a/nextflow_schema.json b/nextflow_schema.json index c0209e5..d4c4cf7 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -74,6 +74,10 @@ "cellpose_model": { "type": "string", "description": "optional model file for cellpose segmentation" + }, + "backsub": { + "type": "boolean", + "description": "boolean to flag whether or not to apply background subtraction" } } }, diff --git a/tests/main.nf.test b/tests/main.nf.test index 61a7137..0787919 100644 --- a/tests/main.nf.test +++ b/tests/main.nf.test @@ -528,6 +528,7 @@ nextflow_workflow { when { params { segmentation = "mesmer" + backsub = true } workflow { """ diff --git a/workflows/mcmicro.nf b/workflows/mcmicro.nf index c602601..5c58a2a 100644 --- a/workflows/mcmicro.nf +++ b/workflows/mcmicro.nf @@ -74,79 +74,29 @@ workflow MCMICRO { ch_versions = ch_versions.mix(ASHLAR.out.versions) // Run Background Correction - ASHLAR_OUT_BACKSUB = ch_markersheet - .map { it.collect{ _1, _2, _3, _4, _5, _6, exposure, background, remove -> [exposure, background, remove]} } - .flatten() - .filter { it != [] } - .toList() - .combine(ASHLAR.out.tif) - .map { it.size() == 2 ? [it[0] + [backsub: false], [it[1]]] : [it[-2] + [backsub: true], it[-1]] } - - ch_backsub_markers = ch_markersheet - .map { ['channel_number,cycle_number,marker_name,exposure,background,remove', - it.collect{ channel_number, cycle_number, marker_name, _1, _2, _3, exposure, background, remove -> - channel_number + "," + cycle_number + "," + marker_name + "," + exposure + "," + background + "," + remove}] } - .flatten() - .map { it.replace('[]', '') } - .collectFile(name: 'markers_backsub.csv', sort: false, newLine: true) - - BACKSUB_INPUT = ASHLAR_OUT_BACKSUB - .combine(ch_backsub_markers) - .dump(tag: 'BACKSUB IN') - .multiMap{ meta, image, marker -> - image: [meta, image] - markers: [meta, marker] - } - - BACKSUB_INPUT_IMAGE = BACKSUB_INPUT.image - .branch{ meta, image -> - exists: meta.backsub - return [meta, image] - } - - BACKSUB_INPUT_MARKERS = BACKSUB_INPUT.markers - .branch{ meta, markers -> - exists: meta.backsub - return [meta, markers] - } - BACKSUB(BACKSUB_INPUT_IMAGE.exists, BACKSUB_INPUT_MARKERS.exists) - - - /* seems like this should replace the blocks (BACKSUB_INPUT, BACKSUB_INPUT_IMAGE and BACKSUB_INPUT_MARKERS) above. - from https://github.com/nextflow-io/nextflow/issues/1208 it seems like this should work, - but as described using null fails - ASHLAR_OUT_BACKSUB - .combine(ch_backsub_markers) - .dump(tag: 'BACKSUB IN') - .multiMap{ meta, image, marker -> - // image: meta.backsub ? [meta, image] : Channel.empty() - // markers: meta.backsub ? [meta, marker] : Channel.empty() - image: meta.backsub ? [meta, image] : null - markers: meta.backsub ? [meta, marker] : null - // image: meta.backsub ? [meta, image] : [] - // markers: meta.backsub ? [meta, marker] : [] - } - | BACKSUB - */ - - /* another idea to replace the blocks (BACKSUB_INPUT, BACKSUB_INPUT_IMAGE and BACKSUB_INPUT_MARKERS) above. - would like to have a single branch condition return 2 channel outputs, - but I don't see how to do it. Is something like this possible? - BACKSUB_INPUT_TEST = ASHLAR_OUT_BACKSUB - .combine(ch_backsub_markers) - .branch{ meta, image, markers -> - exists: meta.backsub - return [[meta1, image], [meta, markers]] - } - BACKSUB(BACKSUB_INPUT_TEST.exists) - */ - - ch_segmentation_input = ASHLAR_OUT_BACKSUB - .concat(BACKSUB.out.backsub_tif) - .map { [it[-2], it[-1]] } - ch_versions = ch_versions.mix(BACKSUB.out.versions) - - + if (params.backsub) { + ch_backsub_markers = ch_markersheet + .map { ['channel_number,cycle_number,marker_name,exposure,background,remove', + it.collect{ channel_number, cycle_number, marker_name, _1, _2, _3, exposure, background, remove -> + channel_number + "," + cycle_number + "," + marker_name + "," + exposure + "," + background + "," + remove}] } + .flatten() + .map { it.replace('[]', '') } + .collectFile(name: 'markers_backsub.csv', sort: false, newLine: true) + + ASHLAR.out.tif + .combine(ch_backsub_markers) + .dump(tag: 'BACKSUB IN') + .multiMap{ meta, image, marker -> + image: [meta, image] + markers: [meta, marker] + } + | BACKSUB + + ch_segmentation_input = BACKSUB.out.backsub_tif + ch_versions = ch_versions.mix(BACKSUB.out.versions) + } else { + ch_segmentation_input = ASHLAR.out.tif + } // Run Segmentation From 0c939b7021dbbb017629f1a08a762430252e64f0 Mon Sep 17 00:00:00 2001 From: RobJY Date: Thu, 26 Sep 2024 13:49:18 -0400 Subject: [PATCH 13/22] made changes suggested in PR --- .../local/utils_nfcore_mcmicro_pipeline/main.nf | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/subworkflows/local/utils_nfcore_mcmicro_pipeline/main.nf b/subworkflows/local/utils_nfcore_mcmicro_pipeline/main.nf index 0cf3757..9b5dbe7 100644 --- a/subworkflows/local/utils_nfcore_mcmicro_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_mcmicro_pipeline/main.nf @@ -222,24 +222,24 @@ def validateInputMarkersheet( markersheet_data ) { } // validate backsub columns if present - def exposure_list = markersheet_data.findResults{ _1, _2, _3, _4, _5, _6, exposure, _8, _9 -> exposure != [] ? exposure : null } - def background_list = markersheet_data.findResults{ _1, _2, _3, _4, _5, _6, _7, background, _9 -> background != [] ? background: null } - def remove_list = markersheet_data.findResults{ _1, _2, _3, _4, _5, _6, _7, _8, remove -> remove != [] ? remove : null } - - if (background_list.size() == 0 && (exposure_list.size() > 0 || remove_list.size() > 0)) { + def exposure_list = markersheet_data.findResults{ _1, _2, _3, _4, _5, _6, exposure, _8, _9 -> exposure ?: null } + def background_list = markersheet_data.findResults{ _1, _2, _3, _4, _5, _6, _7, background, _9 -> background ?: null } + def remove_list = markersheet_data.findResults{ _1, _2, _3, _4, _5, _6, _7, _8, remove -> remove ?: null } + + if (!background_list && (exposure_list || remove_list)) { error("No values in background column, but values in either exposure or remove columns. Must have background column values to perform background subtraction.") - } else if (background_list.size() > 0) { + } else if (background_list) { inter_list = marker_name_list.intersect(background_list) if (inter_list.size() != background_list.size()) { outliers_list = background_list - inter_list error('background column values must exist in the marker_name column. The following background column values do not exist in the marker_name column: ' + outliers_list) } - if (exposure_list.size() == 0) { + if (!exposure_list) { error('You must have at least one value in the exposure column to perform background subtraction') } - if (remove_list.size() == 0) { + if (!remove_list) { error ('You must have at least one value in the remove column to perform background subtraction') } } From 341126ab5a6b0184572cc984e77567d3e8d12d98 Mon Sep 17 00:00:00 2001 From: RobJY Date: Mon, 30 Sep 2024 11:23:32 -0400 Subject: [PATCH 14/22] removed whitespace causing lint error --- subworkflows/local/utils_nfcore_mcmicro_pipeline/main.nf | 2 +- workflows/mcmicro.nf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/subworkflows/local/utils_nfcore_mcmicro_pipeline/main.nf b/subworkflows/local/utils_nfcore_mcmicro_pipeline/main.nf index b99cffd..fb5f4a5 100644 --- a/subworkflows/local/utils_nfcore_mcmicro_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_mcmicro_pipeline/main.nf @@ -228,7 +228,7 @@ def validateInputMarkersheet( markersheet_data ) { def exposure_list = markersheet_data.findResults{ _1, _2, _3, _4, _5, _6, exposure, _8, _9 -> exposure ?: null } def background_list = markersheet_data.findResults{ _1, _2, _3, _4, _5, _6, _7, background, _9 -> background ?: null } def remove_list = markersheet_data.findResults{ _1, _2, _3, _4, _5, _6, _7, _8, remove -> remove ?: null } - + if (!background_list && (exposure_list || remove_list)) { error("No values in background column, but values in either exposure or remove columns. Must have background column values to perform background subtraction.") } else if (background_list) { diff --git a/workflows/mcmicro.nf b/workflows/mcmicro.nf index 90e70cd..c9ac222 100644 --- a/workflows/mcmicro.nf +++ b/workflows/mcmicro.nf @@ -92,7 +92,7 @@ workflow MCMICRO { markers: [meta, marker] } | BACKSUB - + ch_segmentation_input = BACKSUB.out.backsub_tif ch_versions = ch_versions.mix(BACKSUB.out.versions) } else { From 12f6a6921f53ba7160e551ee3e19e46cd489317f Mon Sep 17 00:00:00 2001 From: Robert Young Date: Mon, 30 Sep 2024 11:34:05 -0400 Subject: [PATCH 15/22] Update assets/schema_marker.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Krešimir Beštak <86408271+kbestak@users.noreply.github.com> --- assets/schema_marker.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/schema_marker.json b/assets/schema_marker.json index 255ec03..d65e7a9 100644 --- a/assets/schema_marker.json +++ b/assets/schema_marker.json @@ -36,7 +36,7 @@ "errorMessage": "" }, "exposure": { - "type": "integer" + "type": "number" }, "background": { "type": "string" From decacc3b9ddead6e398583db9ac73112e9ff0929 Mon Sep 17 00:00:00 2001 From: RobJY Date: Fri, 4 Oct 2024 17:01:11 -0400 Subject: [PATCH 16/22] updated backsub test with suggested changes from the PR --- tests/main.nf.test | 24 +++++++++++++---------- tests/main.nf.test.snap | 42 +++++++++++++++++++++++++++++++++-------- 2 files changed, 48 insertions(+), 18 deletions(-) diff --git a/tests/main.nf.test b/tests/main.nf.test index 063e998..afca367 100644 --- a/tests/main.nf.test +++ b/tests/main.nf.test @@ -581,7 +581,7 @@ nextflow_workflow { [], ], [ - [id:"TEST2", cycle_number:2, channel_count:4], + [id:"TEST1", cycle_number:2, channel_count:4], "https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/imaging/background_subtraction/cycif_tonsil_registered.ome.tif", [], [], @@ -593,10 +593,18 @@ nextflow_workflow { [2,1,'Na/K ATPase',[],[],[],100,[],[]], [3,1,'CD3',[],[],[],100,[],[]], [4,1,'CD45RO',[],[],[],100,[],[]], - [5,2,'DNA 2',[],[],[],100,[],'TRUE'], - [6,2,'Antigen Ki67',[],[],[],100,'Na/K ATPase',[]], - [7,2,'Pan-cytokeratin',[],[],[],100,'CD3',[]], - [8,2,'Aortic smooth muscle actin',[],[],[],100,'CD45RO',[]] + [5,1,'DNA 2',[],[],[],100,[],[]], + [6,1,'Antigen Ki67',[],[],[],100,[],[]], + [7,1,'Pan-cytokeratin',[],[],[],100,[],[]], + [8,1,'Aortic smooth muscle actin',[],[],[],100,[],[]], + [9,2,'DNA 1 1',[],[],[],100,[],[]], + [10,2,'Na/K ATPase 2',[],[],[],100,[],[]], + [11,2,'CD3 2',[],[],[],100,'Na/K ATPase',[]], + [12,2,'CD45RO 2',[],[],[],100,[],[]], + [13,2,'DNA 2 2',[],[],[],100,[],'TRUE'], + [14,2,'Antigen Ki67 2',[],[],[],100,[],[]], + [15,2,'Pan-cytokeratin 2',[],[],[],100,'CD3',[]], + [16,2,'Aortic smooth muscle actin 2',[],[],[],100,'CD45RO 2',[]] ], ) """ @@ -608,13 +616,9 @@ nextflow_workflow { { assert snapshot ( path("$outputDir/registration/ashlar/TEST1.ome.tif"), - path("$outputDir/registration/ashlar/TEST2.ome.tif"), path("$outputDir/segmentation/deepcell_mesmer/mask_TEST1.tif"), - path("$outputDir/segmentation/deepcell_mesmer/mask_TEST2.tif"), - CsvUtils.roundAndHashCsv("$outputDir/quantification/mcquant/mesmer/TEST1_mask_TEST1.csv"), - CsvUtils.roundAndHashCsv("$outputDir/quantification/mcquant/mesmer/TEST2_mask_TEST2.csv"), + CsvUtils.summarizeCsv("$outputDir/quantification/mcquant/mesmer/TEST1_mask_TEST1.csv"), path("$outputDir/backsub/TEST1.backsub.ome.tif"), - path("$outputDir/backsub/TEST2.backsub.ome.tif"), ).match() }, { assert workflow.success } diff --git a/tests/main.nf.test.snap b/tests/main.nf.test.snap index 89959b2..0791adc 100644 --- a/tests/main.nf.test.snap +++ b/tests/main.nf.test.snap @@ -115,20 +115,46 @@ }, "cycle: no illumination correction, backsub": { "content": [ - "TEST1.ome.tif:md5,7eacee6365a04ff065e40d7cc887917d", - "TEST2.ome.tif:md5,7eacee6365a04ff065e40d7cc887917d", + "TEST1.ome.tif:md5,2b3989015265b18296baa94418b82f2b", "mask_TEST1.tif:md5,3103759bd55b9deeb8c8a0dade798d9a", - "mask_TEST2.tif:md5,3103759bd55b9deeb8c8a0dade798d9a", - "TEST1_mask_TEST1.csv:rounded:md5,ab3ffe8c74831b9a3e73bd2e86599f66", - "TEST2_mask_TEST2.csv:rounded:md5,ab3ffe8c74831b9a3e73bd2e86599f66", - "TEST1.backsub.ome.tif:md5,2b0efce79022043471680d0772e395fd", - "TEST2.backsub.ome.tif:md5,2b0efce79022043471680d0772e395fd" + { + "headers": [ + "CellID", + "DNA 1", + "Na/K ATPase", + "CD3", + "CD45RO", + "DNA 2", + "Antigen Ki67", + "Pan-cytokeratin", + "Aortic smooth muscle actin", + "DNA 1 1", + "Na/K ATPase 2", + "CD3 2", + "CD45RO 2", + "DNA 2 2", + "Antigen Ki67 2", + "Pan-cytokeratin 2", + "Aortic smooth muscle actin 2", + "X_centroid", + "Y_centroid", + "Area", + "MajorAxisLength", + "MinorAxisLength", + "Eccentricity", + "Solidity", + "Extent", + "Orientation" + ], + "rowCount": 2111 + }, + "TEST1.backsub.ome.tif:md5,a5cca143a875fa0d97b314926e04ef2b" ], "meta": { "nf-test": "0.9.0", "nextflow": "24.04.4" }, - "timestamp": "2024-09-30T10:11:23.44358966" + "timestamp": "2024-10-04T16:45:31.380793042" }, "cycle: multiple file ashlar input with multiple samples no correction, multiple segmentation": { "content": [ From 07ec4d05fcf1523a3e71d1384c7de093cc983bea Mon Sep 17 00:00:00 2001 From: RobJY Date: Tue, 8 Oct 2024 12:47:00 -0400 Subject: [PATCH 17/22] bug fix for backsub test --- tests/main.nf.test | 14 +++----------- tests/main.nf.test.snap | 14 +++----------- 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/tests/main.nf.test b/tests/main.nf.test index afca367..a825ebf 100644 --- a/tests/main.nf.test +++ b/tests/main.nf.test @@ -576,13 +576,13 @@ nextflow_workflow { input[0] = Channel.of( [ [id:"TEST1", cycle_number:1, channel_count:4], - "https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/imaging/background_subtraction/cycif_tonsil_registered.ome.tif", + "https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/imaging/ome-tiff/cycif-tonsil-cycle1.ome.tif", [], [], ], [ [id:"TEST1", cycle_number:2, channel_count:4], - "https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/imaging/background_subtraction/cycif_tonsil_registered.ome.tif", + "https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/imaging/ome-tiff/cycif-tonsil-cycle2.ome.tif", [], [], ], @@ -596,15 +596,7 @@ nextflow_workflow { [5,1,'DNA 2',[],[],[],100,[],[]], [6,1,'Antigen Ki67',[],[],[],100,[],[]], [7,1,'Pan-cytokeratin',[],[],[],100,[],[]], - [8,1,'Aortic smooth muscle actin',[],[],[],100,[],[]], - [9,2,'DNA 1 1',[],[],[],100,[],[]], - [10,2,'Na/K ATPase 2',[],[],[],100,[],[]], - [11,2,'CD3 2',[],[],[],100,'Na/K ATPase',[]], - [12,2,'CD45RO 2',[],[],[],100,[],[]], - [13,2,'DNA 2 2',[],[],[],100,[],'TRUE'], - [14,2,'Antigen Ki67 2',[],[],[],100,[],[]], - [15,2,'Pan-cytokeratin 2',[],[],[],100,'CD3',[]], - [16,2,'Aortic smooth muscle actin 2',[],[],[],100,'CD45RO 2',[]] + [8,1,'Aortic smooth muscle actin',[],[],[],100,[],[]] ], ) """ diff --git a/tests/main.nf.test.snap b/tests/main.nf.test.snap index 0791adc..e67b011 100644 --- a/tests/main.nf.test.snap +++ b/tests/main.nf.test.snap @@ -115,7 +115,7 @@ }, "cycle: no illumination correction, backsub": { "content": [ - "TEST1.ome.tif:md5,2b3989015265b18296baa94418b82f2b", + "TEST1.ome.tif:md5,fd414b610e189f3e805c8e99f4e78c09", "mask_TEST1.tif:md5,3103759bd55b9deeb8c8a0dade798d9a", { "headers": [ @@ -128,14 +128,6 @@ "Antigen Ki67", "Pan-cytokeratin", "Aortic smooth muscle actin", - "DNA 1 1", - "Na/K ATPase 2", - "CD3 2", - "CD45RO 2", - "DNA 2 2", - "Antigen Ki67 2", - "Pan-cytokeratin 2", - "Aortic smooth muscle actin 2", "X_centroid", "Y_centroid", "Area", @@ -148,13 +140,13 @@ ], "rowCount": 2111 }, - "TEST1.backsub.ome.tif:md5,a5cca143a875fa0d97b314926e04ef2b" + "TEST1.backsub.ome.tif:md5,82b15c88057ab8fd8248402a687997ea" ], "meta": { "nf-test": "0.9.0", "nextflow": "24.04.4" }, - "timestamp": "2024-10-04T16:45:31.380793042" + "timestamp": "2024-10-08T12:33:35.316668616" }, "cycle: multiple file ashlar input with multiple samples no correction, multiple segmentation": { "content": [ From 4f3921b0a49dee7bd04c0fd10ce7ea488e9004a8 Mon Sep 17 00:00:00 2001 From: RobJY Date: Tue, 8 Oct 2024 14:26:05 -0400 Subject: [PATCH 18/22] lint fix for schema --- nextflow_schema.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nextflow_schema.json b/nextflow_schema.json index aca087e..47847a2 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -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/mcmicro/master/nextflow_schema.json", "title": "nf-core/mcmicro pipeline parameters", "description": "Whole-slide multiplexed microscopy image analysis", From 54928139c9cd3912bd2fe83dfa480ac0139b004b Mon Sep 17 00:00:00 2001 From: RobJY Date: Wed, 9 Oct 2024 10:56:02 -0400 Subject: [PATCH 19/22] accepting automatic lint fixes --- .github/CONTRIBUTING.md | 10 +++++----- .github/PULL_REQUEST_TEMPLATE.md | 2 +- .github/workflows/linting.yml | 23 ++++++++++++++++++---- .github/workflows/linting_comment.yml | 2 +- .prettierignore | 1 + docs/images/nf-core-mcmicro_logo_dark.png | Bin 25994 -> 26175 bytes 6 files changed, 27 insertions(+), 11 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index c032c19..90d1418 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -19,7 +19,7 @@ If you'd like to write some code for nf-core/mcmicro, the standard workflow is a 1. Check that there isn't already an issue about your idea in the [nf-core/mcmicro issues](https://github.com/nf-core/mcmicro/issues) to avoid duplicating work. If there isn't one already, please create one so that others know you're working on this 2. [Fork](https://help.github.com/en/github/getting-started-with-github/fork-a-repo) the [nf-core/mcmicro repository](https://github.com/nf-core/mcmicro) to your GitHub account 3. Make the necessary changes / additions within your forked repository following [Pipeline conventions](#pipeline-contribution-conventions) -4. Use `nf-core schema build` and add any new parameters to the pipeline JSON schema (requires [nf-core tools](https://github.com/nf-core/tools) >= 1.10). +4. Use `nf-core pipelines schema build` and add any new parameters to the pipeline JSON schema (requires [nf-core tools](https://github.com/nf-core/tools) >= 1.10). 5. Submit a Pull Request against the `dev` branch and wait for the code to be reviewed and merged If you're not used to this workflow with git, you can start with some [docs from GitHub](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests) or even their [excellent `git` resources](https://try.github.io/). @@ -40,7 +40,7 @@ There are typically two types of tests that run: ### Lint tests `nf-core` has a [set of guidelines](https://nf-co.re/developers/guidelines) which all pipelines must adhere to. -To enforce these and ensure that all pipelines stay in sync, we have developed a helper tool which runs checks on the pipeline code. This is in the [nf-core/tools repository](https://github.com/nf-core/tools) and once installed can be run locally with the `nf-core lint ` command. +To enforce these and ensure that all pipelines stay in sync, we have developed a helper tool which runs checks on the pipeline code. This is in the [nf-core/tools repository](https://github.com/nf-core/tools) and once installed can be run locally with the `nf-core pipelines lint ` command. If any failures or warnings are encountered, please follow the listed URL for more documentation. @@ -75,7 +75,7 @@ If you wish to contribute a new step, please use the following coding standards: 2. Write the process block (see below). 3. Define the output channel if needed (see below). 4. Add any new parameters to `nextflow.config` with a default (see below). -5. Add any new parameters to `nextflow_schema.json` with help text (via the `nf-core schema build` tool). +5. Add any new parameters to `nextflow_schema.json` with help text (via the `nf-core pipelines schema build` tool). 6. Add sanity checks and validation for all relevant parameters. 7. Perform local tests to validate that the new code works as expected. 8. If applicable, add a new test command in `.github/workflow/ci.yml`. @@ -86,7 +86,7 @@ If you wish to contribute a new step, please use the following coding standards: Parameters should be initialised / defined with default values in `nextflow.config` under the `params` scope. -Once there, use `nf-core schema build` to add to `nextflow_schema.json`. +Once there, use `nf-core pipelines schema build` to add to `nextflow_schema.json`. ### Default processes resource requirements @@ -103,7 +103,7 @@ Please use the following naming schemes, to make it easy to understand what is g ### Nextflow version bumping -If you are using a new feature from core Nextflow, you may bump the minimum required version of nextflow in the pipeline with: `nf-core bump-version --nextflow . [min-nf-version]` +If you are using a new feature from core Nextflow, you may bump the minimum required version of nextflow in the pipeline with: `nf-core pipelines bump-version --nextflow . [min-nf-version]` ### Images and figures diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index b705b38..bd914f1 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -17,7 +17,7 @@ Learn more about contributing: [CONTRIBUTING.md](https://github.com/nf-core/mcmi - [ ] If you've fixed a bug or added code that should be tested, add tests! - [ ] If you've added a new tool - have you followed the pipeline conventions in the [contribution docs](https://github.com/nf-core/mcmicro/tree/master/.github/CONTRIBUTING.md) - [ ] If necessary, also make a PR on the nf-core/mcmicro _branch_ on the [nf-core/test-datasets](https://github.com/nf-core/test-datasets) repository. -- [ ] Make sure your code lints (`nf-core lint`). +- [ ] Make sure your code lints (`nf-core pipelines lint`). - [ ] Ensure the test suite passes (`nextflow run . -profile test,docker --outdir `). - [ ] Check for unexpected warnings in debug mode (`nextflow run . -profile debug,test,docker --outdir `). - [ ] Usage Documentation in `docs/usage.md` is updated. diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 1fcafe8..b882838 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -1,6 +1,6 @@ name: nf-core linting # This workflow is triggered on pushes and PRs to the repository. -# It runs the `nf-core lint` and markdown lint tests to ensure +# It runs the `nf-core pipelines lint` and markdown lint tests to ensure # that the code meets the nf-core guidelines. on: push: @@ -41,17 +41,32 @@ jobs: python-version: "3.12" architecture: "x64" + - name: read .nf-core.yml + uses: pietrobolcato/action-read-yaml@1.0.0 + id: read_yml + with: + config: ${{ github.workspace }}/.nf-core.yaml + - name: Install dependencies run: | python -m pip install --upgrade pip - pip install nf-core + pip install nf-core==${{ steps.read_yml.outputs['nf_core_version'] }} + + - name: Run nf-core pipelines lint + if: ${{ github.base_ref != 'master' }} + env: + GITHUB_COMMENTS_URL: ${{ github.event.pull_request.comments_url }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_PR_COMMIT: ${{ github.event.pull_request.head.sha }} + run: nf-core -l lint_log.txt pipelines lint --dir ${GITHUB_WORKSPACE} --markdown lint_results.md - - name: Run nf-core lint + - name: Run nf-core pipelines lint --release + if: ${{ github.base_ref == 'master' }} env: GITHUB_COMMENTS_URL: ${{ github.event.pull_request.comments_url }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_PR_COMMIT: ${{ github.event.pull_request.head.sha }} - run: nf-core -l lint_log.txt lint --dir ${GITHUB_WORKSPACE} --markdown lint_results.md + run: nf-core -l lint_log.txt pipelines lint --release --dir ${GITHUB_WORKSPACE} --markdown lint_results.md - name: Save PR number if: ${{ always() }} diff --git a/.github/workflows/linting_comment.yml b/.github/workflows/linting_comment.yml index 40acc23..42e519b 100644 --- a/.github/workflows/linting_comment.yml +++ b/.github/workflows/linting_comment.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download lint results - uses: dawidd6/action-download-artifact@09f2f74827fd3a8607589e5ad7f9398816f540fe # v3 + uses: dawidd6/action-download-artifact@bf251b5aa9c2f7eeb574a96ee720e24f801b7c11 # v6 with: workflow: linting.yml workflow_conclusion: completed diff --git a/.prettierignore b/.prettierignore index 437d763..610e506 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,4 @@ + email_template.html adaptivecard.json slackreport.json diff --git a/docs/images/nf-core-mcmicro_logo_dark.png b/docs/images/nf-core-mcmicro_logo_dark.png index 399125c5558010a7a926568c62eb685251c904aa..a04f1864748dacde5ba6b103a1f5c32bd5621e4a 100644 GIT binary patch delta 25445 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 delta 25232 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 z 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 From 296151701394b774e94cd65024630063cda718d1 Mon Sep 17 00:00:00 2001 From: RobJY Date: Wed, 9 Oct 2024 14:33:16 -0400 Subject: [PATCH 20/22] updated config from new template --- nextflow.config | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/nextflow.config b/nextflow.config index cc31602..512dd97 100644 --- a/nextflow.config +++ b/nextflow.config @@ -51,7 +51,6 @@ params { config_profile_contact = null config_profile_url = null - // Max resource options // Defaults only, expecting to be overwritten max_memory = '128.GB' max_cpus = 16 @@ -69,19 +68,6 @@ params { // Load base.config by default for all pipelines includeConfig 'conf/base.config' -// Load nf-core custom profiles from different Institutions -try { - includeConfig "${params.custom_config_base}/nfcore_custom.config" -} catch (Exception e) { - System.err.println("WARNING: Could not load nf-core/config profiles: ${params.custom_config_base}/nfcore_custom.config") -} - -// Load nf-core/mcmicro custom profiles from different institutions. -try { - includeConfig "${params.custom_config_base}/pipeline/mcmicro.config" -} catch (Exception e) { - System.err.println("WARNING: Could not load nf-core/config/mcmicro profiles: ${params.custom_config_base}/pipeline/mcmicro.config") -} profiles { debug { dumpHashes = true @@ -191,6 +177,13 @@ profiles { } } +// Load nf-core custom profiles from different Institutions +includeConfig !System.getenv('NXF_OFFLINE') && params.custom_config_base ? "${params.custom_config_base}/nfcore_custom.config" : "/dev/null" + +// Load nf-core/mcmicro custom profiles from different institutions. +// TODO nf-core: Optionally, you can add a pipeline-specific nf-core config at https://github.com/nf-core/configs +// includeConfig !System.getenv('NXF_OFFLINE') && params.custom_config_base ? "${params.custom_config_base}/pipeline/mcmicro.config" : "/dev/null" + // Set default registry for Apptainer, Docker, Podman and Singularity independent of -profile // Will not be used unless Apptainer / Docker / Podman / Singularity are enabled // Set to your registry if you have a mirror of containers From 32558e2a981d8dba016e2c29cf1f7eeea053cf3f Mon Sep 17 00:00:00 2001 From: RobJY Date: Thu, 10 Oct 2024 11:02:37 -0400 Subject: [PATCH 21/22] added suggested template changes --- .github/CONTRIBUTING.md | 2 +- .github/workflows/linting.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 90d1418..61f7b8b 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -90,7 +90,7 @@ Once there, use `nf-core pipelines schema build` to add to `nextflow_schema.json ### Default processes resource requirements -Sensible defaults for process resource requirements (CPUs / memory / time) for a process should be defined in `conf/base.config`. These should generally be specified generic with `withLabel:` selectors so they can be shared across multiple processes/steps of the pipeline. A nf-core standard set of labels that should be followed where possible can be seen in the [nf-core pipeline template](https://github.com/nf-core/tools/blob/master/nf_core/pipeline-template/conf/base.config), which has the default process as a single core-process, and then different levels of multi-core configurations for increasingly large memory requirements defined with standardised labels. +Sensible defaults for process resource requirements (CPUs / memory / time) for a process should be defined in `conf/base.config`. These should generally be specified generic with `withLabel:` selectors so they can be shared across multiple processes/steps of the pipeline. A nf-core standard set of labels that should be followed where possible can be seen in the [nf-core pipeline template](https://github.com/nf-core/tools/blob/main/nf_core/pipeline-template/conf/base.config), which has the default process as a single core-process, and then different levels of multi-core configurations for increasingly large memory requirements defined with standardised labels. The process resources can be passed on to the tool dynamically within the process with the `${task.cpus}` and `${task.memory}` variables in the `script:` block. diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index b882838..a502573 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -42,10 +42,10 @@ jobs: architecture: "x64" - name: read .nf-core.yml - uses: pietrobolcato/action-read-yaml@1.0.0 + uses: pietrobolcato/action-read-yaml@1.1.0 id: read_yml with: - config: ${{ github.workspace }}/.nf-core.yaml + config: ${{ github.workspace }}/.nf-core.yml - name: Install dependencies run: | From 4589851bd991a7e350cfb98e67a5b93449eedefd Mon Sep 17 00:00:00 2001 From: RobJY Date: Thu, 7 Nov 2024 11:12:35 -0500 Subject: [PATCH 22/22] added nf-core-mcmicro_logo_dark.png to files_unchanged section of .nf-core.yml to fix lint error --- .nf-core.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.nf-core.yml b/.nf-core.yml index 18dd43d..497569f 100644 --- a/.nf-core.yml +++ b/.nf-core.yml @@ -4,6 +4,7 @@ lint: - params.input files_unchanged: - .gitignore + - docs/images/nf-core-mcmicro_logo_dark.png nf_core_version: 3.0.2 org_path: null repository_type: pipeline