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
79 changes: 17 additions & 62 deletions .github/workflows/test_and_deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,76 +2,31 @@
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries

# name: tests
name: Run tests and upload coverage

on:
push:
branches:
- main
tags:
- "v*" # Push events to matching v*, i.e. v1.0, v20.15.10
pull_request:
branches:
- main
workflow_dispatch:
on:
push

jobs:
test:
name: ${{ matrix.platform }} py${{ matrix.python-version }}
runs-on: ${{ matrix.platform }}
strategy:
matrix:
platform: [ubuntu-latest, windows-latest, macos-latest]
python-version: ['3.8', '3.9', '3.10']

name: Run tests and collect coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 2

- name: Set up Python ${{ matrix.python-version }}
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
# note: if you need dependencies from conda, considering using
# setup-miniconda: https://github.com/conda-incubator/setup-miniconda
# and
# tox-conda: https://github.com/tox-dev/tox-conda
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install setuptools tox tox-gh-actions

# this runs the platform-specific tests declared in tox.ini
- name: Test with tox
uses: GabrielBB/xvfb-action@v1
with:
run: python -m tox
env:
PLATFORM: ${{ matrix.platform }}
- name: Install dependencies
run: pip install ".[test]"

- name: Coverage
uses: codecov/codecov-action@v2
- name: Run tests
run: pytest --cov --cov-branch --cov-report=xml

