Skip to content

Commit b281743

Browse files
authored
Merge pull request #12 from HiDiHlabs/dev
Prepare release 0.2.0
2 parents 3911e7f + a7fe73d commit b281743

22 files changed

Lines changed: 465 additions & 216 deletions

Cargo.toml

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
11
[package]
2-
name = "_utils_rust"
3-
version = "0.1.0"
4-
edition = "2021"
2+
name = "_utils_rust"
3+
version = "0.1.0"
4+
edition = "2021"
5+
rust-version = "1.76.0"
56

67
[lib]
78
name = "_utils_rust"
89
crate-type = ["cdylib"]
910

1011
[dependencies]
1112
bincode = { version = "1.3" }
12-
indexmap = { version = "2.1.0", features = ["rayon"] }
13+
indexmap = { version = ">= 2.1, < 2.6", features = ["rayon"] }
1314
itertools = { version = "0.12.1" }
1415
ndarray = { version = "0.15.6", features = ["rayon"] }
1516
ndarray-stats = { version = "0.5.1" }
1617
num = { version = "0.4.1" }
17-
numpy = { version = "0.21.0" }
18+
numpy = { version = "0.21" }
1819
polars = { version = "0.41", features = ["partition_by", "dtype-categorical"] }
1920
polars-arrow = { version = "0.41" }
20-
pyo3 = { version = "0.21.0", features = ["extension-module"] }
21-
pyo3-polars = { version = "0.15.0" }
22-
rayon = { version = "1.8.0" }
23-
sprs = { version = "0.11.1", features = ["serde"] }
21+
pyo3 = { version = "0.21", features = ["extension-module"] }
22+
pyo3-polars = { version = "0.15" }
23+
rayon = { version = "1.8" }
24+
sprs = { version = "= 0.11.1", features = ["serde"] }

