Skip to content

Commit 818558e

Browse files
authored
Merge pull request #34 from HiDiHlabs/update
Updates and refactors
2 parents c1a7385 + 773876f commit 818558e

File tree

14 files changed

+103
-125
lines changed

14 files changed

+103
-125
lines changed

.github/workflows/cibuildwheel.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
- name: set up python
2525
uses: actions/setup-python@v5
2626
with:
27-
python-version: '3.10'
27+
python-version: '3.11'
2828

2929
- uses: actions-rust-lang/setup-rust-toolchain@v1
3030

.pre-commit-config.yaml

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,12 @@ repos:
1818
- id: no-commit-to-branch
1919
args: [--branch=main]
2020
- repo: https://github.com/astral-sh/ruff-pre-commit
21-
rev: v0.11.4
21+
rev: v0.11.12
2222
hooks:
23-
- id: ruff
24-
args: [--fix]
25-
- repo: https://github.com/PyCQA/isort
26-
rev: 6.0.1
27-
hooks:
28-
- id: isort
29-
- repo: https://github.com/psf/black
30-
rev: 25.1.0
31-
hooks:
32-
- id: black
23+
- id: ruff-check
24+
- id: ruff-format
3325
- repo: https://github.com/pre-commit/mirrors-mypy
34-
rev: v1.15.0
26+
rev: v1.16.0
3527
hooks:
3628
- id: mypy
3729
additional_dependencies:
@@ -49,7 +41,7 @@ repos:
4941
additional_dependencies:
5042
- tomli
5143
- repo: https://github.com/google/yamlfmt
52-
rev: v0.16.0
44+
rev: v0.17.0
5345
hooks:
5446
- id: yamlfmt
5547
- repo: https://github.com/executablebooks/mdformat

.readthedocs.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ version: 2
22
build:
33
os: ubuntu-lts-latest
44
tools:
5-
python: "3.10"
5+
python: "3.11"
66
rust: latest
77
sphinx:
88
configuration: docs/source/conf.py

Cargo.toml

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "_utils_rust"
33
version = "0.1.0"
44
edition = "2021"
5-
rust-version = "1.76.0"
5+
rust-version = "1.85.0"
66

77
[lib]
88
name = "_utils_rust"
@@ -11,14 +11,14 @@ crate-type = ["cdylib"]
1111
[dependencies]
1212
bincode = { version = "1.3" }
1313
indexmap = { version = ">= 2.1, < 2.6", features = ["rayon"] }
14-
itertools = { version = "0.12.1" }
15-
ndarray = { version = "0.15.6", features = ["rayon"] }
16-
ndarray-stats = { version = "0.5.1" }
14+
itertools = { version = "0.13" }
15+
ndarray = { version = "0.16", features = ["rayon"] }
16+
ndarray-stats = { version = "0.6" }
1717
num = { version = "0.4.1" }
18-
numpy = { version = "0.21" }
19-
polars = { version = "0.41", features = ["partition_by", "dtype-categorical"] }
20-
polars-arrow = { version = "0.41" }
21-
pyo3 = { version = "0.21", features = ["extension-module"] }
22-
pyo3-polars = { version = "0.15" }
18+
numpy = { version = "0.24" }
19+
polars = { version = "0.48", features = ["partition_by", "dtype-categorical"] }
20+
polars-arrow = { version = "0.48" }
21+
pyo3 = { version = "0.24.2", features = ["extension-module"] }
22+
pyo3-polars = { version = "0.21", features = ["dtype-categorical"] }
2323
rayon = { version = "1.8" }
24-
sprs = { version = "= 0.11.1", features = ["serde"] }
24+
sprs = { version = "0.11.2", features = ["serde"] }

README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
# sainsc
22

