Build branch openpipeline_spatial/v0.1 with version v0.1.1 to openpipeline_spatial on branch v0.1 (2baba3f)

Build pipeline: openpipelines-bio.openpipeline-spatial.0.1.1-5jtw5

Source commit: 2baba3fff0

Source message: merge main
This commit is contained in:
CI
2025-10-02 09:03:43 +00:00
parent 187fb7b5df
commit 15f459882d
93 changed files with 1974 additions and 430 deletions

5
.gitignore vendored
View File

@@ -56,3 +56,8 @@ trace*.txt
# vscode
.vscode/launch.json
.vscode/settings.json
# linting
renv.lock
.Rprofile
renv/

View File

@@ -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

21
LICENSE Normal file
View File

@@ -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.

55
README.md Normal file
View File

@@ -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.
Its 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, its 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/).

View File

@@ -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")'

View File

@@ -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, .]

View File

@@ -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")

View File

@@ -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"

View File

@@ -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,6 +55,10 @@ engines:
- procps
- type: python
__merge__: [/src/base/requirements/anndata_mudata.yaml, /src/base/requirements/squidpy.yaml]
test_setup:
- type: apt
packages:
- zip
__merge__: [ /src/base/requirements/python_test_setup.yaml, . ]
runners:
- type: executable

View File

@@ -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()
)
return files[0]
else:
cosmx_output_bundle = Path(cosmx_output_bundle)
assert os.path.isdir(cosmx_output_bundle), (
"Input is expected to be a (compressed) directory."
)
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"])
adata = sq.read.nanostring(
path=par["input"], counts_file=counts_file, meta_file=meta_file, fov_file=fov_file
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"])
if __name__ == "__main__":
main()

View File

@@ -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__]))

View File

@@ -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

View File

@@ -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)

View File

@@ -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"

View File

@@ -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:

View File

@@ -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,22 +21,47 @@ 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 _retrieve_input_data(xenium_output_bundle):
# 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",
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:
"""Convert cell IDs to string format, decoding bytes if necessary."""
@@ -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")

View File

@@ -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"

View File

@@ -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

View File

@@ -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"],

View File

@@ -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__]))

View File

@@ -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

View File

@@ -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)

View File

@@ -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"

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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"

View File

@@ -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")

View File

@@ -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

View File

@@ -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"

View File

@@ -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 " - \`<dataset_id>_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()
)
return files[0]
else:
cosmx_output_bundle = Path(cosmx_output_bundle)
assert os.path.isdir(cosmx_output_bundle), (
"Input is expected to be a (compressed) directory."
)
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"])
adata = sq.read.nanostring(
path=par["input"], counts_file=counts_file, meta_file=meta_file, fov_file=fov_file
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"])
if __name__ == "__main__":
main()
VIASHMAIN
python -B "\$tempscript" &
wait "\$!"

View File

@@ -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

View File

@@ -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"

View File

@@ -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" &

View File

@@ -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)
}

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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,22 +1196,47 @@ 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 _retrieve_input_data(xenium_output_bundle):
# 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",
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:
"""Convert cell IDs to string format, decoding bytes if necessary."""
@@ -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")

View File

@@ -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

View File

@@ -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"

View File

@@ -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"],

View File

@@ -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

View File

@@ -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"

View File

@@ -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" &

View File

@@ -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)
}

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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",

View File

@@ -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'
}

View File

@@ -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

View File

@@ -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"

View File

@@ -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()
)
return files[0]
else:
cosmx_output_bundle = Path(cosmx_output_bundle)
assert os.path.isdir(cosmx_output_bundle), (
"Input is expected to be a (compressed) directory."
)
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"])
adata = sq.read.nanostring(
path=par["input"], counts_file=counts_file, meta_file=meta_file, fov_file=fov_file
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"])
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",

View File

@@ -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 - `<dataset_id>_exprMat_file.csv`: File containing the counts.\n - `<dataset_id>`_metadata_file: File containing the spatial coordinates and additional cell-level metadata.\n - `<dataset_id>_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'
}

View File

@@ -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

View File

@@ -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"

View File

@@ -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",

View File

@@ -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'
}

View File

@@ -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)
}

View File

@@ -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"

View File

@@ -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",

View File

@@ -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'
}

View File

@@ -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"

View File

@@ -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",

View File

@@ -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'
}

View File

@@ -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"

View File

@@ -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,22 +3439,47 @@ 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 _retrieve_input_data(xenium_output_bundle):
# 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",
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:
"""Convert cell IDs to string format, decoding bytes if necessary."""
@@ -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",

View File

@@ -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'
}

View File

@@ -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

View File

@@ -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"

View File

@@ -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",

View File

@@ -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'
}

View File

@@ -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

View File

@@ -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"

View File

@@ -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",

View File

@@ -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'
}

View File

@@ -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)
}

View File

@@ -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"

View File

@@ -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",

View File

@@ -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 ├── <dataset_id>_exprMat_file.csv\n ├── <dataset_id>_fov_positions_file.csv\n ├── <dataset_id>_metadata_file.csv\n └── <dataset_id>_tx_file.csv \n'
author = 'Dorien Roosen, Weiwei Schultz'
}

View File

@@ -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"

View File

@@ -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"
}'''),

View File

@@ -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'
}

View File

@@ -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"

View File

@@ -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" : {

View File

@@ -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'
}

View File

@@ -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"

View File

@@ -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" : {

View File

@@ -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'
}