deploy:
# this will run when you have tagged a commit, starting with "v*"
# and requires that you have put your twine API key in your
# github secrets (see readme for details)
needs: [test]
runs-on: ubuntu-latest
if: contains(github.ref, 'tags')
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
- name: Upload results to Codecov
uses: codecov/codecov-action@v5
with:
python-version: "3.x"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -U setuptools setuptools_scm wheel twine build
- name: Build and publish
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.TWINE_API_KEY }}
run: |
git tag
python -m build .
twine upload dist/*
token: ${{ secrets.CODECOV_TOKEN }}
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,13 @@ venv/

# specific ingores
*.svg
*.xml
# *.xml
*.png
lineageTree2Treex.py
test.tlp
*.mastodon
# *.mastodon
bak_*
# data/*

.coverage

Expand Down
468 changes: 450 additions & 18 deletions notebooks/Basics_of_lineageTree.ipynb

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ classifiers = [
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
]

dependencies = [
"scipy>=1.9",
"numpy>=1.23",
Expand All @@ -41,6 +42,7 @@ dependencies = [
"edist",
"svgwrite",
]

[project.optional-dependencies]
dev = [
"pytest",
Expand All @@ -55,6 +57,10 @@ doc = [
"myst-parser",
"sphinx_book_theme",
]
test = [
"pytest",
"pytest-cov",
]

[project.urls]
"Bug Tracker" = "https://github.com/GuignardLab/LineageTree/issues"
Expand Down
75 changes: 34 additions & 41 deletions src/LineageTree/lineageTree.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,17 @@
from types import MappingProxyType
from typing import Literal

import matplotlib.pyplot as plt
import numpy as np
import svgwrite
from edist import uted
from matplotlib.collections import LineCollection
from packaging.version import Version
from scipy.interpolate import InterpolatedUnivariateSpline
from scipy.sparse import dok_array
from scipy.spatial import Delaunay, KDTree, distance

from .tree_styles import tree_style

try:
from edist import uted
except ImportError:
warnings.warn(
"No edist installed therefore you will not be able to compute the tree edit distance.",
stacklevel=2,
)
import matplotlib.pyplot as plt
import numpy as np
from scipy.interpolate import InterpolatedUnivariateSpline
from scipy.spatial import Delaunay, distance
from scipy.spatial import cKDTree as KDTree

from .utils import (
create_links_and_cycles,
hierarchical_pos,
Expand Down Expand Up @@ -89,7 +80,7 @@ def raising_flag(self, *args, **kwargs):

return raising_flag

def __check_cc_cycles(self, n) -> tuple[bool, set[int]]:
def __check_cc_cycles(self, n: int) -> tuple[bool, set[int]]:
"""Check if the connected component of a given node `n` has a cycle.

Returns
Expand Down Expand Up @@ -146,7 +137,7 @@ def __eq__(self, other) -> bool:
return False

def get_next_id(self) -> int:
"""Computes the next authorized id.
"""Computes the next authorized id and assign it.

Returns
-------
Expand Down Expand Up @@ -198,10 +189,6 @@ def add_chain(
old_node = node
node = self._add_node(pred=[old_node])
self._time[node] = self._time[old_node] + 1
# if not pos:
# self.pos[node] = self.pos[old_node]
# else:
# self.pos[node] = pos
else:
if self._predecessor[node]:
raise Warning("The node already has a predecessor.")
Expand All @@ -213,10 +200,6 @@ def add_chain(
old_node = node
node = self._add_node(succ=[old_node])
self._time[node] = self._time[old_node] - 1
# if not pos:
# self.pos[node] = self.pos[old_node]
# else:
# self.pos[node] = pos
return node

@modifier
Expand Down Expand Up @@ -566,16 +549,16 @@ def node_color(x):
from matplotlib import colormaps

if node_color_map in colormaps:
node_color_map = colormaps[node_color_map]
cmap = colormaps[node_color_map]
else:
node_color_map = colormaps["viridis"]
cmap = colormaps["viridis"]
values = np.array(
[self._successor[node_color][c] for c in self.nodes]
)
normed_vals = normalize_values(values, self.nodes, 1, 0, 1)

def node_color(x):
return [k * 255 for k in node_color_map(normed_vals(x))[:-1]]
return [k * 255 for k in cmap(normed_vals(x))[:-1]]

coloring_edges = stroke_color is not None
if not coloring_edges:
Expand All @@ -587,16 +570,16 @@ def stroke_color(x):
from matplotlib import colormaps

if node_color_map in colormaps:
node_color_map = colormaps[node_color_map]
cmap = colormaps[node_color_map]
else:
node_color_map = colormaps["viridis"]
cmap = colormaps["viridis"]
values = np.array(
[self._successor[stroke_color][c] for c in self.nodes]
)
normed_vals = normalize_values(values, self.nodes, 1, 0, 1)

def stroke_color(x):
return [k * 255 for k in node_color_map(normed_vals(x))[:-1]]
return [k * 255 for k in cmap(normed_vals(x))[:-1]]

prev_x = 0
self.vert_space_factor = vert_space_factor
Expand Down Expand Up @@ -1484,6 +1467,8 @@ def compute_spatial_density(
dict of int to float
dictionary that maps a node id to its spatial density
"""
if not hasattr(self, "spatial_density"):
self.spatial_density = {}
s_vol = 4 / 3.0 * np.pi * th**3
if t_b is None:
t_b = self.t_b
Expand Down Expand Up @@ -1518,14 +1503,22 @@ def compute_k_nearest_neighbours(self, k: int = 10) -> dict[int, set[int]]:
self.kn_graph = {}
for t in set(self._time.values()):
nodes = self.nodes_at_t(t)
use_k = k if k < len(nodes) else len(nodes)
idx3d, nodes = self.get_idx3d(t)
pos = [self.pos[c] for c in nodes]
_, neighbs = idx3d.query(pos, use_k)
out = dict(
zip(nodes, [set(nodes[ni[1:]]) for ni in neighbs], strict=True)
)
self.kn_graph.update(out)
if 1 < len(nodes):
use_k = k if k < len(nodes) else len(nodes)
idx3d, nodes = self.get_idx3d(t)
pos = [self.pos[c] for c in nodes]
_, neighbs = idx3d.query(pos, use_k)
out = dict(
zip(
nodes,
map(set, nodes[neighbs]),
strict=True,
)
)
self.kn_graph.update(out)
else:
n = nodes.pop
self.kn_graph.update({n: {n}})
return self.kn_graph

def compute_spatial_edges(self, th: int = 50) -> dict[int, set[int]]:
Expand Down Expand Up @@ -1575,7 +1568,7 @@ def main_axes(self, time: int | None = None) -> tuple[np.array, np.array]:
sorted eigenvectors (3,)
"""
time_nodes = {
t: len(self.nodes_at_t(t)) for t in range(self.t_b, self._te)
t: len(self.nodes_at_t(t)) for t in range(self.t_b, self.t_e)
}
if time is None:
time = max(time_nodes, key=lambda x: len(time_nodes[x]))
Expand Down Expand Up @@ -1934,7 +1927,7 @@ def draw_tree_graph(
if selected_edges is None:
selected_edges = []
if ax is None:
figure, ax = plt.subplots()
_, ax = plt.subplots()
else:
ax.clear()
if not isinstance(selected_nodes, set):
Expand Down Expand Up @@ -3017,7 +3010,7 @@ def __init__(
else:
if starting_time is not None:
warnings.warn(
f"Both `time` and `starting_time` were provided, `starting_time` was ignored.",
"Both `time` and `starting_time` were provided, `starting_time` was ignored.",
stacklevel=2,
)
self._time = {n: int(time[n]) for n in self.nodes}
Expand Down
File renamed without changes.
Binary file added src/LineageTree/test/data/test-mamut.lT
Binary file not shown.
File renamed without changes.
File renamed without changes.
Loading
Loading