Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,4 @@ jobs:
run: uv run python devtools/lint.py

- name: Run tests
run: uv run pytest
run: uv run pytest -m "not slow"
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

.DEFAULT_GOAL := default

.PHONY: default install install-dev install-docs install-all install-cuda12 install-cuda13 install-tpu lint test upgrade upgrade-dev build clean docs docs-autoapi
.PHONY: default install install-dev install-docs install-all install-cuda12 install-cuda13 install-tpu lint test coverage upgrade upgrade-dev build clean docs docs-autoapi

default: install lint test

Expand Down Expand Up @@ -42,6 +42,9 @@ lint:
test:
uv run pytest

coverage:
uv run pytest --cov=canns --cov-report=term-missing:skip-covered

# Upgrade all dependencies (CPU-only)
upgrade:
uv sync --upgrade --extra cpu --all-groups
Expand Down Expand Up @@ -73,4 +76,4 @@ clean:
-rm -rf .mypy_cache/
-rm -rf .venv/
-rm -rf docs/_build/
-find . -type d -name "__pycache__" -exec rm -rf {} +
-find . -type d -name "__pycache__" -exec rm -rf {} +
2 changes: 1 addition & 1 deletion binder/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
brainx[cpu]
brainpy[cpu]
canns-lib>=0.6.2
tqdm
matplotlib
Expand Down
14 changes: 12 additions & 2 deletions devtools/lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,20 @@
def main():
rprint()

ci = os.environ.get("CI", "").lower() in {"1", "true", "yes"}
fix = not ci

errcount = 0
errcount += run(["codespell", "--ignore-words", f"{codespell_ignore}", *SRC_PATHS, *DOC_PATHS])
errcount += run(["ruff", "check", "--fix", *SRC_PATHS])
errcount += run(["ruff", "format", *SRC_PATHS])
ruff_check_cmd = ["ruff", "check", *SRC_PATHS]
if fix:
ruff_check_cmd.insert(2, "--fix")
errcount += run(ruff_check_cmd)

ruff_format_cmd = ["ruff", "format", *SRC_PATHS]
if not fix:
ruff_format_cmd.insert(2, "--check")
errcount += run(ruff_format_cmd)
# errcount += run(["basedpyright", "--stats", *SRC_PATHS])