33
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4-
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
54
[![Code style: Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
6-
[![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/)
75
[![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/)
86
[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit)](https://github.com/pre-commit/pre-commit)
97

docs/source/tutorials/VisiumHD_CRC.ipynb

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -647,7 +647,7 @@
647647
},
648648
{
649649
"cell_type": "code",
650-
"execution_count": 21,
650+
"execution_count": null,
651651
"id": "672b4258-dc91-4fb0-9158-eeb0870c83c9",
652652
"metadata": {
653653
"editable": true,
@@ -664,14 +664,11 @@
664664
"from matplotlib.colors import to_rgb\n",
665665
"from scipy.sparse import coo_array\n",
666666
"\n",
667-
"from sainsc.lazykde._utils import _apply_color\n",
668-
"\n",
669667
"background = \"black\"\n",
670668
"\n",
671669
"cmap_ls = [cmap[celltype] for celltype in adata_16um.obs[\"leiden\"].cat.categories]\n",
672-
"color_map = tuple(\n",
673-
" (np.array(to_rgb(c)) * 255).round().astype(np.uint8) for c in [background] + cmap_ls\n",
674-
")\n",
670+
"color_map = np.array([to_rgb(c) for c in [background] + cmap_ls])\n",
671+
"\n",
675672
"\n",
676673
"celltype = adata_16um.obs[\"leiden\"].cat.codes\n",
677674
"x = adata_16um.obs[\"array_row\"]\n",
@@ -681,7 +678,7 @@
681678
"ctmap_16um = coo_array(\n",
682679
" (celltype + 1, (x, y)), shape=[ceil(i / 8) for i in visium_hd.shape]\n",
683680
")\n",
684-
"ctmap_16um = _apply_color((ctmap_16um).toarray(), color_map)"
681+
"ctmap_16um = np.take(color_map, ctmap_16um.toarray(), axis=0)"
685682
]
686683
},
687684
{

pyproject.toml

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
[build-system]
2-
requires = ["setuptools>=61.0.0", "setuptools_scm[toml]>=6.2", "setuptools-rust>=1.7"]
2+
requires = ["setuptools>=64", "setuptools-scm>=8", "setuptools-rust>=1.10"]
33
build-backend = "setuptools.build_meta"
44

55

66
[project]
77
name = "sainsc"
88
description = "Segmentation-free Analysis of In Situ Capture data"
99
readme = { file = "README.md", content-type = "text/markdown" }
10-
license = { file = "LICENSE" }
11-
requires-python = ">=3.10,<3.13"
10+
license = "MIT"
11+
license-files = ["LICENSE"]
12+
requires-python = ">=3.11,<3.14"
1213
dynamic = ["version"]
1314

1415
authors = [
@@ -17,20 +18,17 @@ authors = [
1718
dependencies = [
1819
"anndata>=0.9",
1920
"h5py>=3",
20-
"matplotlib",
21+
"matplotlib>=3.7",
2122
"matplotlib-scalebar",
22-
"numba>=0.44",
23-
"numpy>=1.21",
24-
"pandas",
23+
"numpy>=1.24",
24+
"pandas>=2.0",
2525
"polars[pandas]>=1",
26-
"scikit-image>=0.18",
27-
"scipy>=1.9",
26+
"scikit-image>=0.20",
27+
"scipy>=1.10",
2828
"seaborn>=0.11",
29-
"typing-extensions>=4",
3029
]
3130
classifiers = [
3231
"Intended Audience :: Science/Research",
33-
"License :: OSI Approved :: MIT License",
3432
"Operating System :: OS Independent",
3533
"Programming Language :: Python",
3634
"Programming Language :: Python :: 3",
@@ -72,20 +70,21 @@ include = ["sainsc", "sainsc.io", "sainsc.lazykde", "sainsc.utils"]
7270
[[tool.setuptools-rust.ext-modules]]
7371
target = "sainsc._utils_rust"
7472

75-
[tool.isort]
76-
profile = "black"
77-
78-
[tool.black]
79-
target-version = ["py310", "py311", "py312"]
8073

8174
[tool.ruff]
82-
target-version = "py310"
75+
target-version = "py311"
76+
77+
fix = true
78+
show-fixes = true
79+
80+
[tool.ruff.lint]
81+
extend-select = ["I"]
8382

8483
[tool.ruff.lint.per-file-ignores]
8584
"__init__.py" = ["E402"]
8685

8786
[tool.mypy]
88-
python_version = "3.10"
87+
python_version = "3.11"
8988
ignore_missing_imports = true
9089
warn_no_return = false
9190
packages = "sainsc"

sainsc/_utils_rust.pyi

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
from typing import Self
2+
13
import numpy as np
24
from numpy.typing import NDArray
35
from polars import DataFrame
4-
from typing_extensions import Self
56

67
from ._typealias import _Csx, _CsxArray
78

sainsc/lazykde/_LazyKDE.py

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from collections.abc import Iterable
2-
from itertools import chain
3-
from typing import TYPE_CHECKING, Any
2+
from typing import TYPE_CHECKING, Any, Self, TypeVar
43

54
import matplotlib.pyplot as plt
65
import numpy as np
@@ -14,11 +13,9 @@
1413
from matplotlib.patches import Patch
1514
from matplotlib_scalebar.scalebar import ScaleBar
1615
from mpl_toolkits import axes_grid1
17-
from numba import njit
1816
from numpy.typing import NDArray
1917
from scipy.sparse import coo_array, csc_array, csr_array
2018
from skimage.feature import peak_local_max
21-
from typing_extensions import Self
2219

2320
from .._typealias import _Cmap, _Csx, _CsxArray, _Local_Max, _RangeTuple2D
2421
from .._utils import _raise_module_load_error, _validate_n_threads, validate_threads
@@ -33,7 +30,6 @@
3330
from ._utils import (
3431
SCALEBAR_PARAMS,
3532
CosineCelltypeCallable,
36-
_apply_color,
3733
_filter_blobs,
3834
_get_cell_dtype,
3935
_localmax_anndata,
@@ -358,7 +354,6 @@ def load_local_maxima(
358354
}
359355

360356
if self.total_mRNA_KDE is not None:
361-
362357
sdata_dict["total_mRNA"] = Image2DModel.parse(
363358
np.atleast_3d(self.total_mRNA_KDE).T, dims=("c", "y", "x")
364359
)
@@ -416,7 +411,6 @@ def load_local_maxima(
416411
return adata
417412

418413
def _load_KDE_maxima(self, genes: list[str]) -> csc_array | csr_array:
419-
420414
assert self.local_maxima is not None
421415
if self.kernel is None:
422416
raise ValueError("`kernel` must be set before running KDE")
@@ -461,17 +455,13 @@ def filter_background(
461455
If cell type-specific thresholds do not include all cell types or if
462456
using cell type-specific thresholds before cell type assignment.
463457
"""
458+
T = TypeVar("T")
464459

465-
@njit
466460
def _map_celltype_to_value(
467-
ct_map: NDArray[np.integer], thresholds: tuple[float, ...]
461+
ct_map: NDArray[np.integer], thresholds: dict[T, float], classes: list[T]
468462
) -> NDArray[np.floating]:
469-
values = np.zeros(shape=ct_map.shape, dtype=float)
470-
for i in range(ct_map.shape[0]):
471-
for j in range(ct_map.shape[1]):
472-
if ct_map[i, j] >= 0:
473-
values[i, j] = thresholds[ct_map[i, j]]
474-
return values
463+
ordered_thresholds = np.array([0] + [thresholds[ct] for ct in classes])
464+
return np.take(ordered_thresholds, ct_map + 1)
475465

476466
if self.total_mRNA_KDE is None:
477467
raise ValueError(
@@ -485,8 +475,9 @@ def _map_celltype_to_value(
485475
)
486476
elif not all([ct in min_norm.keys() for ct in self.celltypes]):
487477
raise ValueError("'min_norm' does not contain all celltypes.")
488-
idx2threshold = tuple(min_norm[ct] for ct in self.celltypes)
489-
threshold = _map_celltype_to_value(self.celltype_map, idx2threshold)
478+
threshold = _map_celltype_to_value(
479+
self.celltype_map, min_norm, self.celltypes
480+
)
490481
background = self.total_mRNA_KDE < threshold
491482
else:
492483
background = self.total_mRNA_KDE < min_norm
@@ -503,8 +494,9 @@ def _map_celltype_to_value(
503494
)
504495
elif not all([ct in min_cosine.keys() for ct in self.celltypes]):
505496
raise ValueError("'min_cosine' does not contain all celltypes.")
506-
idx2threshold = tuple(min_cosine[ct] for ct in self.celltypes)
507-
threshold = _map_celltype_to_value(self.celltype_map, idx2threshold)
497+
threshold = _map_celltype_to_value(
498+
self.celltype_map, min_cosine, self.celltypes
499+
)
508500
background |= self.cosine_similarity <= threshold
509501
else:
510502
background |= self.cosine_similarity <= min_cosine
@@ -521,8 +513,9 @@ def _map_celltype_to_value(
521513
)
522514
elif not all([ct in min_assignment.keys() for ct in self.celltypes]):
523515
raise ValueError("'min_assignment' does not contain all celltypes.")
524-
idx2threshold = tuple(min_assignment[ct] for ct in self.celltypes)
525-
threshold = _map_celltype_to_value(self.celltype_map, idx2threshold)
516+
threshold = _map_celltype_to_value(
517+
self.celltype_map, min_assignment, self.celltypes
518+
)
526519
background |= self.assignment_score <= threshold
527520
else:
528521
background |= self.assignment_score <= min_assignment
@@ -985,11 +978,11 @@ def plot_celltype_map(
985978
color_map = [to_rgb(c) if isinstance(c, str) else c for c in cmap]
986979

987980
# convert to uint8 to reduce memory of final image
988-
color_map_int = tuple(
989-
(np.array(c) * 255).round().astype(np.uint8)
990-
for c in chain([to_rgb(background)], color_map)
981+
color_map_int = (
982+
(np.array([to_rgb(background)] + color_map) * 255).round().astype(np.uint8)
991983
)
992-
img = _apply_color(celltype_map.T, color_map_int)
984+
985+
img = np.take(color_map_int, celltype_map.T, axis=0)
993986

994987
if return_img:
995988
return img

sainsc/lazykde/_utils.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import numpy as np
55
import pandas as pd
66
from anndata import AnnData
7-
from numba import njit
87
from numpy.typing import NDArray
98
from scipy.sparse import csr_matrix, sparray, spmatrix
109
from skimage.measure import label, regionprops
@@ -19,17 +18,6 @@
1918
"""Default scalebar parameters"""
2019

2120

22-
@njit
23-
def _apply_color(
24-
img_in: NDArray[np.integer], cmap: tuple[NDArray[T], ...]
25-
) -> NDArray[T]:
26-
img = np.empty(shape=(*img_in.shape, 3), dtype=cmap[0].dtype)
27-
for i in range(img_in.shape[0]):
28-
for j in range(img_in.shape[1]):
29-
img[i, j, :] = cmap[img_in[i, j]]
30-
return img
31-
32-
3321
def _get_cell_dtype(n: int) -> np.dtype:
3422
return np.result_type("int8", n)
3523

0 commit comments

Comments
 (0)