diff --git a/.gitignore b/.gitignore index e48038b..9bbe13b 100644 --- a/.gitignore +++ b/.gitignore @@ -55,4 +55,9 @@ trace*.txt # vscode .vscode/launch.json -.vscode/settings.json \ No newline at end of file +.vscode/settings.json + +# linting +renv.lock +.Rprofile +renv/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a4468f..0caf808 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +# openpipeline_spatial 0.1.1 + +## MINOR CHANGES + +* Add a README (PR #21). + +## NEW FUNCTIONALITY + +* `convert`: Updated multiple components to accept spatial output bundles in .zip format (for CosMx, Xenium and Aviti) as input (PR #19). + # openpipeline_spatial 0.1.0 ## NEW FUNCTIONALITY diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c12f874 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 openpipelines-bio + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..50a5df7 --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +# OpenPipeline Spatial + +Extensible spatial single cell analysis pipelines for reproducible and large-scale spatial single cell processing using Viash and Nextflow. + +OpenPipeline Spatial extends the [OpenPipeline](https://github.com/openpipelines-bio/openpipeline/) ecosystem with specialized workflows and components for spatial transcriptomics analysis. It provides standardized, reproducible pipelines that are technology-agnostic and can be used for processing spatial omics data from various technologies and platforms. + +[![ViashHub](https://img.shields.io/badge/ViashHub-openpipeline_spatial-7a4baa.svg)](https://www.viash-hub.com/packages/openpipeline_spatial) +[![GitHub](https://img.shields.io/badge/GitHub-viash--hub%2Fopenpipeline_spatial-blue.svg)](https://github.com/openpipelines-bio/openpipeline_spatial) +[![GitHub +License](https://img.shields.io/github/license/openpipelines-bio/openpipeline_spatial.svg)](https://github.com/openpipelines-bio/openpipeline_spatial/blob/main/LICENSE) +[![GitHub +Issues](https://img.shields.io/github/issues/openpipelines-bio/openpipeline_spatial.svg)](https://github.com/openpipelines-bio/openpipeline_spatial/issues) +[![Viash +version](https://img.shields.io/badge/Viash-v0.9.3-blue.svg)](https://viash.io) + +## Functionality + +OpenPipeline Spatial executes a list of predefined tasks specifically designed for spatial omics data. These discrete steps are also provided as standalone components that can be executed individually with a standardized interface. + +The following spatial-specific workflows are provided: + +- [Ingestion](https://www.viash-hub.com/packages/openpipeline_spatial/latest/components?search=mapping): Whereas many technologies generate count matrices on-instrument, functionality is provided for the mapping & quantification of 10X Visum data. +- [Interoperability](https://www.viash-hub.com/packages/openpipeline_spatial/latest/components?search=convert): To make sure all spatial workflows are technology-agnostic, functionality is provided to convert count matrices from different technologies (e.g. Xenium, CosMx, AtoMx, Aviti) into a common format (H5MU). In addition, functionality is provided to convert between various Spatial data formats (e.g. Seurat, SpatialExperiment, MuData, SpatialData). +- [QC](https://www.viash-hub.com/packages/openpipeline_spatial/latest/components?search=spatial_qc): Calculation of comprehensive quality control metrics. +- [Sample Processing](https://www.viash-hub.com/packages/openpipeline_spatial/latest/components?search=spatial_process_samples): Batch processing of multiple spatial samples, including count-based filtering, normalisation and dimensionality reduction. + +## Extended functionality + +Whereas this package only provides spatial-specific functionality, it is designed to work seamlessly with the core [OpenPipeline package](https://github.com/openpipelines-bio/openpipeline/). This means that all core OpenPipeline workflows and components can be used in conjunction with the spatial-specific ones. For example, the [**integration**](https://www.viash-hub.com/packages/openpipeline/latest/components?search=workflows%2Fintegration) and [**cell type annotation**](https://www.viash-hub.com/packages/openpipeline/latest/components?search=workflows%2Fannotation) workflows can be applied to spatial data after it has been processed using the spatial-specific workflows. + +``` mermaid lang="mermaid" +flowchart LR + demultiplexing["Step 1: Ingestion"] + ingestion["Step 2: QC"] + process_samples["Step 3: Process Samples"] + integration["Step 4: Integration"] + downstream["Step 5: Downstream Analysis"] + demultiplexing-->ingestion-->process_samples-->integration-->downstream +``` + +## Execution via CLI or Seqera Cloud + +The openpipeline_spatial package is available via [Viash +Hub](https://www.viash-hub.com/packages/openpipeline_spatial/latest/), where +you can receive instructions on how to run the end-to-end workflow as +well as individual subworkflows or components. + +It’s possible to run the workflow directly from Seqera Cloud. The necessary Nextflow schema files have been [built and provided with the workflows](https://packages.viash-hub.com/vsh/openpipeline_spatial/-/tree/build/main/target/nextflow?ref_type=heads) in order to use the form-based input. However, Seqera Cloud can not deal with multiple-value parameters for batch processing of multiple samples. Therefore, it’s better to use Viash Hub also here for launching the workflow on Seqera Cloud. + +* Navigate to the [Viash Hub package page](https://www.viash-hub.com/packages/openpipeline_spatial/latest/), select the workflow you want to launch and click the `launch` button. +* Select the execution environment of choice (e.g. `Seqera Cloud`, `CLI` or `Executable`) +* Fill in the form with the required parameters and launch the workflow. + +## Support +For issues specific to spatial analysis, please use the [GitHub issues tracker](https://github.com/openpipelines-bio/openpipeline_spatial/issues). For general OpenPipeline questions, refer to the main [OpenPipeline documentation](https://openpipelines.bio/). diff --git a/_viash.yaml b/_viash.yaml index 715ade4..4708de8 100644 --- a/_viash.yaml +++ b/_viash.yaml @@ -1,28 +1,22 @@ viash_version: 0.9.4 -version: v0.1.0 - +version: v0.1.1 source: src target: target - name: openpipeline_spatial -organization: openpipelines-bio - +organization: vsh links: repository: https://github.com/openpipelines-bio/openpipeline_spatial docker_registry: ghcr.io - repositories: - name: openpipeline repo: openpipeline type: vsh tag: v3.0.0 - info: test_resources: - type: s3 path: s3://openpipelines-bio/openpipeline_spatial/resources_test dest: resources_test - -config_mods: | +config_mods: |- .resources += {path: '/src/workflows/utils/labels.config', dest: 'nextflow_labels.config'} - .runners[.type == 'nextflow'].config.script := 'includeConfig("nextflow_labels.config")' \ No newline at end of file + .runners[.type == 'nextflow'].config.script := 'includeConfig("nextflow_labels.config")' diff --git a/src/convert/from_cells2stats_to_h5mu/config.vsh.yaml b/src/convert/from_cells2stats_to_h5mu/config.vsh.yaml index 445e08b..4c09a6f 100644 --- a/src/convert/from_cells2stats_to_h5mu/config.vsh.yaml +++ b/src/convert/from_cells2stats_to_h5mu/config.vsh.yaml @@ -100,6 +100,7 @@ resources: - type: python_script path: script.py - path: /src/utils/setup_logger.py + - path: /src/utils/unzip_archived_folder.py test_resources: - type: python_script @@ -118,6 +119,9 @@ engines: packages: - pyarrow test_setup: + - type: apt + packages: + - zip - type: python __merge__: [ /src/base/requirements/viashpy.yaml, .] diff --git a/src/convert/from_cells2stats_to_h5mu/script.py b/src/convert/from_cells2stats_to_h5mu/script.py index 3d1d774..e812ab9 100644 --- a/src/convert/from_cells2stats_to_h5mu/script.py +++ b/src/convert/from_cells2stats_to_h5mu/script.py @@ -6,6 +6,8 @@ import mudata as mu import anndata as ad import re import json +import zipfile +import os ## VIASH START par = { @@ -25,6 +27,8 @@ meta = {"resources_dir": "src/utils"} sys.path.append(meta["resources_dir"]) from setup_logger import setup_logger +from unzip_archived_folder import extract_selected_files_from_zip + logger = setup_logger() @@ -162,27 +166,43 @@ def categorize_columns(column_list, target_panel): ) -def main(): - # Read data from Aviti Teton output bundle +def retrieve_input_data(cells2stats_output_bundle): # Expected folder structure (showing only relevant files): # ├── Cytoprofiling/ # │ └── Instrument/ # │ └── RawCellStats.parquet # └── Panel.json - logger.info("Reading input data...") - input_dir = Path(par["input"]) - input_data = { - "count_matrix": input_dir - / "Cytoprofiling" - / "Instrument" - / "RawCellStats.parquet", - "target_panel": input_dir / "Panel.json", + required_file_patterns = { + "target_panel": "**/Panel.json", + "count_matrix": "**/Cytoprofiling/Instrument/RawCellStats.parquet", } - assert all([file.exists() for file in input_data.values()]), ( - f"Not all required input files are found. Make sure that {par['input']} contains {input_data.values()}." + if zipfile.is_zipfile(cells2stats_output_bundle): + cells2stats_output_bundle = extract_selected_files_from_zip( + cells2stats_output_bundle, members=required_file_patterns.values() + ) + else: + cells2stats_output_bundle = Path(cells2stats_output_bundle) + + assert os.path.isdir(cells2stats_output_bundle), ( + "Input is expected to be a (compressed) directory." ) + + input_data = {} + for key, pattern in required_file_patterns.items(): + file = list(cells2stats_output_bundle.glob(pattern)) + assert len(file) == 1, ( + f"Expected exactly one file matching pattern {pattern}, found {len(file)}." + ) + input_data[key] = file[0] + + return input_data + + +def main(): + logger.info("Reading input data...") + input_data = retrieve_input_data(par["input"]) with open(input_data["target_panel"], "r") as f: target_panel = json.load(f) df = pd.read_parquet(input_data["count_matrix"], engine="pyarrow") diff --git a/src/convert/from_cells2stats_to_h5mu/test.py b/src/convert/from_cells2stats_to_h5mu/test.py index a1c6c9b..15a87ff 100644 --- a/src/convert/from_cells2stats_to_h5mu/test.py +++ b/src/convert/from_cells2stats_to_h5mu/test.py @@ -1,6 +1,7 @@ import pytest import sys import mudata as mu +import subprocess ## VIASH START meta = { @@ -51,6 +52,57 @@ def test_simple_execution(run_component, tmp_path): assert all(adata.obsm[obsm].dtype.kind == "f" for obsm in expected_obsm_keys) +def test_compressed_input(run_component, tmp_path): + output = tmp_path / "aviti.h5mu" + zipped_input = tmp_path / "aviti.zip" + + subprocess.run( + ["zip", "-r", str(zipped_input), "aviti"], cwd=meta["resources_dir"], check=True + ) + + # run component + run_component( + [ + "--input", + zipped_input, + "--output", + str(output), + "--output_compression", + "gzip", + ] + ) + + assert output.is_file(), "output file was not created" + + mdata = mu.read_h5mu(output) + assert list(mdata.mod.keys()) == ["rna"], "Expected modality rna" + adata = mdata.mod["rna"] + + assert adata.X.dtype.kind == "f" + expected_obs_keys = [ + "AreaUm", + "Area", + "Tile", + "WellLabel", + "Well", + "Cell", + "NuclearAreaUm", + "NuclearArea", + ] + assert all([obs in expected_obs_keys for obs in adata.obs.columns]) + obs_counts = ["Area", "Cell", "NuclearArea"] + assert all([adata.obs[obs].dtype.kind == "u" for obs in obs_counts]) + obs_areas = ["AreaUm", "NuclearAreaUm"] + assert all([adata.obs[obs].dtype.kind == "f" for obs in obs_areas]) + obs_categories = ["Tile", "WellLabel", "Well"] + assert all([adata.obs[obs].dtype.kind == "O" for obs in obs_categories]) + + expected_obsm_keys = ["spatial", "spatial_um"] + assert list(adata.obsm.keys()) == expected_obsm_keys + assert list(adata.uns.keys()) == expected_obsm_keys + assert all(adata.obsm[obsm].dtype.kind == "f" for obsm in expected_obsm_keys) + + def test_extended_parameters(run_component, tmp_path): output = tmp_path / "aviti_ext.h5mu" diff --git a/src/convert/from_cosmx_to_h5mu/config.vsh.yaml b/src/convert/from_cosmx_to_h5mu/config.vsh.yaml index cf88726..6318fc3 100644 --- a/src/convert/from_cosmx_to_h5mu/config.vsh.yaml +++ b/src/convert/from_cosmx_to_h5mu/config.vsh.yaml @@ -40,6 +40,8 @@ resources: - type: python_script path: script.py - path: /src/utils/setup_logger.py + - path: /src/utils/unzip_archived_folder.py + test_resources: - type: python_script path: test.py @@ -53,7 +55,11 @@ engines: - procps - type: python __merge__: [/src/base/requirements/anndata_mudata.yaml, /src/base/requirements/squidpy.yaml] - __merge__: [ /src/base/requirements/python_test_setup.yaml, .] + test_setup: + - type: apt + packages: + - zip + __merge__: [ /src/base/requirements/python_test_setup.yaml, . ] runners: - type: executable - type: nextflow diff --git a/src/convert/from_cosmx_to_h5mu/script.py b/src/convert/from_cosmx_to_h5mu/script.py index f65f5e1..50f9583 100644 --- a/src/convert/from_cosmx_to_h5mu/script.py +++ b/src/convert/from_cosmx_to_h5mu/script.py @@ -2,11 +2,12 @@ import sys import os import squidpy as sq import mudata as mu -import glob +import zipfile +from pathlib import Path ## VIASH START par = { - "input": "./resources_test/cosmx/Lung5_Rep2_tiny", + "input": "./resources_test/cosmx/Lung5_Rep2_tiny.zip", "output": "./resources_test/cosmx/Lung5_Rep2_tiny.h5mu", "modality": "rna", "output_compression": None, @@ -16,28 +17,57 @@ meta = {"resources_dir": "src/utils"} sys.path.append(meta["resources_dir"]) from setup_logger import setup_logger +from unzip_archived_folder import extract_selected_files_from_zip logger = setup_logger() -def find_matrix_file(suffix): - pattern = os.path.join(par["input"], f"*{suffix}") - files = glob.glob(pattern) - assert len(files) == 1, ( - f"Only one file matching pattern {pattern} should be present" +def retrieve_input_data(cosmx_output_bundle): + # Expected folder structure (showing only relevant files): + # ├── *_exprMat_file.csv + # ├── *_fov_positions_file.csv + # └── *_metadata_file.csv + + required_file_patterns = { + "counts_file": "**/*exprMat_file.csv", + "fov_file": "**/*fov_positions_file.csv", + "meta_file": "**/*metadata_file.csv", + } + if zipfile.is_zipfile(cosmx_output_bundle): + cosmx_output_bundle = extract_selected_files_from_zip( + cosmx_output_bundle, members=required_file_patterns.values() + ) + else: + cosmx_output_bundle = Path(cosmx_output_bundle) + + assert os.path.isdir(cosmx_output_bundle), ( + "Input is expected to be a (compressed) directory." ) - return files[0] + + input_data = {} + for key, pattern in required_file_patterns.items(): + file = list(cosmx_output_bundle.glob(pattern)) + assert len(file) == 1, f"Expected one file for {key}, found {len(file)}." + input_data[key] = file[0] + + return input_data -counts_file = find_matrix_file("exprMat_file.csv") -fov_file = find_matrix_file("fov_positions_file.csv") -meta_file = find_matrix_file("metadata_file.csv") +def main(): + logger.info("Reading in CosMx data...") + input_data = retrieve_input_data(par["input"]) -logger.info("Reading in CosMx data...") -adata = sq.read.nanostring( - path=par["input"], counts_file=counts_file, meta_file=meta_file, fov_file=fov_file -) + adata = sq.read.nanostring( + path=par["input"], + counts_file=input_data["counts_file"], + meta_file=input_data["meta_file"], + fov_file=input_data["fov_file"], + ) -logger.info("Writing output MuData object...") -mdata = mu.MuData({par["modality"]: adata}) -mdata.write_h5mu(par["output"], compression=par["output_compression"]) + logger.info("Writing output MuData object...") + mdata = mu.MuData({par["modality"]: adata}) + mdata.write_h5mu(par["output"], compression=par["output_compression"]) + + +if __name__ == "__main__": + main() diff --git a/src/convert/from_cosmx_to_h5mu/test.py b/src/convert/from_cosmx_to_h5mu/test.py index 002c956..07ed10f 100644 --- a/src/convert/from_cosmx_to_h5mu/test.py +++ b/src/convert/from_cosmx_to_h5mu/test.py @@ -1,6 +1,7 @@ import pytest import sys import mudata as mu +import subprocess def test_simple_execution(run_component, tmp_path): @@ -53,5 +54,62 @@ def test_simple_execution(run_component, tmp_path): assert adata.obsm["spatial_fov"].dtype == "float" +def test_compressed_input(run_component, tmp_path): + output = tmp_path / "cosmx_tiny.h5mu" + zipped_input = tmp_path / "Lung5_Rep2_tiny.zip" + + subprocess.run( + ["zip", "-r", str(zipped_input), "Lung5_Rep2_tiny"], + cwd=meta["resources_dir"], + check=True, + ) + + run_component( + [ + "--input", + zipped_input, + "--dataset_id", + "Lung5_Rep2", + "--num_fovs", + "2", + "--output", + output, + ] + ) + assert output.is_file(), "output file was not created" + + mdata = mu.read_h5mu(output) + assert list(mdata.mod.keys()) == ["rna"], "Expected modality rna" + + adata = mdata.mod["rna"] + + assert list(adata.obs.keys()) == [ + "fov", + "Area", + "AspectRatio", + "CenterX_global_px", + "CenterY_global_px", + "Width", + "Height", + "Mean.MembraneStain", + "Max.MembraneStain", + "Mean.PanCK", + "Max.PanCK", + "Mean.CD45", + "Max.CD45", + "Mean.CD3", + "Max.CD3", + "Mean.DAPI", + "Max.DAPI", + "cell_ID", + ] + + assert list(adata.uns.keys()) == ["spatial"] + assert list(adata.obsm.keys()) == ["spatial", "spatial_fov"] + + assert adata.obsm["spatial"].dtype == "int" + assert adata.obsm["spatial_fov"].dtype == "float" + + if __name__ == "__main__": sys.exit(pytest.main([__file__])) diff --git a/src/convert/from_cosmx_to_spatialexperiment/config.vsh.yaml b/src/convert/from_cosmx_to_spatialexperiment/config.vsh.yaml index 12a2ca9..45a3ce4 100644 --- a/src/convert/from_cosmx_to_spatialexperiment/config.vsh.yaml +++ b/src/convert/from_cosmx_to_spatialexperiment/config.vsh.yaml @@ -58,6 +58,7 @@ arguments: resources: - type: r_script path: script.R + - path: /src/utils/unzip_archived_folder.R test_resources: - type: r_script path: test.R diff --git a/src/convert/from_cosmx_to_spatialexperiment/script.R b/src/convert/from_cosmx_to_spatialexperiment/script.R index e6734e1..7cb6ba4 100644 --- a/src/convert/from_cosmx_to_spatialexperiment/script.R +++ b/src/convert/from_cosmx_to_spatialexperiment/script.R @@ -2,7 +2,7 @@ library(SpatialExperimentIO) ### VIASH START par <- list( - input = "resources_test/cosmx/Lung5_Rep2_tiny", + input = "resources_test/cosmx/test2.zip", add_tx_path = TRUE, add_polygon_path = FALSE, add_fov_positions = TRUE, @@ -11,16 +11,41 @@ par <- list( ), output = "spe_cosmx_test.rds" ) +meta <- list( + resources_dir = "src/utils/" +) ### VIASH END +source(paste0(meta$resources_dir, "/unzip_archived_folder.R")) + +cat("Reading input data...") +if (tools::file_ext(par$input) == "zip") { + expected_file_patterns <- c( + "*.csv", + "*.parquet" + ) + tmp_dir <- extract_selected_files( + par$input, + members = expected_file_patterns + ) + cosmx_output_bundle <- file.path( + tmp_dir, + tools::file_path_sans_ext(basename(par$input)) + ) +} else { + cosmx_output_bundle <- par$input +} + +cat("Setting parameters...") if (par$add_polygon_path == FALSE && par$add_tx_path == FALSE) { add_parquet_paths <- FALSE } else { add_parquet_paths <- TRUE } +cat("Converting to SpatialExperiment...") spe <- readCosmxSXE( - dirName = par$input, + dirName = cosmx_output_bundle, returnType = "SPE", countMatPattern = "exprMat_file.csv", metaDataPattern = "metadata_file.csv", @@ -33,4 +58,5 @@ spe <- readCosmxSXE( altExps = par$alternative_experiment_features ) +cat("Saving output...") saveRDS(spe, file = par$output) diff --git a/src/convert/from_cosmx_to_spatialexperiment/test.R b/src/convert/from_cosmx_to_spatialexperiment/test.R index bbdd5b3..3424487 100644 --- a/src/convert/from_cosmx_to_spatialexperiment/test.R +++ b/src/convert/from_cosmx_to_spatialexperiment/test.R @@ -69,6 +69,74 @@ dim_input <- dim(input) expect_equal(dim_rds, dim_input) +cat("> Checking execution with compressed input\n") + +spe <- paste0(meta[["resources_dir"]], "/Lung5_Rep2_tiny") +out_rds <- "output.rds" + +create_folder_archive <- function( + folder_path, + archive = "Lung5_Rep2_tiny.zip") { + old_wd <- getwd() + on.exit(setwd(old_wd)) + setwd(meta$resources_dir) + system2("zip", c("-r", archive, "Lung5_Rep2_tiny")) + paste0(meta$resources_dir, "/", archive) +} + +zipped_spe <- create_folder_archive(spe) + +cat("> Running ", meta[["name"]], "\n", sep = "") +out <- processx::run( + meta[["executable"]], + c( + "--input", zipped_spe, + "--add_tx_path", TRUE, + "--add_polygon_path", FALSE, + "--output", out_rds + ) +) + +cat("> Checking whether output file exists\n") +expect_equal(out$status, 0) +expect_true(file.exists(out_rds)) + +cat("> Reading output file\n") +obj <- readRDS(file = out_rds) + +cat("> Checking whether Seurat object is in the right format\n") +# Object type +expect_is(obj, "SpatialExperiment") +# Assay structure +expect_equal(names(slot(obj, "assays")), "counts") +# Spatial coordinates +expect_equal( + spatialCoordsNames(obj), + c("CenterX_global_px", "CenterY_global_px") +) +# Alternative experiments +expect_equal(altExpNames(obj), c("NegPrb")) +# Metadata components +expect_named( + metadata(obj), + c("fov_positions", "transcripts"), + ignore.order = TRUE +) +# Parquet paths +expect_true(grepl("\\.parquet$", metadata(obj)[["transcripts"]])) +# Dimensions +input <- readCosmxSXE( + dirName = spe, + addParquetPaths = FALSE, + returnType = "SPE" +) + +dim_rds <- dim(obj) +dim_input <- dim(input) + +expect_equal(dim_rds, dim_input) + + cat("> Checking parameter functionality\n") out_rds_ext <- "output_ext.rds" diff --git a/src/convert/from_xenium_to_h5mu/config.vsh.yaml b/src/convert/from_xenium_to_h5mu/config.vsh.yaml index e47c056..96aa1c4 100644 --- a/src/convert/from_xenium_to_h5mu/config.vsh.yaml +++ b/src/convert/from_xenium_to_h5mu/config.vsh.yaml @@ -44,6 +44,7 @@ resources: - type: python_script path: script.py - path: /src/utils/setup_logger.py + - path: /src/utils/unzip_archived_folder.py test_resources: - type: python_script path: test.py @@ -60,6 +61,9 @@ engines: packages: - pyarrow test_setup: + - type: apt + packages: + - zip - type: python __merge__: [ /src/base/requirements/viashpy.yaml, .] runners: diff --git a/src/convert/from_xenium_to_h5mu/script.py b/src/convert/from_xenium_to_h5mu/script.py index aaf79cb..7053a88 100644 --- a/src/convert/from_xenium_to_h5mu/script.py +++ b/src/convert/from_xenium_to_h5mu/script.py @@ -3,11 +3,13 @@ from pathlib import Path import scanpy as sc import pandas as pd import mudata as mu +import zipfile import json +import os ## VIASH START par = { - "input": "./resources_test/xenium/xenium_tiny", + "input": "test/xenium_tiny.zip", "output": "xenium_tiny_test.h5mu", "output_compression": "gzip", "obsm_coordinates": "spatial", @@ -19,21 +21,46 @@ meta = {"resources_dir": "src/utils"} sys.path.append(meta["resources_dir"]) from setup_logger import setup_logger +from unzip_archived_folder import extract_selected_files_from_zip logger = setup_logger() -# Expected folder structure (showing only relevant files): -# ├── cell_feature_matrix.h5 -# ├── cells.parquet -# ├── experiment.xenium -# └── metrics_summary.csv -input_dir = Path(par["input"]) -input_data = { - "count_matrix": input_dir / "cell_feature_matrix.h5", - "cells_metadata": input_dir / "cells.parquet", - "experiment": input_dir / "experiment.xenium", - "metrics_summary": input_dir / "metrics_summary.csv", -} + +def _retrieve_input_data(xenium_output_bundle): + # Expected folder structure (showing only relevant files): + # ├── cell_feature_matrix.h5 + # ├── cells.parquet + # ├── experiment.xenium + # └── metrics_summary.csv + + required_file_patterns = { + "count_matrix": "**/cell_feature_matrix.h5", + "cells_metadata": "**/cells.parquet", + "experiment": "**/experiment.xenium", + "metrics_summary": "**/metrics_summary.csv", + } + + if zipfile.is_zipfile(xenium_output_bundle): + xenium_output_bundle = extract_selected_files_from_zip( + xenium_output_bundle, + members=[pattern for pattern in required_file_patterns.values()], + ) + else: + xenium_output_bundle = Path(xenium_output_bundle) + + assert os.path.isdir(xenium_output_bundle), ( + "Input is expected to be a (compressed) directory." + ) + + input_data = {} + for key, pattern in required_file_patterns.items(): + file = list(xenium_output_bundle.glob(pattern)) + assert len(file) == 1, ( + f"Expected exactly one file matching pattern {pattern}, found {len(file)}." + ) + input_data[key] = file[0] + + return input_data def _format_cell_id_column(cell_id_column: pd.Series) -> pd.Series: @@ -46,9 +73,7 @@ def _format_cell_id_column(cell_id_column: pd.Series) -> pd.Series: # Read data from Xenium output bundle logger.info("Reading input data...") -assert all([file.exists() for file in input_data.values()]), ( - f"Not all required input files are found. Make sure that {par['input']} contains {input_data.values()}." -) +input_data = _retrieve_input_data(par["input"]) adata = sc.read_10x_h5(input_data["count_matrix"]) metadata = pd.read_parquet(input_data["cells_metadata"], engine="pyarrow") diff --git a/src/convert/from_xenium_to_h5mu/test.py b/src/convert/from_xenium_to_h5mu/test.py index bae39bb..f4389d1 100644 --- a/src/convert/from_xenium_to_h5mu/test.py +++ b/src/convert/from_xenium_to_h5mu/test.py @@ -1,12 +1,13 @@ import pytest import sys +import subprocess import mudata as mu ## VIASH START meta = { - "executable": "./target/executable/convert/from_cellranger_multi_to_h5mu/from_cellranger_multi_to_h5mu", + "executable": "./target/executable/convert/from_xenium_to_h5mu/from_xenium_to_h5mu", "resources_dir": "resources_test/", - "config": "src/convert/from_cellranger_multi_to_h5mu/config.vsh.yaml", + "config": "src/convert/from_xenium_to_h5mu/config.vsh.yaml", } ## VIASH END @@ -62,6 +63,69 @@ def test_simple_execution(run_component, tmp_path): assert all([adata.obs[obs].dtype == "float" for obs in obs_areas]) +def test_compressed_input(run_component, tmp_path): + output = tmp_path / "xenium.h5mu" + zipped_input = tmp_path / "xenium_tiny.zip" + + subprocess.run( + ["zip", "-r", str(zipped_input), "xenium_tiny"], + cwd=meta["resources_dir"], + check=True, + ) + + # run component + run_component( + [ + "--input", + zipped_input, + "--output", + str(output), + "--output_compression", + "gzip", + ] + ) + + assert output.is_file(), "output file was not created" + + mdata = mu.read_h5mu(output) + assert list(mdata.mod.keys()) == ["rna"], "Expected modality rna" + adata = mdata.mod["rna"] + + assert list(adata.obs.keys()) == [ + "transcript_counts", + "control_probe_counts", + "genomic_control_counts", + "control_codeword_counts", + "unassigned_codeword_counts", + "deprecated_codeword_counts", + "total_counts", + "cell_area", + "nucleus_area", + "nucleus_count", + "segmentation_method", + ] + + assert list(adata.uns.keys()) == ["xenium_experiment", "xenium_metrics"] + assert list(adata.obsm.keys()) == ["spatial"] + assert list(adata.var.keys()) == ["gene_ids", "feature_types", "genome"] + + assert adata.X.dtype.kind == "f" + assert all(adata.var["feature_types"] == "Gene Expression") + assert adata.obsm["spatial"].dtype == "float" + obs_counts = [ + "transcript_counts", + "control_probe_counts", + "genomic_control_counts", + "unassigned_codeword_counts", + "deprecated_codeword_counts", + "total_counts", + "nucleus_count", + ] + assert all([adata.obs[obs].dtype == "int" for obs in obs_counts]) + obs_areas = ["cell_area", "nucleus_area"] + assert all([adata.obs[obs].dtype == "float" for obs in obs_areas]) + + def test_rename_fields(run_component, tmp_path): output = tmp_path / "xenium.h5mu" diff --git a/src/convert/from_xenium_to_spatialdata/config.vsh.yaml b/src/convert/from_xenium_to_spatialdata/config.vsh.yaml index 9160bec..2c04882 100644 --- a/src/convert/from_xenium_to_spatialdata/config.vsh.yaml +++ b/src/convert/from_xenium_to_spatialdata/config.vsh.yaml @@ -78,6 +78,7 @@ resources: - type: python_script path: script.py - path: /src/utils/setup_logger.py + - path: /src/utils/unzip_archived_folder.py test_resources: - type: python_script path: test.py @@ -91,6 +92,10 @@ engines: - procps - type: python __merge__: [ /src/base/requirements/spatialdata-io.yaml ] + test_setup: + - type: apt + packages: + - zip __merge__: [ /src/base/requirements/python_test_setup.yaml, .] runners: - type: executable diff --git a/src/convert/from_xenium_to_spatialdata/script.py b/src/convert/from_xenium_to_spatialdata/script.py index bc287b6..adc9b4c 100644 --- a/src/convert/from_xenium_to_spatialdata/script.py +++ b/src/convert/from_xenium_to_spatialdata/script.py @@ -1,9 +1,11 @@ import sys from spatialdata_io import xenium +import zipfile +from pathlib import Path ## VIASH START par = { - "input": "./resources_test/xenium_tiny", + "input": "xenium_tiny.zip", "output": "./test/xenium_tiny.zarr", "cells_boundaries": True, "nucleus_boundaries": True, @@ -22,12 +24,29 @@ meta = {"resources_dir": "src/utils"} sys.path.append(meta["resources_dir"]) from setup_logger import setup_logger +from unzip_archived_folder import unzip_archived_folder logger = setup_logger() logger.info("Reading in Xenium data...") + +if zipfile.is_zipfile(par["input"]): + required_file_patterns = [ + "**/experiment.xenium", + "**/nucleus_boundaries.parquet", + "**/cell_boundaries.parquet", + "**/transcripts.parquet", + "**/cell_feature_matrix.h5", + "**/cells.parquet", + "**/morphology_mip.ome.tif", + "**/morphology_focus.ome.tif", + ] + xenium_output_bundle = unzip_archived_folder(par["input"]) +else: + xenium_output_bundle = Path(par["input"]) + sdata = xenium( - par["input"], + xenium_output_bundle, cells_boundaries=par["cells_boundaries"], nucleus_boundaries=par["nucleus_boundaries"], cells_as_circles=par["cells_as_circles"], diff --git a/src/convert/from_xenium_to_spatialdata/test.py b/src/convert/from_xenium_to_spatialdata/test.py index c7cf3c1..c9e090e 100644 --- a/src/convert/from_xenium_to_spatialdata/test.py +++ b/src/convert/from_xenium_to_spatialdata/test.py @@ -2,6 +2,7 @@ import pytest import os import sys import spatialdata as sd +import subprocess def test_simple_execution(run_component, tmp_path): @@ -31,5 +32,38 @@ def test_simple_execution(run_component, tmp_path): assert (output_sd_path / "zmetadata").is_file(), "zmetadata file was not created" +def test_compressed_input(run_component, tmp_path): + output_sd_path = tmp_path / "sd" + zipped_input = tmp_path / "xenium_tiny.zip" + + subprocess.run( + ["zip", "-r", str(zipped_input), "xenium_tiny"], + cwd=meta["resources_dir"], + check=True, + ) + run_component( + [ + "--input", + zipped_input, + "--output", + output_sd_path, + ] + ) + + assert os.path.exists(output_sd_path), "Output zarr folder was not created" + + sdata = sd.read_zarr(output_sd_path) + assert isinstance(sdata, sd.SpatialData), ( + "the generated output is not a SpatialData object" + ) + + assert os.path.exists(output_sd_path / "images"), "images folder was not created" + assert os.path.exists(output_sd_path / "labels"), "labels folder was not created" + assert os.path.exists(output_sd_path / "points"), "images folder was not created" + assert os.path.exists(output_sd_path / "shapes"), "shapes folder was not created" + assert os.path.exists(output_sd_path / "tables"), "tables folder was not created" + assert (output_sd_path / "zmetadata").is_file(), "zmetadata file was not created" + + if __name__ == "__main__": sys.exit(pytest.main([__file__])) diff --git a/src/convert/from_xenium_to_spatialexperiment/config.vsh.yaml b/src/convert/from_xenium_to_spatialexperiment/config.vsh.yaml index 2c6d45d..569a44c 100644 --- a/src/convert/from_xenium_to_spatialexperiment/config.vsh.yaml +++ b/src/convert/from_xenium_to_spatialexperiment/config.vsh.yaml @@ -51,6 +51,7 @@ arguments: resources: - type: r_script path: script.R + - path: /src/utils/unzip_archived_folder.R test_resources: - type: r_script path: test.R diff --git a/src/convert/from_xenium_to_spatialexperiment/script.R b/src/convert/from_xenium_to_spatialexperiment/script.R index 4060410..16cf22f 100644 --- a/src/convert/from_xenium_to_spatialexperiment/script.R +++ b/src/convert/from_xenium_to_spatialexperiment/script.R @@ -2,7 +2,7 @@ library(SpatialExperimentIO) ### VIASH START par <- list( - input = "resources_test/xenium/xenium_tiny", + input = "resources_test/xenium/temp_dir.zip", add_experiment_xenium = TRUE, add_parquet_paths = TRUE, alternative_experiment_features = c( @@ -11,11 +11,35 @@ par <- list( ), output = "spe_test.rds" ) +meta <- list( + resources_dir = "src/utils/" +) ### VIASH END +source(paste0(meta$resources_dir, "/unzip_archived_folder.R")) +cat("Reading input data...") +if (tools::file_ext(par$input) == "zip") { + required_file_patterns <- c( + "**/cell_feature_matrix.h5", + "**/*.parquet", + "**/experiment.xenium" + ) + tmp_dir <- extract_selected_files( + par$input, + members = required_file_patterns + ) + xenium_output_bundle <- file.path( + tmp_dir, + tools::file_path_sans_ext(basename(par$input)) + ) +} else { + xenium_output_bundle <- par$input +} + +cat("Converting to SpatialExperiment") spe <- readXeniumSXE( - dirName = par$input, + dirName = xenium_output_bundle, returnType = "SPE", countMatPattern = "cell_feature_matrix.h5", metaDataPattern = "cells.parquet", @@ -25,4 +49,5 @@ spe <- readXeniumSXE( altExps = par$alternative_experiment_features ) +cat("Saving output...") saveRDS(spe, file = par$output) diff --git a/src/convert/from_xenium_to_spatialexperiment/test.R b/src/convert/from_xenium_to_spatialexperiment/test.R index 22f1095..539e31c 100644 --- a/src/convert/from_xenium_to_spatialexperiment/test.R +++ b/src/convert/from_xenium_to_spatialexperiment/test.R @@ -71,6 +71,77 @@ dim_input <- dim(input) expect_equal(dim_rds, dim_input) +cat("> Checking execution with compressed input\n") + +spe <- paste0( + meta[["resources_dir"]], + "/xenium_tiny" +) +out_rds <- "output.rds" + +create_folder_archive <- function(folder_path, archive = "xenium_tiny.zip") { + old_wd <- getwd() + on.exit(setwd(old_wd)) + setwd(meta$resources_dir) + system2("zip", c("-r", archive, "xenium_tiny")) + paste0(meta$resources_dir, "/", archive) +} + +zipped_spe <- create_folder_archive(spe) + +cat("> Running ", meta[["name"]], "\n", sep = "") +out <- processx::run( + meta[["executable"]], + c( + "--input", zipped_spe, + "--output", out_rds + ) +) + +cat("> Checking whether output file exists\n") +expect_equal(out$status, 0) +expect_true(file.exists(out_rds)) + +cat("> Reading output file\n") +obj <- readRDS(file = out_rds) + +cat("> Checking whether Seurat object is in the right format\n") +# Object type +expect_is(obj, "SpatialExperiment") +# Assay structure +expect_equal(names(slot(obj, "assays")), "counts") +# Spatial coordinates +expect_equal(spatialCoordsNames(obj), c("x_centroid", "y_centroid")) +# Alternative experiments +expect_equal( + altExpNames(obj), + c("NegControlProbe", "UnassignedCodeword", "NegControlCodeword") +) +# Metadata components +metadata_components <- c( + "experiment.xenium", "transcripts", "cell_boundaries", "nucleus_boundaries" +) +expect_named( + metadata(obj), + metadata_components, + ignore.order = TRUE +) +# Parquet paths +parquet_components <- c("transcripts", "cell_boundaries", "nucleus_boundaries") +for (component in parquet_components) { + expect_true(grepl("\\.parquet$", metadata(obj)[[component]])) +} +# Dimensions +input <- readXeniumSXE( + dirName = spe, + returnType = "SPE" +) +dim_rds <- dim(obj) +dim_input <- dim(input) + +expect_equal(dim_rds, dim_input) + + cat("> Checking parameter functionality\n") out_rds_ext <- "output_ext.rds" diff --git a/src/utils/unzip_archived_folder.R b/src/utils/unzip_archived_folder.R new file mode 100644 index 0000000..78966bc --- /dev/null +++ b/src/utils/unzip_archived_folder.R @@ -0,0 +1,22 @@ +extract_selected_files <- function(zip_path, members) { + # Create a temporary directory for extraction + temp_dir <- tempfile("unzip_dir_") + dir.create(temp_dir) + + # List all files in the archive + all_files <- utils::unzip(zip_path, list = TRUE)$Name + + # Find files matching any of the glob patterns in 'members' + selected <- unique(unlist( + lapply(members, function(pattern) { + regex <- glob2rx(pattern) + grep(regex, all_files, value = TRUE) + }) + )) + + # Extract only the selected files + utils::unzip(zip_path, files = selected, exdir = temp_dir) + + # Return the path to the extracted folder + file.path(temp_dir) +} diff --git a/src/utils/unzip_archived_folder.py b/src/utils/unzip_archived_folder.py new file mode 100644 index 0000000..1e5bf39 --- /dev/null +++ b/src/utils/unzip_archived_folder.py @@ -0,0 +1,50 @@ +import fnmatch +import zipfile +import tempfile +from pathlib import Path +from typing import Union + + +def unzip_archived_folder(archived_folder: Union[str, Path]) -> Union[str, Path]: + """ + Extracts a ZIP archive to a temporary directory and returns the path to the extracted folder. + + Args: + zip_path (Union[str, Path]): Path to the ZIP archive. + + Returns: + extracted_path (Union[str, Path]): Path to the extracted folder inside the temporary directory. + """ + + temp_dir = Path(tempfile.TemporaryDirectory().name) + with zipfile.ZipFile(archived_folder, "r") as archive: + archive.extractall(temp_dir) + + return temp_dir / Path(archived_folder).stem + + +def extract_selected_files_from_zip( + zip_path: Union[str, Path], members: list[Union[str, Path]] +) -> Union[str, Path]: + """ + Extracts selected files (supports glob patterns) from a ZIP archive to a temporary directory. + + Args: + zip_path (Union[str, Path]): Path to the ZIP archive. + members (list[str]): List of file paths within the archive to extract. + + Returns: + Path: Path to the extraction directory. + """ + + temp_dir = Path(tempfile.TemporaryDirectory().name) + + with zipfile.ZipFile(zip_path, "r") as archive: + all_files = archive.namelist() + selected = set() + for pattern in members: + selected.update(fnmatch.filter(all_files, str(pattern))) + for member in selected: + archive.extract(member, temp_dir) + + return temp_dir diff --git a/target/executable/convert/from_cells2stats_to_h5mu/.config.vsh.yaml b/target/executable/convert/from_cells2stats_to_h5mu/.config.vsh.yaml index f3181f1..a272f39 100644 --- a/target/executable/convert/from_cells2stats_to_h5mu/.config.vsh.yaml +++ b/target/executable/convert/from_cells2stats_to_h5mu/.config.vsh.yaml @@ -1,6 +1,6 @@ name: "from_cells2stats_to_h5mu" namespace: "convert" -version: "v0.1.0" +version: "v0.1.1" authors: - name: "Dorien Roosen" roles: @@ -150,6 +150,8 @@ resources: is_executable: true - type: "file" path: "setup_logger.py" +- type: "file" + path: "unzip_archived_folder.py" - type: "file" path: "nextflow_labels.config" dest: "nextflow_labels.config" @@ -256,7 +258,7 @@ engines: id: "docker" image: "python:3.13-slim" target_registry: "images.viash-hub.com" - target_tag: "v0.1.0" + target_tag: "v0.1.1" namespace_separator: "/" setup: - type: "apt" @@ -274,6 +276,10 @@ engines: nelse: exit(1)\")" upgrade: true test_setup: + - type: "apt" + packages: + - "zip" + interactive: false - type: "python" user: false packages: @@ -290,11 +296,11 @@ build_info: output: "target/executable/convert/from_cells2stats_to_h5mu" executable: "target/executable/convert/from_cells2stats_to_h5mu/from_cells2stats_to_h5mu" viash_version: "0.9.4" - git_commit: "3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36" + git_commit: "2baba3fff0930478c26da7e1bd99b29ee5dd83b3" git_remote: "https://github.com/openpipelines-bio/openpipeline_spatial" package_config: name: "openpipeline_spatial" - version: "v0.1.0" + version: "v0.1.1" info: test_resources: - type: "s3" @@ -314,7 +320,7 @@ package_config: )'" - ".engines += { type: \"native\" }" - ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'" - - ".engines[.type == 'docker'].target_tag := 'v0.1.0'" + - ".engines[.type == 'docker'].target_tag := 'v0.1.1'" organization: "vsh" links: repository: "https://github.com/openpipelines-bio/openpipeline_spatial" diff --git a/target/executable/convert/from_cells2stats_to_h5mu/from_cells2stats_to_h5mu b/target/executable/convert/from_cells2stats_to_h5mu/from_cells2stats_to_h5mu index 2265062..b8273a6 100755 --- a/target/executable/convert/from_cells2stats_to_h5mu/from_cells2stats_to_h5mu +++ b/target/executable/convert/from_cells2stats_to_h5mu/from_cells2stats_to_h5mu @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# from_cells2stats_to_h5mu v0.1.0 +# from_cells2stats_to_h5mu v0.1.1 # # This wrapper script is auto-generated by viash 0.9.4 and is thus a derivative # work thereof. This software comes with ABSOLUTELY NO WARRANTY from Data @@ -458,10 +458,10 @@ RUN pip install --upgrade pip && \ LABEL org.opencontainers.image.authors="Dorien Roosen" LABEL org.opencontainers.image.description="Companion container for running component convert from_cells2stats_to_h5mu" -LABEL org.opencontainers.image.created="2025-08-25T12:22:56Z" +LABEL org.opencontainers.image.created="2025-10-02T08:36:59Z" LABEL org.opencontainers.image.source="https://github.com/openpipelines-bio/openpipeline_spatial" -LABEL org.opencontainers.image.revision="3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36" -LABEL org.opencontainers.image.version="v0.1.0" +LABEL org.opencontainers.image.revision="2baba3fff0930478c26da7e1bd99b29ee5dd83b3" +LABEL org.opencontainers.image.version="v0.1.1" VIASHDOCKER fi @@ -578,7 +578,7 @@ VIASH_DOCKER_RUN_ARGS=(-i --rm) # ViashHelp: Display helpful explanation about this executable function ViashHelp { - echo "from_cells2stats_to_h5mu v0.1.0" + echo "from_cells2stats_to_h5mu v0.1.1" echo "" echo "Convert spatial data resulting from Aviti Teton sequencers that have been" echo "processed by the Element Biosciences cells2stats workflow to H5MU format." @@ -722,7 +722,7 @@ while [[ $# -gt 0 ]]; do shift 1 ;; --version) - echo "from_cells2stats_to_h5mu v0.1.0" + echo "from_cells2stats_to_h5mu v0.1.1" exit ;; --input) @@ -923,7 +923,7 @@ if [[ "$VIASH_ENGINE_TYPE" == "docker" ]]; then # determine docker image id if [[ "$VIASH_ENGINE_ID" == 'docker' ]]; then - VIASH_DOCKER_IMAGE_ID='images.viash-hub.com/vsh/openpipeline_spatial/convert/from_cells2stats_to_h5mu:v0.1.0' + VIASH_DOCKER_IMAGE_ID='images.viash-hub.com/vsh/openpipeline_spatial/convert/from_cells2stats_to_h5mu:v0.1.1' fi # print dockerfile @@ -1242,6 +1242,8 @@ import mudata as mu import anndata as ad import re import json +import zipfile +import os ## VIASH START # The following code has been auto-generated by Viash. @@ -1285,6 +1287,8 @@ dep = { sys.path.append(meta["resources_dir"]) from setup_logger import setup_logger +from unzip_archived_folder import extract_selected_files_from_zip + logger = setup_logger() @@ -1422,27 +1426,43 @@ def categorize_columns(column_list, target_panel): ) -def main(): - # Read data from Aviti Teton output bundle +def retrieve_input_data(cells2stats_output_bundle): # Expected folder structure (showing only relevant files): # ├── Cytoprofiling/ # │ └── Instrument/ # │ └── RawCellStats.parquet # └── Panel.json - logger.info("Reading input data...") - input_dir = Path(par["input"]) - input_data = { - "count_matrix": input_dir - / "Cytoprofiling" - / "Instrument" - / "RawCellStats.parquet", - "target_panel": input_dir / "Panel.json", + required_file_patterns = { + "target_panel": "**/Panel.json", + "count_matrix": "**/Cytoprofiling/Instrument/RawCellStats.parquet", } - assert all([file.exists() for file in input_data.values()]), ( - f"Not all required input files are found. Make sure that {par['input']} contains {input_data.values()}." + if zipfile.is_zipfile(cells2stats_output_bundle): + cells2stats_output_bundle = extract_selected_files_from_zip( + cells2stats_output_bundle, members=required_file_patterns.values() + ) + else: + cells2stats_output_bundle = Path(cells2stats_output_bundle) + + assert os.path.isdir(cells2stats_output_bundle), ( + "Input is expected to be a (compressed) directory." ) + + input_data = {} + for key, pattern in required_file_patterns.items(): + file = list(cells2stats_output_bundle.glob(pattern)) + assert len(file) == 1, ( + f"Expected exactly one file matching pattern {pattern}, found {len(file)}." + ) + input_data[key] = file[0] + + return input_data + + +def main(): + logger.info("Reading input data...") + input_data = retrieve_input_data(par["input"]) with open(input_data["target_panel"], "r") as f: target_panel = json.load(f) df = pd.read_parquet(input_data["count_matrix"], engine="pyarrow") diff --git a/target/executable/convert/from_cells2stats_to_h5mu/unzip_archived_folder.py b/target/executable/convert/from_cells2stats_to_h5mu/unzip_archived_folder.py new file mode 100644 index 0000000..1e5bf39 --- /dev/null +++ b/target/executable/convert/from_cells2stats_to_h5mu/unzip_archived_folder.py @@ -0,0 +1,50 @@ +import fnmatch +import zipfile +import tempfile +from pathlib import Path +from typing import Union + + +def unzip_archived_folder(archived_folder: Union[str, Path]) -> Union[str, Path]: + """ + Extracts a ZIP archive to a temporary directory and returns the path to the extracted folder. + + Args: + zip_path (Union[str, Path]): Path to the ZIP archive. + + Returns: + extracted_path (Union[str, Path]): Path to the extracted folder inside the temporary directory. + """ + + temp_dir = Path(tempfile.TemporaryDirectory().name) + with zipfile.ZipFile(archived_folder, "r") as archive: + archive.extractall(temp_dir) + + return temp_dir / Path(archived_folder).stem + + +def extract_selected_files_from_zip( + zip_path: Union[str, Path], members: list[Union[str, Path]] +) -> Union[str, Path]: + """ + Extracts selected files (supports glob patterns) from a ZIP archive to a temporary directory. + + Args: + zip_path (Union[str, Path]): Path to the ZIP archive. + members (list[str]): List of file paths within the archive to extract. + + Returns: + Path: Path to the extraction directory. + """ + + temp_dir = Path(tempfile.TemporaryDirectory().name) + + with zipfile.ZipFile(zip_path, "r") as archive: + all_files = archive.namelist() + selected = set() + for pattern in members: + selected.update(fnmatch.filter(all_files, str(pattern))) + for member in selected: + archive.extract(member, temp_dir) + + return temp_dir diff --git a/target/executable/convert/from_cosmx_to_h5mu/.config.vsh.yaml b/target/executable/convert/from_cosmx_to_h5mu/.config.vsh.yaml index 5c1ee07..b73eabb 100644 --- a/target/executable/convert/from_cosmx_to_h5mu/.config.vsh.yaml +++ b/target/executable/convert/from_cosmx_to_h5mu/.config.vsh.yaml @@ -1,6 +1,6 @@ name: "from_cosmx_to_h5mu" namespace: "convert" -version: "v0.1.0" +version: "v0.1.1" authors: - name: "Dorien Roosen" roles: @@ -81,6 +81,8 @@ resources: is_executable: true - type: "file" path: "setup_logger.py" +- type: "file" + path: "unzip_archived_folder.py" - type: "file" path: "nextflow_labels.config" dest: "nextflow_labels.config" @@ -184,7 +186,7 @@ engines: id: "docker" image: "python:3.12-slim" target_registry: "images.viash-hub.com" - target_tag: "v0.1.0" + target_tag: "v0.1.1" namespace_separator: "/" setup: - type: "apt" @@ -214,6 +216,10 @@ engines: github: - "openpipelines-bio/core#subdirectory=packages/python/openpipeline_testutils" upgrade: true + - type: "apt" + packages: + - "zip" + interactive: false entrypoint: [] cmd: null - type: "native" @@ -225,11 +231,11 @@ build_info: output: "target/executable/convert/from_cosmx_to_h5mu" executable: "target/executable/convert/from_cosmx_to_h5mu/from_cosmx_to_h5mu" viash_version: "0.9.4" - git_commit: "3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36" + git_commit: "2baba3fff0930478c26da7e1bd99b29ee5dd83b3" git_remote: "https://github.com/openpipelines-bio/openpipeline_spatial" package_config: name: "openpipeline_spatial" - version: "v0.1.0" + version: "v0.1.1" info: test_resources: - type: "s3" @@ -249,7 +255,7 @@ package_config: )'" - ".engines += { type: \"native\" }" - ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'" - - ".engines[.type == 'docker'].target_tag := 'v0.1.0'" + - ".engines[.type == 'docker'].target_tag := 'v0.1.1'" organization: "vsh" links: repository: "https://github.com/openpipelines-bio/openpipeline_spatial" diff --git a/target/executable/convert/from_cosmx_to_h5mu/from_cosmx_to_h5mu b/target/executable/convert/from_cosmx_to_h5mu/from_cosmx_to_h5mu index 676779c..077b995 100755 --- a/target/executable/convert/from_cosmx_to_h5mu/from_cosmx_to_h5mu +++ b/target/executable/convert/from_cosmx_to_h5mu/from_cosmx_to_h5mu @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# from_cosmx_to_h5mu v0.1.0 +# from_cosmx_to_h5mu v0.1.1 # # This wrapper script is auto-generated by viash 0.9.4 and is thus a derivative # work thereof. This software comes with ABSOLUTELY NO WARRANTY from Data @@ -459,10 +459,10 @@ RUN pip install --upgrade pip && \ LABEL org.opencontainers.image.authors="Dorien Roosen, Weiwei Schultz" LABEL org.opencontainers.image.description="Companion container for running component convert from_cosmx_to_h5mu" -LABEL org.opencontainers.image.created="2025-08-25T12:22:57Z" +LABEL org.opencontainers.image.created="2025-10-02T08:37:01Z" LABEL org.opencontainers.image.source="https://github.com/openpipelines-bio/openpipeline_spatial" -LABEL org.opencontainers.image.revision="3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36" -LABEL org.opencontainers.image.version="v0.1.0" +LABEL org.opencontainers.image.revision="2baba3fff0930478c26da7e1bd99b29ee5dd83b3" +LABEL org.opencontainers.image.version="v0.1.1" VIASHDOCKER fi @@ -579,7 +579,7 @@ VIASH_DOCKER_RUN_ARGS=(-i --rm) # ViashHelp: Display helpful explanation about this executable function ViashHelp { - echo "from_cosmx_to_h5mu v0.1.0" + echo "from_cosmx_to_h5mu v0.1.1" echo "" echo "Converts the output from NanoString experiment into a MuData objcet." echo " - \`_exprMat_file.csv\`: File containing the counts." @@ -658,7 +658,7 @@ while [[ $# -gt 0 ]]; do shift 1 ;; --version) - echo "from_cosmx_to_h5mu v0.1.0" + echo "from_cosmx_to_h5mu v0.1.1" exit ;; --input) @@ -805,7 +805,7 @@ if [[ "$VIASH_ENGINE_TYPE" == "docker" ]]; then # determine docker image id if [[ "$VIASH_ENGINE_ID" == 'docker' ]]; then - VIASH_DOCKER_IMAGE_ID='images.viash-hub.com/vsh/openpipeline_spatial/convert/from_cosmx_to_h5mu:v0.1.0' + VIASH_DOCKER_IMAGE_ID='images.viash-hub.com/vsh/openpipeline_spatial/convert/from_cosmx_to_h5mu:v0.1.1' fi # print dockerfile @@ -1113,7 +1113,8 @@ import sys import os import squidpy as sq import mudata as mu -import glob +import zipfile +from pathlib import Path ## VIASH START # The following code has been auto-generated by Viash. @@ -1151,31 +1152,60 @@ dep = { sys.path.append(meta["resources_dir"]) from setup_logger import setup_logger +from unzip_archived_folder import extract_selected_files_from_zip logger = setup_logger() -def find_matrix_file(suffix): - pattern = os.path.join(par["input"], f"*{suffix}") - files = glob.glob(pattern) - assert len(files) == 1, ( - f"Only one file matching pattern {pattern} should be present" +def retrieve_input_data(cosmx_output_bundle): + # Expected folder structure (showing only relevant files): + # ├── *_exprMat_file.csv + # ├── *_fov_positions_file.csv + # └── *_metadata_file.csv + + required_file_patterns = { + "counts_file": "**/*exprMat_file.csv", + "fov_file": "**/*fov_positions_file.csv", + "meta_file": "**/*metadata_file.csv", + } + if zipfile.is_zipfile(cosmx_output_bundle): + cosmx_output_bundle = extract_selected_files_from_zip( + cosmx_output_bundle, members=required_file_patterns.values() + ) + else: + cosmx_output_bundle = Path(cosmx_output_bundle) + + assert os.path.isdir(cosmx_output_bundle), ( + "Input is expected to be a (compressed) directory." ) - return files[0] + + input_data = {} + for key, pattern in required_file_patterns.items(): + file = list(cosmx_output_bundle.glob(pattern)) + assert len(file) == 1, f"Expected one file for {key}, found {len(file)}." + input_data[key] = file[0] + + return input_data -counts_file = find_matrix_file("exprMat_file.csv") -fov_file = find_matrix_file("fov_positions_file.csv") -meta_file = find_matrix_file("metadata_file.csv") +def main(): + logger.info("Reading in CosMx data...") + input_data = retrieve_input_data(par["input"]) -logger.info("Reading in CosMx data...") -adata = sq.read.nanostring( - path=par["input"], counts_file=counts_file, meta_file=meta_file, fov_file=fov_file -) + adata = sq.read.nanostring( + path=par["input"], + counts_file=input_data["counts_file"], + meta_file=input_data["meta_file"], + fov_file=input_data["fov_file"], + ) -logger.info("Writing output MuData object...") -mdata = mu.MuData({par["modality"]: adata}) -mdata.write_h5mu(par["output"], compression=par["output_compression"]) + logger.info("Writing output MuData object...") + mdata = mu.MuData({par["modality"]: adata}) + mdata.write_h5mu(par["output"], compression=par["output_compression"]) + + +if __name__ == "__main__": + main() VIASHMAIN python -B "\$tempscript" & wait "\$!" diff --git a/target/executable/convert/from_cosmx_to_h5mu/unzip_archived_folder.py b/target/executable/convert/from_cosmx_to_h5mu/unzip_archived_folder.py new file mode 100644 index 0000000..1e5bf39 --- /dev/null +++ b/target/executable/convert/from_cosmx_to_h5mu/unzip_archived_folder.py @@ -0,0 +1,50 @@ +import fnmatch +import zipfile +import tempfile +from pathlib import Path +from typing import Union + + +def unzip_archived_folder(archived_folder: Union[str, Path]) -> Union[str, Path]: + """ + Extracts a ZIP archive to a temporary directory and returns the path to the extracted folder. + + Args: + zip_path (Union[str, Path]): Path to the ZIP archive. + + Returns: + extracted_path (Union[str, Path]): Path to the extracted folder inside the temporary directory. + """ + + temp_dir = Path(tempfile.TemporaryDirectory().name) + with zipfile.ZipFile(archived_folder, "r") as archive: + archive.extractall(temp_dir) + + return temp_dir / Path(archived_folder).stem + + +def extract_selected_files_from_zip( + zip_path: Union[str, Path], members: list[Union[str, Path]] +) -> Union[str, Path]: + """ + Extracts selected files (supports glob patterns) from a ZIP archive to a temporary directory. + + Args: + zip_path (Union[str, Path]): Path to the ZIP archive. + members (list[str]): List of file paths within the archive to extract. + + Returns: + Path: Path to the extraction directory. + """ + + temp_dir = Path(tempfile.TemporaryDirectory().name) + + with zipfile.ZipFile(zip_path, "r") as archive: + all_files = archive.namelist() + selected = set() + for pattern in members: + selected.update(fnmatch.filter(all_files, str(pattern))) + for member in selected: + archive.extract(member, temp_dir) + + return temp_dir diff --git a/target/executable/convert/from_cosmx_to_spatialexperiment/.config.vsh.yaml b/target/executable/convert/from_cosmx_to_spatialexperiment/.config.vsh.yaml index 45550df..8d8f6fc 100644 --- a/target/executable/convert/from_cosmx_to_spatialexperiment/.config.vsh.yaml +++ b/target/executable/convert/from_cosmx_to_spatialexperiment/.config.vsh.yaml @@ -1,6 +1,6 @@ name: "from_cosmx_to_spatialexperiment" namespace: "convert" -version: "v0.1.0" +version: "v0.1.1" authors: - name: "Dorien Roosen" roles: @@ -98,6 +98,8 @@ resources: - type: "r_script" path: "script.R" is_executable: true +- type: "file" + path: "unzip_archived_folder.R" - type: "file" path: "nextflow_labels.config" dest: "nextflow_labels.config" @@ -202,7 +204,7 @@ engines: id: "docker" image: "rocker/r2u:24.04" target_registry: "images.viash-hub.com" - target_tag: "v0.1.0" + target_tag: "v0.1.1" namespace_separator: "/" setup: - type: "apt" @@ -232,11 +234,11 @@ build_info: output: "target/executable/convert/from_cosmx_to_spatialexperiment" executable: "target/executable/convert/from_cosmx_to_spatialexperiment/from_cosmx_to_spatialexperiment" viash_version: "0.9.4" - git_commit: "3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36" + git_commit: "2baba3fff0930478c26da7e1bd99b29ee5dd83b3" git_remote: "https://github.com/openpipelines-bio/openpipeline_spatial" package_config: name: "openpipeline_spatial" - version: "v0.1.0" + version: "v0.1.1" info: test_resources: - type: "s3" @@ -256,7 +258,7 @@ package_config: )'" - ".engines += { type: \"native\" }" - ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'" - - ".engines[.type == 'docker'].target_tag := 'v0.1.0'" + - ".engines[.type == 'docker'].target_tag := 'v0.1.1'" organization: "vsh" links: repository: "https://github.com/openpipelines-bio/openpipeline_spatial" diff --git a/target/executable/convert/from_cosmx_to_spatialexperiment/from_cosmx_to_spatialexperiment b/target/executable/convert/from_cosmx_to_spatialexperiment/from_cosmx_to_spatialexperiment index f405ae7..be493d1 100755 --- a/target/executable/convert/from_cosmx_to_spatialexperiment/from_cosmx_to_spatialexperiment +++ b/target/executable/convert/from_cosmx_to_spatialexperiment/from_cosmx_to_spatialexperiment @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# from_cosmx_to_spatialexperiment v0.1.0 +# from_cosmx_to_spatialexperiment v0.1.1 # # This wrapper script is auto-generated by viash 0.9.4 and is thus a derivative # work thereof. This software comes with ABSOLUTELY NO WARRANTY from Data @@ -457,10 +457,10 @@ RUN Rscript -e 'options(warn = 2); if (!requireNamespace("BiocManager", quietly LABEL org.opencontainers.image.authors="Dorien Roosen" LABEL org.opencontainers.image.description="Companion container for running component convert from_cosmx_to_spatialexperiment" -LABEL org.opencontainers.image.created="2025-08-25T12:22:56Z" +LABEL org.opencontainers.image.created="2025-10-02T08:37:00Z" LABEL org.opencontainers.image.source="https://github.com/openpipelines-bio/openpipeline_spatial" -LABEL org.opencontainers.image.revision="3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36" -LABEL org.opencontainers.image.version="v0.1.0" +LABEL org.opencontainers.image.revision="2baba3fff0930478c26da7e1bd99b29ee5dd83b3" +LABEL org.opencontainers.image.version="v0.1.1" VIASHDOCKER fi @@ -577,7 +577,7 @@ VIASH_DOCKER_RUN_ARGS=(-i --rm) # ViashHelp: Display helpful explanation about this executable function ViashHelp { - echo "from_cosmx_to_spatialexperiment v0.1.0" + echo "from_cosmx_to_spatialexperiment v0.1.1" echo "" echo "Creates a SpatialExperiment object from the downloaded unzipped CosMx directory" echo "for Nanostring" @@ -678,7 +678,7 @@ while [[ $# -gt 0 ]]; do shift 1 ;; --version) - echo "from_cosmx_to_spatialexperiment v0.1.0" + echo "from_cosmx_to_spatialexperiment v0.1.1" exit ;; --input) @@ -853,7 +853,7 @@ if [[ "$VIASH_ENGINE_TYPE" == "docker" ]]; then # determine docker image id if [[ "$VIASH_ENGINE_ID" == 'docker' ]]; then - VIASH_DOCKER_IMAGE_ID='images.viash-hub.com/vsh/openpipeline_spatial/convert/from_cosmx_to_spatialexperiment:v0.1.0' + VIASH_DOCKER_IMAGE_ID='images.viash-hub.com/vsh/openpipeline_spatial/convert/from_cosmx_to_spatialexperiment:v0.1.1' fi # print dockerfile @@ -1221,14 +1221,36 @@ rm(.viash_orig_warn) ### VIASH END +source(paste0(meta\$resources_dir, "/unzip_archived_folder.R")) + +cat("Reading input data...") +if (tools::file_ext(par\$input) == "zip") { + expected_file_patterns <- c( + "*.csv", + "*.parquet" + ) + tmp_dir <- extract_selected_files( + par\$input, + members = expected_file_patterns + ) + cosmx_output_bundle <- file.path( + tmp_dir, + tools::file_path_sans_ext(basename(par\$input)) + ) +} else { + cosmx_output_bundle <- par\$input +} + +cat("Setting parameters...") if (par\$add_polygon_path == FALSE && par\$add_tx_path == FALSE) { add_parquet_paths <- FALSE } else { add_parquet_paths <- TRUE } +cat("Converting to SpatialExperiment...") spe <- readCosmxSXE( - dirName = par\$input, + dirName = cosmx_output_bundle, returnType = "SPE", countMatPattern = "exprMat_file.csv", metaDataPattern = "metadata_file.csv", @@ -1241,6 +1263,7 @@ spe <- readCosmxSXE( altExps = par\$alternative_experiment_features ) +cat("Saving output...") saveRDS(spe, file = par\$output) VIASHMAIN Rscript "\$tempscript" & diff --git a/target/executable/convert/from_cosmx_to_spatialexperiment/unzip_archived_folder.R b/target/executable/convert/from_cosmx_to_spatialexperiment/unzip_archived_folder.R new file mode 100644 index 0000000..78966bc --- /dev/null +++ b/target/executable/convert/from_cosmx_to_spatialexperiment/unzip_archived_folder.R @@ -0,0 +1,22 @@ +extract_selected_files <- function(zip_path, members) { + # Create a temporary directory for extraction + temp_dir <- tempfile("unzip_dir_") + dir.create(temp_dir) + + # List all files in the archive + all_files <- utils::unzip(zip_path, list = TRUE)$Name + + # Find files matching any of the glob patterns in 'members' + selected <- unique(unlist( + lapply(members, function(pattern) { + regex <- glob2rx(pattern) + grep(regex, all_files, value = TRUE) + }) + )) + + # Extract only the selected files + utils::unzip(zip_path, files = selected, exdir = temp_dir) + + # Return the path to the extracted folder + file.path(temp_dir) +} diff --git a/target/executable/convert/from_h5mu_to_spatialexperiment/.config.vsh.yaml b/target/executable/convert/from_h5mu_to_spatialexperiment/.config.vsh.yaml index 5a18b92..27d0ce4 100644 --- a/target/executable/convert/from_h5mu_to_spatialexperiment/.config.vsh.yaml +++ b/target/executable/convert/from_h5mu_to_spatialexperiment/.config.vsh.yaml @@ -1,6 +1,6 @@ name: "from_h5mu_to_spatialexperiment" namespace: "convert" -version: "v0.1.0" +version: "v0.1.1" authors: - name: "Dorien Roosen" roles: @@ -171,7 +171,7 @@ engines: id: "docker" image: "rocker/r2u:22.04" target_registry: "images.viash-hub.com" - target_tag: "v0.1.0" + target_tag: "v0.1.1" namespace_separator: "/" setup: - type: "apt" @@ -224,11 +224,11 @@ build_info: output: "target/executable/convert/from_h5mu_to_spatialexperiment" executable: "target/executable/convert/from_h5mu_to_spatialexperiment/from_h5mu_to_spatialexperiment" viash_version: "0.9.4" - git_commit: "3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36" + git_commit: "2baba3fff0930478c26da7e1bd99b29ee5dd83b3" git_remote: "https://github.com/openpipelines-bio/openpipeline_spatial" package_config: name: "openpipeline_spatial" - version: "v0.1.0" + version: "v0.1.1" info: test_resources: - type: "s3" @@ -248,7 +248,7 @@ package_config: )'" - ".engines += { type: \"native\" }" - ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'" - - ".engines[.type == 'docker'].target_tag := 'v0.1.0'" + - ".engines[.type == 'docker'].target_tag := 'v0.1.1'" organization: "vsh" links: repository: "https://github.com/openpipelines-bio/openpipeline_spatial" diff --git a/target/executable/convert/from_h5mu_to_spatialexperiment/from_h5mu_to_spatialexperiment b/target/executable/convert/from_h5mu_to_spatialexperiment/from_h5mu_to_spatialexperiment index acb7748..e07b77a 100755 --- a/target/executable/convert/from_h5mu_to_spatialexperiment/from_h5mu_to_spatialexperiment +++ b/target/executable/convert/from_h5mu_to_spatialexperiment/from_h5mu_to_spatialexperiment @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# from_h5mu_to_spatialexperiment v0.1.0 +# from_h5mu_to_spatialexperiment v0.1.1 # # This wrapper script is auto-generated by viash 0.9.4 and is thus a derivative # work thereof. This software comes with ABSOLUTELY NO WARRANTY from Data @@ -458,10 +458,10 @@ RUN Rscript -e 'options(warn = 2); if (!requireNamespace("remotes", quietly = TR LABEL org.opencontainers.image.authors="Dorien Roosen" LABEL org.opencontainers.image.description="Companion container for running component convert from_h5mu_to_spatialexperiment" -LABEL org.opencontainers.image.created="2025-08-25T12:22:57Z" +LABEL org.opencontainers.image.created="2025-10-02T08:37:00Z" LABEL org.opencontainers.image.source="https://github.com/openpipelines-bio/openpipeline_spatial" -LABEL org.opencontainers.image.revision="3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36" -LABEL org.opencontainers.image.version="v0.1.0" +LABEL org.opencontainers.image.revision="2baba3fff0930478c26da7e1bd99b29ee5dd83b3" +LABEL org.opencontainers.image.version="v0.1.1" VIASHDOCKER fi @@ -578,7 +578,7 @@ VIASH_DOCKER_RUN_ARGS=(-i --rm) # ViashHelp: Display helpful explanation about this executable function ViashHelp { - echo "from_h5mu_to_spatialexperiment v0.1.0" + echo "from_h5mu_to_spatialexperiment v0.1.1" echo "" echo "Converts an h5mu file into a SpatialExperiment object." echo "" @@ -650,7 +650,7 @@ while [[ $# -gt 0 ]]; do shift 1 ;; --version) - echo "from_h5mu_to_spatialexperiment v0.1.0" + echo "from_h5mu_to_spatialexperiment v0.1.1" exit ;; --input) @@ -797,7 +797,7 @@ if [[ "$VIASH_ENGINE_TYPE" == "docker" ]]; then # determine docker image id if [[ "$VIASH_ENGINE_ID" == 'docker' ]]; then - VIASH_DOCKER_IMAGE_ID='images.viash-hub.com/vsh/openpipeline_spatial/convert/from_h5mu_to_spatialexperiment:v0.1.0' + VIASH_DOCKER_IMAGE_ID='images.viash-hub.com/vsh/openpipeline_spatial/convert/from_h5mu_to_spatialexperiment:v0.1.1' fi # print dockerfile diff --git a/target/executable/convert/from_spatialdata_to_h5mu/.config.vsh.yaml b/target/executable/convert/from_spatialdata_to_h5mu/.config.vsh.yaml index 1e911ee..31edcc7 100644 --- a/target/executable/convert/from_spatialdata_to_h5mu/.config.vsh.yaml +++ b/target/executable/convert/from_spatialdata_to_h5mu/.config.vsh.yaml @@ -1,6 +1,6 @@ name: "from_spatialdata_to_h5mu" namespace: "convert" -version: "v0.1.0" +version: "v0.1.1" authors: - name: "Dorien Roosen" roles: @@ -180,7 +180,7 @@ engines: id: "docker" image: "python:3.12-slim" target_registry: "images.viash-hub.com" - target_tag: "v0.1.0" + target_tag: "v0.1.1" namespace_separator: "/" setup: - type: "apt" @@ -220,11 +220,11 @@ build_info: output: "target/executable/convert/from_spatialdata_to_h5mu" executable: "target/executable/convert/from_spatialdata_to_h5mu/from_spatialdata_to_h5mu" viash_version: "0.9.4" - git_commit: "3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36" + git_commit: "2baba3fff0930478c26da7e1bd99b29ee5dd83b3" git_remote: "https://github.com/openpipelines-bio/openpipeline_spatial" package_config: name: "openpipeline_spatial" - version: "v0.1.0" + version: "v0.1.1" info: test_resources: - type: "s3" @@ -244,7 +244,7 @@ package_config: )'" - ".engines += { type: \"native\" }" - ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'" - - ".engines[.type == 'docker'].target_tag := 'v0.1.0'" + - ".engines[.type == 'docker'].target_tag := 'v0.1.1'" organization: "vsh" links: repository: "https://github.com/openpipelines-bio/openpipeline_spatial" diff --git a/target/executable/convert/from_spatialdata_to_h5mu/from_spatialdata_to_h5mu b/target/executable/convert/from_spatialdata_to_h5mu/from_spatialdata_to_h5mu index d6922a1..d0a89cd 100755 --- a/target/executable/convert/from_spatialdata_to_h5mu/from_spatialdata_to_h5mu +++ b/target/executable/convert/from_spatialdata_to_h5mu/from_spatialdata_to_h5mu @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# from_spatialdata_to_h5mu v0.1.0 +# from_spatialdata_to_h5mu v0.1.1 # # This wrapper script is auto-generated by viash 0.9.4 and is thus a derivative # work thereof. This software comes with ABSOLUTELY NO WARRANTY from Data @@ -459,10 +459,10 @@ RUN pip install --upgrade pip && \ LABEL org.opencontainers.image.authors="Dorien Roosen, Weiwei Schultz" LABEL org.opencontainers.image.description="Companion container for running component convert from_spatialdata_to_h5mu" -LABEL org.opencontainers.image.created="2025-08-25T12:22:56Z" +LABEL org.opencontainers.image.created="2025-10-02T08:37:00Z" LABEL org.opencontainers.image.source="https://github.com/openpipelines-bio/openpipeline_spatial" -LABEL org.opencontainers.image.revision="3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36" -LABEL org.opencontainers.image.version="v0.1.0" +LABEL org.opencontainers.image.revision="2baba3fff0930478c26da7e1bd99b29ee5dd83b3" +LABEL org.opencontainers.image.version="v0.1.1" VIASHDOCKER fi @@ -579,7 +579,7 @@ VIASH_DOCKER_RUN_ARGS=(-i --rm) # ViashHelp: Display helpful explanation about this executable function ViashHelp { - echo "from_spatialdata_to_h5mu v0.1.0" + echo "from_spatialdata_to_h5mu v0.1.1" echo "" echo "Reads in the Tables field stored in a SpatialData object and converts it to an" echo "h5mu file." @@ -651,7 +651,7 @@ while [[ $# -gt 0 ]]; do shift 1 ;; --version) - echo "from_spatialdata_to_h5mu v0.1.0" + echo "from_spatialdata_to_h5mu v0.1.1" exit ;; --input) @@ -798,7 +798,7 @@ if [[ "$VIASH_ENGINE_TYPE" == "docker" ]]; then # determine docker image id if [[ "$VIASH_ENGINE_ID" == 'docker' ]]; then - VIASH_DOCKER_IMAGE_ID='images.viash-hub.com/vsh/openpipeline_spatial/convert/from_spatialdata_to_h5mu:v0.1.0' + VIASH_DOCKER_IMAGE_ID='images.viash-hub.com/vsh/openpipeline_spatial/convert/from_spatialdata_to_h5mu:v0.1.1' fi # print dockerfile diff --git a/target/executable/convert/from_xenium_to_h5mu/.config.vsh.yaml b/target/executable/convert/from_xenium_to_h5mu/.config.vsh.yaml index 9c71ca9..3dae8b1 100644 --- a/target/executable/convert/from_xenium_to_h5mu/.config.vsh.yaml +++ b/target/executable/convert/from_xenium_to_h5mu/.config.vsh.yaml @@ -1,6 +1,6 @@ name: "from_xenium_to_h5mu" namespace: "convert" -version: "v0.1.0" +version: "v0.1.1" authors: - name: "Dorien Roosen" roles: @@ -97,6 +97,8 @@ resources: is_executable: true - type: "file" path: "setup_logger.py" +- type: "file" + path: "unzip_archived_folder.py" - type: "file" path: "nextflow_labels.config" dest: "nextflow_labels.config" @@ -198,7 +200,7 @@ engines: id: "docker" image: "python:3.12-slim" target_registry: "images.viash-hub.com" - target_tag: "v0.1.0" + target_tag: "v0.1.1" namespace_separator: "/" setup: - type: "apt" @@ -217,6 +219,10 @@ engines: nelse: exit(1)\")" upgrade: true test_setup: + - type: "apt" + packages: + - "zip" + interactive: false - type: "python" user: false packages: @@ -233,11 +239,11 @@ build_info: output: "target/executable/convert/from_xenium_to_h5mu" executable: "target/executable/convert/from_xenium_to_h5mu/from_xenium_to_h5mu" viash_version: "0.9.4" - git_commit: "3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36" + git_commit: "2baba3fff0930478c26da7e1bd99b29ee5dd83b3" git_remote: "https://github.com/openpipelines-bio/openpipeline_spatial" package_config: name: "openpipeline_spatial" - version: "v0.1.0" + version: "v0.1.1" info: test_resources: - type: "s3" @@ -257,7 +263,7 @@ package_config: )'" - ".engines += { type: \"native\" }" - ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'" - - ".engines[.type == 'docker'].target_tag := 'v0.1.0'" + - ".engines[.type == 'docker'].target_tag := 'v0.1.1'" organization: "vsh" links: repository: "https://github.com/openpipelines-bio/openpipeline_spatial" diff --git a/target/executable/convert/from_xenium_to_h5mu/from_xenium_to_h5mu b/target/executable/convert/from_xenium_to_h5mu/from_xenium_to_h5mu index 6c2e854..2f54325 100755 --- a/target/executable/convert/from_xenium_to_h5mu/from_xenium_to_h5mu +++ b/target/executable/convert/from_xenium_to_h5mu/from_xenium_to_h5mu @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# from_xenium_to_h5mu v0.1.0 +# from_xenium_to_h5mu v0.1.1 # # This wrapper script is auto-generated by viash 0.9.4 and is thus a derivative # work thereof. This software comes with ABSOLUTELY NO WARRANTY from Data @@ -458,10 +458,10 @@ RUN pip install --upgrade pip && \ LABEL org.opencontainers.image.authors="Dorien Roosen" LABEL org.opencontainers.image.description="Companion container for running component convert from_xenium_to_h5mu" -LABEL org.opencontainers.image.created="2025-08-25T12:22:56Z" +LABEL org.opencontainers.image.created="2025-10-02T08:37:00Z" LABEL org.opencontainers.image.source="https://github.com/openpipelines-bio/openpipeline_spatial" -LABEL org.opencontainers.image.revision="3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36" -LABEL org.opencontainers.image.version="v0.1.0" +LABEL org.opencontainers.image.revision="2baba3fff0930478c26da7e1bd99b29ee5dd83b3" +LABEL org.opencontainers.image.version="v0.1.1" VIASHDOCKER fi @@ -578,7 +578,7 @@ VIASH_DOCKER_RUN_ARGS=(-i --rm) # ViashHelp: Display helpful explanation about this executable function ViashHelp { - echo "from_xenium_to_h5mu v0.1.0" + echo "from_xenium_to_h5mu v0.1.1" echo "" echo "Converts the output from Xenium to a single .h5mu file, where the count matrix" echo "is written to the \`rna\` modality." @@ -670,7 +670,7 @@ while [[ $# -gt 0 ]]; do shift 1 ;; --version) - echo "from_xenium_to_h5mu v0.1.0" + echo "from_xenium_to_h5mu v0.1.1" exit ;; --input) @@ -839,7 +839,7 @@ if [[ "$VIASH_ENGINE_TYPE" == "docker" ]]; then # determine docker image id if [[ "$VIASH_ENGINE_ID" == 'docker' ]]; then - VIASH_DOCKER_IMAGE_ID='images.viash-hub.com/vsh/openpipeline_spatial/convert/from_xenium_to_h5mu:v0.1.0' + VIASH_DOCKER_IMAGE_ID='images.viash-hub.com/vsh/openpipeline_spatial/convert/from_xenium_to_h5mu:v0.1.1' fi # print dockerfile @@ -1154,7 +1154,9 @@ from pathlib import Path import scanpy as sc import pandas as pd import mudata as mu +import zipfile import json +import os ## VIASH START # The following code has been auto-generated by Viash. @@ -1194,21 +1196,46 @@ dep = { sys.path.append(meta["resources_dir"]) from setup_logger import setup_logger +from unzip_archived_folder import extract_selected_files_from_zip logger = setup_logger() -# Expected folder structure (showing only relevant files): -# ├── cell_feature_matrix.h5 -# ├── cells.parquet -# ├── experiment.xenium -# └── metrics_summary.csv -input_dir = Path(par["input"]) -input_data = { - "count_matrix": input_dir / "cell_feature_matrix.h5", - "cells_metadata": input_dir / "cells.parquet", - "experiment": input_dir / "experiment.xenium", - "metrics_summary": input_dir / "metrics_summary.csv", -} + +def _retrieve_input_data(xenium_output_bundle): + # Expected folder structure (showing only relevant files): + # ├── cell_feature_matrix.h5 + # ├── cells.parquet + # ├── experiment.xenium + # └── metrics_summary.csv + + required_file_patterns = { + "count_matrix": "**/cell_feature_matrix.h5", + "cells_metadata": "**/cells.parquet", + "experiment": "**/experiment.xenium", + "metrics_summary": "**/metrics_summary.csv", + } + + if zipfile.is_zipfile(xenium_output_bundle): + xenium_output_bundle = extract_selected_files_from_zip( + xenium_output_bundle, + members=[pattern for pattern in required_file_patterns.values()], + ) + else: + xenium_output_bundle = Path(xenium_output_bundle) + + assert os.path.isdir(xenium_output_bundle), ( + "Input is expected to be a (compressed) directory." + ) + + input_data = {} + for key, pattern in required_file_patterns.items(): + file = list(xenium_output_bundle.glob(pattern)) + assert len(file) == 1, ( + f"Expected exactly one file matching pattern {pattern}, found {len(file)}." + ) + input_data[key] = file[0] + + return input_data def _format_cell_id_column(cell_id_column: pd.Series) -> pd.Series: @@ -1221,9 +1248,7 @@ def _format_cell_id_column(cell_id_column: pd.Series) -> pd.Series: # Read data from Xenium output bundle logger.info("Reading input data...") -assert all([file.exists() for file in input_data.values()]), ( - f"Not all required input files are found. Make sure that {par['input']} contains {input_data.values()}." -) +input_data = _retrieve_input_data(par["input"]) adata = sc.read_10x_h5(input_data["count_matrix"]) metadata = pd.read_parquet(input_data["cells_metadata"], engine="pyarrow") diff --git a/target/executable/convert/from_xenium_to_h5mu/unzip_archived_folder.py b/target/executable/convert/from_xenium_to_h5mu/unzip_archived_folder.py new file mode 100644 index 0000000..1e5bf39 --- /dev/null +++ b/target/executable/convert/from_xenium_to_h5mu/unzip_archived_folder.py @@ -0,0 +1,50 @@ +import fnmatch +import zipfile +import tempfile +from pathlib import Path +from typing import Union + + +def unzip_archived_folder(archived_folder: Union[str, Path]) -> Union[str, Path]: + """ + Extracts a ZIP archive to a temporary directory and returns the path to the extracted folder. + + Args: + zip_path (Union[str, Path]): Path to the ZIP archive. + + Returns: + extracted_path (Union[str, Path]): Path to the extracted folder inside the temporary directory. + """ + + temp_dir = Path(tempfile.TemporaryDirectory().name) + with zipfile.ZipFile(archived_folder, "r") as archive: + archive.extractall(temp_dir) + + return temp_dir / Path(archived_folder).stem + + +def extract_selected_files_from_zip( + zip_path: Union[str, Path], members: list[Union[str, Path]] +) -> Union[str, Path]: + """ + Extracts selected files (supports glob patterns) from a ZIP archive to a temporary directory. + + Args: + zip_path (Union[str, Path]): Path to the ZIP archive. + members (list[str]): List of file paths within the archive to extract. + + Returns: + Path: Path to the extraction directory. + """ + + temp_dir = Path(tempfile.TemporaryDirectory().name) + + with zipfile.ZipFile(zip_path, "r") as archive: + all_files = archive.namelist() + selected = set() + for pattern in members: + selected.update(fnmatch.filter(all_files, str(pattern))) + for member in selected: + archive.extract(member, temp_dir) + + return temp_dir diff --git a/target/executable/convert/from_xenium_to_spatialdata/.config.vsh.yaml b/target/executable/convert/from_xenium_to_spatialdata/.config.vsh.yaml index 8391477..61737f0 100644 --- a/target/executable/convert/from_xenium_to_spatialdata/.config.vsh.yaml +++ b/target/executable/convert/from_xenium_to_spatialdata/.config.vsh.yaml @@ -1,6 +1,6 @@ name: "from_xenium_to_spatialdata" namespace: "convert" -version: "v0.1.0" +version: "v0.1.1" authors: - name: "Dorien Roosen" roles: @@ -173,6 +173,8 @@ resources: is_executable: true - type: "file" path: "setup_logger.py" +- type: "file" + path: "unzip_archived_folder.py" - type: "file" path: "nextflow_labels.config" dest: "nextflow_labels.config" @@ -278,7 +280,7 @@ engines: id: "docker" image: "python:3.12-slim" target_registry: "images.viash-hub.com" - target_tag: "v0.1.0" + target_tag: "v0.1.1" namespace_separator: "/" setup: - type: "apt" @@ -303,6 +305,10 @@ engines: github: - "openpipelines-bio/core#subdirectory=packages/python/openpipeline_testutils" upgrade: true + - type: "apt" + packages: + - "zip" + interactive: false entrypoint: [] cmd: null - type: "native" @@ -314,11 +320,11 @@ build_info: output: "target/executable/convert/from_xenium_to_spatialdata" executable: "target/executable/convert/from_xenium_to_spatialdata/from_xenium_to_spatialdata" viash_version: "0.9.4" - git_commit: "3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36" + git_commit: "2baba3fff0930478c26da7e1bd99b29ee5dd83b3" git_remote: "https://github.com/openpipelines-bio/openpipeline_spatial" package_config: name: "openpipeline_spatial" - version: "v0.1.0" + version: "v0.1.1" info: test_resources: - type: "s3" @@ -338,7 +344,7 @@ package_config: )'" - ".engines += { type: \"native\" }" - ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'" - - ".engines[.type == 'docker'].target_tag := 'v0.1.0'" + - ".engines[.type == 'docker'].target_tag := 'v0.1.1'" organization: "vsh" links: repository: "https://github.com/openpipelines-bio/openpipeline_spatial" diff --git a/target/executable/convert/from_xenium_to_spatialdata/from_xenium_to_spatialdata b/target/executable/convert/from_xenium_to_spatialdata/from_xenium_to_spatialdata index 52e0171..9af340c 100755 --- a/target/executable/convert/from_xenium_to_spatialdata/from_xenium_to_spatialdata +++ b/target/executable/convert/from_xenium_to_spatialdata/from_xenium_to_spatialdata @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# from_xenium_to_spatialdata v0.1.0 +# from_xenium_to_spatialdata v0.1.1 # # This wrapper script is auto-generated by viash 0.9.4 and is thus a derivative # work thereof. This software comes with ABSOLUTELY NO WARRANTY from Data @@ -458,10 +458,10 @@ RUN pip install --upgrade pip && \ LABEL org.opencontainers.image.authors="Dorien Roosen, Weiwei Schultz" LABEL org.opencontainers.image.description="Companion container for running component convert from_xenium_to_spatialdata" -LABEL org.opencontainers.image.created="2025-08-25T12:22:57Z" +LABEL org.opencontainers.image.created="2025-10-02T08:36:59Z" LABEL org.opencontainers.image.source="https://github.com/openpipelines-bio/openpipeline_spatial" -LABEL org.opencontainers.image.revision="3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36" -LABEL org.opencontainers.image.version="v0.1.0" +LABEL org.opencontainers.image.revision="2baba3fff0930478c26da7e1bd99b29ee5dd83b3" +LABEL org.opencontainers.image.version="v0.1.1" VIASHDOCKER fi @@ -578,7 +578,7 @@ VIASH_DOCKER_RUN_ARGS=(-i --rm) # ViashHelp: Display helpful explanation about this executable function ViashHelp { - echo "from_xenium_to_spatialdata v0.1.0" + echo "from_xenium_to_spatialdata v0.1.1" echo "" echo "Converts the output from 10X Genomics Xenium dataset into a SpatialData objcet." echo "By default, the following files will be converted:" @@ -711,7 +711,7 @@ while [[ $# -gt 0 ]]; do shift 1 ;; --version) - echo "from_xenium_to_spatialdata v0.1.0" + echo "from_xenium_to_spatialdata v0.1.1" exit ;; --input) @@ -951,7 +951,7 @@ if [[ "$VIASH_ENGINE_TYPE" == "docker" ]]; then # determine docker image id if [[ "$VIASH_ENGINE_ID" == 'docker' ]]; then - VIASH_DOCKER_IMAGE_ID='images.viash-hub.com/vsh/openpipeline_spatial/convert/from_xenium_to_spatialdata:v0.1.0' + VIASH_DOCKER_IMAGE_ID='images.viash-hub.com/vsh/openpipeline_spatial/convert/from_xenium_to_spatialdata:v0.1.1' fi # print dockerfile @@ -1340,6 +1340,8 @@ trap interrupt INT SIGINT cat > "\$tempscript" << 'VIASHMAIN' import sys from spatialdata_io import xenium +import zipfile +from pathlib import Path ## VIASH START # The following code has been auto-generated by Viash. @@ -1386,12 +1388,29 @@ dep = { sys.path.append(meta["resources_dir"]) from setup_logger import setup_logger +from unzip_archived_folder import unzip_archived_folder logger = setup_logger() logger.info("Reading in Xenium data...") + +if zipfile.is_zipfile(par["input"]): + required_file_patterns = [ + "**/experiment.xenium", + "**/nucleus_boundaries.parquet", + "**/cell_boundaries.parquet", + "**/transcripts.parquet", + "**/cell_feature_matrix.h5", + "**/cells.parquet", + "**/morphology_mip.ome.tif", + "**/morphology_focus.ome.tif", + ] + xenium_output_bundle = unzip_archived_folder(par["input"]) +else: + xenium_output_bundle = Path(par["input"]) + sdata = xenium( - par["input"], + xenium_output_bundle, cells_boundaries=par["cells_boundaries"], nucleus_boundaries=par["nucleus_boundaries"], cells_as_circles=par["cells_as_circles"], diff --git a/target/executable/convert/from_xenium_to_spatialdata/unzip_archived_folder.py b/target/executable/convert/from_xenium_to_spatialdata/unzip_archived_folder.py new file mode 100644 index 0000000..1e5bf39 --- /dev/null +++ b/target/executable/convert/from_xenium_to_spatialdata/unzip_archived_folder.py @@ -0,0 +1,50 @@ +import fnmatch +import zipfile +import tempfile +from pathlib import Path +from typing import Union + + +def unzip_archived_folder(archived_folder: Union[str, Path]) -> Union[str, Path]: + """ + Extracts a ZIP archive to a temporary directory and returns the path to the extracted folder. + + Args: + zip_path (Union[str, Path]): Path to the ZIP archive. + + Returns: + extracted_path (Union[str, Path]): Path to the extracted folder inside the temporary directory. + """ + + temp_dir = Path(tempfile.TemporaryDirectory().name) + with zipfile.ZipFile(archived_folder, "r") as archive: + archive.extractall(temp_dir) + + return temp_dir / Path(archived_folder).stem + + +def extract_selected_files_from_zip( + zip_path: Union[str, Path], members: list[Union[str, Path]] +) -> Union[str, Path]: + """ + Extracts selected files (supports glob patterns) from a ZIP archive to a temporary directory. + + Args: + zip_path (Union[str, Path]): Path to the ZIP archive. + members (list[str]): List of file paths within the archive to extract. + + Returns: + Path: Path to the extraction directory. + """ + + temp_dir = Path(tempfile.TemporaryDirectory().name) + + with zipfile.ZipFile(zip_path, "r") as archive: + all_files = archive.namelist() + selected = set() + for pattern in members: + selected.update(fnmatch.filter(all_files, str(pattern))) + for member in selected: + archive.extract(member, temp_dir) + + return temp_dir diff --git a/target/executable/convert/from_xenium_to_spatialexperiment/.config.vsh.yaml b/target/executable/convert/from_xenium_to_spatialexperiment/.config.vsh.yaml index 6f22bf0..ef77583 100644 --- a/target/executable/convert/from_xenium_to_spatialexperiment/.config.vsh.yaml +++ b/target/executable/convert/from_xenium_to_spatialexperiment/.config.vsh.yaml @@ -1,6 +1,6 @@ name: "from_xenium_to_spatialexperiment" namespace: "convert" -version: "v0.1.0" +version: "v0.1.1" authors: - name: "Dorien Roosen" roles: @@ -88,6 +88,8 @@ resources: - type: "r_script" path: "script.R" is_executable: true +- type: "file" + path: "unzip_archived_folder.R" - type: "file" path: "nextflow_labels.config" dest: "nextflow_labels.config" @@ -192,7 +194,7 @@ engines: id: "docker" image: "rocker/r2u:24.04" target_registry: "images.viash-hub.com" - target_tag: "v0.1.0" + target_tag: "v0.1.1" namespace_separator: "/" setup: - type: "apt" @@ -222,11 +224,11 @@ build_info: output: "target/executable/convert/from_xenium_to_spatialexperiment" executable: "target/executable/convert/from_xenium_to_spatialexperiment/from_xenium_to_spatialexperiment" viash_version: "0.9.4" - git_commit: "3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36" + git_commit: "2baba3fff0930478c26da7e1bd99b29ee5dd83b3" git_remote: "https://github.com/openpipelines-bio/openpipeline_spatial" package_config: name: "openpipeline_spatial" - version: "v0.1.0" + version: "v0.1.1" info: test_resources: - type: "s3" @@ -246,7 +248,7 @@ package_config: )'" - ".engines += { type: \"native\" }" - ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'" - - ".engines[.type == 'docker'].target_tag := 'v0.1.0'" + - ".engines[.type == 'docker'].target_tag := 'v0.1.1'" organization: "vsh" links: repository: "https://github.com/openpipelines-bio/openpipeline_spatial" diff --git a/target/executable/convert/from_xenium_to_spatialexperiment/from_xenium_to_spatialexperiment b/target/executable/convert/from_xenium_to_spatialexperiment/from_xenium_to_spatialexperiment index 79becd4..e3019c2 100755 --- a/target/executable/convert/from_xenium_to_spatialexperiment/from_xenium_to_spatialexperiment +++ b/target/executable/convert/from_xenium_to_spatialexperiment/from_xenium_to_spatialexperiment @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# from_xenium_to_spatialexperiment v0.1.0 +# from_xenium_to_spatialexperiment v0.1.1 # # This wrapper script is auto-generated by viash 0.9.4 and is thus a derivative # work thereof. This software comes with ABSOLUTELY NO WARRANTY from Data @@ -457,10 +457,10 @@ RUN Rscript -e 'options(warn = 2); if (!requireNamespace("BiocManager", quietly LABEL org.opencontainers.image.authors="Dorien Roosen" LABEL org.opencontainers.image.description="Companion container for running component convert from_xenium_to_spatialexperiment" -LABEL org.opencontainers.image.created="2025-08-25T12:22:56Z" +LABEL org.opencontainers.image.created="2025-10-02T08:37:00Z" LABEL org.opencontainers.image.source="https://github.com/openpipelines-bio/openpipeline_spatial" -LABEL org.opencontainers.image.revision="3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36" -LABEL org.opencontainers.image.version="v0.1.0" +LABEL org.opencontainers.image.revision="2baba3fff0930478c26da7e1bd99b29ee5dd83b3" +LABEL org.opencontainers.image.version="v0.1.1" VIASHDOCKER fi @@ -577,7 +577,7 @@ VIASH_DOCKER_RUN_ARGS=(-i --rm) # ViashHelp: Display helpful explanation about this executable function ViashHelp { - echo "from_xenium_to_spatialexperiment v0.1.0" + echo "from_xenium_to_spatialexperiment v0.1.1" echo "" echo "Creates a SpatialExperiment object from the downloaded unzipped Xenium Output" echo "Bundle directory" @@ -673,7 +673,7 @@ while [[ $# -gt 0 ]]; do shift 1 ;; --version) - echo "from_xenium_to_spatialexperiment v0.1.0" + echo "from_xenium_to_spatialexperiment v0.1.1" exit ;; --input) @@ -837,7 +837,7 @@ if [[ "$VIASH_ENGINE_TYPE" == "docker" ]]; then # determine docker image id if [[ "$VIASH_ENGINE_ID" == 'docker' ]]; then - VIASH_DOCKER_IMAGE_ID='images.viash-hub.com/vsh/openpipeline_spatial/convert/from_xenium_to_spatialexperiment:v0.1.0' + VIASH_DOCKER_IMAGE_ID='images.viash-hub.com/vsh/openpipeline_spatial/convert/from_xenium_to_spatialexperiment:v0.1.1' fi # print dockerfile @@ -1195,9 +1195,30 @@ rm(.viash_orig_warn) ### VIASH END +source(paste0(meta\$resources_dir, "/unzip_archived_folder.R")) +cat("Reading input data...") +if (tools::file_ext(par\$input) == "zip") { + required_file_patterns <- c( + "**/cell_feature_matrix.h5", + "**/*.parquet", + "**/experiment.xenium" + ) + tmp_dir <- extract_selected_files( + par\$input, + members = required_file_patterns + ) + xenium_output_bundle <- file.path( + tmp_dir, + tools::file_path_sans_ext(basename(par\$input)) + ) +} else { + xenium_output_bundle <- par\$input +} + +cat("Converting to SpatialExperiment") spe <- readXeniumSXE( - dirName = par\$input, + dirName = xenium_output_bundle, returnType = "SPE", countMatPattern = "cell_feature_matrix.h5", metaDataPattern = "cells.parquet", @@ -1207,6 +1228,7 @@ spe <- readXeniumSXE( altExps = par\$alternative_experiment_features ) +cat("Saving output...") saveRDS(spe, file = par\$output) VIASHMAIN Rscript "\$tempscript" & diff --git a/target/executable/convert/from_xenium_to_spatialexperiment/unzip_archived_folder.R b/target/executable/convert/from_xenium_to_spatialexperiment/unzip_archived_folder.R new file mode 100644 index 0000000..78966bc --- /dev/null +++ b/target/executable/convert/from_xenium_to_spatialexperiment/unzip_archived_folder.R @@ -0,0 +1,22 @@ +extract_selected_files <- function(zip_path, members) { + # Create a temporary directory for extraction + temp_dir <- tempfile("unzip_dir_") + dir.create(temp_dir) + + # List all files in the archive + all_files <- utils::unzip(zip_path, list = TRUE)$Name + + # Find files matching any of the glob patterns in 'members' + selected <- unique(unlist( + lapply(members, function(pattern) { + regex <- glob2rx(pattern) + grep(regex, all_files, value = TRUE) + }) + )) + + # Extract only the selected files + utils::unzip(zip_path, files = selected, exdir = temp_dir) + + # Return the path to the extracted folder + file.path(temp_dir) +} diff --git a/target/executable/filter/subset_cosmx/.config.vsh.yaml b/target/executable/filter/subset_cosmx/.config.vsh.yaml index 045937e..0760596 100644 --- a/target/executable/filter/subset_cosmx/.config.vsh.yaml +++ b/target/executable/filter/subset_cosmx/.config.vsh.yaml @@ -1,6 +1,6 @@ name: "subset_cosmx" namespace: "filter" -version: "v0.1.0" +version: "v0.1.1" authors: - name: "Dorien Roosen" roles: @@ -191,7 +191,7 @@ engines: id: "docker" image: "python:3.12-slim" target_registry: "images.viash-hub.com" - target_tag: "v0.1.0" + target_tag: "v0.1.1" namespace_separator: "/" setup: - type: "apt" @@ -227,11 +227,11 @@ build_info: output: "target/executable/filter/subset_cosmx" executable: "target/executable/filter/subset_cosmx/subset_cosmx" viash_version: "0.9.4" - git_commit: "3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36" + git_commit: "2baba3fff0930478c26da7e1bd99b29ee5dd83b3" git_remote: "https://github.com/openpipelines-bio/openpipeline_spatial" package_config: name: "openpipeline_spatial" - version: "v0.1.0" + version: "v0.1.1" info: test_resources: - type: "s3" @@ -251,7 +251,7 @@ package_config: )'" - ".engines += { type: \"native\" }" - ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'" - - ".engines[.type == 'docker'].target_tag := 'v0.1.0'" + - ".engines[.type == 'docker'].target_tag := 'v0.1.1'" organization: "vsh" links: repository: "https://github.com/openpipelines-bio/openpipeline_spatial" diff --git a/target/executable/filter/subset_cosmx/subset_cosmx b/target/executable/filter/subset_cosmx/subset_cosmx index 00a5e34..856e4bd 100755 --- a/target/executable/filter/subset_cosmx/subset_cosmx +++ b/target/executable/filter/subset_cosmx/subset_cosmx @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# subset_cosmx v0.1.0 +# subset_cosmx v0.1.1 # # This wrapper script is auto-generated by viash 0.9.4 and is thus a derivative # work thereof. This software comes with ABSOLUTELY NO WARRANTY from Data @@ -458,10 +458,10 @@ RUN pip install --upgrade pip && \ LABEL org.opencontainers.image.authors="Dorien Roosen, Weiwei Schultz" LABEL org.opencontainers.image.description="Companion container for running component filter subset_cosmx" -LABEL org.opencontainers.image.created="2025-08-25T12:22:56Z" +LABEL org.opencontainers.image.created="2025-10-02T08:36:59Z" LABEL org.opencontainers.image.source="https://github.com/openpipelines-bio/openpipeline_spatial" -LABEL org.opencontainers.image.revision="3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36" -LABEL org.opencontainers.image.version="v0.1.0" +LABEL org.opencontainers.image.revision="2baba3fff0930478c26da7e1bd99b29ee5dd83b3" +LABEL org.opencontainers.image.version="v0.1.1" VIASHDOCKER fi @@ -578,7 +578,7 @@ VIASH_DOCKER_RUN_ARGS=(-i --rm) # ViashHelp: Display helpful explanation about this executable function ViashHelp { - echo "subset_cosmx v0.1.0" + echo "subset_cosmx v0.1.1" echo "" echo "Filters the output from NanoString experiment to keep only a subset of the" echo "fields of view." @@ -666,7 +666,7 @@ while [[ $# -gt 0 ]]; do shift 1 ;; --version) - echo "subset_cosmx v0.1.0" + echo "subset_cosmx v0.1.1" exit ;; --input) @@ -824,7 +824,7 @@ if [[ "$VIASH_ENGINE_TYPE" == "docker" ]]; then # determine docker image id if [[ "$VIASH_ENGINE_ID" == 'docker' ]]; then - VIASH_DOCKER_IMAGE_ID='images.viash-hub.com/vsh/openpipeline_spatial/filter/subset_cosmx:v0.1.0' + VIASH_DOCKER_IMAGE_ID='images.viash-hub.com/vsh/openpipeline_spatial/filter/subset_cosmx:v0.1.1' fi # print dockerfile diff --git a/target/executable/mapping/spaceranger_count/.config.vsh.yaml b/target/executable/mapping/spaceranger_count/.config.vsh.yaml index cd60e18..75d9c83 100644 --- a/target/executable/mapping/spaceranger_count/.config.vsh.yaml +++ b/target/executable/mapping/spaceranger_count/.config.vsh.yaml @@ -1,6 +1,6 @@ name: "spaceranger_count" namespace: "mapping" -version: "v0.1.0" +version: "v0.1.1" authors: - name: "Jakub Majercik" roles: @@ -408,7 +408,7 @@ engines: id: "docker" image: "ghcr.io/data-intuitive/spaceranger:3.1" target_registry: "images.viash-hub.com" - target_tag: "v0.1.0" + target_tag: "v0.1.1" namespace_separator: "/" setup: - type: "docker" @@ -426,11 +426,11 @@ build_info: output: "target/executable/mapping/spaceranger_count" executable: "target/executable/mapping/spaceranger_count/spaceranger_count" viash_version: "0.9.4" - git_commit: "3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36" + git_commit: "2baba3fff0930478c26da7e1bd99b29ee5dd83b3" git_remote: "https://github.com/openpipelines-bio/openpipeline_spatial" package_config: name: "openpipeline_spatial" - version: "v0.1.0" + version: "v0.1.1" info: test_resources: - type: "s3" @@ -450,7 +450,7 @@ package_config: )'" - ".engines += { type: \"native\" }" - ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'" - - ".engines[.type == 'docker'].target_tag := 'v0.1.0'" + - ".engines[.type == 'docker'].target_tag := 'v0.1.1'" organization: "vsh" links: repository: "https://github.com/openpipelines-bio/openpipeline_spatial" diff --git a/target/executable/mapping/spaceranger_count/spaceranger_count b/target/executable/mapping/spaceranger_count/spaceranger_count index 7d86342..6cba61e 100755 --- a/target/executable/mapping/spaceranger_count/spaceranger_count +++ b/target/executable/mapping/spaceranger_count/spaceranger_count @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# spaceranger_count v0.1.0 +# spaceranger_count v0.1.1 # # This wrapper script is auto-generated by viash 0.9.4 and is thus a derivative # work thereof. This software comes with ABSOLUTELY NO WARRANTY from Data @@ -453,10 +453,10 @@ apt upgrade -y && apt install -y procps && rm -rf /var/lib/apt/lists/* LABEL org.opencontainers.image.authors="Jakub Majercik" LABEL org.opencontainers.image.description="Companion container for running component mapping spaceranger_count" -LABEL org.opencontainers.image.created="2025-08-25T12:22:57Z" +LABEL org.opencontainers.image.created="2025-10-02T08:36:59Z" LABEL org.opencontainers.image.source="https://github.com/openpipelines-bio/openpipeline_spatial" -LABEL org.opencontainers.image.revision="3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36" -LABEL org.opencontainers.image.version="v0.1.0" +LABEL org.opencontainers.image.revision="2baba3fff0930478c26da7e1bd99b29ee5dd83b3" +LABEL org.opencontainers.image.version="v0.1.1" VIASHDOCKER fi @@ -573,7 +573,7 @@ VIASH_DOCKER_RUN_ARGS=(-i --rm) # ViashHelp: Display helpful explanation about this executable function ViashHelp { - echo "spaceranger_count v0.1.0" + echo "spaceranger_count v0.1.1" echo "" echo "Count gene expression and protein expression reads from a single capture area." echo "" @@ -775,7 +775,7 @@ while [[ $# -gt 0 ]]; do shift 1 ;; --version) - echo "spaceranger_count v0.1.0" + echo "spaceranger_count v0.1.1" exit ;; --gex_reference) @@ -1135,7 +1135,7 @@ if [[ "$VIASH_ENGINE_TYPE" == "docker" ]]; then # determine docker image id if [[ "$VIASH_ENGINE_ID" == 'docker' ]]; then - VIASH_DOCKER_IMAGE_ID='images.viash-hub.com/vsh/openpipeline_spatial/mapping/spaceranger_count:v0.1.0' + VIASH_DOCKER_IMAGE_ID='images.viash-hub.com/vsh/openpipeline_spatial/mapping/spaceranger_count:v0.1.1' fi # print dockerfile diff --git a/target/nextflow/convert/from_cells2stats_to_h5mu/.config.vsh.yaml b/target/nextflow/convert/from_cells2stats_to_h5mu/.config.vsh.yaml index 09f394e..665363c 100644 --- a/target/nextflow/convert/from_cells2stats_to_h5mu/.config.vsh.yaml +++ b/target/nextflow/convert/from_cells2stats_to_h5mu/.config.vsh.yaml @@ -1,6 +1,6 @@ name: "from_cells2stats_to_h5mu" namespace: "convert" -version: "v0.1.0" +version: "v0.1.1" authors: - name: "Dorien Roosen" roles: @@ -150,6 +150,8 @@ resources: is_executable: true - type: "file" path: "setup_logger.py" +- type: "file" + path: "unzip_archived_folder.py" - type: "file" path: "nextflow_labels.config" dest: "nextflow_labels.config" @@ -256,7 +258,7 @@ engines: id: "docker" image: "python:3.13-slim" target_registry: "images.viash-hub.com" - target_tag: "v0.1.0" + target_tag: "v0.1.1" namespace_separator: "/" setup: - type: "apt" @@ -274,6 +276,10 @@ engines: nelse: exit(1)\")" upgrade: true test_setup: + - type: "apt" + packages: + - "zip" + interactive: false - type: "python" user: false packages: @@ -290,11 +296,11 @@ build_info: output: "target/nextflow/convert/from_cells2stats_to_h5mu" executable: "target/nextflow/convert/from_cells2stats_to_h5mu/main.nf" viash_version: "0.9.4" - git_commit: "3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36" + git_commit: "2baba3fff0930478c26da7e1bd99b29ee5dd83b3" git_remote: "https://github.com/openpipelines-bio/openpipeline_spatial" package_config: name: "openpipeline_spatial" - version: "v0.1.0" + version: "v0.1.1" info: test_resources: - type: "s3" @@ -314,7 +320,7 @@ package_config: )'" - ".engines += { type: \"native\" }" - ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'" - - ".engines[.type == 'docker'].target_tag := 'v0.1.0'" + - ".engines[.type == 'docker'].target_tag := 'v0.1.1'" organization: "vsh" links: repository: "https://github.com/openpipelines-bio/openpipeline_spatial" diff --git a/target/nextflow/convert/from_cells2stats_to_h5mu/main.nf b/target/nextflow/convert/from_cells2stats_to_h5mu/main.nf index 39037dc..2fa4d7a 100644 --- a/target/nextflow/convert/from_cells2stats_to_h5mu/main.nf +++ b/target/nextflow/convert/from_cells2stats_to_h5mu/main.nf @@ -1,4 +1,4 @@ -// from_cells2stats_to_h5mu v0.1.0 +// from_cells2stats_to_h5mu v0.1.1 // // This wrapper script is auto-generated by viash 0.9.4 and is thus a derivative // work thereof. This software comes with ABSOLUTELY NO WARRANTY from Data @@ -3035,7 +3035,7 @@ meta = [ "config": processConfig(readJsonBlob('''{ "name" : "from_cells2stats_to_h5mu", "namespace" : "convert", - "version" : "v0.1.0", + "version" : "v0.1.1", "authors" : [ { "name" : "Dorien Roosen", @@ -3214,6 +3214,10 @@ meta = [ "type" : "file", "path" : "/src/utils/setup_logger.py" }, + { + "type" : "file", + "path" : "/src/utils/unzip_archived_folder.py" + }, { "type" : "file", "path" : "/src/workflows/utils/labels.config", @@ -3336,7 +3340,7 @@ meta = [ "id" : "docker", "image" : "python:3.13-slim", "target_registry" : "images.viash-hub.com", - "target_tag" : "v0.1.0", + "target_tag" : "v0.1.1", "namespace_separator" : "/", "setup" : [ { @@ -3361,6 +3365,13 @@ meta = [ } ], "test_setup" : [ + { + "type" : "apt", + "packages" : [ + "zip" + ], + "interactive" : false + }, { "type" : "python", "user" : false, @@ -3382,12 +3393,12 @@ meta = [ "engine" : "docker|native", "output" : "/workdir/root/repo/target/nextflow/convert/from_cells2stats_to_h5mu", "viash_version" : "0.9.4", - "git_commit" : "3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36", + "git_commit" : "2baba3fff0930478c26da7e1bd99b29ee5dd83b3", "git_remote" : "https://github.com/openpipelines-bio/openpipeline_spatial" }, "package_config" : { "name" : "openpipeline_spatial", - "version" : "v0.1.0", + "version" : "v0.1.1", "info" : { "test_resources" : [ { @@ -3412,7 +3423,7 @@ meta = [ ".resources += {path: '/src/workflows/utils/labels.config', dest: 'nextflow_labels.config'}\n.runners[.type == 'nextflow'].config.script := 'includeConfig(\\"nextflow_labels.config\\")'", ".engines += { type: \\"native\\" }", ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'", - ".engines[.type == 'docker'].target_tag := 'v0.1.0'" + ".engines[.type == 'docker'].target_tag := 'v0.1.1'" ], "organization" : "vsh", "links" : { @@ -3440,6 +3451,8 @@ import mudata as mu import anndata as ad import re import json +import zipfile +import os ## VIASH START # The following code has been auto-generated by Viash. @@ -3483,6 +3496,8 @@ dep = { sys.path.append(meta["resources_dir"]) from setup_logger import setup_logger +from unzip_archived_folder import extract_selected_files_from_zip + logger = setup_logger() @@ -3620,27 +3635,43 @@ def categorize_columns(column_list, target_panel): ) -def main(): - # Read data from Aviti Teton output bundle +def retrieve_input_data(cells2stats_output_bundle): # Expected folder structure (showing only relevant files): # ├── Cytoprofiling/ # │ └── Instrument/ # │ └── RawCellStats.parquet # └── Panel.json - logger.info("Reading input data...") - input_dir = Path(par["input"]) - input_data = { - "count_matrix": input_dir - / "Cytoprofiling" - / "Instrument" - / "RawCellStats.parquet", - "target_panel": input_dir / "Panel.json", + required_file_patterns = { + "target_panel": "**/Panel.json", + "count_matrix": "**/Cytoprofiling/Instrument/RawCellStats.parquet", } - assert all([file.exists() for file in input_data.values()]), ( - f"Not all required input files are found. Make sure that {par['input']} contains {input_data.values()}." + if zipfile.is_zipfile(cells2stats_output_bundle): + cells2stats_output_bundle = extract_selected_files_from_zip( + cells2stats_output_bundle, members=required_file_patterns.values() + ) + else: + cells2stats_output_bundle = Path(cells2stats_output_bundle) + + assert os.path.isdir(cells2stats_output_bundle), ( + "Input is expected to be a (compressed) directory." ) + + input_data = {} + for key, pattern in required_file_patterns.items(): + file = list(cells2stats_output_bundle.glob(pattern)) + assert len(file) == 1, ( + f"Expected exactly one file matching pattern {pattern}, found {len(file)}." + ) + input_data[key] = file[0] + + return input_data + + +def main(): + logger.info("Reading input data...") + input_data = retrieve_input_data(par["input"]) with open(input_data["target_panel"], "r") as f: target_panel = json.load(f) df = pd.read_parquet(input_data["count_matrix"], engine="pyarrow") @@ -4123,7 +4154,7 @@ meta["defaults"] = [ "container" : { "registry" : "images.viash-hub.com", "image" : "vsh/openpipeline_spatial/convert/from_cells2stats_to_h5mu", - "tag" : "v0.1.0" + "tag" : "v0.1.1" }, "label" : [ "lowmem", diff --git a/target/nextflow/convert/from_cells2stats_to_h5mu/nextflow.config b/target/nextflow/convert/from_cells2stats_to_h5mu/nextflow.config index 98b884f..877bf42 100644 --- a/target/nextflow/convert/from_cells2stats_to_h5mu/nextflow.config +++ b/target/nextflow/convert/from_cells2stats_to_h5mu/nextflow.config @@ -2,7 +2,7 @@ manifest { name = 'convert/from_cells2stats_to_h5mu' mainScript = 'main.nf' nextflowVersion = '!>=20.12.1-edge' - version = 'v0.1.0' + version = 'v0.1.1' description = 'Convert spatial data resulting from Aviti Teton sequencers that have been processed by the Element Biosciences cells2stats workflow to H5MU format.\n\nThis component processes cells2stats count matrices to create a standardized H5MU file for downstream analysis.\n\nThe component reads:\n- Parquet file containing the count matrix and metadata\n- Panel.json with target and batch information\n\nAnd outputs an H5MU file with:\n- Count data as the main .X matrix\n- Spatial coordinates in obsm\n- Cell Paint intensities in obsm (optional)\n- Nuclear count data as a layer (optional)\n- CellProfiler morphology metrics in obsm (optional)\n- Unassigned targets in obsm (optional)\n' author = 'Dorien Roosen' } diff --git a/target/nextflow/convert/from_cells2stats_to_h5mu/unzip_archived_folder.py b/target/nextflow/convert/from_cells2stats_to_h5mu/unzip_archived_folder.py new file mode 100644 index 0000000..1e5bf39 --- /dev/null +++ b/target/nextflow/convert/from_cells2stats_to_h5mu/unzip_archived_folder.py @@ -0,0 +1,50 @@ +import fnmatch +import zipfile +import tempfile +from pathlib import Path +from typing import Union + + +def unzip_archived_folder(archived_folder: Union[str, Path]) -> Union[str, Path]: + """ + Extracts a ZIP archive to a temporary directory and returns the path to the extracted folder. + + Args: + zip_path (Union[str, Path]): Path to the ZIP archive. + + Returns: + extracted_path (Union[str, Path]): Path to the extracted folder inside the temporary directory. + """ + + temp_dir = Path(tempfile.TemporaryDirectory().name) + with zipfile.ZipFile(archived_folder, "r") as archive: + archive.extractall(temp_dir) + + return temp_dir / Path(archived_folder).stem + + +def extract_selected_files_from_zip( + zip_path: Union[str, Path], members: list[Union[str, Path]] +) -> Union[str, Path]: + """ + Extracts selected files (supports glob patterns) from a ZIP archive to a temporary directory. + + Args: + zip_path (Union[str, Path]): Path to the ZIP archive. + members (list[str]): List of file paths within the archive to extract. + + Returns: + Path: Path to the extraction directory. + """ + + temp_dir = Path(tempfile.TemporaryDirectory().name) + + with zipfile.ZipFile(zip_path, "r") as archive: + all_files = archive.namelist() + selected = set() + for pattern in members: + selected.update(fnmatch.filter(all_files, str(pattern))) + for member in selected: + archive.extract(member, temp_dir) + + return temp_dir diff --git a/target/nextflow/convert/from_cosmx_to_h5mu/.config.vsh.yaml b/target/nextflow/convert/from_cosmx_to_h5mu/.config.vsh.yaml index 07ce5ce..9144666 100644 --- a/target/nextflow/convert/from_cosmx_to_h5mu/.config.vsh.yaml +++ b/target/nextflow/convert/from_cosmx_to_h5mu/.config.vsh.yaml @@ -1,6 +1,6 @@ name: "from_cosmx_to_h5mu" namespace: "convert" -version: "v0.1.0" +version: "v0.1.1" authors: - name: "Dorien Roosen" roles: @@ -81,6 +81,8 @@ resources: is_executable: true - type: "file" path: "setup_logger.py" +- type: "file" + path: "unzip_archived_folder.py" - type: "file" path: "nextflow_labels.config" dest: "nextflow_labels.config" @@ -184,7 +186,7 @@ engines: id: "docker" image: "python:3.12-slim" target_registry: "images.viash-hub.com" - target_tag: "v0.1.0" + target_tag: "v0.1.1" namespace_separator: "/" setup: - type: "apt" @@ -214,6 +216,10 @@ engines: github: - "openpipelines-bio/core#subdirectory=packages/python/openpipeline_testutils" upgrade: true + - type: "apt" + packages: + - "zip" + interactive: false entrypoint: [] cmd: null - type: "native" @@ -225,11 +231,11 @@ build_info: output: "target/nextflow/convert/from_cosmx_to_h5mu" executable: "target/nextflow/convert/from_cosmx_to_h5mu/main.nf" viash_version: "0.9.4" - git_commit: "3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36" + git_commit: "2baba3fff0930478c26da7e1bd99b29ee5dd83b3" git_remote: "https://github.com/openpipelines-bio/openpipeline_spatial" package_config: name: "openpipeline_spatial" - version: "v0.1.0" + version: "v0.1.1" info: test_resources: - type: "s3" @@ -249,7 +255,7 @@ package_config: )'" - ".engines += { type: \"native\" }" - ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'" - - ".engines[.type == 'docker'].target_tag := 'v0.1.0'" + - ".engines[.type == 'docker'].target_tag := 'v0.1.1'" organization: "vsh" links: repository: "https://github.com/openpipelines-bio/openpipeline_spatial" diff --git a/target/nextflow/convert/from_cosmx_to_h5mu/main.nf b/target/nextflow/convert/from_cosmx_to_h5mu/main.nf index d175290..960299a 100644 --- a/target/nextflow/convert/from_cosmx_to_h5mu/main.nf +++ b/target/nextflow/convert/from_cosmx_to_h5mu/main.nf @@ -1,4 +1,4 @@ -// from_cosmx_to_h5mu v0.1.0 +// from_cosmx_to_h5mu v0.1.1 // // This wrapper script is auto-generated by viash 0.9.4 and is thus a derivative // work thereof. This software comes with ABSOLUTELY NO WARRANTY from Data @@ -3036,7 +3036,7 @@ meta = [ "config": processConfig(readJsonBlob('''{ "name" : "from_cosmx_to_h5mu", "namespace" : "convert", - "version" : "v0.1.0", + "version" : "v0.1.1", "authors" : [ { "name" : "Dorien Roosen", @@ -3152,6 +3152,10 @@ meta = [ "type" : "file", "path" : "/src/utils/setup_logger.py" }, + { + "type" : "file", + "path" : "/src/utils/unzip_archived_folder.py" + }, { "type" : "file", "path" : "/src/workflows/utils/labels.config", @@ -3274,7 +3278,7 @@ meta = [ "id" : "docker", "image" : "python:3.12-slim", "target_registry" : "images.viash-hub.com", - "target_tag" : "v0.1.0", + "target_tag" : "v0.1.1", "namespace_separator" : "/", "setup" : [ { @@ -3317,6 +3321,13 @@ meta = [ "openpipelines-bio/core#subdirectory=packages/python/openpipeline_testutils" ], "upgrade" : true + }, + { + "type" : "apt", + "packages" : [ + "zip" + ], + "interactive" : false } ] }, @@ -3331,12 +3342,12 @@ meta = [ "engine" : "docker|native", "output" : "/workdir/root/repo/target/nextflow/convert/from_cosmx_to_h5mu", "viash_version" : "0.9.4", - "git_commit" : "3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36", + "git_commit" : "2baba3fff0930478c26da7e1bd99b29ee5dd83b3", "git_remote" : "https://github.com/openpipelines-bio/openpipeline_spatial" }, "package_config" : { "name" : "openpipeline_spatial", - "version" : "v0.1.0", + "version" : "v0.1.1", "info" : { "test_resources" : [ { @@ -3361,7 +3372,7 @@ meta = [ ".resources += {path: '/src/workflows/utils/labels.config', dest: 'nextflow_labels.config'}\n.runners[.type == 'nextflow'].config.script := 'includeConfig(\\"nextflow_labels.config\\")'", ".engines += { type: \\"native\\" }", ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'", - ".engines[.type == 'docker'].target_tag := 'v0.1.0'" + ".engines[.type == 'docker'].target_tag := 'v0.1.1'" ], "organization" : "vsh", "links" : { @@ -3385,7 +3396,8 @@ import sys import os import squidpy as sq import mudata as mu -import glob +import zipfile +from pathlib import Path ## VIASH START # The following code has been auto-generated by Viash. @@ -3423,31 +3435,60 @@ dep = { sys.path.append(meta["resources_dir"]) from setup_logger import setup_logger +from unzip_archived_folder import extract_selected_files_from_zip logger = setup_logger() -def find_matrix_file(suffix): - pattern = os.path.join(par["input"], f"*{suffix}") - files = glob.glob(pattern) - assert len(files) == 1, ( - f"Only one file matching pattern {pattern} should be present" +def retrieve_input_data(cosmx_output_bundle): + # Expected folder structure (showing only relevant files): + # ├── *_exprMat_file.csv + # ├── *_fov_positions_file.csv + # └── *_metadata_file.csv + + required_file_patterns = { + "counts_file": "**/*exprMat_file.csv", + "fov_file": "**/*fov_positions_file.csv", + "meta_file": "**/*metadata_file.csv", + } + if zipfile.is_zipfile(cosmx_output_bundle): + cosmx_output_bundle = extract_selected_files_from_zip( + cosmx_output_bundle, members=required_file_patterns.values() + ) + else: + cosmx_output_bundle = Path(cosmx_output_bundle) + + assert os.path.isdir(cosmx_output_bundle), ( + "Input is expected to be a (compressed) directory." ) - return files[0] + + input_data = {} + for key, pattern in required_file_patterns.items(): + file = list(cosmx_output_bundle.glob(pattern)) + assert len(file) == 1, f"Expected one file for {key}, found {len(file)}." + input_data[key] = file[0] + + return input_data -counts_file = find_matrix_file("exprMat_file.csv") -fov_file = find_matrix_file("fov_positions_file.csv") -meta_file = find_matrix_file("metadata_file.csv") +def main(): + logger.info("Reading in CosMx data...") + input_data = retrieve_input_data(par["input"]) -logger.info("Reading in CosMx data...") -adata = sq.read.nanostring( - path=par["input"], counts_file=counts_file, meta_file=meta_file, fov_file=fov_file -) + adata = sq.read.nanostring( + path=par["input"], + counts_file=input_data["counts_file"], + meta_file=input_data["meta_file"], + fov_file=input_data["fov_file"], + ) -logger.info("Writing output MuData object...") -mdata = mu.MuData({par["modality"]: adata}) -mdata.write_h5mu(par["output"], compression=par["output_compression"]) + logger.info("Writing output MuData object...") + mdata = mu.MuData({par["modality"]: adata}) + mdata.write_h5mu(par["output"], compression=par["output_compression"]) + + +if __name__ == "__main__": + main() VIASHMAIN python -B "$tempscript" ''' @@ -3830,7 +3871,7 @@ meta["defaults"] = [ "container" : { "registry" : "images.viash-hub.com", "image" : "vsh/openpipeline_spatial/convert/from_cosmx_to_h5mu", - "tag" : "v0.1.0" + "tag" : "v0.1.1" }, "label" : [ "lowmem", diff --git a/target/nextflow/convert/from_cosmx_to_h5mu/nextflow.config b/target/nextflow/convert/from_cosmx_to_h5mu/nextflow.config index 6148394..7a1e188 100644 --- a/target/nextflow/convert/from_cosmx_to_h5mu/nextflow.config +++ b/target/nextflow/convert/from_cosmx_to_h5mu/nextflow.config @@ -2,7 +2,7 @@ manifest { name = 'convert/from_cosmx_to_h5mu' mainScript = 'main.nf' nextflowVersion = '!>=20.12.1-edge' - version = 'v0.1.0' + version = 'v0.1.1' description = 'Converts the output from NanoString experiment into a MuData objcet.\n - `_exprMat_file.csv`: File containing the counts.\n - ``_metadata_file: File containing the spatial coordinates and additional cell-level metadata.\n - `_fov_file.csv`: File containing the coordinates of all the fields of view.\nIn addition to reading the regular Nanostring output, it loads CellComposite and CellLabels directories, if present,\ncontaining the images.\n' author = 'Dorien Roosen, Weiwei Schultz' } diff --git a/target/nextflow/convert/from_cosmx_to_h5mu/unzip_archived_folder.py b/target/nextflow/convert/from_cosmx_to_h5mu/unzip_archived_folder.py new file mode 100644 index 0000000..1e5bf39 --- /dev/null +++ b/target/nextflow/convert/from_cosmx_to_h5mu/unzip_archived_folder.py @@ -0,0 +1,50 @@ +import fnmatch +import zipfile +import tempfile +from pathlib import Path +from typing import Union + + +def unzip_archived_folder(archived_folder: Union[str, Path]) -> Union[str, Path]: + """ + Extracts a ZIP archive to a temporary directory and returns the path to the extracted folder. + + Args: + zip_path (Union[str, Path]): Path to the ZIP archive. + + Returns: + extracted_path (Union[str, Path]): Path to the extracted folder inside the temporary directory. + """ + + temp_dir = Path(tempfile.TemporaryDirectory().name) + with zipfile.ZipFile(archived_folder, "r") as archive: + archive.extractall(temp_dir) + + return temp_dir / Path(archived_folder).stem + + +def extract_selected_files_from_zip( + zip_path: Union[str, Path], members: list[Union[str, Path]] +) -> Union[str, Path]: + """ + Extracts selected files (supports glob patterns) from a ZIP archive to a temporary directory. + + Args: + zip_path (Union[str, Path]): Path to the ZIP archive. + members (list[str]): List of file paths within the archive to extract. + + Returns: + Path: Path to the extraction directory. + """ + + temp_dir = Path(tempfile.TemporaryDirectory().name) + + with zipfile.ZipFile(zip_path, "r") as archive: + all_files = archive.namelist() + selected = set() + for pattern in members: + selected.update(fnmatch.filter(all_files, str(pattern))) + for member in selected: + archive.extract(member, temp_dir) + + return temp_dir diff --git a/target/nextflow/convert/from_cosmx_to_spatialexperiment/.config.vsh.yaml b/target/nextflow/convert/from_cosmx_to_spatialexperiment/.config.vsh.yaml index cdeb627..568311c 100644 --- a/target/nextflow/convert/from_cosmx_to_spatialexperiment/.config.vsh.yaml +++ b/target/nextflow/convert/from_cosmx_to_spatialexperiment/.config.vsh.yaml @@ -1,6 +1,6 @@ name: "from_cosmx_to_spatialexperiment" namespace: "convert" -version: "v0.1.0" +version: "v0.1.1" authors: - name: "Dorien Roosen" roles: @@ -98,6 +98,8 @@ resources: - type: "r_script" path: "script.R" is_executable: true +- type: "file" + path: "unzip_archived_folder.R" - type: "file" path: "nextflow_labels.config" dest: "nextflow_labels.config" @@ -202,7 +204,7 @@ engines: id: "docker" image: "rocker/r2u:24.04" target_registry: "images.viash-hub.com" - target_tag: "v0.1.0" + target_tag: "v0.1.1" namespace_separator: "/" setup: - type: "apt" @@ -232,11 +234,11 @@ build_info: output: "target/nextflow/convert/from_cosmx_to_spatialexperiment" executable: "target/nextflow/convert/from_cosmx_to_spatialexperiment/main.nf" viash_version: "0.9.4" - git_commit: "3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36" + git_commit: "2baba3fff0930478c26da7e1bd99b29ee5dd83b3" git_remote: "https://github.com/openpipelines-bio/openpipeline_spatial" package_config: name: "openpipeline_spatial" - version: "v0.1.0" + version: "v0.1.1" info: test_resources: - type: "s3" @@ -256,7 +258,7 @@ package_config: )'" - ".engines += { type: \"native\" }" - ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'" - - ".engines[.type == 'docker'].target_tag := 'v0.1.0'" + - ".engines[.type == 'docker'].target_tag := 'v0.1.1'" organization: "vsh" links: repository: "https://github.com/openpipelines-bio/openpipeline_spatial" diff --git a/target/nextflow/convert/from_cosmx_to_spatialexperiment/main.nf b/target/nextflow/convert/from_cosmx_to_spatialexperiment/main.nf index b42b963..3471a05 100644 --- a/target/nextflow/convert/from_cosmx_to_spatialexperiment/main.nf +++ b/target/nextflow/convert/from_cosmx_to_spatialexperiment/main.nf @@ -1,4 +1,4 @@ -// from_cosmx_to_spatialexperiment v0.1.0 +// from_cosmx_to_spatialexperiment v0.1.1 // // This wrapper script is auto-generated by viash 0.9.4 and is thus a derivative // work thereof. This software comes with ABSOLUTELY NO WARRANTY from Data @@ -3035,7 +3035,7 @@ meta = [ "config": processConfig(readJsonBlob('''{ "name" : "from_cosmx_to_spatialexperiment", "namespace" : "convert", - "version" : "v0.1.0", + "version" : "v0.1.1", "authors" : [ { "name" : "Dorien Roosen", @@ -3158,6 +3158,10 @@ meta = [ "path" : "script.R", "is_executable" : true }, + { + "type" : "file", + "path" : "/src/utils/unzip_archived_folder.R" + }, { "type" : "file", "path" : "/src/workflows/utils/labels.config", @@ -3280,7 +3284,7 @@ meta = [ "id" : "docker", "image" : "rocker/r2u:24.04", "target_registry" : "images.viash-hub.com", - "target_tag" : "v0.1.0", + "target_tag" : "v0.1.1", "namespace_separator" : "/", "setup" : [ { @@ -3322,12 +3326,12 @@ meta = [ "engine" : "docker|native", "output" : "/workdir/root/repo/target/nextflow/convert/from_cosmx_to_spatialexperiment", "viash_version" : "0.9.4", - "git_commit" : "3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36", + "git_commit" : "2baba3fff0930478c26da7e1bd99b29ee5dd83b3", "git_remote" : "https://github.com/openpipelines-bio/openpipeline_spatial" }, "package_config" : { "name" : "openpipeline_spatial", - "version" : "v0.1.0", + "version" : "v0.1.1", "info" : { "test_resources" : [ { @@ -3352,7 +3356,7 @@ meta = [ ".resources += {path: '/src/workflows/utils/labels.config', dest: 'nextflow_labels.config'}\n.runners[.type == 'nextflow'].config.script := 'includeConfig(\\"nextflow_labels.config\\")'", ".engines += { type: \\"native\\" }", ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'", - ".engines[.type == 'docker'].target_tag := 'v0.1.0'" + ".engines[.type == 'docker'].target_tag := 'v0.1.1'" ], "organization" : "vsh", "links" : { @@ -3418,14 +3422,36 @@ rm(.viash_orig_warn) ### VIASH END +source(paste0(meta\\$resources_dir, "/unzip_archived_folder.R")) + +cat("Reading input data...") +if (tools::file_ext(par\\$input) == "zip") { + expected_file_patterns <- c( + "*.csv", + "*.parquet" + ) + tmp_dir <- extract_selected_files( + par\\$input, + members = expected_file_patterns + ) + cosmx_output_bundle <- file.path( + tmp_dir, + tools::file_path_sans_ext(basename(par\\$input)) + ) +} else { + cosmx_output_bundle <- par\\$input +} + +cat("Setting parameters...") if (par\\$add_polygon_path == FALSE && par\\$add_tx_path == FALSE) { add_parquet_paths <- FALSE } else { add_parquet_paths <- TRUE } +cat("Converting to SpatialExperiment...") spe <- readCosmxSXE( - dirName = par\\$input, + dirName = cosmx_output_bundle, returnType = "SPE", countMatPattern = "exprMat_file.csv", metaDataPattern = "metadata_file.csv", @@ -3438,6 +3464,7 @@ spe <- readCosmxSXE( altExps = par\\$alternative_experiment_features ) +cat("Saving output...") saveRDS(spe, file = par\\$output) VIASHMAIN Rscript "$tempscript" @@ -3821,7 +3848,7 @@ meta["defaults"] = [ "container" : { "registry" : "images.viash-hub.com", "image" : "vsh/openpipeline_spatial/convert/from_cosmx_to_spatialexperiment", - "tag" : "v0.1.0" + "tag" : "v0.1.1" }, "label" : [ "lowmem", diff --git a/target/nextflow/convert/from_cosmx_to_spatialexperiment/nextflow.config b/target/nextflow/convert/from_cosmx_to_spatialexperiment/nextflow.config index fd28d4a..c588344 100644 --- a/target/nextflow/convert/from_cosmx_to_spatialexperiment/nextflow.config +++ b/target/nextflow/convert/from_cosmx_to_spatialexperiment/nextflow.config @@ -2,7 +2,7 @@ manifest { name = 'convert/from_cosmx_to_spatialexperiment' mainScript = 'main.nf' nextflowVersion = '!>=20.12.1-edge' - version = 'v0.1.0' + version = 'v0.1.1' description = 'Creates a SpatialExperiment object from the downloaded unzipped CosMx directory for Nanostring\nCosMx spatial gene expression data, and saves it as a SpatialExperiment object.\nThe constructor assumes the downloaded unzipped CosMx Folder has the following structure:\n\nMandatory files\n· | — *_exprMat_file.csv\n· | — *_metadata_file.csv\nOptional files, by default added to the metadata() as a list of paths (will be converted to parquet):\n· | —*_fov_positions_file.csv\n· | — *_tx_file.csv\n· | — *_polygons.csv\n' author = 'Dorien Roosen' } diff --git a/target/nextflow/convert/from_cosmx_to_spatialexperiment/unzip_archived_folder.R b/target/nextflow/convert/from_cosmx_to_spatialexperiment/unzip_archived_folder.R new file mode 100644 index 0000000..78966bc --- /dev/null +++ b/target/nextflow/convert/from_cosmx_to_spatialexperiment/unzip_archived_folder.R @@ -0,0 +1,22 @@ +extract_selected_files <- function(zip_path, members) { + # Create a temporary directory for extraction + temp_dir <- tempfile("unzip_dir_") + dir.create(temp_dir) + + # List all files in the archive + all_files <- utils::unzip(zip_path, list = TRUE)$Name + + # Find files matching any of the glob patterns in 'members' + selected <- unique(unlist( + lapply(members, function(pattern) { + regex <- glob2rx(pattern) + grep(regex, all_files, value = TRUE) + }) + )) + + # Extract only the selected files + utils::unzip(zip_path, files = selected, exdir = temp_dir) + + # Return the path to the extracted folder + file.path(temp_dir) +} diff --git a/target/nextflow/convert/from_h5mu_to_spatialexperiment/.config.vsh.yaml b/target/nextflow/convert/from_h5mu_to_spatialexperiment/.config.vsh.yaml index 31e1dfb..71e1fb5 100644 --- a/target/nextflow/convert/from_h5mu_to_spatialexperiment/.config.vsh.yaml +++ b/target/nextflow/convert/from_h5mu_to_spatialexperiment/.config.vsh.yaml @@ -1,6 +1,6 @@ name: "from_h5mu_to_spatialexperiment" namespace: "convert" -version: "v0.1.0" +version: "v0.1.1" authors: - name: "Dorien Roosen" roles: @@ -171,7 +171,7 @@ engines: id: "docker" image: "rocker/r2u:22.04" target_registry: "images.viash-hub.com" - target_tag: "v0.1.0" + target_tag: "v0.1.1" namespace_separator: "/" setup: - type: "apt" @@ -224,11 +224,11 @@ build_info: output: "target/nextflow/convert/from_h5mu_to_spatialexperiment" executable: "target/nextflow/convert/from_h5mu_to_spatialexperiment/main.nf" viash_version: "0.9.4" - git_commit: "3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36" + git_commit: "2baba3fff0930478c26da7e1bd99b29ee5dd83b3" git_remote: "https://github.com/openpipelines-bio/openpipeline_spatial" package_config: name: "openpipeline_spatial" - version: "v0.1.0" + version: "v0.1.1" info: test_resources: - type: "s3" @@ -248,7 +248,7 @@ package_config: )'" - ".engines += { type: \"native\" }" - ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'" - - ".engines[.type == 'docker'].target_tag := 'v0.1.0'" + - ".engines[.type == 'docker'].target_tag := 'v0.1.1'" organization: "vsh" links: repository: "https://github.com/openpipelines-bio/openpipeline_spatial" diff --git a/target/nextflow/convert/from_h5mu_to_spatialexperiment/main.nf b/target/nextflow/convert/from_h5mu_to_spatialexperiment/main.nf index 5bc5856..dfb6659 100644 --- a/target/nextflow/convert/from_h5mu_to_spatialexperiment/main.nf +++ b/target/nextflow/convert/from_h5mu_to_spatialexperiment/main.nf @@ -1,4 +1,4 @@ -// from_h5mu_to_spatialexperiment v0.1.0 +// from_h5mu_to_spatialexperiment v0.1.1 // // This wrapper script is auto-generated by viash 0.9.4 and is thus a derivative // work thereof. This software comes with ABSOLUTELY NO WARRANTY from Data @@ -3035,7 +3035,7 @@ meta = [ "config": processConfig(readJsonBlob('''{ "name" : "from_h5mu_to_spatialexperiment", "namespace" : "convert", - "version" : "v0.1.0", + "version" : "v0.1.1", "authors" : [ { "name" : "Dorien Roosen", @@ -3257,7 +3257,7 @@ meta = [ "id" : "docker", "image" : "rocker/r2u:22.04", "target_registry" : "images.viash-hub.com", - "target_tag" : "v0.1.0", + "target_tag" : "v0.1.1", "namespace_separator" : "/", "setup" : [ { @@ -3332,12 +3332,12 @@ meta = [ "engine" : "docker|native", "output" : "/workdir/root/repo/target/nextflow/convert/from_h5mu_to_spatialexperiment", "viash_version" : "0.9.4", - "git_commit" : "3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36", + "git_commit" : "2baba3fff0930478c26da7e1bd99b29ee5dd83b3", "git_remote" : "https://github.com/openpipelines-bio/openpipeline_spatial" }, "package_config" : { "name" : "openpipeline_spatial", - "version" : "v0.1.0", + "version" : "v0.1.1", "info" : { "test_resources" : [ { @@ -3362,7 +3362,7 @@ meta = [ ".resources += {path: '/src/workflows/utils/labels.config', dest: 'nextflow_labels.config'}\n.runners[.type == 'nextflow'].config.script := 'includeConfig(\\"nextflow_labels.config\\")'", ".engines += { type: \\"native\\" }", ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'", - ".engines[.type == 'docker'].target_tag := 'v0.1.0'" + ".engines[.type == 'docker'].target_tag := 'v0.1.1'" ], "organization" : "vsh", "links" : { @@ -3910,7 +3910,7 @@ meta["defaults"] = [ "container" : { "registry" : "images.viash-hub.com", "image" : "vsh/openpipeline_spatial/convert/from_h5mu_to_spatialexperiment", - "tag" : "v0.1.0" + "tag" : "v0.1.1" }, "label" : [ "lowmem", diff --git a/target/nextflow/convert/from_h5mu_to_spatialexperiment/nextflow.config b/target/nextflow/convert/from_h5mu_to_spatialexperiment/nextflow.config index d87780a..8e3d648 100644 --- a/target/nextflow/convert/from_h5mu_to_spatialexperiment/nextflow.config +++ b/target/nextflow/convert/from_h5mu_to_spatialexperiment/nextflow.config @@ -2,7 +2,7 @@ manifest { name = 'convert/from_h5mu_to_spatialexperiment' mainScript = 'main.nf' nextflowVersion = '!>=20.12.1-edge' - version = 'v0.1.0' + version = 'v0.1.1' description = 'Converts an h5mu file into a SpatialExperiment object.\n' author = 'Dorien Roosen' } diff --git a/target/nextflow/convert/from_spatialdata_to_h5mu/.config.vsh.yaml b/target/nextflow/convert/from_spatialdata_to_h5mu/.config.vsh.yaml index 35bab66..17532c6 100644 --- a/target/nextflow/convert/from_spatialdata_to_h5mu/.config.vsh.yaml +++ b/target/nextflow/convert/from_spatialdata_to_h5mu/.config.vsh.yaml @@ -1,6 +1,6 @@ name: "from_spatialdata_to_h5mu" namespace: "convert" -version: "v0.1.0" +version: "v0.1.1" authors: - name: "Dorien Roosen" roles: @@ -180,7 +180,7 @@ engines: id: "docker" image: "python:3.12-slim" target_registry: "images.viash-hub.com" - target_tag: "v0.1.0" + target_tag: "v0.1.1" namespace_separator: "/" setup: - type: "apt" @@ -220,11 +220,11 @@ build_info: output: "target/nextflow/convert/from_spatialdata_to_h5mu" executable: "target/nextflow/convert/from_spatialdata_to_h5mu/main.nf" viash_version: "0.9.4" - git_commit: "3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36" + git_commit: "2baba3fff0930478c26da7e1bd99b29ee5dd83b3" git_remote: "https://github.com/openpipelines-bio/openpipeline_spatial" package_config: name: "openpipeline_spatial" - version: "v0.1.0" + version: "v0.1.1" info: test_resources: - type: "s3" @@ -244,7 +244,7 @@ package_config: )'" - ".engines += { type: \"native\" }" - ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'" - - ".engines[.type == 'docker'].target_tag := 'v0.1.0'" + - ".engines[.type == 'docker'].target_tag := 'v0.1.1'" organization: "vsh" links: repository: "https://github.com/openpipelines-bio/openpipeline_spatial" diff --git a/target/nextflow/convert/from_spatialdata_to_h5mu/main.nf b/target/nextflow/convert/from_spatialdata_to_h5mu/main.nf index c804b1e..d6e8bfb 100644 --- a/target/nextflow/convert/from_spatialdata_to_h5mu/main.nf +++ b/target/nextflow/convert/from_spatialdata_to_h5mu/main.nf @@ -1,4 +1,4 @@ -// from_spatialdata_to_h5mu v0.1.0 +// from_spatialdata_to_h5mu v0.1.1 // // This wrapper script is auto-generated by viash 0.9.4 and is thus a derivative // work thereof. This software comes with ABSOLUTELY NO WARRANTY from Data @@ -3036,7 +3036,7 @@ meta = [ "config": processConfig(readJsonBlob('''{ "name" : "from_spatialdata_to_h5mu", "namespace" : "convert", - "version" : "v0.1.0", + "version" : "v0.1.1", "authors" : [ { "name" : "Dorien Roosen", @@ -3274,7 +3274,7 @@ meta = [ "id" : "docker", "image" : "python:3.12-slim", "target_registry" : "images.viash-hub.com", - "target_tag" : "v0.1.0", + "target_tag" : "v0.1.1", "namespace_separator" : "/", "setup" : [ { @@ -3330,12 +3330,12 @@ meta = [ "engine" : "docker|native", "output" : "/workdir/root/repo/target/nextflow/convert/from_spatialdata_to_h5mu", "viash_version" : "0.9.4", - "git_commit" : "3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36", + "git_commit" : "2baba3fff0930478c26da7e1bd99b29ee5dd83b3", "git_remote" : "https://github.com/openpipelines-bio/openpipeline_spatial" }, "package_config" : { "name" : "openpipeline_spatial", - "version" : "v0.1.0", + "version" : "v0.1.1", "info" : { "test_resources" : [ { @@ -3360,7 +3360,7 @@ meta = [ ".resources += {path: '/src/workflows/utils/labels.config', dest: 'nextflow_labels.config'}\n.runners[.type == 'nextflow'].config.script := 'includeConfig(\\"nextflow_labels.config\\")'", ".engines += { type: \\"native\\" }", ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'", - ".engines[.type == 'docker'].target_tag := 'v0.1.0'" + ".engines[.type == 'docker'].target_tag := 'v0.1.1'" ], "organization" : "vsh", "links" : { @@ -3814,7 +3814,7 @@ meta["defaults"] = [ "container" : { "registry" : "images.viash-hub.com", "image" : "vsh/openpipeline_spatial/convert/from_spatialdata_to_h5mu", - "tag" : "v0.1.0" + "tag" : "v0.1.1" }, "label" : [ "lowmem", diff --git a/target/nextflow/convert/from_spatialdata_to_h5mu/nextflow.config b/target/nextflow/convert/from_spatialdata_to_h5mu/nextflow.config index c723588..ffd2f02 100644 --- a/target/nextflow/convert/from_spatialdata_to_h5mu/nextflow.config +++ b/target/nextflow/convert/from_spatialdata_to_h5mu/nextflow.config @@ -2,7 +2,7 @@ manifest { name = 'convert/from_spatialdata_to_h5mu' mainScript = 'main.nf' nextflowVersion = '!>=20.12.1-edge' - version = 'v0.1.0' + version = 'v0.1.1' description = 'Reads in the Tables field stored in a SpatialData object and converts it to an h5mu file.\n \n' author = 'Dorien Roosen, Weiwei Schultz' } diff --git a/target/nextflow/convert/from_xenium_to_h5mu/.config.vsh.yaml b/target/nextflow/convert/from_xenium_to_h5mu/.config.vsh.yaml index ffe3f9c..93c2e07 100644 --- a/target/nextflow/convert/from_xenium_to_h5mu/.config.vsh.yaml +++ b/target/nextflow/convert/from_xenium_to_h5mu/.config.vsh.yaml @@ -1,6 +1,6 @@ name: "from_xenium_to_h5mu" namespace: "convert" -version: "v0.1.0" +version: "v0.1.1" authors: - name: "Dorien Roosen" roles: @@ -97,6 +97,8 @@ resources: is_executable: true - type: "file" path: "setup_logger.py" +- type: "file" + path: "unzip_archived_folder.py" - type: "file" path: "nextflow_labels.config" dest: "nextflow_labels.config" @@ -198,7 +200,7 @@ engines: id: "docker" image: "python:3.12-slim" target_registry: "images.viash-hub.com" - target_tag: "v0.1.0" + target_tag: "v0.1.1" namespace_separator: "/" setup: - type: "apt" @@ -217,6 +219,10 @@ engines: nelse: exit(1)\")" upgrade: true test_setup: + - type: "apt" + packages: + - "zip" + interactive: false - type: "python" user: false packages: @@ -233,11 +239,11 @@ build_info: output: "target/nextflow/convert/from_xenium_to_h5mu" executable: "target/nextflow/convert/from_xenium_to_h5mu/main.nf" viash_version: "0.9.4" - git_commit: "3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36" + git_commit: "2baba3fff0930478c26da7e1bd99b29ee5dd83b3" git_remote: "https://github.com/openpipelines-bio/openpipeline_spatial" package_config: name: "openpipeline_spatial" - version: "v0.1.0" + version: "v0.1.1" info: test_resources: - type: "s3" @@ -257,7 +263,7 @@ package_config: )'" - ".engines += { type: \"native\" }" - ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'" - - ".engines[.type == 'docker'].target_tag := 'v0.1.0'" + - ".engines[.type == 'docker'].target_tag := 'v0.1.1'" organization: "vsh" links: repository: "https://github.com/openpipelines-bio/openpipeline_spatial" diff --git a/target/nextflow/convert/from_xenium_to_h5mu/main.nf b/target/nextflow/convert/from_xenium_to_h5mu/main.nf index 608902e..7b32872 100644 --- a/target/nextflow/convert/from_xenium_to_h5mu/main.nf +++ b/target/nextflow/convert/from_xenium_to_h5mu/main.nf @@ -1,4 +1,4 @@ -// from_xenium_to_h5mu v0.1.0 +// from_xenium_to_h5mu v0.1.1 // // This wrapper script is auto-generated by viash 0.9.4 and is thus a derivative // work thereof. This software comes with ABSOLUTELY NO WARRANTY from Data @@ -3035,7 +3035,7 @@ meta = [ "config": processConfig(readJsonBlob('''{ "name" : "from_xenium_to_h5mu", "namespace" : "convert", - "version" : "v0.1.0", + "version" : "v0.1.1", "authors" : [ { "name" : "Dorien Roosen", @@ -3162,6 +3162,10 @@ meta = [ "type" : "file", "path" : "/src/utils/setup_logger.py" }, + { + "type" : "file", + "path" : "/src/utils/unzip_archived_folder.py" + }, { "type" : "file", "path" : "/src/workflows/utils/labels.config", @@ -3284,7 +3288,7 @@ meta = [ "id" : "docker", "image" : "python:3.12-slim", "target_registry" : "images.viash-hub.com", - "target_tag" : "v0.1.0", + "target_tag" : "v0.1.1", "namespace_separator" : "/", "setup" : [ { @@ -3310,6 +3314,13 @@ meta = [ } ], "test_setup" : [ + { + "type" : "apt", + "packages" : [ + "zip" + ], + "interactive" : false + }, { "type" : "python", "user" : false, @@ -3331,12 +3342,12 @@ meta = [ "engine" : "docker|native", "output" : "/workdir/root/repo/target/nextflow/convert/from_xenium_to_h5mu", "viash_version" : "0.9.4", - "git_commit" : "3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36", + "git_commit" : "2baba3fff0930478c26da7e1bd99b29ee5dd83b3", "git_remote" : "https://github.com/openpipelines-bio/openpipeline_spatial" }, "package_config" : { "name" : "openpipeline_spatial", - "version" : "v0.1.0", + "version" : "v0.1.1", "info" : { "test_resources" : [ { @@ -3361,7 +3372,7 @@ meta = [ ".resources += {path: '/src/workflows/utils/labels.config', dest: 'nextflow_labels.config'}\n.runners[.type == 'nextflow'].config.script := 'includeConfig(\\"nextflow_labels.config\\")'", ".engines += { type: \\"native\\" }", ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'", - ".engines[.type == 'docker'].target_tag := 'v0.1.0'" + ".engines[.type == 'docker'].target_tag := 'v0.1.1'" ], "organization" : "vsh", "links" : { @@ -3386,7 +3397,9 @@ from pathlib import Path import scanpy as sc import pandas as pd import mudata as mu +import zipfile import json +import os ## VIASH START # The following code has been auto-generated by Viash. @@ -3426,21 +3439,46 @@ dep = { sys.path.append(meta["resources_dir"]) from setup_logger import setup_logger +from unzip_archived_folder import extract_selected_files_from_zip logger = setup_logger() -# Expected folder structure (showing only relevant files): -# ├── cell_feature_matrix.h5 -# ├── cells.parquet -# ├── experiment.xenium -# └── metrics_summary.csv -input_dir = Path(par["input"]) -input_data = { - "count_matrix": input_dir / "cell_feature_matrix.h5", - "cells_metadata": input_dir / "cells.parquet", - "experiment": input_dir / "experiment.xenium", - "metrics_summary": input_dir / "metrics_summary.csv", -} + +def _retrieve_input_data(xenium_output_bundle): + # Expected folder structure (showing only relevant files): + # ├── cell_feature_matrix.h5 + # ├── cells.parquet + # ├── experiment.xenium + # └── metrics_summary.csv + + required_file_patterns = { + "count_matrix": "**/cell_feature_matrix.h5", + "cells_metadata": "**/cells.parquet", + "experiment": "**/experiment.xenium", + "metrics_summary": "**/metrics_summary.csv", + } + + if zipfile.is_zipfile(xenium_output_bundle): + xenium_output_bundle = extract_selected_files_from_zip( + xenium_output_bundle, + members=[pattern for pattern in required_file_patterns.values()], + ) + else: + xenium_output_bundle = Path(xenium_output_bundle) + + assert os.path.isdir(xenium_output_bundle), ( + "Input is expected to be a (compressed) directory." + ) + + input_data = {} + for key, pattern in required_file_patterns.items(): + file = list(xenium_output_bundle.glob(pattern)) + assert len(file) == 1, ( + f"Expected exactly one file matching pattern {pattern}, found {len(file)}." + ) + input_data[key] = file[0] + + return input_data def _format_cell_id_column(cell_id_column: pd.Series) -> pd.Series: @@ -3453,9 +3491,7 @@ def _format_cell_id_column(cell_id_column: pd.Series) -> pd.Series: # Read data from Xenium output bundle logger.info("Reading input data...") -assert all([file.exists() for file in input_data.values()]), ( - f"Not all required input files are found. Make sure that {par['input']} contains {input_data.values()}." -) +input_data = _retrieve_input_data(par["input"]) adata = sc.read_10x_h5(input_data["count_matrix"]) metadata = pd.read_parquet(input_data["cells_metadata"], engine="pyarrow") @@ -3862,7 +3898,7 @@ meta["defaults"] = [ "container" : { "registry" : "images.viash-hub.com", "image" : "vsh/openpipeline_spatial/convert/from_xenium_to_h5mu", - "tag" : "v0.1.0" + "tag" : "v0.1.1" }, "label" : [ "lowmem", diff --git a/target/nextflow/convert/from_xenium_to_h5mu/nextflow.config b/target/nextflow/convert/from_xenium_to_h5mu/nextflow.config index 83579b7..9da800c 100644 --- a/target/nextflow/convert/from_xenium_to_h5mu/nextflow.config +++ b/target/nextflow/convert/from_xenium_to_h5mu/nextflow.config @@ -2,7 +2,7 @@ manifest { name = 'convert/from_xenium_to_h5mu' mainScript = 'main.nf' nextflowVersion = '!>=20.12.1-edge' - version = 'v0.1.0' + version = 'v0.1.1' description = 'Converts the output from Xenium to a single .h5mu file, where the count matrix is written to the `rna` modality.\nThe following files are expected to be present in the Xenium output bundle:\n├── cell_feature_matrix.h5\n├── cells.parquet\n├── experiment.xenium\n└── metrics_summary.csv\n' author = 'Dorien Roosen' } diff --git a/target/nextflow/convert/from_xenium_to_h5mu/unzip_archived_folder.py b/target/nextflow/convert/from_xenium_to_h5mu/unzip_archived_folder.py new file mode 100644 index 0000000..1e5bf39 --- /dev/null +++ b/target/nextflow/convert/from_xenium_to_h5mu/unzip_archived_folder.py @@ -0,0 +1,50 @@ +import fnmatch +import zipfile +import tempfile +from pathlib import Path +from typing import Union + + +def unzip_archived_folder(archived_folder: Union[str, Path]) -> Union[str, Path]: + """ + Extracts a ZIP archive to a temporary directory and returns the path to the extracted folder. + + Args: + zip_path (Union[str, Path]): Path to the ZIP archive. + + Returns: + extracted_path (Union[str, Path]): Path to the extracted folder inside the temporary directory. + """ + + temp_dir = Path(tempfile.TemporaryDirectory().name) + with zipfile.ZipFile(archived_folder, "r") as archive: + archive.extractall(temp_dir) + + return temp_dir / Path(archived_folder).stem + + +def extract_selected_files_from_zip( + zip_path: Union[str, Path], members: list[Union[str, Path]] +) -> Union[str, Path]: + """ + Extracts selected files (supports glob patterns) from a ZIP archive to a temporary directory. + + Args: + zip_path (Union[str, Path]): Path to the ZIP archive. + members (list[str]): List of file paths within the archive to extract. + + Returns: + Path: Path to the extraction directory. + """ + + temp_dir = Path(tempfile.TemporaryDirectory().name) + + with zipfile.ZipFile(zip_path, "r") as archive: + all_files = archive.namelist() + selected = set() + for pattern in members: + selected.update(fnmatch.filter(all_files, str(pattern))) + for member in selected: + archive.extract(member, temp_dir) + + return temp_dir diff --git a/target/nextflow/convert/from_xenium_to_spatialdata/.config.vsh.yaml b/target/nextflow/convert/from_xenium_to_spatialdata/.config.vsh.yaml index b6ebe2a..408d2dd 100644 --- a/target/nextflow/convert/from_xenium_to_spatialdata/.config.vsh.yaml +++ b/target/nextflow/convert/from_xenium_to_spatialdata/.config.vsh.yaml @@ -1,6 +1,6 @@ name: "from_xenium_to_spatialdata" namespace: "convert" -version: "v0.1.0" +version: "v0.1.1" authors: - name: "Dorien Roosen" roles: @@ -173,6 +173,8 @@ resources: is_executable: true - type: "file" path: "setup_logger.py" +- type: "file" + path: "unzip_archived_folder.py" - type: "file" path: "nextflow_labels.config" dest: "nextflow_labels.config" @@ -278,7 +280,7 @@ engines: id: "docker" image: "python:3.12-slim" target_registry: "images.viash-hub.com" - target_tag: "v0.1.0" + target_tag: "v0.1.1" namespace_separator: "/" setup: - type: "apt" @@ -303,6 +305,10 @@ engines: github: - "openpipelines-bio/core#subdirectory=packages/python/openpipeline_testutils" upgrade: true + - type: "apt" + packages: + - "zip" + interactive: false entrypoint: [] cmd: null - type: "native" @@ -314,11 +320,11 @@ build_info: output: "target/nextflow/convert/from_xenium_to_spatialdata" executable: "target/nextflow/convert/from_xenium_to_spatialdata/main.nf" viash_version: "0.9.4" - git_commit: "3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36" + git_commit: "2baba3fff0930478c26da7e1bd99b29ee5dd83b3" git_remote: "https://github.com/openpipelines-bio/openpipeline_spatial" package_config: name: "openpipeline_spatial" - version: "v0.1.0" + version: "v0.1.1" info: test_resources: - type: "s3" @@ -338,7 +344,7 @@ package_config: )'" - ".engines += { type: \"native\" }" - ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'" - - ".engines[.type == 'docker'].target_tag := 'v0.1.0'" + - ".engines[.type == 'docker'].target_tag := 'v0.1.1'" organization: "vsh" links: repository: "https://github.com/openpipelines-bio/openpipeline_spatial" diff --git a/target/nextflow/convert/from_xenium_to_spatialdata/main.nf b/target/nextflow/convert/from_xenium_to_spatialdata/main.nf index b4e5a62..cb2055f 100644 --- a/target/nextflow/convert/from_xenium_to_spatialdata/main.nf +++ b/target/nextflow/convert/from_xenium_to_spatialdata/main.nf @@ -1,4 +1,4 @@ -// from_xenium_to_spatialdata v0.1.0 +// from_xenium_to_spatialdata v0.1.1 // // This wrapper script is auto-generated by viash 0.9.4 and is thus a derivative // work thereof. This software comes with ABSOLUTELY NO WARRANTY from Data @@ -3036,7 +3036,7 @@ meta = [ "config": processConfig(readJsonBlob('''{ "name" : "from_xenium_to_spatialdata", "namespace" : "convert", - "version" : "v0.1.0", + "version" : "v0.1.1", "authors" : [ { "name" : "Dorien Roosen", @@ -3251,6 +3251,10 @@ meta = [ "type" : "file", "path" : "/src/utils/setup_logger.py" }, + { + "type" : "file", + "path" : "/src/utils/unzip_archived_folder.py" + }, { "type" : "file", "path" : "/src/workflows/utils/labels.config", @@ -3373,7 +3377,7 @@ meta = [ "id" : "docker", "image" : "python:3.12-slim", "target_registry" : "images.viash-hub.com", - "target_tag" : "v0.1.0", + "target_tag" : "v0.1.1", "namespace_separator" : "/", "setup" : [ { @@ -3411,6 +3415,13 @@ meta = [ "openpipelines-bio/core#subdirectory=packages/python/openpipeline_testutils" ], "upgrade" : true + }, + { + "type" : "apt", + "packages" : [ + "zip" + ], + "interactive" : false } ] }, @@ -3425,12 +3436,12 @@ meta = [ "engine" : "docker|native", "output" : "/workdir/root/repo/target/nextflow/convert/from_xenium_to_spatialdata", "viash_version" : "0.9.4", - "git_commit" : "3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36", + "git_commit" : "2baba3fff0930478c26da7e1bd99b29ee5dd83b3", "git_remote" : "https://github.com/openpipelines-bio/openpipeline_spatial" }, "package_config" : { "name" : "openpipeline_spatial", - "version" : "v0.1.0", + "version" : "v0.1.1", "info" : { "test_resources" : [ { @@ -3455,7 +3466,7 @@ meta = [ ".resources += {path: '/src/workflows/utils/labels.config', dest: 'nextflow_labels.config'}\n.runners[.type == 'nextflow'].config.script := 'includeConfig(\\"nextflow_labels.config\\")'", ".engines += { type: \\"native\\" }", ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'", - ".engines[.type == 'docker'].target_tag := 'v0.1.0'" + ".engines[.type == 'docker'].target_tag := 'v0.1.1'" ], "organization" : "vsh", "links" : { @@ -3477,6 +3488,8 @@ tempscript=".viash_script.py" cat > "$tempscript" << VIASHMAIN import sys from spatialdata_io import xenium +import zipfile +from pathlib import Path ## VIASH START # The following code has been auto-generated by Viash. @@ -3523,12 +3536,29 @@ dep = { sys.path.append(meta["resources_dir"]) from setup_logger import setup_logger +from unzip_archived_folder import unzip_archived_folder logger = setup_logger() logger.info("Reading in Xenium data...") + +if zipfile.is_zipfile(par["input"]): + required_file_patterns = [ + "**/experiment.xenium", + "**/nucleus_boundaries.parquet", + "**/cell_boundaries.parquet", + "**/transcripts.parquet", + "**/cell_feature_matrix.h5", + "**/cells.parquet", + "**/morphology_mip.ome.tif", + "**/morphology_focus.ome.tif", + ] + xenium_output_bundle = unzip_archived_folder(par["input"]) +else: + xenium_output_bundle = Path(par["input"]) + sdata = xenium( - par["input"], + xenium_output_bundle, cells_boundaries=par["cells_boundaries"], nucleus_boundaries=par["nucleus_boundaries"], cells_as_circles=par["cells_as_circles"], @@ -3927,7 +3957,7 @@ meta["defaults"] = [ "container" : { "registry" : "images.viash-hub.com", "image" : "vsh/openpipeline_spatial/convert/from_xenium_to_spatialdata", - "tag" : "v0.1.0" + "tag" : "v0.1.1" }, "label" : [ "lowmem", diff --git a/target/nextflow/convert/from_xenium_to_spatialdata/nextflow.config b/target/nextflow/convert/from_xenium_to_spatialdata/nextflow.config index 50dd1c2..8a26d3c 100644 --- a/target/nextflow/convert/from_xenium_to_spatialdata/nextflow.config +++ b/target/nextflow/convert/from_xenium_to_spatialdata/nextflow.config @@ -2,7 +2,7 @@ manifest { name = 'convert/from_xenium_to_spatialdata' mainScript = 'main.nf' nextflowVersion = '!>=20.12.1-edge' - version = 'v0.1.0' + version = 'v0.1.1' description = 'Converts the output from 10X Genomics Xenium dataset into a SpatialData objcet.\nBy default, the following files will be converted:\n - `experiment.xenium`: File containing specifications.\n - `nucleus_boundaries.parquet`: Polygons of nucleus boundaries.\n - `cell_boundaries.parquet`: Polygons of cell boundaries.\n - `transcripts.parquet`: File containing transcripts.\n - `cell_feature_matrix.h5`: File containing cell feature matrix.\n - `cells.parquet`: File containing cell metadata.\n - `morphology_mip.ome.tif`: File containing morphology mip.\n - `morphology_focus.ome.tif`: File containing morphology focus.\n \n' author = 'Dorien Roosen, Weiwei Schultz' } diff --git a/target/nextflow/convert/from_xenium_to_spatialdata/unzip_archived_folder.py b/target/nextflow/convert/from_xenium_to_spatialdata/unzip_archived_folder.py new file mode 100644 index 0000000..1e5bf39 --- /dev/null +++ b/target/nextflow/convert/from_xenium_to_spatialdata/unzip_archived_folder.py @@ -0,0 +1,50 @@ +import fnmatch +import zipfile +import tempfile +from pathlib import Path +from typing import Union + + +def unzip_archived_folder(archived_folder: Union[str, Path]) -> Union[str, Path]: + """ + Extracts a ZIP archive to a temporary directory and returns the path to the extracted folder. + + Args: + zip_path (Union[str, Path]): Path to the ZIP archive. + + Returns: + extracted_path (Union[str, Path]): Path to the extracted folder inside the temporary directory. + """ + + temp_dir = Path(tempfile.TemporaryDirectory().name) + with zipfile.ZipFile(archived_folder, "r") as archive: + archive.extractall(temp_dir) + + return temp_dir / Path(archived_folder).stem + + +def extract_selected_files_from_zip( + zip_path: Union[str, Path], members: list[Union[str, Path]] +) -> Union[str, Path]: + """ + Extracts selected files (supports glob patterns) from a ZIP archive to a temporary directory. + + Args: + zip_path (Union[str, Path]): Path to the ZIP archive. + members (list[str]): List of file paths within the archive to extract. + + Returns: + Path: Path to the extraction directory. + """ + + temp_dir = Path(tempfile.TemporaryDirectory().name) + + with zipfile.ZipFile(zip_path, "r") as archive: + all_files = archive.namelist() + selected = set() + for pattern in members: + selected.update(fnmatch.filter(all_files, str(pattern))) + for member in selected: + archive.extract(member, temp_dir) + + return temp_dir diff --git a/target/nextflow/convert/from_xenium_to_spatialexperiment/.config.vsh.yaml b/target/nextflow/convert/from_xenium_to_spatialexperiment/.config.vsh.yaml index dc3a548..c7f890e 100644 --- a/target/nextflow/convert/from_xenium_to_spatialexperiment/.config.vsh.yaml +++ b/target/nextflow/convert/from_xenium_to_spatialexperiment/.config.vsh.yaml @@ -1,6 +1,6 @@ name: "from_xenium_to_spatialexperiment" namespace: "convert" -version: "v0.1.0" +version: "v0.1.1" authors: - name: "Dorien Roosen" roles: @@ -88,6 +88,8 @@ resources: - type: "r_script" path: "script.R" is_executable: true +- type: "file" + path: "unzip_archived_folder.R" - type: "file" path: "nextflow_labels.config" dest: "nextflow_labels.config" @@ -192,7 +194,7 @@ engines: id: "docker" image: "rocker/r2u:24.04" target_registry: "images.viash-hub.com" - target_tag: "v0.1.0" + target_tag: "v0.1.1" namespace_separator: "/" setup: - type: "apt" @@ -222,11 +224,11 @@ build_info: output: "target/nextflow/convert/from_xenium_to_spatialexperiment" executable: "target/nextflow/convert/from_xenium_to_spatialexperiment/main.nf" viash_version: "0.9.4" - git_commit: "3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36" + git_commit: "2baba3fff0930478c26da7e1bd99b29ee5dd83b3" git_remote: "https://github.com/openpipelines-bio/openpipeline_spatial" package_config: name: "openpipeline_spatial" - version: "v0.1.0" + version: "v0.1.1" info: test_resources: - type: "s3" @@ -246,7 +248,7 @@ package_config: )'" - ".engines += { type: \"native\" }" - ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'" - - ".engines[.type == 'docker'].target_tag := 'v0.1.0'" + - ".engines[.type == 'docker'].target_tag := 'v0.1.1'" organization: "vsh" links: repository: "https://github.com/openpipelines-bio/openpipeline_spatial" diff --git a/target/nextflow/convert/from_xenium_to_spatialexperiment/main.nf b/target/nextflow/convert/from_xenium_to_spatialexperiment/main.nf index 519e90e..783a14c 100644 --- a/target/nextflow/convert/from_xenium_to_spatialexperiment/main.nf +++ b/target/nextflow/convert/from_xenium_to_spatialexperiment/main.nf @@ -1,4 +1,4 @@ -// from_xenium_to_spatialexperiment v0.1.0 +// from_xenium_to_spatialexperiment v0.1.1 // // This wrapper script is auto-generated by viash 0.9.4 and is thus a derivative // work thereof. This software comes with ABSOLUTELY NO WARRANTY from Data @@ -3035,7 +3035,7 @@ meta = [ "config": processConfig(readJsonBlob('''{ "name" : "from_xenium_to_spatialexperiment", "namespace" : "convert", - "version" : "v0.1.0", + "version" : "v0.1.1", "authors" : [ { "name" : "Dorien Roosen", @@ -3147,6 +3147,10 @@ meta = [ "path" : "script.R", "is_executable" : true }, + { + "type" : "file", + "path" : "/src/utils/unzip_archived_folder.R" + }, { "type" : "file", "path" : "/src/workflows/utils/labels.config", @@ -3269,7 +3273,7 @@ meta = [ "id" : "docker", "image" : "rocker/r2u:24.04", "target_registry" : "images.viash-hub.com", - "target_tag" : "v0.1.0", + "target_tag" : "v0.1.1", "namespace_separator" : "/", "setup" : [ { @@ -3311,12 +3315,12 @@ meta = [ "engine" : "docker|native", "output" : "/workdir/root/repo/target/nextflow/convert/from_xenium_to_spatialexperiment", "viash_version" : "0.9.4", - "git_commit" : "3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36", + "git_commit" : "2baba3fff0930478c26da7e1bd99b29ee5dd83b3", "git_remote" : "https://github.com/openpipelines-bio/openpipeline_spatial" }, "package_config" : { "name" : "openpipeline_spatial", - "version" : "v0.1.0", + "version" : "v0.1.1", "info" : { "test_resources" : [ { @@ -3341,7 +3345,7 @@ meta = [ ".resources += {path: '/src/workflows/utils/labels.config', dest: 'nextflow_labels.config'}\n.runners[.type == 'nextflow'].config.script := 'includeConfig(\\"nextflow_labels.config\\")'", ".engines += { type: \\"native\\" }", ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'", - ".engines[.type == 'docker'].target_tag := 'v0.1.0'" + ".engines[.type == 'docker'].target_tag := 'v0.1.1'" ], "organization" : "vsh", "links" : { @@ -3406,9 +3410,30 @@ rm(.viash_orig_warn) ### VIASH END +source(paste0(meta\\$resources_dir, "/unzip_archived_folder.R")) +cat("Reading input data...") +if (tools::file_ext(par\\$input) == "zip") { + required_file_patterns <- c( + "**/cell_feature_matrix.h5", + "**/*.parquet", + "**/experiment.xenium" + ) + tmp_dir <- extract_selected_files( + par\\$input, + members = required_file_patterns + ) + xenium_output_bundle <- file.path( + tmp_dir, + tools::file_path_sans_ext(basename(par\\$input)) + ) +} else { + xenium_output_bundle <- par\\$input +} + +cat("Converting to SpatialExperiment") spe <- readXeniumSXE( - dirName = par\\$input, + dirName = xenium_output_bundle, returnType = "SPE", countMatPattern = "cell_feature_matrix.h5", metaDataPattern = "cells.parquet", @@ -3418,6 +3443,7 @@ spe <- readXeniumSXE( altExps = par\\$alternative_experiment_features ) +cat("Saving output...") saveRDS(spe, file = par\\$output) VIASHMAIN Rscript "$tempscript" @@ -3801,7 +3827,7 @@ meta["defaults"] = [ "container" : { "registry" : "images.viash-hub.com", "image" : "vsh/openpipeline_spatial/convert/from_xenium_to_spatialexperiment", - "tag" : "v0.1.0" + "tag" : "v0.1.1" }, "label" : [ "lowmem", diff --git a/target/nextflow/convert/from_xenium_to_spatialexperiment/nextflow.config b/target/nextflow/convert/from_xenium_to_spatialexperiment/nextflow.config index 9a71841..926cbf7 100644 --- a/target/nextflow/convert/from_xenium_to_spatialexperiment/nextflow.config +++ b/target/nextflow/convert/from_xenium_to_spatialexperiment/nextflow.config @@ -2,7 +2,7 @@ manifest { name = 'convert/from_xenium_to_spatialexperiment' mainScript = 'main.nf' nextflowVersion = '!>=20.12.1-edge' - version = 'v0.1.0' + version = 'v0.1.1' description = 'Creates a SpatialExperiment object from the downloaded unzipped Xenium Output Bundle directory\nfor 10x Genomics Xenium spatial gene expression data, and saves it as a SpatialExperiment object.\nThe constructor assumes the downloaded unzipped Xenium Output Bundle has the following structure:\n\nMandatory files\n· | — cell_feature_matrix.h5\n· | — cells.parquet\nOptional files, by default added to the metadata() as a list of paths (will be converted to parquet):\n· | — transcripts.parquet\n· | — cell_boundaries.parquet\n· | — nucleus_boundaries.parquet\n· | — experiment.xenium\n' author = 'Dorien Roosen' } diff --git a/target/nextflow/convert/from_xenium_to_spatialexperiment/unzip_archived_folder.R b/target/nextflow/convert/from_xenium_to_spatialexperiment/unzip_archived_folder.R new file mode 100644 index 0000000..78966bc --- /dev/null +++ b/target/nextflow/convert/from_xenium_to_spatialexperiment/unzip_archived_folder.R @@ -0,0 +1,22 @@ +extract_selected_files <- function(zip_path, members) { + # Create a temporary directory for extraction + temp_dir <- tempfile("unzip_dir_") + dir.create(temp_dir) + + # List all files in the archive + all_files <- utils::unzip(zip_path, list = TRUE)$Name + + # Find files matching any of the glob patterns in 'members' + selected <- unique(unlist( + lapply(members, function(pattern) { + regex <- glob2rx(pattern) + grep(regex, all_files, value = TRUE) + }) + )) + + # Extract only the selected files + utils::unzip(zip_path, files = selected, exdir = temp_dir) + + # Return the path to the extracted folder + file.path(temp_dir) +} diff --git a/target/nextflow/filter/subset_cosmx/.config.vsh.yaml b/target/nextflow/filter/subset_cosmx/.config.vsh.yaml index 2a163c6..bd09341 100644 --- a/target/nextflow/filter/subset_cosmx/.config.vsh.yaml +++ b/target/nextflow/filter/subset_cosmx/.config.vsh.yaml @@ -1,6 +1,6 @@ name: "subset_cosmx" namespace: "filter" -version: "v0.1.0" +version: "v0.1.1" authors: - name: "Dorien Roosen" roles: @@ -191,7 +191,7 @@ engines: id: "docker" image: "python:3.12-slim" target_registry: "images.viash-hub.com" - target_tag: "v0.1.0" + target_tag: "v0.1.1" namespace_separator: "/" setup: - type: "apt" @@ -227,11 +227,11 @@ build_info: output: "target/nextflow/filter/subset_cosmx" executable: "target/nextflow/filter/subset_cosmx/main.nf" viash_version: "0.9.4" - git_commit: "3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36" + git_commit: "2baba3fff0930478c26da7e1bd99b29ee5dd83b3" git_remote: "https://github.com/openpipelines-bio/openpipeline_spatial" package_config: name: "openpipeline_spatial" - version: "v0.1.0" + version: "v0.1.1" info: test_resources: - type: "s3" @@ -251,7 +251,7 @@ package_config: )'" - ".engines += { type: \"native\" }" - ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'" - - ".engines[.type == 'docker'].target_tag := 'v0.1.0'" + - ".engines[.type == 'docker'].target_tag := 'v0.1.1'" organization: "vsh" links: repository: "https://github.com/openpipelines-bio/openpipeline_spatial" diff --git a/target/nextflow/filter/subset_cosmx/main.nf b/target/nextflow/filter/subset_cosmx/main.nf index aeec12c..306e62b 100644 --- a/target/nextflow/filter/subset_cosmx/main.nf +++ b/target/nextflow/filter/subset_cosmx/main.nf @@ -1,4 +1,4 @@ -// subset_cosmx v0.1.0 +// subset_cosmx v0.1.1 // // This wrapper script is auto-generated by viash 0.9.4 and is thus a derivative // work thereof. This software comes with ABSOLUTELY NO WARRANTY from Data @@ -3036,7 +3036,7 @@ meta = [ "config": processConfig(readJsonBlob('''{ "name" : "subset_cosmx", "namespace" : "filter", - "version" : "v0.1.0", + "version" : "v0.1.1", "authors" : [ { "name" : "Dorien Roosen", @@ -3281,7 +3281,7 @@ meta = [ "id" : "docker", "image" : "python:3.12-slim", "target_registry" : "images.viash-hub.com", - "target_tag" : "v0.1.0", + "target_tag" : "v0.1.1", "namespace_separator" : "/", "setup" : [ { @@ -3333,12 +3333,12 @@ meta = [ "engine" : "docker|native", "output" : "/workdir/root/repo/target/nextflow/filter/subset_cosmx", "viash_version" : "0.9.4", - "git_commit" : "3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36", + "git_commit" : "2baba3fff0930478c26da7e1bd99b29ee5dd83b3", "git_remote" : "https://github.com/openpipelines-bio/openpipeline_spatial" }, "package_config" : { "name" : "openpipeline_spatial", - "version" : "v0.1.0", + "version" : "v0.1.1", "info" : { "test_resources" : [ { @@ -3363,7 +3363,7 @@ meta = [ ".resources += {path: '/src/workflows/utils/labels.config', dest: 'nextflow_labels.config'}\n.runners[.type == 'nextflow'].config.script := 'includeConfig(\\"nextflow_labels.config\\")'", ".engines += { type: \\"native\\" }", ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'", - ".engines[.type == 'docker'].target_tag := 'v0.1.0'" + ".engines[.type == 'docker'].target_tag := 'v0.1.1'" ], "organization" : "vsh", "links" : { @@ -3858,7 +3858,7 @@ meta["defaults"] = [ "container" : { "registry" : "images.viash-hub.com", "image" : "vsh/openpipeline_spatial/filter/subset_cosmx", - "tag" : "v0.1.0" + "tag" : "v0.1.1" }, "label" : [ "lowmem", diff --git a/target/nextflow/filter/subset_cosmx/nextflow.config b/target/nextflow/filter/subset_cosmx/nextflow.config index 9ee0841..eb6ae01 100644 --- a/target/nextflow/filter/subset_cosmx/nextflow.config +++ b/target/nextflow/filter/subset_cosmx/nextflow.config @@ -2,7 +2,7 @@ manifest { name = 'filter/subset_cosmx' mainScript = 'main.nf' nextflowVersion = '!>=20.12.1-edge' - version = 'v0.1.0' + version = 'v0.1.1' description = 'Filters the output from NanoString experiment to keep only a subset of the fields of view.\nExpected input folder structure:\npath/to/dataset/\n ├── CellComposite/\n ├── CellLabels/\n ├── CellOverlay/\n ├── CompartmentLabels/\n ├── _exprMat_file.csv\n ├── _fov_positions_file.csv\n ├── _metadata_file.csv\n └── _tx_file.csv \n' author = 'Dorien Roosen, Weiwei Schultz' } diff --git a/target/nextflow/mapping/spaceranger_count/.config.vsh.yaml b/target/nextflow/mapping/spaceranger_count/.config.vsh.yaml index 8fae21c..3904863 100644 --- a/target/nextflow/mapping/spaceranger_count/.config.vsh.yaml +++ b/target/nextflow/mapping/spaceranger_count/.config.vsh.yaml @@ -1,6 +1,6 @@ name: "spaceranger_count" namespace: "mapping" -version: "v0.1.0" +version: "v0.1.1" authors: - name: "Jakub Majercik" roles: @@ -408,7 +408,7 @@ engines: id: "docker" image: "ghcr.io/data-intuitive/spaceranger:3.1" target_registry: "images.viash-hub.com" - target_tag: "v0.1.0" + target_tag: "v0.1.1" namespace_separator: "/" setup: - type: "docker" @@ -426,11 +426,11 @@ build_info: output: "target/nextflow/mapping/spaceranger_count" executable: "target/nextflow/mapping/spaceranger_count/main.nf" viash_version: "0.9.4" - git_commit: "3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36" + git_commit: "2baba3fff0930478c26da7e1bd99b29ee5dd83b3" git_remote: "https://github.com/openpipelines-bio/openpipeline_spatial" package_config: name: "openpipeline_spatial" - version: "v0.1.0" + version: "v0.1.1" info: test_resources: - type: "s3" @@ -450,7 +450,7 @@ package_config: )'" - ".engines += { type: \"native\" }" - ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'" - - ".engines[.type == 'docker'].target_tag := 'v0.1.0'" + - ".engines[.type == 'docker'].target_tag := 'v0.1.1'" organization: "vsh" links: repository: "https://github.com/openpipelines-bio/openpipeline_spatial" diff --git a/target/nextflow/mapping/spaceranger_count/main.nf b/target/nextflow/mapping/spaceranger_count/main.nf index 57e77cb..05e5ca1 100644 --- a/target/nextflow/mapping/spaceranger_count/main.nf +++ b/target/nextflow/mapping/spaceranger_count/main.nf @@ -1,4 +1,4 @@ -// spaceranger_count v0.1.0 +// spaceranger_count v0.1.1 // // This wrapper script is auto-generated by viash 0.9.4 and is thus a derivative // work thereof. This software comes with ABSOLUTELY NO WARRANTY from Data @@ -3035,7 +3035,7 @@ meta = [ "config": processConfig(readJsonBlob('''{ "name" : "spaceranger_count", "namespace" : "mapping", - "version" : "v0.1.0", + "version" : "v0.1.1", "authors" : [ { "name" : "Jakub Majercik", @@ -3526,7 +3526,7 @@ meta = [ "id" : "docker", "image" : "ghcr.io/data-intuitive/spaceranger:3.1", "target_registry" : "images.viash-hub.com", - "target_tag" : "v0.1.0", + "target_tag" : "v0.1.1", "namespace_separator" : "/", "setup" : [ { @@ -3548,12 +3548,12 @@ meta = [ "engine" : "docker|native", "output" : "/workdir/root/repo/target/nextflow/mapping/spaceranger_count", "viash_version" : "0.9.4", - "git_commit" : "3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36", + "git_commit" : "2baba3fff0930478c26da7e1bd99b29ee5dd83b3", "git_remote" : "https://github.com/openpipelines-bio/openpipeline_spatial" }, "package_config" : { "name" : "openpipeline_spatial", - "version" : "v0.1.0", + "version" : "v0.1.1", "info" : { "test_resources" : [ { @@ -3578,7 +3578,7 @@ meta = [ ".resources += {path: '/src/workflows/utils/labels.config', dest: 'nextflow_labels.config'}\n.runners[.type == 'nextflow'].config.script := 'includeConfig(\\"nextflow_labels.config\\")'", ".engines += { type: \\"native\\" }", ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'", - ".engines[.type == 'docker'].target_tag := 'v0.1.0'" + ".engines[.type == 'docker'].target_tag := 'v0.1.1'" ], "organization" : "vsh", "links" : { @@ -4072,7 +4072,7 @@ meta["defaults"] = [ "container" : { "registry" : "images.viash-hub.com", "image" : "vsh/openpipeline_spatial/mapping/spaceranger_count", - "tag" : "v0.1.0" + "tag" : "v0.1.1" }, "tag" : "$id" }'''), diff --git a/target/nextflow/mapping/spaceranger_count/nextflow.config b/target/nextflow/mapping/spaceranger_count/nextflow.config index d6b6de0..42e302e 100644 --- a/target/nextflow/mapping/spaceranger_count/nextflow.config +++ b/target/nextflow/mapping/spaceranger_count/nextflow.config @@ -2,7 +2,7 @@ manifest { name = 'mapping/spaceranger_count' mainScript = 'main.nf' nextflowVersion = '!>=20.12.1-edge' - version = 'v0.1.0' + version = 'v0.1.1' description = 'Count gene expression and protein expression reads from a single capture area.' author = 'Jakub Majercik' } diff --git a/target/nextflow/workflows/multiomics/spatial_process_samples/.config.vsh.yaml b/target/nextflow/workflows/multiomics/spatial_process_samples/.config.vsh.yaml index c8ff452..1412650 100644 --- a/target/nextflow/workflows/multiomics/spatial_process_samples/.config.vsh.yaml +++ b/target/nextflow/workflows/multiomics/spatial_process_samples/.config.vsh.yaml @@ -1,6 +1,6 @@ name: "spatial_process_samples" namespace: "workflows/multiomics" -version: "v0.1.0" +version: "v0.1.1" authors: - name: "Dries Schaumont" roles: @@ -640,13 +640,13 @@ build_info: output: "target/nextflow/workflows/multiomics/spatial_process_samples" executable: "target/nextflow/workflows/multiomics/spatial_process_samples/main.nf" viash_version: "0.9.4" - git_commit: "3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36" + git_commit: "2baba3fff0930478c26da7e1bd99b29ee5dd83b3" git_remote: "https://github.com/openpipelines-bio/openpipeline_spatial" dependencies: - "target/dependencies/vsh/vsh/openpipeline/v3.0.0/nextflow/workflows/multiomics/process_samples" package_config: name: "openpipeline_spatial" - version: "v0.1.0" + version: "v0.1.1" info: test_resources: - type: "s3" @@ -666,7 +666,7 @@ package_config: )'" - ".engines += { type: \"native\" }" - ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'" - - ".engines[.type == 'docker'].target_tag := 'v0.1.0'" + - ".engines[.type == 'docker'].target_tag := 'v0.1.1'" organization: "vsh" links: repository: "https://github.com/openpipelines-bio/openpipeline_spatial" diff --git a/target/nextflow/workflows/multiomics/spatial_process_samples/main.nf b/target/nextflow/workflows/multiomics/spatial_process_samples/main.nf index f252fe6..8c7d784 100644 --- a/target/nextflow/workflows/multiomics/spatial_process_samples/main.nf +++ b/target/nextflow/workflows/multiomics/spatial_process_samples/main.nf @@ -1,4 +1,4 @@ -// spatial_process_samples v0.1.0 +// spatial_process_samples v0.1.1 // // This wrapper script is auto-generated by viash 0.9.4 and is thus a derivative // work thereof. This software comes with ABSOLUTELY NO WARRANTY from Data @@ -3037,7 +3037,7 @@ meta = [ "config": processConfig(readJsonBlob('''{ "name" : "spatial_process_samples", "namespace" : "workflows/multiomics", - "version" : "v0.1.0", + "version" : "v0.1.1", "authors" : [ { "name" : "Dries Schaumont", @@ -3807,12 +3807,12 @@ meta = [ "engine" : "native", "output" : "/workdir/root/repo/target/nextflow/workflows/multiomics/spatial_process_samples", "viash_version" : "0.9.4", - "git_commit" : "3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36", + "git_commit" : "2baba3fff0930478c26da7e1bd99b29ee5dd83b3", "git_remote" : "https://github.com/openpipelines-bio/openpipeline_spatial" }, "package_config" : { "name" : "openpipeline_spatial", - "version" : "v0.1.0", + "version" : "v0.1.1", "info" : { "test_resources" : [ { @@ -3837,7 +3837,7 @@ meta = [ ".resources += {path: '/src/workflows/utils/labels.config', dest: 'nextflow_labels.config'}\n.runners[.type == 'nextflow'].config.script := 'includeConfig(\\"nextflow_labels.config\\")'", ".engines += { type: \\"native\\" }", ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'", - ".engines[.type == 'docker'].target_tag := 'v0.1.0'" + ".engines[.type == 'docker'].target_tag := 'v0.1.1'" ], "organization" : "vsh", "links" : { diff --git a/target/nextflow/workflows/multiomics/spatial_process_samples/nextflow.config b/target/nextflow/workflows/multiomics/spatial_process_samples/nextflow.config index 31394ec..f988924 100644 --- a/target/nextflow/workflows/multiomics/spatial_process_samples/nextflow.config +++ b/target/nextflow/workflows/multiomics/spatial_process_samples/nextflow.config @@ -2,7 +2,7 @@ manifest { name = 'workflows/multiomics/spatial_process_samples' mainScript = 'main.nf' nextflowVersion = '!>=20.12.1-edge' - version = 'v0.1.0' + version = 'v0.1.1' description = 'A pipeline to pre-process multiple spatial omics samples.' author = 'Dries Schaumont, Dorien Roosen, Weiwei Schultz' } diff --git a/target/nextflow/workflows/qc/spatial_qc/.config.vsh.yaml b/target/nextflow/workflows/qc/spatial_qc/.config.vsh.yaml index 08390c7..dbc4b28 100644 --- a/target/nextflow/workflows/qc/spatial_qc/.config.vsh.yaml +++ b/target/nextflow/workflows/qc/spatial_qc/.config.vsh.yaml @@ -1,6 +1,6 @@ name: "spatial_qc" namespace: "workflows/qc" -version: "v0.1.0" +version: "v0.1.1" authors: - name: "Dries Schaumont" roles: @@ -387,13 +387,13 @@ build_info: output: "target/nextflow/workflows/qc/spatial_qc" executable: "target/nextflow/workflows/qc/spatial_qc/main.nf" viash_version: "0.9.4" - git_commit: "3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36" + git_commit: "2baba3fff0930478c26da7e1bd99b29ee5dd83b3" git_remote: "https://github.com/openpipelines-bio/openpipeline_spatial" dependencies: - "target/dependencies/vsh/vsh/openpipeline/v3.0.0/nextflow/workflows/qc/qc" package_config: name: "openpipeline_spatial" - version: "v0.1.0" + version: "v0.1.1" info: test_resources: - type: "s3" @@ -413,7 +413,7 @@ package_config: )'" - ".engines += { type: \"native\" }" - ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'" - - ".engines[.type == 'docker'].target_tag := 'v0.1.0'" + - ".engines[.type == 'docker'].target_tag := 'v0.1.1'" organization: "vsh" links: repository: "https://github.com/openpipelines-bio/openpipeline_spatial" diff --git a/target/nextflow/workflows/qc/spatial_qc/main.nf b/target/nextflow/workflows/qc/spatial_qc/main.nf index b0e84db..d02122d 100644 --- a/target/nextflow/workflows/qc/spatial_qc/main.nf +++ b/target/nextflow/workflows/qc/spatial_qc/main.nf @@ -1,4 +1,4 @@ -// spatial_qc v0.1.0 +// spatial_qc v0.1.1 // // This wrapper script is auto-generated by viash 0.9.4 and is thus a derivative // work thereof. This software comes with ABSOLUTELY NO WARRANTY from Data @@ -3037,7 +3037,7 @@ meta = [ "config": processConfig(readJsonBlob('''{ "name" : "spatial_qc", "namespace" : "workflows/qc", - "version" : "v0.1.0", + "version" : "v0.1.1", "authors" : [ { "name" : "Dries Schaumont", @@ -3505,12 +3505,12 @@ meta = [ "engine" : "native", "output" : "/workdir/root/repo/target/nextflow/workflows/qc/spatial_qc", "viash_version" : "0.9.4", - "git_commit" : "3f8c1506dc59a8c9bb2a3fa6acb40e4e40291e36", + "git_commit" : "2baba3fff0930478c26da7e1bd99b29ee5dd83b3", "git_remote" : "https://github.com/openpipelines-bio/openpipeline_spatial" }, "package_config" : { "name" : "openpipeline_spatial", - "version" : "v0.1.0", + "version" : "v0.1.1", "info" : { "test_resources" : [ { @@ -3535,7 +3535,7 @@ meta = [ ".resources += {path: '/src/workflows/utils/labels.config', dest: 'nextflow_labels.config'}\n.runners[.type == 'nextflow'].config.script := 'includeConfig(\\"nextflow_labels.config\\")'", ".engines += { type: \\"native\\" }", ".engines[.type == 'docker'].target_registry := 'images.viash-hub.com'", - ".engines[.type == 'docker'].target_tag := 'v0.1.0'" + ".engines[.type == 'docker'].target_tag := 'v0.1.1'" ], "organization" : "vsh", "links" : { diff --git a/target/nextflow/workflows/qc/spatial_qc/nextflow.config b/target/nextflow/workflows/qc/spatial_qc/nextflow.config index 50edaa1..1bfe070 100644 --- a/target/nextflow/workflows/qc/spatial_qc/nextflow.config +++ b/target/nextflow/workflows/qc/spatial_qc/nextflow.config @@ -2,7 +2,7 @@ manifest { name = 'workflows/qc/spatial_qc' mainScript = 'main.nf' nextflowVersion = '!>=20.12.1-edge' - version = 'v0.1.0' + version = 'v0.1.1' description = 'A pipeline to add basic qc statistics to a MuData containing spatial data.' author = 'Dries Schaumont, Dorien Roosen, Weiwei Schultz' }