rprint()
Expand Down
2 changes: 1 addition & 1 deletion environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ dependencies:
- scipy
- tqdm
- pip:
- brainx[cpu]
- brainpy[cpu]
- canns-lib>=0.6.2
- canns
27 changes: 23 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ dependencies = [
dev = [
"pytest>=8.3.5",
"pytest-sugar>=1.0.0",
"pytest-cov>=6.0.0",
"ruff>=0.11.9",
"codespell>=2.4.1",
"rich>=14.0.0",
Expand Down Expand Up @@ -116,7 +117,7 @@ gui = [

[project.scripts]
# Add script entry points here:
canns = "canns:master"
canns = "canns.__main__:main"
canns-tui = "canns.pipeline.launcher:main"
canns-gallery = "canns.pipeline.gallery:main"
canns-gui = "canns.pipeline.asa_gui:main"
Expand Down Expand Up @@ -245,13 +246,31 @@ reportOperatorIssue = false
# skip = "foo.py,bar.py"

[tool.pytest.ini_options]
python_files = ["*.py"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
testpaths = [
"src",
"tests",
]
norecursedirs = []
filterwarnings = []
addopts = "--assert=plain"
addopts = "--assert=plain -ra"
markers = [
"integration: tests that exercise multi-module flows or external dependencies",
"visualization: tests that generate plots or graphics",
"slow: tests that are notably slower than unit tests",
]

[tool.coverage.run]
branch = true
source = ["canns"]

[tool.coverage.report]
skip_covered = true
show_missing = true
exclude_lines = [
"pragma: no cover",
"if TYPE_CHECKING:",
"if __name__ == .__main__.:",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (testing): Coverage exclude pattern for main guard looks incorrect and likely won’t match the intended line.

exclude_lines uses regex matching, so "if __name__ == .__main__.:" is unlikely to match a typical guard like if __name__ == "__main__":. The . characters will match any character and the quoting doesn’t align with the usual pattern. If you want to ignore standard __main__ guards, consider a more accurate regex such as "if __name__ == ['\"]__main__['\"]:" or a simpler literal match that reflects the actual line format.

"raise NotImplementedError",
]
31 changes: 25 additions & 6 deletions src/canns/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,10 @@
>>> print(list(canns.data.DATASETS))
"""

from . import analyzer as analyzer
from . import data as data
from . import models as models
from . import pipeline as pipeline
from . import trainer as trainer
from . import utils as utils
from __future__ import annotations

import importlib
from typing import TYPE_CHECKING

# Version information
try:
Expand Down Expand Up @@ -49,6 +47,27 @@
>>> print(canns.version_info)
"""

_LAZY_SUBMODULES = {
"analyzer",
"data",
"models",
"pipeline",
"trainer",
"utils",
}

if TYPE_CHECKING: # pragma: no cover
from . import analyzer, data, models, pipeline, trainer, utils


def __getattr__(name: str):
if name in _LAZY_SUBMODULES:
module = importlib.import_module(f"{__name__}.{name}")
globals()[name] = module
return module
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


__all__ = [
"analyzer",
"data",
Expand Down
69 changes: 69 additions & 0 deletions src/canns/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"""Command-line entry point for the CANNs toolkit.

`pip/uv install canns` installs several console scripts:
- `canns`: convenience wrapper (this module)
- `canns-tui`: Textual launcher (ASA / Gallery)
- `canns-gallery`: Gallery TUI
- `canns-gui`: ASA GUI (requires `canns[gui]`)
"""

from __future__ import annotations

import argparse
import sys
from collections.abc import Sequence


def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser(prog="canns", description="CANNs toolkit entry point.")
parser.add_argument(
"--version",
action="store_true",
help="Print installed CANNs version and exit.",
)
group = parser.add_mutually_exclusive_group()
group.add_argument("--asa", action="store_true", help="Run the ASA TUI directly.")
group.add_argument("--gallery", action="store_true", help="Run the model gallery TUI directly.")
group.add_argument("--gui", action="store_true", help="Run the ASA GUI (requires canns[gui]).")

args = parser.parse_args(list(argv) if argv is not None else None)

if args.version:
try:
from importlib.metadata import version

print(version("canns"))
except Exception:
try:
from canns._version import __version__

print(__version__)
except Exception:
print("unknown")
return 0

if args.gui:
from canns.pipeline.asa_gui import main as gui_main

return int(gui_main())

if args.gallery:
from canns.pipeline.gallery import main as gallery_main

gallery_main()
return 0

if args.asa:
from canns.pipeline.asa import main as asa_main

asa_main()
return 0

from canns.pipeline.launcher import main as launcher_main

launcher_main()
return 0


if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))
1 change: 0 additions & 1 deletion src/canns/analyzer/data/asa/cohospace_scatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from .path import _align_activity_to_coords, skew_transform
from .utils import _ensure_parent_dir, _ensure_plot_config


# =====================================================================
# CohoSpace visualization and selectivity metrics (CohoScore)
# =====================================================================
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

from __future__ import annotations

from typing import Any

import numpy as np
from matplotlib import pyplot as plt
from scipy.ndimage import gaussian_filter1d
Expand Down
4 changes: 1 addition & 3 deletions src/canns/analyzer/visualization/theta_sweep_plots.py
Original file line number Diff line number Diff line change
Expand Up @@ -792,9 +792,7 @@ def plot_internal_position_trajectory(
tuple: ``(figure, axis)`` objects.
"""
if internal_position.ndim != 2 or internal_position.shape[1] != 2:
raise ValueError(
f"internal_position must be (T, 2), got shape {internal_position.shape}"
)
raise ValueError(f"internal_position must be (T, 2), got shape {internal_position.shape}")
if position.ndim != 2 or position.shape[1] != 2:
raise ValueError(f"position must be (T, 2), got shape {position.shape}")
if internal_position.shape[0] != position.shape[0]:
Expand Down
Loading