diff --git a/.flake8 b/.flake8
deleted file mode 100644
index 43ded24c..00000000
--- a/.flake8
+++ /dev/null
@@ -1,62 +0,0 @@
-# Can't yet be moved to the pyproject.toml due to https://github.com/PyCQA/flake8/issues/234
-[flake8]
-max-line-length = 120
-ignore =
- # line break before a binary operator -> black does not adhere to PEP8
- W503
- # line break occured after a binary operator -> black does not adhere to PEP8
- W504
- # line too long -> we accept long comment lines; black gets rid of long code lines
- E501
- # whitespace before : -> black does not adhere to PEP8
- E203
- # line break before binary operator -> black does not adhere to PEP8
- W503
- # missing whitespace after ,', ';', or ':' -> black does not adhere to PEP8
- E231
- # continuation line over-indented for hanging indent -> black does not adhere to PEP8
- E126
- # too many leading '#' for block comment -> this is fine for indicating sections
- E262
- # Do not assign a lambda expression, use a def -> lambda expression assignments are convenient
- E731
- # allow I, O, l as variable names -> I is the identity matrix
- E741
- # Missing docstring in public package
- D104
- # Missing docstring in public module
- D100
- # Missing docstring in __init__
- D107
- # Errors from function calls in argument defaults. These are fine when the result is immutable.
- B008
- # Missing docstring in magic method
- D105
- # format string does contain unindexed parameters
- P101
- # first line should end with a period [Bug: doesn't work with single-line docstrings]
- D400
- # First line should be in imperative mood; try rephrasing
- D401
- # Abstract base class without abstractmethod.
- B024
-exclude = .git,__pycache__,build,docs/_build,dist
-per-file-ignores =
- tests/*: D
- */__init__.py: F401
- src/spatialdata_io/_constants/_enum.py: RST, D
-rst-roles =
- class,
- func,
- ref,
- attr,
- cite:p,
- cite:t,
-rst-directives =
- envvar,
- exception,
- seealso,
-rst-substitutions =
- version,
-extend-ignore =
- RST307,
diff --git a/.github/workflows/prepare_test_data.yaml b/.github/workflows/prepare_test_data.yaml
index bf979d96..728d12c3 100644
--- a/.github/workflows/prepare_test_data.yaml
+++ b/.github/workflows/prepare_test_data.yaml
@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Download test datasets
run: |
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index 299ed867..5fc710d5 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -9,9 +9,9 @@ jobs:
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Set up Python 3.12
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index c4d08869..9dc14695 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -26,9 +26,9 @@ jobs:
PYTHON: ${{ matrix.python }}
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python }}
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
@@ -37,7 +37,7 @@ jobs:
run: |
echo "::set-output name=dir::$(pip cache dir)"
- name: Restore pip cache
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ${{ steps.pip-cache-dir.outputs.dir }}
key: pip-${{ runner.os }}-${{ env.pythonLocation }}-${{ hashFiles('**/pyproject.toml') }}
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index a133e82c..6f2127a8 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -6,39 +6,22 @@ default_stages:
- pre-push
minimum_pre_commit_version: 2.16.0
repos:
- - repo: https://github.com/psf/black
- rev: 25.1.0
- hooks:
- - id: black
- repo: https://github.com/rbubley/mirrors-prettier
rev: v3.5.1
hooks:
- id: prettier
- - repo: https://github.com/asottile/blacken-docs
- rev: 1.19.1
- hooks:
- - id: blacken-docs
- - repo: https://github.com/PyCQA/isort
- rev: 6.0.0
+ - repo: https://github.com/astral-sh/ruff-pre-commit
+ rev: v0.11.2
hooks:
- - id: isort
+ - id: ruff
+ args: [--fix, --exit-non-zero-on-fix, --unsafe-fixes]
+ - id: ruff-format
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.15.0
hooks:
- id: mypy
additional_dependencies: [numpy, types-PyYAML]
exclude: docs/
- - repo: https://github.com/asottile/yesqa
- rev: v1.5.0
- hooks:
- - id: yesqa
- additional_dependencies:
- - flake8-tidy-imports
- - flake8-docstrings
- - flake8-rst-docstrings
- - flake8-comprehensions
- - flake8-bugbear
- - flake8-blind-except
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
@@ -49,31 +32,6 @@ repos:
args: [--fix=lf]
- id: trailing-whitespace
- id: check-case-conflict
- - repo: https://github.com/PyCQA/autoflake
- rev: v2.3.1
- hooks:
- - id: autoflake
- args:
- - --in-place
- - --remove-all-unused-imports
- - --remove-unused-variable
- - --ignore-init-module-imports
- - repo: https://github.com/PyCQA/flake8
- rev: 7.1.2
- hooks:
- - id: flake8
- additional_dependencies:
- - flake8-tidy-imports
- - flake8-docstrings
- - flake8-rst-docstrings
- - flake8-comprehensions
- - flake8-bugbear
- - flake8-blind-except
- - repo: https://github.com/asottile/pyupgrade
- rev: v3.19.1
- hooks:
- - id: pyupgrade
- args: [--py3-plus, --py310-plus, --keep-runtime-typing]
- repo: local
hooks:
- id: forbid-to-commit
@@ -83,7 +41,3 @@ repos:
Fix the merge conflicts manually and remove the .rej files.
language: fail
files: '.*\.rej$'
- - repo: https://github.com/PyCQA/doc8
- rev: v1.1.2
- hooks:
- - id: doc8
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
index f3a78576..31d8fef4 100644
--- a/.readthedocs.yaml
+++ b/.readthedocs.yaml
@@ -1,9 +1,8 @@
-# https://docs.readthedocs.io/en/stable/config-file/v2.html
version: 2
build:
- os: ubuntu-20.04
+ os: ubuntu-24.04
tools:
- python: "3.10"
+ python: "3.12"
sphinx:
configuration: docs/conf.py
fail_on_warning: true
diff --git a/docs/conf.py b/docs/conf.py
index 24e42ba9..5a10a5b4 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -9,6 +9,7 @@
from datetime import datetime
from importlib.metadata import metadata
from pathlib import Path
+
import spatialdata_io.experimental
_ = spatialdata_io.experimental
diff --git a/docs/extensions/typed_returns.py b/docs/extensions/typed_returns.py
index 94478130..d044c698 100644
--- a/docs/extensions/typed_returns.py
+++ b/docs/extensions/typed_returns.py
@@ -11,7 +11,7 @@ def _process_return(lines):
m = re.fullmatch(r"(?P\w+)\s+:\s+(?P[\w.]+)", line)
if m:
# Once this is in scanpydoc, we can use the fancy hover stuff
- yield f'-{m["param"]} (:class:`~{m["type"]}`)'
+ yield f"-{m['param']} (:class:`~{m['type']}`)"
else:
yield line
diff --git a/pyproject.toml b/pyproject.toml
index 81f0ae28..fefc1a37 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -78,32 +78,108 @@ addopts = [
"--import-mode=importlib", # allow using test files with same name
]
-[tool.isort]
-include_trailing_comma = true
-multi_line_output = 3
-profile = "black"
-skip_glob = ["docs/*"]
-
-[tool.black]
+[tool.ruff]
line-length = 120
-target-version = ['py310']
-include = '\.pyi?$'
-exclude = '''
-(
- /(
- \.eggs
- | \.git
- | \.hg
- | \.mypy_cache
- | \.tox
- | \.venv
- | _build
- | buck-out
- | build
- | dist
- )/
-)
-'''
+exclude = [
+ ".git",
+ ".tox",
+ "__pycache__",
+ "build",
+ "docs/_build",
+ "dist",
+ "setup.py",
+]
+lint.select = [
+ "F", # Errors detected by Pyflakes
+ "E", # Error detected by Pycodestyle
+ "W", # Warning detected by Pycodestyle
+ "I", # isort
+ "D", # pydocstyle
+ "B", # flake8-bugbear
+ "TID", # flake8-tidy-imports
+ "C4", # flake8-comprehensions
+ "BLE", # flake8-blind-except
+ "UP", # pyupgrade
+ "RUF100", # Report unused noqa directives
+ "TCH", # Typing imports
+ "NPY", # Numpy specific rules
+ # "PTH", # Use pathlib
+ # "S" # Security
+]
+lint.ignore = [
+ # Do not catch blind exception: `Exception`
+ "BLE001",
+ # Errors from function calls in argument defaults. These are fine when the result is immutable.
+ "B008",
+ # line too long -> we accept long comment lines; black gets rid of long code lines
+ "E501",
+ # Do not assign a lambda expression, use a def -> lambda expression assignments are convenient
+ "E731",
+ # allow I, O, l as variable names -> I is the identity matrix
+ "E741",
+ # Missing docstring in public module
+ "D100",
+ # undocumented-public-class
+ "D101",
+ # Missing docstring in public method
+ "D102",
+ # Missing docstring in public function
+ "D103",
+ # Missing docstring in public package
+ "D104",
+ # __magic__ methods are are often self-explanatory, allow missing docstrings
+ "D105",
+ # Missing docstring in public nested class
+ "D106",
+ # Missing docstring in __init__
+ "D107",
+ ## Disable one in each pair of mutually incompatible rules
+ # We don’t want a blank line before a class docstring
+ "D203",
+ # 1 blank line required after class docstring
+ "D204",
+ # first line should end with a period [Bug: doesn't work with single-line docstrings]
+ # We want docstrings to start immediately after the opening triple quote
+ "D213",
+ # Section underline is over-indented ("{name}")
+ "D215",
+ # First line should be in imperative mood; try rephrasing
+ "D401",
+ # First word of the first line should be capitalized: {} -> {}
+ "D403",
+ # First word of the docstring should not be "This"
+ "D404",
+ # Section name should end with a newline ("{name}")
+ "D406",
+ # Missing dashed underline after section ("{name}")
+ "D407",
+ # Section underline should be in the line following the section's name ("{name}")
+ "D408",
+ # Section underline should match the length of its name ("{name}")
+ "D409",
+ # No blank lines allowed between a section header and its content ("{name}")
+ "D412",
+ # Missing blank line after last section ("{name}")
+ "D413",
+ # Missing argument description in the docstring
+ "D417",
+ # camcelcase imported as lowercase
+ "N813",
+ # module import not at top level of file
+ "E402",
+ # open()` should be replaced by `Path.open()
+ "PTH123",
+ # subprocess` call: check for execution of untrusted input - https://github.com/PyCQA/bandit/issues/333
+ "S603",
+ # Starting a process with a partial executable path
+ "S607",
+ # Prefer absolute imports over relative imports from parent modules
+ "TID252",
+ # Standard pseudo-random generators are not suitable for cryptographic purposes
+ "S311",
+ # Unused imports
+ "F401",
+]
[tool.jupytext]
formats = "ipynb,md"
diff --git a/src/spatialdata_io/__main__.py b/src/spatialdata_io/__main__.py
index 1d2f714a..c65e46f4 100644
--- a/src/spatialdata_io/__main__.py
+++ b/src/spatialdata_io/__main__.py
@@ -29,8 +29,7 @@
@click.group()
def cli() -> None:
- """
- Convert standard technology data formats to SpatialData object.
+ """Convert standard technology data formats to SpatialData object.
Usage:
@@ -66,7 +65,7 @@ def _input_output_click_options(func: Callable[..., None]) -> Callable[..., None
help="Whether the .fcs file is provided if False a .csv file is expected. [default: True]",
)
def codex_wrapper(input: str, output: str, fcs: bool = True) -> None:
- """Codex conversion to SpatialData"""
+ """Codex conversion to SpatialData."""
sdata = codex(input, fcs=fcs) # type: ignore[name-defined] # noqa: F821
sdata.write(output)
@@ -76,7 +75,7 @@ def codex_wrapper(input: str, output: str, fcs: bool = True) -> None:
@click.option("--dataset-id", type=str, default=None, help="Name of the dataset [default: None]")
@click.option("--transcripts", type=bool, default=True, help="Whether to load transcript information. [default: True]")
def cosmx_wrapper(input: str, output: str, dataset_id: str | None = None, transcripts: bool = True) -> None:
- """Cosmic conversion to SpatialData"""
+ """Cosmic conversion to SpatialData."""
sdata = cosmx(input, dataset_id=dataset_id, transcripts=transcripts) # type: ignore[name-defined] # noqa: F821
sdata.write(output)
@@ -84,7 +83,7 @@ def cosmx_wrapper(input: str, output: str, dataset_id: str | None = None, transc
@cli.command(name="curio")
@_input_output_click_options
def curio_wrapper(input: str, output: str) -> None:
- """Curio conversion to SpatialData"""
+ """Curio conversion to SpatialData."""
sdata = curio(input) # type: ignore[name-defined] # noqa: F821
sdata.write(output)
@@ -117,7 +116,7 @@ def dbit_wrapper(
border: bool = True,
border_scale: float = 1,
) -> None:
- """Conversion of DBit-seq to SpatialData"""
+ """Conversion of DBit-seq to SpatialData."""
sdata = dbit( # type: ignore[name-defined] # noqa: F821
input,
anndata_path=anndata_path,
@@ -174,7 +173,7 @@ def iss_wrapper(
multiscale_image: bool = True,
multiscale_labels: bool = True,
) -> None:
- """ISS conversion to SpatialData"""
+ """ISS conversion to SpatialData."""
sdata = iss( # type: ignore[name-defined] # noqa: F821
input,
raw_relative_path,
@@ -194,7 +193,7 @@ def iss_wrapper(
)
@click.option("--output", "-o", type=click.Path(), help="Path to the output.zarr file.", required=True)
def mcmicro_wrapper(input: str, output: str) -> None:
- """Conversion of MCMicro to SpatialData"""
+ """Conversion of MCMicro to SpatialData."""
sdata = mcmicro(input) # type: ignore[name-defined] # noqa: F821
sdata.write(output)
@@ -233,7 +232,7 @@ def merscope_wrapper(
cells_table: bool = True,
mosaic_images: bool = True,
) -> None:
- """Merscope conversion to SpatialData"""
+ """Merscope conversion to SpatialData."""
sdata = merscope( # type: ignore[name-defined] # noqa: F821
input,
vpt_outputs=vpt_outputs,
@@ -273,7 +272,7 @@ def seqfish_wrapper(
cells_as_circles: bool = False,
rois: list[int] | None = None,
) -> None:
- """Seqfish conversion to SpatialData"""
+ """Seqfish conversion to SpatialData."""
rois = list(rois) if rois else None
sdata = seqfish( # type: ignore[name-defined] # noqa: F821
input,
@@ -296,7 +295,7 @@ def seqfish_wrapper(
help="What kind of labels to use. [default: 'deepcell']",
)
def steinbock_wrapper(input: str, output: str, labels_kind: Literal["deepcell", "ilastik"] = "deepcell") -> None:
- """Steinbock conversion to SpatialData"""
+ """Steinbock conversion to SpatialData."""
sdata = steinbock(input, labels_kind=labels_kind) # type: ignore[name-defined] # noqa: F821
sdata.write(output)
@@ -320,7 +319,7 @@ def stereoseq_wrapper(
read_square_bin: bool = True,
optional_tif: bool = False,
) -> None:
- """Stereoseq conversion to SpatialData"""
+ """Stereoseq conversion to SpatialData."""
sdata = stereoseq(input, dataset_id=dataset_id, read_square_bin=read_square_bin, optional_tif=optional_tif) # type: ignore[name-defined] # noqa: F821
sdata.write(output)
@@ -361,7 +360,7 @@ def visium_wrapper(
tissue_positions_file: str | Path | None = None,
scalefactors_file: str | Path | None = None,
) -> None:
- """Visium conversion to SpatialData"""
+ """Visium conversion to SpatialData."""
sdata = visium( # type: ignore[name-defined] # noqa: F821
input,
dataset_id=dataset_id,
@@ -424,7 +423,7 @@ def visium_hd_wrapper(
load_all_images: bool = False,
annotate_table_by_labels: bool = False,
) -> None:
- """Visium HD conversion to SpatialData"""
+ """Visium HD conversion to SpatialData."""
sdata = visium_hd( # type: ignore[name-defined] # noqa: F821
path=input,
dataset_id=dataset_id,
@@ -483,7 +482,7 @@ def xenium_wrapper(
cells_table: bool = True,
n_jobs: int = 1,
) -> None:
- """Xenium conversion to SpatialData"""
+ """Xenium conversion to SpatialData."""
sdata = xenium( # type: ignore[name-defined] # noqa: F821
input,
cells_boundaries=cells_boundaries,
@@ -637,7 +636,7 @@ def read_generic_wrapper(
data_axes: str | None = None,
coordinate_system: str | None = None,
) -> None:
- """Read generic data to SpatialData"""
+ """Read generic data to SpatialData."""
if data_axes is not None and "".join(sorted(data_axes)) not in ["cxy", "cxyz"]:
raise ValueError("data_axes must be a permutation of 'cyx' or 'czyx'.")
generic_to_zarr(input=input, output=output, name=name, data_axes=data_axes, coordinate_system=coordinate_system)
diff --git a/src/spatialdata_io/_constants/_constants.py b/src/spatialdata_io/_constants/_constants.py
index 70b09e63..33795f92 100644
--- a/src/spatialdata_io/_constants/_constants.py
+++ b/src/spatialdata_io/_constants/_constants.py
@@ -301,7 +301,7 @@ class McmicroKeys(ModeEnum):
@unique
class MerscopeKeys(ModeEnum):
- """Keys for *MERSCOPE* data (Vizgen plateform)"""
+ """Keys for *MERSCOPE* data (Vizgen plateform)."""
# files and directories
IMAGES_DIR = "images"
diff --git a/src/spatialdata_io/_constants/_enum.py b/src/spatialdata_io/_constants/_enum.py
index 55decf37..764f5efd 100644
--- a/src/spatialdata_io/_constants/_enum.py
+++ b/src/spatialdata_io/_constants/_enum.py
@@ -42,7 +42,7 @@ def wrapper(*args: Any, **kwargs: Any) -> "ErrorFormatterABC":
class ABCEnumMeta(EnumMeta, ABCMeta):
def __call__(cls, *args: Any, **kwargs: Any) -> Any:
if getattr(cls, "__error_format__", None) is None:
- raise TypeError(f"Can't instantiate class `{cls.__name__}` " f"without `__error_format__` class attribute.")
+ raise TypeError(f"Can't instantiate class `{cls.__name__}` without `__error_format__` class attribute.")
return super().__call__(*args, **kwargs)
def __new__(cls, clsname: str, superclasses: tuple[type], attributedict: dict[str, Any]) -> "ABCEnumMeta":
@@ -51,7 +51,7 @@ def __new__(cls, clsname: str, superclasses: tuple[type], attributedict: dict[st
return res
-class ErrorFormatterABC(ABC):
+class ErrorFormatterABC(ABC): # noqa
"""Mixin class that formats invalid value when constructing an enum."""
__error_format__ = "Invalid option `{0}` for `{1}`. Valid options are: `{2}`."
@@ -59,7 +59,9 @@ class ErrorFormatterABC(ABC):
@classmethod
def _format(cls, value: Enum) -> str:
return cls.__error_format__.format(
- value, cls.__name__, [m.value for m in cls.__members__.values()] # type: ignore[attr-defined]
+ value,
+ cls.__name__,
+ [m.value for m in cls.__members__.values()], # type: ignore[attr-defined]
)
diff --git a/src/spatialdata_io/_docs.py b/src/spatialdata_io/_docs.py
index 34efbd5e..958d8f67 100644
--- a/src/spatialdata_io/_docs.py
+++ b/src/spatialdata_io/_docs.py
@@ -1,11 +1,13 @@
from __future__ import annotations
-from collections.abc import Callable
from textwrap import dedent
-from typing import Any
+from typing import TYPE_CHECKING, Any
+if TYPE_CHECKING:
+ from collections.abc import Callable
-def inject_docs(**kwargs: Any) -> Callable[..., Any]: # noqa: D103
+
+def inject_docs(**kwargs: Any) -> Callable[..., Any]:
# taken from scanpy
def decorator(obj: Any) -> Any:
obj.__doc__ = dedent(obj.__doc__).format(**kwargs)
diff --git a/src/spatialdata_io/_utils.py b/src/spatialdata_io/_utils.py
index 90dee2ac..c73a4d69 100644
--- a/src/spatialdata_io/_utils.py
+++ b/src/spatialdata_io/_utils.py
@@ -2,8 +2,10 @@
import functools
import warnings
-from collections.abc import Callable
-from typing import Any, TypeVar
+from typing import TYPE_CHECKING, Any, TypeVar
+
+if TYPE_CHECKING:
+ from collections.abc import Callable
RT = TypeVar("RT")
@@ -11,8 +13,7 @@
# these two functions should be removed and imported from spatialdata._utils once the multi_table branch, which
# introduces them, is merged
def deprecation_alias(**aliases: str) -> Callable[[Callable[..., RT]], Callable[..., RT]]:
- """
- Decorate a function to warn user of use of arguments set for deprecation.
+ """Decorate a function to warn user of use of arguments set for deprecation.
Parameters
----------
diff --git a/src/spatialdata_io/converters/generic_to_zarr.py b/src/spatialdata_io/converters/generic_to_zarr.py
index 11c5c658..eaf4f0f8 100644
--- a/src/spatialdata_io/converters/generic_to_zarr.py
+++ b/src/spatialdata_io/converters/generic_to_zarr.py
@@ -21,8 +21,7 @@ def generic_to_zarr(
data_axes: str | None = None,
coordinate_system: str | None = None,
) -> None:
- """
- Read generic data from an input file and save it as a SpatialData zarr store.
+ """Read generic data from an input file and save it as a SpatialData zarr store.
Parameters
----------
diff --git a/src/spatialdata_io/converters/legacy_anndata.py b/src/spatialdata_io/converters/legacy_anndata.py
index ed0806c2..3dcf05ee 100644
--- a/src/spatialdata_io/converters/legacy_anndata.py
+++ b/src/spatialdata_io/converters/legacy_anndata.py
@@ -1,9 +1,9 @@
from __future__ import annotations
import warnings
+from typing import TYPE_CHECKING
import numpy as np
-from anndata import AnnData
from spatialdata import (
SpatialData,
get_centroids,
@@ -15,6 +15,9 @@
from spatialdata.models import Image2DModel, ShapesModel, TableModel, get_table_keys
from spatialdata.transformations import Identity, Scale
+if TYPE_CHECKING:
+ from anndata import AnnData
+
def to_legacy_anndata(
sdata: SpatialData,
@@ -22,8 +25,7 @@ def to_legacy_anndata(
table_name: str | None = None,
include_images: bool = False,
) -> AnnData:
- """
- Convert a SpatialData object to a (legacy) spatial AnnData object.
+ """Convert a SpatialData object to a (legacy) spatial AnnData object.
This is useful for using packages expecting spatial information in AnnData, for example Scanpy and older versions
of Squidpy. Using this format for any new package is not recommended.
@@ -113,9 +115,9 @@ def to_legacy_anndata(
assert len(css) == 1, "The SpatialData object has more than one coordinate system. Please specify one."
coordinate_system = css[0]
else:
- assert (
- coordinate_system in css
- ), f"The SpatialData object does not have the coordinate system {coordinate_system}."
+ assert coordinate_system in css, (
+ f"The SpatialData object does not have the coordinate system {coordinate_system}."
+ )
sdata = sdata.filter_by_coordinate_system(coordinate_system)
if table_name is None:
@@ -219,8 +221,7 @@ def to_legacy_anndata(
def from_legacy_anndata(adata: AnnData) -> SpatialData:
- """
- Convert (legacy) spatial AnnData object to SpatialData object.
+ """Convert (legacy) spatial AnnData object to SpatialData object.
This is useful for parsing a (legacy) spatial AnnData object, for example the ones produced by Scanpy and older
version of Squidpy.
@@ -288,9 +289,9 @@ def from_legacy_anndata(adata: AnnData) -> SpatialData:
# construct the spatialdata elements
if hires is not None:
# prepare the hires image
- assert (
- tissue_hires_scalef is not None
- ), "tissue_hires_scalef is required when an the hires image is present"
+ assert tissue_hires_scalef is not None, (
+ "tissue_hires_scalef is required when an the hires image is present"
+ )
hires_image = Image2DModel.parse(
hires, dims=("y", "x", "c"), transformations={f"{dataset_id}_downscaled_hires": Identity()}
)
@@ -301,9 +302,9 @@ def from_legacy_anndata(adata: AnnData) -> SpatialData:
shapes_transformations[f"{dataset_id}_downscaled_hires"] = scale_hires
if lowres is not None:
# prepare the lowres image
- assert (
- tissue_lowres_scalef is not None
- ), "tissue_lowres_scalef is required when an the lowres image is present"
+ assert tissue_lowres_scalef is not None, (
+ "tissue_lowres_scalef is required when an the lowres image is present"
+ )
lowres_image = Image2DModel.parse(
lowres, dims=("y", "x", "c"), transformations={f"{dataset_id}_downscaled_lowres": Identity()}
)
diff --git a/src/spatialdata_io/readers/_utils/_read_10x_h5.py b/src/spatialdata_io/readers/_utils/_read_10x_h5.py
index 4c5b33cc..a7ffa566 100644
--- a/src/spatialdata_io/readers/_utils/_read_10x_h5.py
+++ b/src/spatialdata_io/readers/_utils/_read_10x_h5.py
@@ -44,8 +44,7 @@ def _read_10x_h5(
genome: str | None = None,
gex_only: bool = True,
) -> AnnData:
- """
- Read 10x-Genomics-formatted hdf5 file.
+ """Read 10x-Genomics-formatted hdf5 file.
Parameters
----------
@@ -84,7 +83,7 @@ def _read_10x_h5(
if genome not in adata.var["genome"].values:
raise ValueError(
f"Could not find data corresponding to genome `{genome}` in `{filename}`. "
- f'Available genomes are: {list(adata.var["genome"].unique())}.'
+ f"Available genomes are: {list(adata.var['genome'].unique())}."
)
adata = adata[:, adata.var["genome"] == genome]
if gex_only:
@@ -126,7 +125,7 @@ def _read_v3_10x_h5(filename: str | Path, *, start: Any | None = None) -> AnnDat
)
return adata
except KeyError:
- raise Exception("File is missing one or more required datasets.")
+ raise Exception("File is missing one or more required datasets.") from None
def _collect_datasets(dsets: dict[str, Any], group: h5py.Group) -> None:
diff --git a/src/spatialdata_io/readers/_utils/_utils.py b/src/spatialdata_io/readers/_utils/_utils.py
index 82676098..6072fbc8 100644
--- a/src/spatialdata_io/readers/_utils/_utils.py
+++ b/src/spatialdata_io/readers/_utils/_utils.py
@@ -1,11 +1,10 @@
from __future__ import annotations
import os
-from collections.abc import Mapping
from pathlib import Path
-from typing import Any, Union
+from typing import TYPE_CHECKING, Any, Union
-from anndata import AnnData, read_text
+from anndata.io import read_text
from h5py import File
from ome_types import from_tiff
from ome_types.model import Pixels, UnitsLength
@@ -13,7 +12,12 @@
from spatialdata_io.readers._utils._read_10x_h5 import _read_10x_h5
-PathLike = Union[os.PathLike, str] # type:ignore[type-arg]
+if TYPE_CHECKING:
+ from collections.abc import Mapping
+
+ from anndata import AnnData
+
+PathLike = os.PathLike | str # type:ignore[type-arg]
def _read_counts(
@@ -53,7 +57,7 @@ def _read_counts(
try:
from scanpy.readwrite import read_10x_mtx
except ImportError:
- raise ImportError("Please install scanpy to read 10x mtx files, `pip install scanpy`.")
+ raise ImportError("Please install scanpy to read 10x mtx files, `pip install scanpy`.") from None
prefix = counts_file.replace("matrix.mtx.gz", "")
adata = read_10x_mtx(path, prefix=prefix, **kwargs)
else:
diff --git a/src/spatialdata_io/readers/codex.py b/src/spatialdata_io/readers/codex.py
index 3fd7a6d8..ef36b01c 100644
--- a/src/spatialdata_io/readers/codex.py
+++ b/src/spatialdata_io/readers/codex.py
@@ -2,10 +2,9 @@
import os
import re
-from collections.abc import Mapping
from pathlib import Path
from types import MappingProxyType
-from typing import Any
+from typing import TYPE_CHECKING, Any
import anndata as ad
import pandas as pd
@@ -18,6 +17,9 @@
from spatialdata_io._constants._constants import CodexKeys
from spatialdata_io._docs import inject_docs
+if TYPE_CHECKING:
+ from collections.abc import Mapping
+
__all__ = ["codex"]
@@ -27,8 +29,7 @@ def codex(
fcs: bool = True,
imread_kwargs: Mapping[str, Any] = MappingProxyType({}),
) -> SpatialData:
- """
- Read *CODEX* formatted dataset.
+ """Read *CODEX* formatted dataset.
This function reads the following files:
diff --git a/src/spatialdata_io/readers/cosmx.py b/src/spatialdata_io/readers/cosmx.py
index 561fe914..a3960168 100644
--- a/src/spatialdata_io/readers/cosmx.py
+++ b/src/spatialdata_io/readers/cosmx.py
@@ -2,17 +2,15 @@
import os
import re
-from collections.abc import Mapping
from pathlib import Path
from types import MappingProxyType
-from typing import Any
+from typing import TYPE_CHECKING, Any
import dask.array as da
import numpy as np
import pandas as pd
import pyarrow as pa
from anndata import AnnData
-from dask.dataframe import DataFrame as DaskDataFrame
from dask_image.imread import imread
from scipy.sparse import csr_matrix
from skimage.transform import estimate_transform
@@ -24,6 +22,11 @@
from spatialdata_io._constants._constants import CosmxKeys
from spatialdata_io._docs import inject_docs
+if TYPE_CHECKING:
+ from collections.abc import Mapping
+
+ from dask.dataframe import DataFrame as DaskDataFrame
+
__all__ = ["cosmx"]
@@ -35,8 +38,7 @@ def cosmx(
imread_kwargs: Mapping[str, Any] = MappingProxyType({}),
image_models_kwargs: Mapping[str, Any] = MappingProxyType({}),
) -> SpatialData:
- """
- Read *Cosmx Nanostring* data.
+ """Read *Cosmx Nanostring* data.
This function reads the following files:
diff --git a/src/spatialdata_io/readers/curio.py b/src/spatialdata_io/readers/curio.py
index d0d9d1a9..55b0b00e 100644
--- a/src/spatialdata_io/readers/curio.py
+++ b/src/spatialdata_io/readers/curio.py
@@ -19,8 +19,7 @@
def curio(
path: str | Path,
) -> SpatialData:
- """
- Read *Curio* formatted dataset.
+ """Read *Curio* formatted dataset.
This function reads the following files:
@@ -73,7 +72,7 @@ def curio(
categories = metrics[CurioKeys.CATEGORY].unique()
for cat in categories:
df = metrics.loc[metrics[CurioKeys.CATEGORY] == cat]
- adata.uns[cat] = dict(zip(df.iloc[:, 0], df.iloc[:, 1]))
+ adata.uns[cat] = dict(zip(df.iloc[:, 0], df.iloc[:, 1], strict=False))
adata.uns[CurioKeys.TOP_CLUSTER_DEFINING_FEATURES] = var_features_clusters
# adding Moran's I information in adata.var, for the variable for which it is available
diff --git a/src/spatialdata_io/readers/dbit.py b/src/spatialdata_io/readers/dbit.py
index 4fc1afdb..bd4d20d2 100644
--- a/src/spatialdata_io/readers/dbit.py
+++ b/src/spatialdata_io/readers/dbit.py
@@ -4,6 +4,7 @@
import re
from pathlib import Path
from re import Pattern
+from typing import TYPE_CHECKING
import anndata as ad
import numpy as np
@@ -11,7 +12,6 @@
import shapely
import spatialdata as sd
from dask_image.imread import imread
-from numpy.typing import NDArray
from spatialdata import SpatialData
from spatialdata._logging import logger
from xarray import DataArray
@@ -19,6 +19,9 @@
from spatialdata_io._constants._constants import DbitKeys
from spatialdata_io._docs import inject_docs
+if TYPE_CHECKING:
+ from numpy.typing import NDArray
+
__all__ = ["dbit"]
@@ -29,8 +32,7 @@ def _check_path(
path_specific: str | Path | None = None,
optional_arg: bool = False,
) -> tuple[Path | None, bool]:
- """
- Check that the path is valid and match a regex pattern.
+ """Check that the path is valid and match a regex pattern.
Parameters
----------
@@ -104,15 +106,14 @@ def _check_path(
logger.warning(message)
return file_path, flag
else:
- raise IndexError(message)
+ raise IndexError(message) from None
logger.warning(f"{file_path} is used.")
return file_path, flag
def _barcode_check(barcode_file: Path) -> pd.DataFrame:
- """
- Check that the barcode file is formatted as expected.
+ """Check that the barcode file is formatted as expected.
What do we expect :
A tab separated file, headless, with 2 columns:
@@ -176,8 +177,7 @@ def _barcode_check(barcode_file: Path) -> pd.DataFrame:
def _xy2edges(xy: list[int], scale: float = 1.0, border: bool = True, border_scale: float = 1) -> NDArray[np.double]:
- """
- Construct vertex coordinate of a square from the barcode coordinates.
+ """Construct vertex coordinate of a square from the barcode coordinates.
The constructed square has a scalable border.
@@ -225,8 +225,7 @@ def dbit(
border: bool = True,
border_scale: float = 1,
) -> SpatialData:
- """
- Read DBiT experiment data (Deterministic Barcoding in Tissue)
+ """Read DBiT experiment data (Deterministic Barcoding in Tissue).
This function reads the following files:
@@ -279,10 +278,16 @@ def dbit(
# search for files paths. Gives priority to files matching the pattern found in path.
anndata_path_checked = _check_path(
- path=path, path_specific=anndata_path, pattern=patt_h5ad, key=DbitKeys.COUNTS_FILE # type: ignore
+ path=path, # type: ignore
+ path_specific=anndata_path,
+ pattern=patt_h5ad,
+ key=DbitKeys.COUNTS_FILE,
)[0]
barcode_position_checked = _check_path(
- path=path, path_specific=barcode_position, pattern=patt_barcode, key=DbitKeys.BARCODE_POSITION # type: ignore
+ path=path, # type: ignore
+ path_specific=barcode_position,
+ pattern=patt_barcode,
+ key=DbitKeys.BARCODE_POSITION,
)[0]
image_path_checked, hasimage = _check_path(
path=path, # type: ignore
diff --git a/src/spatialdata_io/readers/generic.py b/src/spatialdata_io/readers/generic.py
index 3ada671d..904b94dc 100644
--- a/src/spatialdata_io/readers/generic.py
+++ b/src/spatialdata_io/readers/generic.py
@@ -1,17 +1,21 @@
from __future__ import annotations
import warnings
-from collections.abc import Sequence
from pathlib import Path
+from typing import TYPE_CHECKING
import numpy as np
from dask_image.imread import imread
-from geopandas import GeoDataFrame
from spatialdata._docs import docstring_parameter
from spatialdata.models import Image2DModel, ShapesModel
from spatialdata.models._utils import DEFAULT_COORDINATE_SYSTEM
from spatialdata.transformations import Identity
-from xarray import DataArray
+
+if TYPE_CHECKING:
+ from collections.abc import Sequence
+
+ from geopandas import GeoDataFrame
+ from xarray import DataArray
VALID_IMAGE_TYPES = [".tif", ".tiff", ".png", ".jpg", ".jpeg"]
VALID_SHAPE_TYPES = [".geojson"]
@@ -29,8 +33,7 @@ def generic(
data_axes: Sequence[str] | None = None,
coordinate_system: str | None = None,
) -> DataArray | GeoDataFrame:
- """
- Read a generic shapes or image file and save it as SpatialData zarr.
+ """Read a generic shapes or image file and save it as SpatialData zarr.
Supported image types: {valid_image_types}.
Supported shape types: {valid_shape_types} (only Polygons and MultiPolygons are supported).
@@ -66,12 +69,12 @@ def generic(
def geojson(input: Path, coordinate_system: str) -> GeoDataFrame:
- """Reads a GeoJSON file and returns a parsed GeoDataFrame spatial element"""
+ """Reads a GeoJSON file and returns a parsed GeoDataFrame spatial element."""
return ShapesModel.parse(input, transformations={coordinate_system: Identity()})
def image(input: Path, data_axes: Sequence[str], coordinate_system: str) -> DataArray:
- """Reads an image file and returns a parsed Image2D spatial element"""
+ """Reads an image file and returns a parsed Image2D spatial element."""
# this function is just a draft, the more general one will be available when
# https://github.com/scverse/spatialdata-io/pull/234 is merged
image = imread(input)
diff --git a/src/spatialdata_io/readers/iss.py b/src/spatialdata_io/readers/iss.py
index 78547f9e..63c86571 100644
--- a/src/spatialdata_io/readers/iss.py
+++ b/src/spatialdata_io/readers/iss.py
@@ -1,9 +1,8 @@
from __future__ import annotations
-from collections.abc import Mapping
from pathlib import Path
from types import MappingProxyType
-from typing import Any
+from typing import TYPE_CHECKING, Any
import anndata as ad
from dask_image.imread import imread
@@ -14,6 +13,9 @@
from spatialdata_io._docs import inject_docs
+if TYPE_CHECKING:
+ from collections.abc import Mapping
+
__all__ = ["iss"]
@@ -31,8 +33,7 @@ def iss(
image_models_kwargs: Mapping[str, Any] = MappingProxyType({}),
labels_models_kwargs: Mapping[str, Any] = MappingProxyType({}),
) -> SpatialData:
- """
- Read *Sanger ISS* formatted dataset.
+ """Read *Sanger ISS* formatted dataset.
This function reads the following files:
diff --git a/src/spatialdata_io/readers/macsima.py b/src/spatialdata_io/readers/macsima.py
index 58e962eb..8bbe0532 100644
--- a/src/spatialdata_io/readers/macsima.py
+++ b/src/spatialdata_io/readers/macsima.py
@@ -2,12 +2,11 @@
import warnings
from collections import defaultdict
-from collections.abc import Mapping
from copy import deepcopy
from dataclasses import dataclass
from pathlib import Path
from types import MappingProxyType
-from typing import Any
+from typing import TYPE_CHECKING, Any
import anndata as ad
import dask.array as da
@@ -24,6 +23,9 @@
parse_physical_size,
)
+if TYPE_CHECKING:
+ from collections.abc import Mapping
+
__all__ = ["macsima"]
@@ -185,8 +187,7 @@ def macsima(
skip_rounds: list[int] | None = None,
include_cycle_in_channel_name: bool = False,
) -> SpatialData:
- """
- Read *MACSima* formatted dataset.
+ """Read *MACSima* formatted dataset.
This function reads images from a MACSima cyclic imaging experiment. Metadata of the cycle rounds is parsed from
the image names. The channel names are parsed from the OME metadata.
diff --git a/src/spatialdata_io/readers/mcmicro.py b/src/spatialdata_io/readers/mcmicro.py
index 04096c71..f6cca509 100644
--- a/src/spatialdata_io/readers/mcmicro.py
+++ b/src/spatialdata_io/readers/mcmicro.py
@@ -1,10 +1,9 @@
from __future__ import annotations
import re
-from collections.abc import Mapping
from pathlib import Path
from types import MappingProxyType
-from typing import Any
+from typing import TYPE_CHECKING, Any
import anndata as ad
import numpy as np
@@ -12,8 +11,6 @@
import yaml
from anndata import AnnData
from dask_image.imread import imread
-from multiscale_spatial_image.multiscale_spatial_image import MultiscaleSpatialImage
-from spatial_image import SpatialImage
from spatialdata import SpatialData
from spatialdata.models import Image2DModel, Labels2DModel, TableModel
from spatialdata.transformations import Identity, Translation, set_transformation
@@ -21,6 +18,12 @@
from spatialdata_io._constants._constants import McmicroKeys
+if TYPE_CHECKING:
+ from collections.abc import Mapping
+
+ from multiscale_spatial_image.multiscale_spatial_image import MultiscaleSpatialImage
+ from spatial_image import SpatialImage
+
__all__ = ["mcmicro"]
@@ -48,8 +51,7 @@ def mcmicro(
image_models_kwargs: Mapping[str, Any] = MappingProxyType({}),
labels_models_kwargs: Mapping[str, Any] = MappingProxyType({}),
) -> SpatialData:
- """
- Read a *Mcmicro* output into a SpatialData object.
+ """Read a *Mcmicro* output into a SpatialData object.
.. seealso::
diff --git a/src/spatialdata_io/readers/merscope.py b/src/spatialdata_io/readers/merscope.py
index fe0f73fb..3c20639b 100644
--- a/src/spatialdata_io/readers/merscope.py
+++ b/src/spatialdata_io/readers/merscope.py
@@ -2,10 +2,9 @@
import re
import warnings
-from collections.abc import Callable, Mapping
from pathlib import Path
from types import MappingProxyType
-from typing import Any, Literal
+from typing import TYPE_CHECKING, Any, Literal
import anndata
import dask.dataframe as dd
@@ -24,6 +23,9 @@
from spatialdata_io._constants._constants import MerscopeKeys
from spatialdata_io._docs import inject_docs
+if TYPE_CHECKING:
+ from collections.abc import Callable, Mapping
+
SUPPORTED_BACKENDS = ["dask_image", "rioxarray"]
@@ -37,8 +39,7 @@ def _get_channel_names(images_dir: Path) -> list[str]:
def _get_file_paths(path: Path, vpt_outputs: Path | str | dict[str, Any] | None) -> tuple[Path, Path, Path]:
- """
- Gets the MERSCOPE file paths when vpt_outputs is provided
+ """Gets the MERSCOPE file paths when vpt_outputs is provided.
That is, (i) the file of transcript per cell, (ii) the cell metadata file, and (iii) the cell boundary file
"""
@@ -58,9 +59,9 @@ def _get_file_paths(path: Path, vpt_outputs: Path | str | dict[str, Any] | None)
]
valid_boundaries = [path for path in plausible_boundaries if path.exists()]
- assert (
- valid_boundaries
- ), f"Boundary file not found - expected to find one of these files: {', '.join(map(str, plausible_boundaries))}"
+ assert valid_boundaries, (
+ f"Boundary file not found - expected to find one of these files: {', '.join(map(str, plausible_boundaries))}"
+ )
return (
vpt_outputs / MerscopeKeys.COUNTS_FILE,
@@ -95,8 +96,7 @@ def merscope(
imread_kwargs: Mapping[str, Any] = MappingProxyType({}),
image_models_kwargs: Mapping[str, Any] = MappingProxyType({}),
) -> SpatialData:
- """
- Read *MERSCOPE* data from Vizgen.
+ """Read *MERSCOPE* data from Vizgen.
This function reads the following files:
@@ -160,9 +160,9 @@ def merscope(
assert isinstance(image_models_kwargs, dict)
image_models_kwargs["scale_factors"] = [2, 2, 2, 2]
- assert (
- backend is None or backend in SUPPORTED_BACKENDS
- ), f"Backend '{backend} not supported. Should be one of: {', '.join(SUPPORTED_BACKENDS)}"
+ assert backend is None or backend in SUPPORTED_BACKENDS, (
+ f"Backend '{backend} not supported. Should be one of: {', '.join(SUPPORTED_BACKENDS)}"
+ )
path = Path(path).absolute()
count_path, obs_path, boundaries_path = _get_file_paths(path, vpt_outputs)
@@ -234,7 +234,7 @@ def _get_reader(backend: str | None) -> Callable: # type: ignore[type-arg]
if backend is not None:
return _rioxarray_load_merscope if backend == "rioxarray" else _dask_image_load_merscope
try:
- import rioxarray # noqa: F401
+ import rioxarray
return _rioxarray_load_merscope
except ModuleNotFoundError:
@@ -255,7 +255,7 @@ def _rioxarray_load_merscope(
except ModuleNotFoundError:
raise ModuleNotFoundError(
"Using rioxarray backend requires to install the rioxarray library (`pip install rioxarray`)"
- )
+ ) from None
from rasterio.errors import NotGeoreferencedWarning
warnings.simplefilter("ignore", category=NotGeoreferencedWarning)
diff --git a/src/spatialdata_io/readers/seqfish.py b/src/spatialdata_io/readers/seqfish.py
index 27431c09..276f27b5 100644
--- a/src/spatialdata_io/readers/seqfish.py
+++ b/src/spatialdata_io/readers/seqfish.py
@@ -3,10 +3,9 @@
import os
import re
import xml.etree.ElementTree as ET
-from collections.abc import Mapping
from pathlib import Path
from types import MappingProxyType
-from typing import Any
+from typing import TYPE_CHECKING, Any
import anndata as ad
import numpy as np
@@ -26,6 +25,9 @@
from spatialdata_io._constants._constants import SeqfishKeys as SK
from spatialdata_io._docs import inject_docs
+if TYPE_CHECKING:
+ from collections.abc import Mapping
+
__all__ = ["seqfish"]
@@ -41,8 +43,7 @@ def seqfish(
imread_kwargs: Mapping[str, Any] = MappingProxyType({}),
raster_models_scale_factors: list[float] | None = None,
) -> SpatialData:
- """
- Read *seqfish* formatted dataset.
+ """Read *seqfish* formatted dataset.
This function reads the following files:
@@ -200,7 +201,6 @@ def get_transcript_file(roi: str) -> str:
points = {}
if load_points:
for x in rois_str:
-
# prepare data
name = f"{os.path.splitext(get_transcript_file(x))[0]}"
p = pd.read_csv(path / get_transcript_file(x), delimiter=",")
@@ -217,7 +217,7 @@ def get_transcript_file(roi: str) -> str:
shapes = {}
if cells_as_circles:
- for x, adata in zip(rois_str, tables.values()):
+ for x, adata in zip(rois_str, tables.values(), strict=False):
shapes[f"{os.path.splitext(get_cell_file(x))[0]}"] = ShapesModel.parse(
adata.obsm[SK.SPATIAL_KEY],
geometry=0,
diff --git a/src/spatialdata_io/readers/steinbock.py b/src/spatialdata_io/readers/steinbock.py
index b3ea707c..fc91ec82 100644
--- a/src/spatialdata_io/readers/steinbock.py
+++ b/src/spatialdata_io/readers/steinbock.py
@@ -1,15 +1,12 @@
from __future__ import annotations
import os
-from collections.abc import Mapping
from pathlib import Path
from types import MappingProxyType
-from typing import Any, Literal
+from typing import TYPE_CHECKING, Any, Literal
import anndata as ad
from dask_image.imread import imread
-from multiscale_spatial_image.multiscale_spatial_image import MultiscaleSpatialImage
-from spatial_image import SpatialImage
from spatialdata import SpatialData
from spatialdata._logging import logger
from spatialdata.models import Image2DModel, Labels2DModel, TableModel
@@ -17,6 +14,12 @@
from spatialdata_io._constants._constants import SteinbockKeys
+if TYPE_CHECKING:
+ from collections.abc import Mapping
+
+ from multiscale_spatial_image.multiscale_spatial_image import MultiscaleSpatialImage
+ from spatial_image import SpatialImage
+
__all__ = ["steinbock"]
@@ -26,8 +29,7 @@ def steinbock(
imread_kwargs: Mapping[str, Any] = MappingProxyType({}),
image_models_kwargs: Mapping[str, Any] = MappingProxyType({}),
) -> SpatialData:
- """
- Read a *Steinbock* output into a SpatialData object.
+ """Read a *Steinbock* output into a SpatialData object.
.. seealso::
@@ -58,8 +60,7 @@ def steinbock(
labels = {}
if len(set(samples).difference(set(samples_labels))):
logger.warning(
- f"Samples {set(samples).difference(set(samples_labels))} have images but no labels. "
- "They will be ignored."
+ f"Samples {set(samples).difference(set(samples_labels))} have images but no labels. They will be ignored."
)
for sample in samples:
images[f"{sample}_image"] = _get_images(
diff --git a/src/spatialdata_io/readers/stereoseq.py b/src/spatialdata_io/readers/stereoseq.py
index c2af7951..e4c83af4 100644
--- a/src/spatialdata_io/readers/stereoseq.py
+++ b/src/spatialdata_io/readers/stereoseq.py
@@ -2,10 +2,9 @@
import os
import re
-from collections.abc import Mapping
from pathlib import Path
from types import MappingProxyType
-from typing import Any
+from typing import TYPE_CHECKING, Any
import anndata as ad
import h5py
@@ -23,6 +22,9 @@
from spatialdata_io._docs import inject_docs
from spatialdata_io.readers._utils._utils import _initialize_raster_models_kwargs
+if TYPE_CHECKING:
+ from collections.abc import Mapping
+
__all__ = ["stereoseq"]
@@ -35,8 +37,7 @@ def stereoseq(
imread_kwargs: Mapping[str, Any] = MappingProxyType({}),
image_models_kwargs: Mapping[str, Any] = MappingProxyType({}),
) -> SpatialData:
- """
- Read *Stereo-seq* formatted dataset.
+ """Read *Stereo-seq* formatted dataset.
Parameters
----------
@@ -311,7 +312,7 @@ def stereoseq(
y_coords = df_coords.filter(regex="y_")
polygons = []
for (x_index, x_row), (y_index, y_row) in tqdm(
- zip(x_coords.iterrows(), y_coords.iterrows()), desc="creating polygons", total=len(df_coords)
+ zip(x_coords.iterrows(), y_coords.iterrows(), strict=False), desc="creating polygons", total=len(df_coords)
):
assert x_index == y_index
# the polygonal cells coordinates are stored as offsets from the centroids, so let's add the centroids
diff --git a/src/spatialdata_io/readers/visium.py b/src/spatialdata_io/readers/visium.py
index da7a8037..76a41898 100644
--- a/src/spatialdata_io/readers/visium.py
+++ b/src/spatialdata_io/readers/visium.py
@@ -3,10 +3,9 @@
import json
import os
import re
-from collections.abc import Mapping
from pathlib import Path
from types import MappingProxyType
-from typing import Any
+from typing import TYPE_CHECKING, Any
import numpy as np
import pandas as pd
@@ -22,6 +21,9 @@
from spatialdata_io._docs import inject_docs
from spatialdata_io.readers._utils._utils import _read_counts
+if TYPE_CHECKING:
+ from collections.abc import Mapping
+
__all__ = ["visium"]
@@ -38,8 +40,7 @@ def visium(
image_models_kwargs: Mapping[str, Any] = MappingProxyType({}),
**kwargs: Any,
) -> SpatialData:
- """
- Read *10x Genomics* Visium formatted dataset.
+ """Read *10x Genomics* Visium formatted dataset.
This function reads the following files:
diff --git a/src/spatialdata_io/readers/visium_hd.py b/src/spatialdata_io/readers/visium_hd.py
index 723a2857..6a5f3bf6 100644
--- a/src/spatialdata_io/readers/visium_hd.py
+++ b/src/spatialdata_io/readers/visium_hd.py
@@ -3,10 +3,9 @@
import json
import re
import warnings
-from collections.abc import Mapping
from pathlib import Path
from types import MappingProxyType
-from typing import Any
+from typing import TYPE_CHECKING, Any
import h5py
import numpy as np
@@ -15,17 +14,14 @@
from dask_image.imread import imread
from geopandas import GeoDataFrame
from imageio import imread as imread2
-from multiscale_spatial_image import MultiscaleSpatialImage
from numpy.random import default_rng
from skimage.transform import ProjectiveTransform, warp
-from spatial_image import SpatialImage
from spatialdata import (
SpatialData,
get_extent,
rasterize_bins,
rasterize_bins_link_table_to_labels,
)
-from spatialdata._types import ArrayLike
from spatialdata.models import Image2DModel, ShapesModel, TableModel
from spatialdata.transformations import Affine, Identity, Scale, set_transformation
from xarray import DataArray
@@ -33,6 +29,13 @@
from spatialdata_io._constants._constants import VisiumHDKeys
from spatialdata_io._docs import inject_docs
+if TYPE_CHECKING:
+ from collections.abc import Mapping
+
+ from multiscale_spatial_image import MultiscaleSpatialImage
+ from spatial_image import SpatialImage
+ from spatialdata._types import ArrayLike
+
RNG = default_rng(0)
@@ -51,8 +54,7 @@ def visium_hd(
image_models_kwargs: Mapping[str, Any] = MappingProxyType({}),
anndata_kwargs: Mapping[str, Any] = MappingProxyType({}),
) -> SpatialData:
- """
- Read *10x Genomics* Visium HD formatted dataset.
+ """Read *10x Genomics* Visium HD formatted dataset.
.. seealso::
@@ -479,9 +481,9 @@ def _load_image(
if data.shape[-1] == 3: # HE image in RGB format
data = data.transpose(2, 0, 1)
else:
- assert data.shape[0] == min(
- data.shape
- ), "When the image is not in RGB, the first dimension should be the number of channels."
+ assert data.shape[0] == min(data.shape), (
+ "When the image is not in RGB, the first dimension should be the number of channels."
+ )
image = DataArray(data, dims=("c", "y", "x"))
parsed = Image2DModel.parse(
@@ -510,8 +512,7 @@ def _projective_matrix_is_affine(projective_matrix: ArrayLike) -> bool:
def _decompose_projective_matrix(projective_matrix: ArrayLike) -> tuple[ArrayLike, ArrayLike]:
- """
- Decompose a projective transformation matrix into an affine transformation and a projective shift.
+ """Decompose a projective transformation matrix into an affine transformation and a projective shift.
Parameters
----------
@@ -551,8 +552,7 @@ def _parse_metadata(path: Path, filename_prefix: str) -> tuple[dict[str, Any], d
def _get_transform_matrices(metadata: dict[str, Any], hd_layout: dict[str, Any]) -> dict[str, ArrayLike]:
- """
- Gets 4 projective transformation matrices, describing how to align the CytAssist, spots and microscope coordinates.
+ """Gets 4 projective transformation matrices, describing how to align the CytAssist, spots and microscope coordinates.
Parameters
----------
diff --git a/src/spatialdata_io/readers/xenium.py b/src/spatialdata_io/readers/xenium.py
index ff036067..5940ae3d 100644
--- a/src/spatialdata_io/readers/xenium.py
+++ b/src/spatialdata_io/readers/xenium.py
@@ -7,10 +7,9 @@
import tempfile
import warnings
import zipfile
-from collections.abc import Mapping
from pathlib import Path
from types import MappingProxyType
-from typing import Any
+from typing import TYPE_CHECKING, Any
import dask.array as da
import numpy as np
@@ -19,7 +18,6 @@
import pyarrow.parquet as pq
import tifffile
import zarr
-from anndata import AnnData
from dask.dataframe import read_parquet
from dask_image.imread import imread
from geopandas import GeoDataFrame
@@ -28,7 +26,6 @@
from shapely import Polygon
from spatialdata import SpatialData
from spatialdata._core.query.relational_query import get_element_instances
-from spatialdata._types import ArrayLike
from spatialdata.models import (
Image2DModel,
Labels2DModel,
@@ -45,6 +42,12 @@
from spatialdata_io.readers._utils._read_10x_h5 import _read_10x_h5
from spatialdata_io.readers._utils._utils import _initialize_raster_models_kwargs
+if TYPE_CHECKING:
+ from collections.abc import Mapping
+
+ from anndata import AnnData
+ from spatialdata._types import ArrayLike
+
__all__ = ["xenium", "xenium_aligned_image", "xenium_explorer_selection"]
@@ -68,8 +71,7 @@ def xenium(
image_models_kwargs: Mapping[str, Any] = MappingProxyType({}),
labels_models_kwargs: Mapping[str, Any] = MappingProxyType({}),
) -> SpatialData:
- """
- Read a *10X Genomics Xenium* dataset into a SpatialData object.
+ """Read a *10X Genomics Xenium* dataset into a SpatialData object.
This function reads the following files:
@@ -324,9 +326,9 @@ def filter(self, record: logging.LogRecord) -> bool:
logger = tifffile.logger()
logger.addFilter(IgnoreSpecificMessage())
image_models_kwargs = dict(image_models_kwargs)
- assert (
- "c_coords" not in image_models_kwargs
- ), "The channel names for the morphology focus images are handled internally"
+ assert "c_coords" not in image_models_kwargs, (
+ "The channel names for the morphology focus images are handled internally"
+ )
image_models_kwargs["c_coords"] = list(channel_names.values())
images["morphology_focus"] = _get_images(
morphology_focus_dir,
@@ -475,8 +477,7 @@ def _get_cells_metadata_table_from_zarr(
file: str,
specs: dict[str, Any],
) -> AnnData:
- """
- Read cells metadata from ``{xx.CELLS_ZARR}``.
+ """Read cells metadata from ``{xx.CELLS_ZARR}``.
Read the cells summary table, which contains the z_level information for versions < 2.0.0, and also the
nucleus_count for versions >= 2.0.0.
@@ -602,8 +603,7 @@ def xenium_aligned_image(
rgba: bool = False,
c_coords: list[str] | None = None,
) -> DataTree:
- """
- Read an image aligned to a Xenium dataset, with an optional alignment file.
+ """Read an image aligned to a Xenium dataset, with an optional alignment file.
Parameters
----------
@@ -731,7 +731,6 @@ def _parse_version_of_xenium_analyzer(
specs: dict[str, Any],
hide_warning: bool = True,
) -> packaging.version.Version | None:
-
# After using xeniumranger (e.g. 3.0.1.1) to resegment data from previous versions (e.g. xenium-1.6.0.7), a new dict is added to
# `specs`, named 'xenium_ranger', which contains the key 'version' and whose value specifies the version of xeniumranger used to
# resegment the data (e.g. 'xenium-3.0.1.1').
@@ -782,14 +781,16 @@ def cell_id_str_from_prefix_suffix_uint32(cell_id_prefix: ArrayLike, dataset_suf
cell_id_prefix_hex_shifted = ["".join([hex_shift[c] for c in x]) for x in cell_id_prefix_hex]
# merge the prefix and the suffix
- cell_id_str = [str(x[0]).rjust(8, "a") + f"-{x[1]}" for x in zip(cell_id_prefix_hex_shifted, dataset_suffix)]
+ cell_id_str = [
+ str(x[0]).rjust(8, "a") + f"-{x[1]}" for x in zip(cell_id_prefix_hex_shifted, dataset_suffix, strict=False)
+ ]
return np.array(cell_id_str)
def prefix_suffix_uint32_from_cell_id_str(cell_id_str: ArrayLike) -> tuple[ArrayLike, ArrayLike]:
# parse the string into the prefix and suffix
- cell_id_prefix_str, dataset_suffix = zip(*[x.split("-") for x in cell_id_str])
+ cell_id_prefix_str, dataset_suffix = zip(*[x.split("-") for x in cell_id_str], strict=False)
dataset_suffix_int = [int(x) for x in dataset_suffix]
# reverse the shifted hex conversion
diff --git a/tests/_utils.py b/tests/_utils.py
index 9787d1bb..ee7b4c70 100644
--- a/tests/_utils.py
+++ b/tests/_utils.py
@@ -4,8 +4,7 @@
def skip_if_below_python_version() -> pytest.mark.skipif:
- """
- Decorator to skip tests if the Python version is below a specified version.
+ """Decorator to skip tests if the Python version is below a specified version.
This decorator prevents running tests on unsupported Python versions. Update the `MIN_VERSION`
constant to change the minimum Python version required for the tests.
diff --git a/tests/test_basic.py b/tests/test_basic.py
index fc332082..71ac4d3a 100644
--- a/tests/test_basic.py
+++ b/tests/test_basic.py
@@ -2,4 +2,4 @@
def test_package_has_version() -> None:
- spatialdata_io.__version__
+ assert spatialdata_io.__version__
diff --git a/tests/test_macsima.py b/tests/test_macsima.py
index cf7ab2f7..63d6ca24 100644
--- a/tests/test_macsima.py
+++ b/tests/test_macsima.py
@@ -147,7 +147,7 @@ def test_cycle_metadata(dataset: str, expected: list[str]) -> None:
def test_parsing_style() -> None:
with pytest.raises(ValueError):
- macsima(Path("."), parsing_style="not_a_parsing_style")
+ macsima(Path(), parsing_style="not_a_parsing_style")
@pytest.mark.parametrize(
@@ -168,7 +168,7 @@ def test_mci_sort_by_channel() -> None:
cycles = [2, 0, 1]
mci = MultiChannelImage(
data=[RNG.random((size, size), chunks=(10, 10)) for size in sizes],
- metadata=[ChannelMetadata(name=c_name, cycle=cycle) for c_name, cycle in zip(c_names, cycles)],
+ metadata=[ChannelMetadata(name=c_name, cycle=cycle) for c_name, cycle in zip(c_names, cycles, strict=False)],
)
assert mci.get_channel_names() == c_names
assert [x.shape[0] for x in mci.data] == sizes