README.md

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,20 @@ by exporting data in [`AnnData`](https://anndata.readthedocs.io/) or
2121

2222
## Installation
2323

24-
`sainsc` will be made available on [PyPI](https://pypi.org/) and
25-
[bioconda](https://bioconda.github.io/). For detailed installation instructions
26-
please refer to the [documentation](https://sainsc.readthedocs.io/en/stable/installation.html).
24+
`sainsc` is available on [PyPI](https://pypi.org/) and [bioconda](https://bioconda.github.io/).
25+
26+
```sh
27+
# PyPI
28+
pip install sainsc
29+
```
30+
31+
```sh
32+
# or conda
33+
conda install bioconda::sainsc
34+
```
35+
36+
For detailed installation instructions please refer to the
37+
[documentation](https://sainsc.readthedocs.io/page/installation.html).
2738

2839
## Documentation
2940

docs/source/installation.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Bioconda and ``conda``
2424

2525
If you prefer the installation using
2626
`Miniconda <https://docs.anaconda.com/miniconda/>`_ you can install from the
27-
`bioconda <https://bioconda.github.io/>`_ channel.
27+
`bioconda <http://bioconda.github.io/recipes/sainsc/README.html>`_ channel.
2828

2929
.. code-block:: bash
3030

docs/source/quickstart.ipynb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
"source": [
5454
"from pathlib import Path\n",
5555
"\n",
56-
"from sainsc import read_StereoSeq"
56+
"from sainsc.io import read_StereoSeq"
5757
]
5858
},
5959
{
@@ -331,9 +331,9 @@
331331
],
332332
"metadata": {
333333
"kernelspec": {
334-
"display_name": "Python [conda env:muellni-sainsc2]",
334+
"display_name": "Python 3",
335335
"language": "python",
336-
"name": "conda-env-muellni-sainsc2-py"
336+
"name": "python3"
337337
},
338338
"language_info": {
339339
"codemirror_mode": {

docs/source/usage.ipynb

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"import numpy as np\n",
4141
"import pandas as pd\n",
4242
"\n",
43-
"from sainsc import read_StereoSeq"
43+
"from sainsc.io import read_StereoSeq"
4444
]
4545
},
4646
{
@@ -992,9 +992,9 @@
992992
"metadata": {},
993993
"source": [
994994
"To analyse other technologies beside Stereo-seq such as imaging-based (e.g. Xenium) we only need to generate a [`sainsc.GridCounts`](#sainsc.GridCounts) or [`sainsc.LazyKDE`](#sainsc.LazyKDE) instance from a dataframe and then proceed as previously described. \n",
995-
"If the data is stored in GEM file format you can use the [`sainsc.io.read_gem_file`](#sainsc.io.read_gem_file) function to conveniently read the file and ensure the correct format.\n",
996-
"Otherwise we can manually prepare the dataframe as shown below for the case of Xenium data (obtained from [10x website](https://www.10xgenomics.com/datasets/fresh-frozen-mouse-brain-replicates-1-standard)).\n",
995+
"If the data is stored in GEM file format you can use the [`sainsc.io.read_gem_file`](#sainsc.io.read_gem_file) function to conveniently read the file and ensure the correct format. Other technologies, such as Xenium or Vizgen are already supported as well. Have a look at the [`sainsc.io`](#sainsc.io) module.\n",
997996
"\n",
997+
"In case the technology/file format is not supported, we can manually prepare the data as shown below for the case of Xenium (obtained from [10x website](https://www.10xgenomics.com/datasets/fresh-frozen-mouse-brain-replicates-1-standard)).\n",
998998
"The dataframe needs to contain a 'gene', 'x', and 'y' column. If a 'count' column is present it will be used otherwise a count of 1 (single transcript) will be assumed for each row. If the 'x' and 'y' columns are integers they will be assumed as indices of a square grid. Otherwise we can additionally define a bin size to use when instantiating the objects."
999999
]
10001000
},
@@ -1028,7 +1028,7 @@
10281028
"id": "48494ac5-9f69-4ece-9dd1-a10c94cf88aa",
10291029
"metadata": {},
10301030
"source": [
1031-
"We can now generate a [`sainsc.GridCounts`](#sainsc.GridCounts) object from this dataframe. We will generate bins with a size of 500 nm."
1031+
"We can now generate a [`sainsc.GridCounts`](#sainsc.GridCounts) object from this `polars.DataFrame`. We will generate bins with a size of 500 nm."
10321032
]
10331033
},
10341034
{
@@ -1062,7 +1062,7 @@
10621062
"id": "72bda2b1-b6ed-4204-a144-5390ba8ad35a",
10631063
"metadata": {},
10641064
"source": [
1065-
"Alternatively we can directly generate a [`sainsc.LazyKDE`](#sainsc.LazyKDE) object. This has the additional benefit, that we can supply either a `polars.DataFrame` or a `pandas.DataFrame`. [`sainsc.GridCounts`](#sainsc.GridCounts) can only be generated from a `polars.DataFrame`."
1065+
"Alternatively, we can directly generate a [`sainsc.LazyKDE`](#sainsc.LazyKDE) object. This has the additional benefit, that we can supply either a `polars.DataFrame` or a `pandas.DataFrame`. [`sainsc.GridCounts`](#sainsc.GridCounts) can only be generated from a `polars.DataFrame`."
10661066
]
10671067
},
10681068
{
@@ -1208,9 +1208,9 @@
12081208
],
12091209
"metadata": {
12101210
"kernelspec": {
1211-
"display_name": "Python [conda env:muellni-sainsc2]",
1211+
"display_name": "Python 3",
12121212
"language": "python",
1213-
"name": "conda-env-muellni-sainsc2-py"
1213+
"name": "python3"
12141214
},
12151215
"language_info": {
12161216
"codemirror_mode": {

pyproject.toml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ dependencies = [
2525
"scikit-image>=0.18",
2626
"scipy>=1.9",
2727
"seaborn>=0.11",
28+
"typing-extensions>=4",
2829
]
2930
classifiers = [
3031
"Intended Audience :: Science/Research",
@@ -52,10 +53,10 @@ data = ["pooch>=1"]
5253
dev = ["sainsc[data,docs,spatialdata]", "pre-commit"]
5354

5455
[project.urls]
55-
homepage = "https://github.com/HiDiHlabs/sainsc"
56-
documentation = "https://sainsc.readthedocs.io"
57-
repository = "https://github.com/HiDiHlabs/sainsc"
58-
56+
Homepage = "https://github.com/HiDiHlabs/sainsc"
57+
Documentation = "https://sainsc.readthedocs.io"
58+
Repository = "https://github.com/HiDiHlabs/sainsc"
59+
Issues = "https://github.com/HiDiHlabs/sainsc/issues"
5960

6061
[tool]
6162

@@ -79,6 +80,9 @@ target-version = ["py310", "py311", "py312"]
7980
[tool.ruff]
8081
target-version = "py310"
8182

83+
[tool.ruff.lint.per-file-ignores]
84+
"__init__.py" = ["E402"]
85+
8286
[tool.mypy]
8387
python_version = "3.10"
8488
ignore_missing_imports = true

sainsc/__init__.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,10 @@
55
except PackageNotFoundError:
66
__version__ = "unknown version"
77

8+
del PackageNotFoundError, version
9+
10+
from . import io, lazykde, utils
811
from ._utils_rust import GridCounts
9-
from .io import read_StereoSeq, read_StereoSeq_bins
10-
from .lazykde import LazyKDE, gaussian_kernel
12+
from .lazykde import LazyKDE
1113

12-
__all__ = [
13-
"GridCounts",
14-
"LazyKDE",
15-
"gaussian_kernel",
16-
"read_StereoSeq",
17-
"read_StereoSeq_bins",
18-
]
14+
__all__ = ["io", "lazykde", "utils", "GridCounts", "LazyKDE"]

sainsc/_utils.py

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import functools
12
import os
2-
from typing import NoReturn
3+
from typing import Callable, NoReturn, ParamSpec, TypeVar
34

45
import numpy as np
56
import pandas as pd
@@ -9,15 +10,40 @@
910

1011

1112
def _get_n_cpus() -> int:
12-
return len(os.sched_getaffinity(0))
13+
available_cpus = len(os.sched_getaffinity(0))
14+
return min(available_cpus, 32)
15+
16+
17+
P = ParamSpec("P")
18+
T = TypeVar("T")
19+
20+
21+
def _validate_n_threads(n_threads: int | None) -> int:
22+
if n_threads is None:
23+
n_threads = 0
24+
if n_threads < 0:
25+
raise ValueError("`n_threads` must be >= 0.")
26+
else:
27+
return n_threads if n_threads > 0 else _get_n_cpus()
28+
29+
30+
def validate_threads(func: Callable[P, T]) -> Callable[P, T]:
31+
@functools.wraps(func)
32+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
33+
n_threads = kwargs.get("n_threads", 0)
34+
assert n_threads is None or isinstance(n_threads, int)
35+
kwargs["n_threads"] = _validate_n_threads(n_threads)
36+
return func(*args, **kwargs)
37+
38+
return wrapper
1339

1440

1541
def _get_coordinate_index(
1642
x: NDArray[np.integer],
1743
y: NDArray[np.integer],
1844
*,
1945
name: str | None = None,
20-
n_threads: int = 1,
46+
n_threads: int | None = None,
2147
) -> pd.Index:
2248
x_i32: NDArray[np.int32] = x.astype(np.int32, copy=False)
2349
y_i32: NDArray[np.int32] = y.astype(np.int32, copy=False)
@@ -27,18 +53,6 @@ def _get_coordinate_index(
2753
)
2854

2955

30-
def _bin_coordinates(df: pd.DataFrame, bin_size: float) -> pd.DataFrame:
31-
df = df.assign(
32-
x=lambda df: _get_bin_coordinate(df["x"].to_numpy(), bin_size),
33-
y=lambda df: _get_bin_coordinate(df["y"].to_numpy(), bin_size),
34-
)
35-
return df
36-
37-
38-
def _get_bin_coordinate(coor: NDArray[np.number], bin_size: float) -> NDArray[np.int32]:
39-
return np.floor(coor / bin_size).astype(np.int32, copy=False)
40-
41-
4256
def _raise_module_load_error(e: Exception, fn: str, pkg: str, extra: str) -> NoReturn:
4357
raise ModuleNotFoundError(
4458
f"`{fn}` requires '{pkg}' to be installed, e.g. via the '{extra}' extra."

sainsc/_utils_rust.pyi

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import numpy as np
22
from numpy.typing import NDArray
33
from polars import DataFrame
4+
from typing_extensions import Self
45

56
from ._typealias import _Csx, _CsxArray
67

@@ -92,8 +93,8 @@ class GridCounts:
9293
resolution : float, optional
9394
Resolution as nm / pixel.
9495
n_threads : int, optional
95-
Number of threads used for reading and processing file. If `None` this will
96-
default to the number of logical CPUs.
96+
Number of threads used for processing. If `None` or 0 this will default to
97+
the number of logical CPUs.
9798
9899
Raises
99100
------
@@ -109,7 +110,7 @@ class GridCounts:
109110
resolution: float | None = None,
110111
binsize: float | None = None,
111112
n_threads: int | None = None,
112-
): # -> Self
113+
) -> Self:
113114
"""
114115
Initialize from dataframe.
115116
@@ -127,8 +128,8 @@ class GridCounts:
127128
Resolution of each coordinate unit in nm. The default is 1,000 i.e. measurements
128129
are in um.
129130
n_threads : int, optional
130-
Number of threads used for initializing :py:class:`sainsc.LazyKDE`.
131-
If `None` this will default to the number of logical CPUs.
131+
Number of threads used for processing. If `None` or 0 this will default to
132+
the number of logical CPUs.
132133
133134
Returns
134135
-------
@@ -251,7 +252,7 @@ class GridCounts:
251252
"""
252253

253254
@resolution.setter
254-
def resolution(self, resolution: float): ...
255+
def resolution(self, resolution: float | None): ...
255256
@property
256257
def n_threads(self) -> int:
257258
"""
@@ -264,4 +265,4 @@ class GridCounts:
264265
"""
265266

266267
@n_threads.setter
267-
def n_threads(self, n_threads: int): ...
268+
def n_threads(self, n_threads: int | None): ...

sainsc/io/__init__.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,26 @@
1-
from ._io import read_gem_file, read_StereoSeq, read_StereoSeq_bins
1+
"""
2+
This module contains functionality supporting reading data of different
3+
spatially-resolved transcriptomics technologies and file formats.
4+
"""
25

3-
__all__ = ["read_gem_file", "read_StereoSeq", "read_StereoSeq_bins"]
6+
from ._io import (
7+
VIZGEN_CTRLS,
8+
XENIUM_CTRLS,
9+
read_gem_file,
10+
read_gem_header,
11+
read_StereoSeq,
12+
read_StereoSeq_bins,
13+
read_Vizgen,
14+
read_Xenium,
15+
)
16+
17+
__all__ = [
18+
"VIZGEN_CTRLS",
19+
"XENIUM_CTRLS",
20+
"read_gem_file",
21+
"read_gem_header",
22+
"read_StereoSeq",
23+
"read_StereoSeq_bins",
24+
"read_Vizgen",
25+
"read_Xenium",
26+
]

0 commit comments

Comments
 (0)