diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c99842..416d53b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## Version 0.0.6 +- Added `read_tenx_visium()` function to load 10x Visium data as SpatialExperiment - Added `combine_columns` function - Implemented `__eq__` override for `SpatialImage` subclasses diff --git a/setup.cfg b/setup.cfg index 96788bb..5aea9b3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -52,9 +52,9 @@ install_requires = biocframe>=0.6.1 biocutils>=0.2 summarizedexperiment>=0.5 - singlecellexperiment>=0.5.6 + singlecellexperiment>=0.5.7 pillow>=11.0 - requests + scipy~=1.13 geopandas @@ -67,12 +67,16 @@ exclude = # Add here additional requirements for extra features, to install with: # `pip install SpatialExperiment[PDF]` like: # PDF = ReportLab; RXP +optional = + pandas + requests # Add here test requirements (semicolon/line-separated) testing = setuptools pytest pytest-cov + %(optional)s [options.entry_points] # Add here console scripts like: diff --git a/src/spatialexperiment/__init__.py b/src/spatialexperiment/__init__.py index fbadfab..6f611da 100644 --- a/src/spatialexperiment/__init__.py +++ b/src/spatialexperiment/__init__.py @@ -16,6 +16,7 @@ del version, PackageNotFoundError from .ProxySFE import ProxySpatialFeatureExperiment +from .io import read_tenx_visium from .SpatialExperiment import SpatialExperiment from .SpatialImage import ( LoadedSpatialImage, @@ -24,3 +25,15 @@ VirtualSpatialImage, construct_spatial_image_class, ) + +__all__ = [ + "ProxySpatialFeatureExperiment", + "read_tenx_visium", + "SpatialExperiment", + "LoadedSpatialImage", + "RemoteSpatialImage", + "StoredSpatialImage", + "VirtualSpatialImage", + "construct_spatial_image_class", +] + diff --git a/src/spatialexperiment/_imgutils.py b/src/spatialexperiment/_imgutils.py index 9aa67b0..f6082b8 100644 --- a/src/spatialexperiment/_imgutils.py +++ b/src/spatialexperiment/_imgutils.py @@ -1,12 +1,91 @@ from typing import Union +import os +from io import BytesIO +from pathlib import Path +from urllib.parse import urlparse +from PIL import Image from biocframe import BiocFrame +from .SpatialImage import construct_spatial_image_class __author__ = "keviny2" __copyright__ = "keviny2" __license__ = "MIT" + +def read_image(input_image): + """Read image from PIL Image, file path, or URL. + + Args: + input_image: PIL Image, string path to local file, or URL string. + + Returns: + The loaded image. + + Raises: + TypeError: If input is not PIL Image, path string, or URL string. + """ + import requests + + if isinstance(input_image, Image.Image): + return input_image + + if isinstance(input_image, (str, Path)): + is_url = urlparse(str(input_image)).scheme in ("http", "https", "ftp") + if is_url: + response = requests.get(input_image) + return Image.open(BytesIO(response.content)) + else: + return Image.open(input_image) + + raise TypeError(f"Expected PIL Image, path, or URL. Got {type(input_image)}") + + +def get_img_data( + img: Union[str, os.PathLike], + scale_factor: str, + sample_id: str, + image_id: str, + load: bool = True +) -> BiocFrame: + """ + Construct an image data dataframe. + + Args: + img: + A path or url to the image file. + + scale_factor: + The scale factor associated with the image. + + sample_id: + A unique identifier for the sample to which the image belongs. + + image_id: + A unique identifier for the image itself. + + load: + A boolean specifying whether the image(s) should be loaded into memory? If False, will store the path/URL instead. + Defaults to `True`. + + Returns: + The image data. + """ + if load: + img = read_image(img) + + spi = construct_spatial_image_class(img) + return BiocFrame( + { + "sample_id": [sample_id], + "image_id": [image_id], + "data": [spi], + "scale_factor": [scale_factor] + } + ) + + def retrieve_rows_by_id( img_data: BiocFrame, sample_id: Union[str, bool, None] = None, @@ -16,7 +95,9 @@ def retrieve_rows_by_id( Retrieve rows from `img_data` based on specified `sample_id` and `image_id`. Args: - img_data: The data from which to retrieve rows. + img_data: + The data from which to retrieve rows. + sample_id: - `sample_id=True`: Matches all samples. - `sample_id=None`: Matches the first sample. diff --git a/src/spatialexperiment/_initutils.py b/src/spatialexperiment/_initutils.py index 1569889..6e58a86 100644 --- a/src/spatialexperiment/_initutils.py +++ b/src/spatialexperiment/_initutils.py @@ -3,7 +3,7 @@ from biocframe import BiocFrame from PIL import Image -from SpatialImage import SpatialImage +from .SpatialImage import construct_spatial_image_class from summarizedexperiment._frameutils import _sanitize_frame __author__ = "keviny2" @@ -96,7 +96,7 @@ def construct_img_data( spis = [] for image_source in image_sources: result = Image.open(image_source) if load_image else image_source - spi = SpatialImage(result) + spi = construct_spatial_image_class(result) spis.append(spi) img_data = { diff --git a/src/spatialexperiment/io/__init__.py b/src/spatialexperiment/io/__init__.py new file mode 100644 index 0000000..e12bd3c --- /dev/null +++ b/src/spatialexperiment/io/__init__.py @@ -0,0 +1,4 @@ +from .tenx_visium import read_tenx_visium + +__all__ = ["read_tenx_visium"] + diff --git a/src/spatialexperiment/io/tenx_visium.py b/src/spatialexperiment/io/tenx_visium.py new file mode 100644 index 0000000..5bb0f89 --- /dev/null +++ b/src/spatialexperiment/io/tenx_visium.py @@ -0,0 +1,316 @@ +"""Creates a ``SpatialExperiment`` from the Space Ranger output directories for 10x Genomics Visium spatial gene expression data""" + +from typing import List, Union, Optional +from warnings import warn +import os +import re +import json + +from biocframe import BiocFrame +import biocutils as ut +from singlecellexperiment import read_tenx_mtx +from ..SpatialExperiment import SpatialExperiment +from .._imgutils import get_img_data +from .._initutils import construct_spatial_coords_from_names + + +def read_tissue_positions(tissue_positions_path) -> 'pd.DataFrame': + """Read and parse tissue position file. + + Args: + tissue_positions_path: The path to tissue positions file from Space Ranger output. + Can be either 'tissue_positions.csv' or 'tissue_positions_list.csv'. + + Returns: + A DataFrame with the tissue positions. + """ + import pandas as pd + column_names = [ + "barcode", + "in_tissue", + "array_row", + "array_col", + "pxl_row_in_fullres", + "pxl_col_in_fullres", + ] + + has_header = "list" not in os.path.basename(tissue_positions_path) + + tissue_positions = pd.read_csv( + tissue_positions_path, header=0 if has_header else None, names=column_names + ) + tissue_positions = tissue_positions.set_index("barcode") + tissue_positions["in_tissue"] = tissue_positions["in_tissue"].astype(bool) + + return tissue_positions + + +def read_img_data( + path: str = ".", + sample_ids: Optional[List[str]] = None, + image_sources: Optional[List[str]] = None, + scale_factors: str = None, + load: bool = True +) -> BiocFrame: + """Read in images and scale factors for 10x Genomics Visium data, and return as a valid `img_data` object. + + Args: + path: + A path where to find one or more images. + + sample_ids: + The `sample_id`'s for the ``SpatialExperiment`` object. + + image_sources: + The source path(s) to the image(s). + + scale_factors: + The .json file where to find the scale factors. + + load: + A boolean specifying whether the image(s) should be loaded into memory? If False, will store the path/URL instead. + Defaults to `True`. + """ + # get sample identifiers + if sample_ids is None: + raise ValueError("`sample_id` mustn't be NULL.") + + if not isinstance(sample_ids, list) or not all( + isinstance(s, str) for s in sample_ids + ): + raise TypeError("`sample_id` must be a list of strings.") + + if len(set(sample_ids)) != len(path): + raise ValueError( + "The number of unique sample_ids must match the length of path." + ) + + # put images into list with one element per sample + if image_sources is None: + image_sources = [os.path.join(p, "tissue_lowres_image.png") for p in path] + + if scale_factors is None: + scale_factors = [os.path.join(p, "scalefactors_json.json") for p in path] + + images = [[img for img in image_sources if p in img] for p in path] + + img_data = BiocFrame( + {"sample_id": [], "image_id": [], "data": [], "scale_factor": []} + ) + for i, sample_id in enumerate(sample_ids): + with open(scale_factors[i], "r") as f: + curr_scale_factors = json.load(f) + + for image in images[i]: + base_name = os.path.basename(image) + image_name = re.sub(r"\..*$", "", base_name) + image_id = { + "tissue_lowres_image": "lowres", + "tissue_hires_image": "hires", + "detected_tissue_image": "detected", + "aligned_fiducials": "aligned", + }.get(image_name, None) + + scale_factor_name = {"lowres": "tissue_lowres_scalef"}.get( + image_id, "tissue_hires_scalef" + ) + scale_factor = next( + ( + value + for key, value in curr_scale_factors.items() + if scale_factor_name in key + ), + None, + ) + curr_image_data = get_img_data( + img=image, + scale_factor=scale_factor, + sample_id=sample_id, + image_id=image_id, + load=load + ) + img_data = img_data.combine_rows(curr_image_data) + + return img_data + + +def read_tenx_visium( + samples: List[Union[str, os.PathLike]], + sample_ids: Optional[List[str]] = None, + type: str = "HDF5", + data: str = "filtered", + images: List[str] = "lowres", + load: bool = True, +): + """Create a ``SpatialExperiment`` from the Space Ranger output directories for 10x Genomics Visium spatial gene expression data. + + Args: + samples: + A list of strings specifying one or more directories, each corresponding to a 10x Genomics Visium sample; if provided, names will be used as sample identifiers. + + sample_ids: + A list of strings specifying unique sample identifiers, one for each directory specified via `samples`. + + type: + A string specifying the type of format to read count data from. Valid values are ['auto', 'sparse', 'prefix', 'HDF5'] (see [read10xCounts](https://rdrr.io/github/MarioniLab/DropletUtils/man/read10xCounts.html)). + + data: + A string specifying whether to read in filtered (spots mapped to tissue) or raw data (all spots). Valid values are "filtered", "raw". + + images: + A single string or a list of strings specifying which images to include. Valid values are "lowres", "hires", "fullres", "detected", "aligned". + + load: + A boolean specifying whether the image(s) should be loaded into memory? If False, will store the path/URL instead. + Defaults to `True`. + """ + # check validity of input arguments + allowed_types = ["HDF5", "sparse", "auto", "prefix"] + allowed_data = ["filtered", "raw"] + allowed_images = ["lowres", "hires", "detected", "aligned"] + + if type not in allowed_types: + raise ValueError(f"`type` must be one of {allowed_types}. got `{type}`.") + + if data not in allowed_data: + raise ValueError(f"`data` must be one of {allowed_data}. got `{data}`") + + if isinstance(images, str): + images = [images] + + for image in images: + if image not in allowed_images: + raise ValueError( + f"`images` must be one of {allowed_images}. got `{image}`." + ) + + if sample_ids is None: + sample_ids = [f"sample{str(i).zfill(2)}" for i in range(1, len(samples) + 1)] + elif isinstance(sample_ids, str): + warn(f"converting string sample_id to list: [{sample_ids}]") + sample_ids = [sample_ids] + elif not ut.is_list_of_type(sample_ids, str): + raise ValueError("`sample_ids` must be a list of strings") + elif len(set(sample_ids)) != len(samples): + raise ValueError("`sample_ids` should contain as many unique values as `samples`") + + if isinstance(samples, str): + warn(f"converting string samples to list: [{samples}]") + samples = [samples] + + # add "outs/" directory if not already included + for i, sample in enumerate(samples): + if os.path.basename(sample) != "outs": + samples[i] = os.path.join(sample, "outs") + + # setup file paths + ext = ".h5" if type == "HDF5" else "" + counts_dirs = [f"{data}_feature_bc_matrix{ext}" for _ in samples] + counts_dir_paths = [ + os.path.join(sample, fn) for sample, fn in zip(samples, counts_dirs) + ] + + # spatial parts + spatial_dir_paths = [os.path.join(sample, "spatial") for sample in samples] + + allowed_suffixes = [ + "", + "_list", + ] # `tissue_positions_list.csv` was renamed to `tissue_positions.csv` in Space Ranger v2.0.0 + + tissue_positions_paths = [ + os.path.join(spatial_dir, f"tissue_positions{suffix}.csv") + for spatial_dir in spatial_dir_paths + for suffix in allowed_suffixes + ] + tissue_positions_paths = [ + tissue_positions_path + for tissue_positions_path in tissue_positions_paths + if os.path.exists(tissue_positions_path) + ] + scale_factors_paths = [ + os.path.join(spatial_dir, "scalefactors_json.json") + for spatial_dir in spatial_dir_paths + ] + + # read image data + image_files_mapper = { + "lowres": "tissue_lowres_image.png", + "hires": "tissue_hires_image.png", + "detected": "detected_tissue_image.jpg", + "aligned": "aligned_fiducials.jpg", + } + + image_files = [ + image_files_mapper[image] for image in images if image in image_files_mapper + ] + image_file_paths = [ + os.path.join(spatial_dir, image_file) + for spatial_dir in spatial_dir_paths + for image_file in image_files + ] + + missing_files = [ + not os.path.exists(image_file_path) for image_file_path in image_file_paths + ] + + if all(missing_files): + raise FileNotFoundError(f"No matching files found for 'images={images}'") + + elif any(missing_files): + print( + "Skipping missing images\n " + + "\n ".join( + image_file_path + for image_file_path, missing in zip(image_file_paths, missing_files) + if missing + ) + ) + image_file_paths = [ + image_file_path + for image_file_path, missing in zip(image_file_paths, missing_files) + if not missing + ] + + image = read_img_data( + path=samples, + sample_ids=sample_ids, + image_sources=image_file_paths, + scale_factors=scale_factors_paths, + load=load + ) + + spes = [] + for i, counts_dir_path in enumerate(counts_dir_paths): + sce = read_tenx_mtx(counts_dir_path) + tissue_positions = read_tissue_positions(tissue_positions_paths[i]) + + barcodes = sce.column_data["barcode"] + sce = sce.set_column_names(barcodes) + + obs = list(set(sce.col_names).intersection(set(tissue_positions.index))) + sce = sce[:, obs] + + tissue_positions = tissue_positions.loc[obs, :] + tissue_positions["sample_id"] = sample_ids[i] + spatial_coords, column_data = construct_spatial_coords_from_names( + spatial_coords_names=["pxl_col_in_fullres", "pxl_row_in_fullres"], + column_data=tissue_positions + ) + + spe = SpatialExperiment( + assays=sce.assays, + row_data=BiocFrame( + { + "symbol": sce.row_data["gene_symbols"] + } + ), + column_data=column_data, + spatial_coords=spatial_coords + ) + spes.append(spe) + + spe_combined = ut.combine_columns(*spes) + spe_combined.img_data = image + + return spe_combined diff --git a/tests/10xVisium/section1/outs/raw_feature_bc_matrix/barcodes.tsv b/tests/10xVisium/section1/outs/raw_feature_bc_matrix/barcodes.tsv new file mode 100644 index 0000000..35d6883 --- /dev/null +++ b/tests/10xVisium/section1/outs/raw_feature_bc_matrix/barcodes.tsv @@ -0,0 +1,50 @@ +AAACAACGAATAGTTC-1 +AAACAAGTATCTCCCA-1 +AAACAATCTACTAGCA-1 +AAACACCAATAACTGC-1 +AAACAGAGCGACTCCT-1 +AAACAGCTTTCAGAAG-1 +AAACAGGGTCTATATT-1 +AAACAGTGTTCCTGGG-1 +AAACATGGTGAGAGGA-1 +AAACATTTCCCGGATT-1 +AAACCACTACACAGAT-1 +AAACCCGAACGAAATC-1 +AAACCGGAAATGTTAA-1 +AAACCGGGTAGGTACC-1 +AAACCGTTCGTCCAGG-1 +AAACCTAAGCAGCCGG-1 +AAACCTCATGAAGTTG-1 +AAACGAAGAACATACC-1 +AAACGAAGATGGAGTA-1 +AAACGACAGTCTTGCC-1 +AAACGAGACGGTTGAT-1 +AAACGCCCGAGATCGG-1 +AAACGCTGGGCACGAC-1 +AAACGGGCGTACGGGT-1 +AAACGGGTTGGTATCC-1 +AAACGGTTGCGAACTG-1 +AAACGTGTTCGCCCTA-1 +AAACTAACGTGGCGAC-1 +AAACTCGGTTCGCAAT-1 +AAACTCGTGATATAAG-1 +AAACTGCTGGCTCCAA-1 +AAACTTAATTGCACGC-1 +AAACTTGCAAACGTAT-1 +AAAGAATGACCTTAGA-1 +AAAGAATGTGGACTAA-1 +AAAGACATGAAGTTTA-1 +AAAGACCCAAGTCGCG-1 +AAAGACTGGGCGCTTT-1 +AAAGCTTGCCTACATA-1 +AAAGGCCCTATAATAC-1 +AAAGGCTACGGACCAT-1 +AAAGGCTCTCGCGCCG-1 +AAAGGGATGTAGCAAG-1 +AAAGGGCAGCTTGAAT-1 +AAAGGTAAGCTGTACC-1 +AAAGGTCAACGACATG-1 +AAAGTAGCATTGCTCA-1 +AAAGTCACTGATGTAA-1 +AAAGTCGACCCTCAGT-1 +AAAGTGCCATCAATTA-1 diff --git a/tests/10xVisium/section1/outs/raw_feature_bc_matrix/features.tsv b/tests/10xVisium/section1/outs/raw_feature_bc_matrix/features.tsv new file mode 100644 index 0000000..6183113 --- /dev/null +++ b/tests/10xVisium/section1/outs/raw_feature_bc_matrix/features.tsv @@ -0,0 +1,50 @@ +ENSMUSG00000051951 Xkr4 Gene Expression +ENSMUSG00000089699 Gm1992 Gene Expression +ENSMUSG00000102343 Gm37381 Gene Expression +ENSMUSG00000025900 Rp1 Gene Expression +ENSMUSG00000025902 Sox17 Gene Expression +ENSMUSG00000104328 Gm37323 Gene Expression +ENSMUSG00000033845 Mrpl15 Gene Expression +ENSMUSG00000025903 Lypla1 Gene Expression +ENSMUSG00000104217 Gm37988 Gene Expression +ENSMUSG00000033813 Tcea1 Gene Expression +ENSMUSG00000002459 Rgs20 Gene Expression +ENSMUSG00000085623 Gm16041 Gene Expression +ENSMUSG00000033793 Atp6v1h Gene Expression +ENSMUSG00000025905 Oprk1 Gene Expression +ENSMUSG00000033774 Npbwr1 Gene Expression +ENSMUSG00000025907 Rb1cc1 Gene Expression +ENSMUSG00000090031 4732440D04Rik Gene Expression +ENSMUSG00000087247 Alkal1 Gene Expression +ENSMUSG00000033740 St18 Gene Expression +ENSMUSG00000051285 Pcmtd1 Gene Expression +ENSMUSG00000097797 Gm26901 Gene Expression +ENSMUSG00000103067 Gm30414 Gene Expression +ENSMUSG00000025909 Sntg1 Gene Expression +ENSMUSG00000061024 Rrs1 Gene Expression +ENSMUSG00000025911 Adhfe1 Gene Expression +ENSMUSG00000067879 3110035E14Rik Gene Expression +ENSMUSG00000099827 Gm29520 Gene Expression +ENSMUSG00000025912 Mybl1 Gene Expression +ENSMUSG00000045210 Vcpip1 Gene Expression +ENSMUSG00000097893 1700034P13Rik Gene Expression +ENSMUSG00000025915 Sgk3 Gene Expression +ENSMUSG00000046101 Mcmdc2 Gene Expression +ENSMUSG00000098234 Snhg6 Gene Expression +ENSMUSG00000099032 Tcf24 Gene Expression +ENSMUSG00000025916 Ppp1r42 Gene Expression +ENSMUSG00000087199 Gm15818 Gene Expression +ENSMUSG00000025917 Cops5 Gene Expression +ENSMUSG00000056763 Cspp1 Gene Expression +ENSMUSG00000067851 Arfgef1 Gene Expression +ENSMUSG00000042501 Cpa6 Gene Expression +ENSMUSG00000048960 Prex2 Gene Expression +ENSMUSG00000057715 A830018L16Rik Gene Expression +ENSMUSG00000097171 Gm17644 Gene Expression +ENSMUSG00000101314 Gm29663 Gene Expression +ENSMUSG00000016918 Sulf1 Gene Expression +ENSMUSG00000025938 Slco5a1 Gene Expression +ENSMUSG00000099498 Gm29283 Gene Expression +ENSMUSG00000042414 Prdm14 Gene Expression +ENSMUSG00000005886 Ncoa2 Gene Expression +ENSMUSG00000101476 Gm29570 Gene Expression diff --git a/tests/10xVisium/section1/outs/raw_feature_bc_matrix/matrix.mtx b/tests/10xVisium/section1/outs/raw_feature_bc_matrix/matrix.mtx new file mode 100644 index 0000000..775653f --- /dev/null +++ b/tests/10xVisium/section1/outs/raw_feature_bc_matrix/matrix.mtx @@ -0,0 +1,340 @@ +%%MatrixMarket matrix coordinate integer general +50 50 338 +13 1 1 +16 1 1 +26 1 1 +29 1 1 +37 1 3 +38 1 1 +45 1 1 +7 2 1 +8 2 1 +10 2 2 +13 2 4 +16 2 1 +20 2 3 +24 2 1 +25 2 2 +33 2 1 +40 2 1 +41 2 2 +42 2 2 +49 2 1 +7 3 1 +8 3 1 +10 3 1 +13 3 2 +26 3 1 +33 3 1 +38 3 1 +41 3 2 +42 3 1 +10 4 2 +13 4 1 +16 4 2 +20 4 1 +24 4 1 +29 4 1 +33 4 2 +37 4 4 +38 4 1 +39 4 2 +41 4 2 +42 4 1 +13 5 6 +16 5 1 +20 5 1 +25 5 1 +26 5 3 +29 5 1 +33 5 3 +41 5 1 +42 5 1 +16 6 1 +10 7 1 +20 7 1 +25 7 2 +37 7 1 +41 7 1 +49 7 1 +13 8 1 +41 8 1 +29 9 1 +10 10 1 +26 10 2 +16 12 1 +7 14 2 +10 14 1 +16 14 4 +25 14 1 +33 14 4 +37 14 2 +41 14 1 +49 14 1 +7 15 2 +8 15 1 +10 15 1 +13 15 4 +16 15 1 +20 15 1 +25 15 1 +26 15 1 +28 15 2 +29 15 3 +31 15 1 +33 15 1 +37 15 2 +39 15 3 +13 16 2 +26 16 1 +5 17 1 +7 17 3 +8 17 3 +10 17 6 +11 17 3 +13 17 5 +14 17 2 +16 17 6 +20 17 3 +23 17 2 +25 17 1 +26 17 1 +29 17 2 +31 17 1 +32 17 1 +33 17 2 +37 17 8 +39 17 1 +41 17 1 +45 17 7 +1 18 1 +7 18 1 +10 18 2 +11 18 1 +13 18 6 +16 18 6 +20 18 3 +24 18 1 +26 18 4 +29 18 4 +31 18 1 +33 18 3 +37 18 1 +39 18 2 +45 18 1 +5 21 1 +7 21 4 +10 21 1 +11 21 3 +16 21 3 +19 21 1 +20 21 1 +23 21 1 +24 21 1 +26 21 2 +28 21 1 +29 21 2 +33 21 5 +37 21 2 +39 21 1 +42 21 1 +49 21 1 +29 22 1 +7 25 1 +10 25 1 +13 25 1 +16 25 1 +26 25 1 +8 26 1 +11 26 1 +13 26 1 +16 26 2 +23 26 1 +25 26 1 +26 26 1 +33 26 3 +37 26 3 +41 26 1 +42 26 2 +46 26 1 +26 27 2 +37 27 1 +41 27 1 +7 29 2 +8 29 1 +20 29 1 +38 29 1 +41 29 1 +42 29 1 +45 29 1 +1 31 1 +5 31 1 +7 31 1 +10 31 1 +13 31 1 +16 31 3 +19 31 1 +26 31 1 +29 31 1 +33 31 3 +38 31 1 +39 31 1 +41 31 2 +49 31 1 +7 32 1 +15 32 1 +16 32 1 +28 32 1 +33 32 1 +39 32 1 +45 32 2 +7 33 2 +8 33 1 +10 33 2 +11 33 1 +13 33 4 +14 33 2 +20 33 1 +24 33 1 +26 33 1 +29 33 1 +33 33 3 +39 33 1 +41 33 3 +45 33 2 +11 34 1 +16 34 1 +20 35 1 +16 36 1 +26 36 1 +5 37 1 +8 37 2 +10 37 3 +11 37 1 +13 37 8 +16 37 1 +20 37 3 +21 37 1 +25 37 1 +26 37 7 +29 37 2 +33 37 4 +37 37 3 +39 37 2 +41 37 1 +42 37 3 +49 37 2 +1 38 1 +13 38 2 +20 38 1 +29 38 1 +39 38 2 +7 40 4 +10 40 3 +13 40 6 +14 40 3 +16 40 2 +20 40 1 +24 40 1 +29 40 2 +33 40 1 +37 40 7 +38 40 1 +39 40 2 +42 40 1 +45 40 2 +49 40 2 +7 41 3 +10 41 2 +13 41 9 +14 41 1 +16 41 2 +20 41 1 +26 41 1 +29 41 3 +33 41 3 +37 41 6 +38 41 1 +39 41 4 +41 41 2 +42 41 1 +49 41 1 +8 42 1 +10 42 4 +13 42 1 +19 42 1 +20 42 1 +24 42 1 +25 42 1 +33 42 2 +37 42 2 +45 42 1 +8 43 1 +13 43 1 +16 43 1 +26 43 1 +31 43 1 +39 43 1 +45 43 1 +49 43 1 +1 44 1 +7 44 3 +10 44 4 +13 44 6 +16 44 1 +19 44 1 +20 44 3 +24 44 1 +26 44 6 +29 44 2 +32 44 1 +33 44 9 +37 44 4 +39 44 3 +41 44 1 +42 44 1 +49 44 1 +13 45 1 +37 45 1 +16 46 1 +10 47 2 +13 47 2 +16 47 1 +20 47 1 +24 47 1 +29 47 4 +31 47 2 +37 47 3 +39 47 1 +42 47 1 +49 47 1 +7 48 5 +8 48 1 +10 48 3 +11 48 1 +13 48 17 +16 48 5 +20 48 4 +23 48 1 +24 48 2 +26 48 19 +28 48 1 +29 48 2 +31 48 1 +33 48 4 +37 48 10 +39 48 3 +41 48 1 +42 48 3 +49 48 4 +5 49 1 +7 49 1 +8 49 1 +10 49 2 +11 49 1 +13 49 1 +14 49 1 +20 49 1 +33 49 2 +37 49 2 +39 49 1 diff --git a/tests/10xVisium/section1/outs/spatial/scalefactors_json.json b/tests/10xVisium/section1/outs/spatial/scalefactors_json.json new file mode 100644 index 0000000..9d7dbe6 --- /dev/null +++ b/tests/10xVisium/section1/outs/spatial/scalefactors_json.json @@ -0,0 +1 @@ +{"spot_diameter_fullres": 89.44476048022638, "tissue_hires_scalef": 0.17011142, "fiducial_diameter_fullres": 144.48769000651953, "tissue_lowres_scalef": 0.051033426} \ No newline at end of file diff --git a/tests/10xVisium/section1/outs/spatial/tissue_lowres_image.png b/tests/10xVisium/section1/outs/spatial/tissue_lowres_image.png new file mode 100644 index 0000000..8e13265 Binary files /dev/null and b/tests/10xVisium/section1/outs/spatial/tissue_lowres_image.png differ diff --git a/tests/10xVisium/section1/outs/spatial/tissue_positions_list.csv b/tests/10xVisium/section1/outs/spatial/tissue_positions_list.csv new file mode 100644 index 0000000..e05970b --- /dev/null +++ b/tests/10xVisium/section1/outs/spatial/tissue_positions_list.csv @@ -0,0 +1,50 @@ +AAACAACGAATAGTTC-1,0,0,16,1252,2312 +AAACAAGTATCTCCCA-1,1,50,102,7237,8230 +AAACAATCTACTAGCA-1,1,3,43,1611,4170 +AAACACCAATAACTGC-1,1,59,19,8315,2519 +AAACAGAGCGACTCCT-1,1,14,94,2927,7679 +AAACAGCTTTCAGAAG-1,0,43,9,6400,1831 +AAACAGGGTCTATATT-1,0,47,13,6879,2106 +AAACAGTGTTCCTGGG-1,0,73,43,9991,4170 +AAACATGGTGAGAGGA-1,0,62,0,8674,1212 +AAACATTTCCCGGATT-1,0,61,97,8554,7886 +AAACCACTACACAGAT-1,0,3,117,1610,9261 +AAACCCGAACGAAATC-1,0,45,115,6639,9124 +AAACCGGAAATGTTAA-1,0,54,124,7716,9743 +AAACCGGGTAGGTACC-1,1,42,28,6280,3138 +AAACCGTTCGTCCAGG-1,1,52,42,7477,4101 +AAACCTAAGCAGCCGG-1,0,65,83,9033,6922 +AAACCTCATGAAGTTG-1,1,37,19,5681,2519 +AAACGAAGAACATACC-1,1,6,64,1970,5615 +AAACGAAGATGGAGTA-1,0,58,4,8195,1487 +AAACGACAGTCTTGCC-1,0,2,118,1491,9330 +AAACGAGACGGTTGAT-1,1,35,79,5442,6647 +AAACGCCCGAGATCGG-1,0,4,108,1730,8642 +AAACGCTGGGCACGAC-1,0,70,126,9631,9881 +AAACGGGCGTACGGGT-1,0,65,91,9033,7473 +AAACGGGTTGGTATCC-1,0,1,23,1371,2794 +AAACGGTTGCGAACTG-1,1,67,59,9273,5271 +AAACGTGTTCGCCCTA-1,0,14,118,2927,9330 +AAACTAACGTGGCGAC-1,0,8,110,2209,8780 +AAACTCGGTTCGCAAT-1,1,66,70,9153,6028 +AAACTCGTGATATAAG-1,0,23,113,4005,8986 +AAACTGCTGGCTCCAA-1,1,45,67,6639,5821 +AAACTTAATTGCACGC-1,0,64,12,8914,2037 +AAACTTGCAAACGTAT-1,1,45,19,6639,2519 +AAAGAATGACCTTAGA-1,0,64,2,8914,1349 +AAAGAATGTGGACTAA-1,0,71,105,9751,8436 +AAAGACATGAAGTTTA-1,0,0,92,1251,7541 +AAAGACCCAAGTCGCG-1,1,10,48,2449,4514 +AAAGACTGGGCGCTTT-1,0,29,15,4724,2243 +AAAGCTTGCCTACATA-1,0,26,122,4364,9605 +AAAGGCCCTATAATAC-1,1,66,22,9153,2725 +AAAGGCTACGGACCAT-1,1,62,54,8674,4927 +AAAGGCTCTCGCGCCG-1,1,55,55,7836,4996 +AAAGGGATGTAGCAAG-1,1,24,62,4125,5477 +AAAGGGCAGCTTGAAT-1,1,24,26,4125,3000 +AAAGGTAAGCTGTACC-1,0,10,106,2448,8504 +AAAGGTCAACGACATG-1,0,0,112,1251,8917 +AAAGTAGCATTGCTCA-1,1,51,27,7357,3069 +AAAGTCACTGATGTAA-1,1,10,52,2449,4789 +AAAGTCGACCCTCAGT-1,1,37,15,5681,2244 +AAAGTGCCATCAATTA-1,0,63,125,8793,9812 diff --git a/tests/10xVisium/section2/outs/raw_feature_bc_matrix/barcodes.tsv b/tests/10xVisium/section2/outs/raw_feature_bc_matrix/barcodes.tsv new file mode 100644 index 0000000..35d6883 --- /dev/null +++ b/tests/10xVisium/section2/outs/raw_feature_bc_matrix/barcodes.tsv @@ -0,0 +1,50 @@ +AAACAACGAATAGTTC-1 +AAACAAGTATCTCCCA-1 +AAACAATCTACTAGCA-1 +AAACACCAATAACTGC-1 +AAACAGAGCGACTCCT-1 +AAACAGCTTTCAGAAG-1 +AAACAGGGTCTATATT-1 +AAACAGTGTTCCTGGG-1 +AAACATGGTGAGAGGA-1 +AAACATTTCCCGGATT-1 +AAACCACTACACAGAT-1 +AAACCCGAACGAAATC-1 +AAACCGGAAATGTTAA-1 +AAACCGGGTAGGTACC-1 +AAACCGTTCGTCCAGG-1 +AAACCTAAGCAGCCGG-1 +AAACCTCATGAAGTTG-1 +AAACGAAGAACATACC-1 +AAACGAAGATGGAGTA-1 +AAACGACAGTCTTGCC-1 +AAACGAGACGGTTGAT-1 +AAACGCCCGAGATCGG-1 +AAACGCTGGGCACGAC-1 +AAACGGGCGTACGGGT-1 +AAACGGGTTGGTATCC-1 +AAACGGTTGCGAACTG-1 +AAACGTGTTCGCCCTA-1 +AAACTAACGTGGCGAC-1 +AAACTCGGTTCGCAAT-1 +AAACTCGTGATATAAG-1 +AAACTGCTGGCTCCAA-1 +AAACTTAATTGCACGC-1 +AAACTTGCAAACGTAT-1 +AAAGAATGACCTTAGA-1 +AAAGAATGTGGACTAA-1 +AAAGACATGAAGTTTA-1 +AAAGACCCAAGTCGCG-1 +AAAGACTGGGCGCTTT-1 +AAAGCTTGCCTACATA-1 +AAAGGCCCTATAATAC-1 +AAAGGCTACGGACCAT-1 +AAAGGCTCTCGCGCCG-1 +AAAGGGATGTAGCAAG-1 +AAAGGGCAGCTTGAAT-1 +AAAGGTAAGCTGTACC-1 +AAAGGTCAACGACATG-1 +AAAGTAGCATTGCTCA-1 +AAAGTCACTGATGTAA-1 +AAAGTCGACCCTCAGT-1 +AAAGTGCCATCAATTA-1 diff --git a/tests/10xVisium/section2/outs/raw_feature_bc_matrix/features.tsv b/tests/10xVisium/section2/outs/raw_feature_bc_matrix/features.tsv new file mode 100644 index 0000000..6183113 --- /dev/null +++ b/tests/10xVisium/section2/outs/raw_feature_bc_matrix/features.tsv @@ -0,0 +1,50 @@ +ENSMUSG00000051951 Xkr4 Gene Expression +ENSMUSG00000089699 Gm1992 Gene Expression +ENSMUSG00000102343 Gm37381 Gene Expression +ENSMUSG00000025900 Rp1 Gene Expression +ENSMUSG00000025902 Sox17 Gene Expression +ENSMUSG00000104328 Gm37323 Gene Expression +ENSMUSG00000033845 Mrpl15 Gene Expression +ENSMUSG00000025903 Lypla1 Gene Expression +ENSMUSG00000104217 Gm37988 Gene Expression +ENSMUSG00000033813 Tcea1 Gene Expression +ENSMUSG00000002459 Rgs20 Gene Expression +ENSMUSG00000085623 Gm16041 Gene Expression +ENSMUSG00000033793 Atp6v1h Gene Expression +ENSMUSG00000025905 Oprk1 Gene Expression +ENSMUSG00000033774 Npbwr1 Gene Expression +ENSMUSG00000025907 Rb1cc1 Gene Expression +ENSMUSG00000090031 4732440D04Rik Gene Expression +ENSMUSG00000087247 Alkal1 Gene Expression +ENSMUSG00000033740 St18 Gene Expression +ENSMUSG00000051285 Pcmtd1 Gene Expression +ENSMUSG00000097797 Gm26901 Gene Expression +ENSMUSG00000103067 Gm30414 Gene Expression +ENSMUSG00000025909 Sntg1 Gene Expression +ENSMUSG00000061024 Rrs1 Gene Expression +ENSMUSG00000025911 Adhfe1 Gene Expression +ENSMUSG00000067879 3110035E14Rik Gene Expression +ENSMUSG00000099827 Gm29520 Gene Expression +ENSMUSG00000025912 Mybl1 Gene Expression +ENSMUSG00000045210 Vcpip1 Gene Expression +ENSMUSG00000097893 1700034P13Rik Gene Expression +ENSMUSG00000025915 Sgk3 Gene Expression +ENSMUSG00000046101 Mcmdc2 Gene Expression +ENSMUSG00000098234 Snhg6 Gene Expression +ENSMUSG00000099032 Tcf24 Gene Expression +ENSMUSG00000025916 Ppp1r42 Gene Expression +ENSMUSG00000087199 Gm15818 Gene Expression +ENSMUSG00000025917 Cops5 Gene Expression +ENSMUSG00000056763 Cspp1 Gene Expression +ENSMUSG00000067851 Arfgef1 Gene Expression +ENSMUSG00000042501 Cpa6 Gene Expression +ENSMUSG00000048960 Prex2 Gene Expression +ENSMUSG00000057715 A830018L16Rik Gene Expression +ENSMUSG00000097171 Gm17644 Gene Expression +ENSMUSG00000101314 Gm29663 Gene Expression +ENSMUSG00000016918 Sulf1 Gene Expression +ENSMUSG00000025938 Slco5a1 Gene Expression +ENSMUSG00000099498 Gm29283 Gene Expression +ENSMUSG00000042414 Prdm14 Gene Expression +ENSMUSG00000005886 Ncoa2 Gene Expression +ENSMUSG00000101476 Gm29570 Gene Expression diff --git a/tests/10xVisium/section2/outs/raw_feature_bc_matrix/matrix.mtx b/tests/10xVisium/section2/outs/raw_feature_bc_matrix/matrix.mtx new file mode 100644 index 0000000..775653f --- /dev/null +++ b/tests/10xVisium/section2/outs/raw_feature_bc_matrix/matrix.mtx @@ -0,0 +1,340 @@ +%%MatrixMarket matrix coordinate integer general +50 50 338 +13 1 1 +16 1 1 +26 1 1 +29 1 1 +37 1 3 +38 1 1 +45 1 1 +7 2 1 +8 2 1 +10 2 2 +13 2 4 +16 2 1 +20 2 3 +24 2 1 +25 2 2 +33 2 1 +40 2 1 +41 2 2 +42 2 2 +49 2 1 +7 3 1 +8 3 1 +10 3 1 +13 3 2 +26 3 1 +33 3 1 +38 3 1 +41 3 2 +42 3 1 +10 4 2 +13 4 1 +16 4 2 +20 4 1 +24 4 1 +29 4 1 +33 4 2 +37 4 4 +38 4 1 +39 4 2 +41 4 2 +42 4 1 +13 5 6 +16 5 1 +20 5 1 +25 5 1 +26 5 3 +29 5 1 +33 5 3 +41 5 1 +42 5 1 +16 6 1 +10 7 1 +20 7 1 +25 7 2 +37 7 1 +41 7 1 +49 7 1 +13 8 1 +41 8 1 +29 9 1 +10 10 1 +26 10 2 +16 12 1 +7 14 2 +10 14 1 +16 14 4 +25 14 1 +33 14 4 +37 14 2 +41 14 1 +49 14 1 +7 15 2 +8 15 1 +10 15 1 +13 15 4 +16 15 1 +20 15 1 +25 15 1 +26 15 1 +28 15 2 +29 15 3 +31 15 1 +33 15 1 +37 15 2 +39 15 3 +13 16 2 +26 16 1 +5 17 1 +7 17 3 +8 17 3 +10 17 6 +11 17 3 +13 17 5 +14 17 2 +16 17 6 +20 17 3 +23 17 2 +25 17 1 +26 17 1 +29 17 2 +31 17 1 +32 17 1 +33 17 2 +37 17 8 +39 17 1 +41 17 1 +45 17 7 +1 18 1 +7 18 1 +10 18 2 +11 18 1 +13 18 6 +16 18 6 +20 18 3 +24 18 1 +26 18 4 +29 18 4 +31 18 1 +33 18 3 +37 18 1 +39 18 2 +45 18 1 +5 21 1 +7 21 4 +10 21 1 +11 21 3 +16 21 3 +19 21 1 +20 21 1 +23 21 1 +24 21 1 +26 21 2 +28 21 1 +29 21 2 +33 21 5 +37 21 2 +39 21 1 +42 21 1 +49 21 1 +29 22 1 +7 25 1 +10 25 1 +13 25 1 +16 25 1 +26 25 1 +8 26 1 +11 26 1 +13 26 1 +16 26 2 +23 26 1 +25 26 1 +26 26 1 +33 26 3 +37 26 3 +41 26 1 +42 26 2 +46 26 1 +26 27 2 +37 27 1 +41 27 1 +7 29 2 +8 29 1 +20 29 1 +38 29 1 +41 29 1 +42 29 1 +45 29 1 +1 31 1 +5 31 1 +7 31 1 +10 31 1 +13 31 1 +16 31 3 +19 31 1 +26 31 1 +29 31 1 +33 31 3 +38 31 1 +39 31 1 +41 31 2 +49 31 1 +7 32 1 +15 32 1 +16 32 1 +28 32 1 +33 32 1 +39 32 1 +45 32 2 +7 33 2 +8 33 1 +10 33 2 +11 33 1 +13 33 4 +14 33 2 +20 33 1 +24 33 1 +26 33 1 +29 33 1 +33 33 3 +39 33 1 +41 33 3 +45 33 2 +11 34 1 +16 34 1 +20 35 1 +16 36 1 +26 36 1 +5 37 1 +8 37 2 +10 37 3 +11 37 1 +13 37 8 +16 37 1 +20 37 3 +21 37 1 +25 37 1 +26 37 7 +29 37 2 +33 37 4 +37 37 3 +39 37 2 +41 37 1 +42 37 3 +49 37 2 +1 38 1 +13 38 2 +20 38 1 +29 38 1 +39 38 2 +7 40 4 +10 40 3 +13 40 6 +14 40 3 +16 40 2 +20 40 1 +24 40 1 +29 40 2 +33 40 1 +37 40 7 +38 40 1 +39 40 2 +42 40 1 +45 40 2 +49 40 2 +7 41 3 +10 41 2 +13 41 9 +14 41 1 +16 41 2 +20 41 1 +26 41 1 +29 41 3 +33 41 3 +37 41 6 +38 41 1 +39 41 4 +41 41 2 +42 41 1 +49 41 1 +8 42 1 +10 42 4 +13 42 1 +19 42 1 +20 42 1 +24 42 1 +25 42 1 +33 42 2 +37 42 2 +45 42 1 +8 43 1 +13 43 1 +16 43 1 +26 43 1 +31 43 1 +39 43 1 +45 43 1 +49 43 1 +1 44 1 +7 44 3 +10 44 4 +13 44 6 +16 44 1 +19 44 1 +20 44 3 +24 44 1 +26 44 6 +29 44 2 +32 44 1 +33 44 9 +37 44 4 +39 44 3 +41 44 1 +42 44 1 +49 44 1 +13 45 1 +37 45 1 +16 46 1 +10 47 2 +13 47 2 +16 47 1 +20 47 1 +24 47 1 +29 47 4 +31 47 2 +37 47 3 +39 47 1 +42 47 1 +49 47 1 +7 48 5 +8 48 1 +10 48 3 +11 48 1 +13 48 17 +16 48 5 +20 48 4 +23 48 1 +24 48 2 +26 48 19 +28 48 1 +29 48 2 +31 48 1 +33 48 4 +37 48 10 +39 48 3 +41 48 1 +42 48 3 +49 48 4 +5 49 1 +7 49 1 +8 49 1 +10 49 2 +11 49 1 +13 49 1 +14 49 1 +20 49 1 +33 49 2 +37 49 2 +39 49 1 diff --git a/tests/10xVisium/section2/outs/spatial/scalefactors_json.json b/tests/10xVisium/section2/outs/spatial/scalefactors_json.json new file mode 100644 index 0000000..9d7dbe6 --- /dev/null +++ b/tests/10xVisium/section2/outs/spatial/scalefactors_json.json @@ -0,0 +1 @@ +{"spot_diameter_fullres": 89.44476048022638, "tissue_hires_scalef": 0.17011142, "fiducial_diameter_fullres": 144.48769000651953, "tissue_lowres_scalef": 0.051033426} \ No newline at end of file diff --git a/tests/10xVisium/section2/outs/spatial/tissue_lowres_image.png b/tests/10xVisium/section2/outs/spatial/tissue_lowres_image.png new file mode 100644 index 0000000..8e13265 Binary files /dev/null and b/tests/10xVisium/section2/outs/spatial/tissue_lowres_image.png differ diff --git a/tests/10xVisium/section2/outs/spatial/tissue_positions_list.csv b/tests/10xVisium/section2/outs/spatial/tissue_positions_list.csv new file mode 100644 index 0000000..5b72154 --- /dev/null +++ b/tests/10xVisium/section2/outs/spatial/tissue_positions_list.csv @@ -0,0 +1,49 @@ +AAACAAGTATCTCCCA-1,1,50,102,7237,8230 +AAACAATCTACTAGCA-1,1,3,43,1611,4170 +AAACACCAATAACTGC-1,1,59,19,8315,2519 +AAACAGAGCGACTCCT-1,1,14,94,2927,7679 +AAACAGCTTTCAGAAG-1,0,43,9,6400,1831 +AAACAGGGTCTATATT-1,0,47,13,6879,2106 +AAACAGTGTTCCTGGG-1,0,73,43,9991,4170 +AAACATGGTGAGAGGA-1,0,62,0,8674,1212 +AAACATTTCCCGGATT-1,0,61,97,8554,7886 +AAACCACTACACAGAT-1,0,3,117,1610,9261 +AAACCCGAACGAAATC-1,0,45,115,6639,9124 +AAACCGGAAATGTTAA-1,0,54,124,7716,9743 +AAACCGGGTAGGTACC-1,1,42,28,6280,3138 +AAACCGTTCGTCCAGG-1,1,52,42,7477,4101 +AAACCTAAGCAGCCGG-1,0,65,83,9033,6922 +AAACCTCATGAAGTTG-1,1,37,19,5681,2519 +AAACGAAGAACATACC-1,1,6,64,1970,5615 +AAACGAAGATGGAGTA-1,0,58,4,8195,1487 +AAACGACAGTCTTGCC-1,0,2,118,1491,9330 +AAACGAGACGGTTGAT-1,1,35,79,5442,6647 +AAACGCCCGAGATCGG-1,0,4,108,1730,8642 +AAACGCTGGGCACGAC-1,0,70,126,9631,9881 +AAACGGGCGTACGGGT-1,0,65,91,9033,7473 +AAACGGGTTGGTATCC-1,0,1,23,1371,2794 +AAACGGTTGCGAACTG-1,1,67,59,9273,5271 +AAACGTGTTCGCCCTA-1,0,14,118,2927,9330 +AAACTAACGTGGCGAC-1,0,8,110,2209,8780 +AAACTCGGTTCGCAAT-1,1,66,70,9153,6028 +AAACTCGTGATATAAG-1,0,23,113,4005,8986 +AAACTGCTGGCTCCAA-1,1,45,67,6639,5821 +AAACTTAATTGCACGC-1,0,64,12,8914,2037 +AAACTTGCAAACGTAT-1,1,45,19,6639,2519 +AAAGAATGACCTTAGA-1,0,64,2,8914,1349 +AAAGAATGTGGACTAA-1,0,71,105,9751,8436 +AAAGACATGAAGTTTA-1,0,0,92,1251,7541 +AAAGACCCAAGTCGCG-1,1,10,48,2449,4514 +AAAGACTGGGCGCTTT-1,0,29,15,4724,2243 +AAAGCTTGCCTACATA-1,0,26,122,4364,9605 +AAAGGCCCTATAATAC-1,1,66,22,9153,2725 +AAAGGCTACGGACCAT-1,1,62,54,8674,4927 +AAAGGCTCTCGCGCCG-1,1,55,55,7836,4996 +AAAGGGATGTAGCAAG-1,1,24,62,4125,5477 +AAAGGGCAGCTTGAAT-1,1,24,26,4125,3000 +AAAGGTAAGCTGTACC-1,0,10,106,2448,8504 +AAAGGTCAACGACATG-1,0,0,112,1251,8917 +AAAGTAGCATTGCTCA-1,1,51,27,7357,3069 +AAAGTCACTGATGTAA-1,1,10,52,2449,4789 +AAAGTCGACCCTCAGT-1,1,37,15,5681,2244 +AAAGTGCCATCAATTA-1,0,63,125,8793,9812 diff --git a/tests/conftest.py b/tests/conftest.py index 386b519..8667128 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,4 @@ +import os import pytest import numpy as np from biocframe import BiocFrame @@ -76,3 +77,15 @@ def spe(): ) return spe_instance + +@pytest.fixture +def dir(): + return "tests/10xVisium" + +@pytest.fixture +def sample_ids(): + return ["section1", "section2"] + +@pytest.fixture +def samples(dir, sample_ids): + return [os.path.join(dir, sample_id, "outs") for sample_id in sample_ids] \ No newline at end of file diff --git a/tests/test_read_tenx_visium.py b/tests/test_read_tenx_visium.py new file mode 100644 index 0000000..9560205 --- /dev/null +++ b/tests/test_read_tenx_visium.py @@ -0,0 +1,173 @@ +import os +import json +import numpy as np +import pandas as pd + +import biocutils as ut +from spatialexperiment import read_tenx_visium, SpatialExperiment, VirtualSpatialImage, LoadedSpatialImage + + +def test_read_tenx_visium(samples, sample_ids): + spe = read_tenx_visium( + samples=samples, + sample_ids=sample_ids, + type="sparse", + data="raw", + images="lowres", + load=False, + ) + + assert isinstance(spe, SpatialExperiment) + assert all( + [ + isinstance(img, VirtualSpatialImage) + for img in spe.get_img(sample_id=True, image_id=True) + ] + ) + + column_names = [ + "barcode", + "in_tissue", + "array_row", + "array_col", + "pxl_col_in_fullres", + "pxl_row_in_fullres", + ] + + tissue_positions_paths = [ + os.path.join(sample, "spatial", "tissue_positions_list.csv") for sample in samples + ] + tissue_positions_list = [ + pd.read_csv(tissue_positions_path, header=None, index_col=0, names=column_names) + for tissue_positions_path in tissue_positions_paths + ] + + sample_ids = np.repeat( + sample_ids, + [len(tissue_positions_df) for tissue_positions_df in tissue_positions_list], + ) + + tissue_positions = pd.concat(tissue_positions_list, axis=0) + tissue_positions["sample_id"] = sample_ids + tissue_positions["in_tissue"] = tissue_positions["in_tissue"].astype(bool) + + assert np.array_equal( + pd.crosstab(spe.column_data["sample_id"], spe.column_data["in_tissue"]).values, + pd.crosstab( + tissue_positions["sample_id"], tissue_positions["in_tissue"] + ).values, + ) + + scale_factor_paths = [ + os.path.join(sample, "spatial", "scalefactors_json.json") for sample in samples + ] + scale_factors = [] + for scale_factor_path in scale_factor_paths: + with open(scale_factor_path) as f: + scale_factors.append(json.load(f)["tissue_lowres_scalef"]) + scale_factors = np.array(scale_factors) + + assert np.array_equal(spe.img_data["scale_factor"], scale_factors) + + +def test_load_true(samples, sample_ids): + spe = read_tenx_visium( + samples=samples, + sample_ids=sample_ids, + type="sparse", + data="raw", + images="lowres", + load=True, + ) + + assert all( + [ + isinstance(img, LoadedSpatialImage) + for img in spe.get_img(sample_id=True, image_id=True) + ] + ) + + +def test_outs_dir(dir, samples, sample_ids): + dir = "tests/10xVisium" + sample_ids = ["section1", "section2"] + samples = [os.path.join(dir, sample_id, "outs") for sample_id in sample_ids] + + samples2 = samples3 = [os.path.join(dir, sample_id) for sample_id in sample_ids] + samples3[0] = os.path.join(samples3[0], "outs") + + spe1 = read_tenx_visium( + samples=samples, + sample_ids=sample_ids, + type="sparse", + data="raw", + images="lowres", + load=False, + ) + + spe2 = read_tenx_visium( + samples=samples2, + sample_ids=sample_ids, + type="sparse", + data="raw", + images="lowres", + load=False, + ) + + spe3 = read_tenx_visium( + samples=samples3, + sample_ids=sample_ids, + type="sparse", + data="raw", + images="lowres", + load=False, + ) + + assert spe1.column_data.to_pandas().equals(spe2.column_data.to_pandas()) + assert spe1.column_data.to_pandas().equals(spe3.column_data.to_pandas()) + + assert spe1.row_data.to_pandas().equals(spe2.row_data.to_pandas()) + assert spe1.row_data.to_pandas().equals(spe3.row_data.to_pandas()) + + assert spe1.img_data.to_pandas().equals(spe2.img_data.to_pandas()) + assert spe1.img_data.to_pandas().equals(spe3.img_data.to_pandas()) + + assert spe1.spatial_coords.to_pandas().equals(spe2.spatial_coords.to_pandas()) + assert spe1.spatial_coords.to_pandas().equals(spe3.spatial_coords.to_pandas()) + + +def test_tissue_positions_files(samples, sample_ids): + samples = samples + samples + sample_ids = sample_ids + [sample_id + "rep" for sample_id in sample_ids] + + spatial_coords_1 = read_tenx_visium( + samples=samples[0], + sample_ids=sample_ids[0], + type="sparse", + data="raw", + images="lowres", + load=False + ).spatial_coords + + spatial_coords_2 = read_tenx_visium( + samples=samples[1], + sample_ids=sample_ids[1], + type="sparse", + data="raw", + images="lowres", + load=False + ).spatial_coords + + spatial_coords_multi = read_tenx_visium( + samples=samples, + sample_ids=sample_ids, + type="sparse", + data="raw", + images="lowres", + load=False + ).spatial_coords + + assert spatial_coords_multi.shape[0] == 2 * spatial_coords_1.shape[0] + 2 * spatial_coords_2.shape[0] + + combined = ut.combine_rows(spatial_coords_1, spatial_coords_2, spatial_coords_1, spatial_coords_2) + assert spatial_coords_multi.to_pandas().equals(combined.to_pandas()) diff --git a/tests/test_spe_combine.py b/tests/test_spe_combine.py index 23d456b..9af6363 100644 --- a/tests/test_spe_combine.py +++ b/tests/test_spe_combine.py @@ -72,14 +72,14 @@ def test_relaxed_combine_columns(spe): spe2 = spe.set_assays( { "counts": np.random.poisson(lam=10, size=(nrows, ncols)), - "normalized": np.random.normal(size=(nrows, ncols)) + "normalized": np.random.normal(size=(nrows, ncols)), }, - in_place=False + in_place=False, ) with pytest.raises(Exception): combined = ut.combine_columns(spe, spe2) - + combined = ut.relaxed_combine_columns(spe, spe2) assert combined is not None assert isinstance(combined, SpatialExperiment)