diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 681ff836..0c1125f9 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -48,6 +48,7 @@ You need Python 3.8+ and the following tools: - Poetry_ - Nox_ - nox-poetry_ +- poetry-bumpversion_ Install the package with development requirements: @@ -55,17 +56,16 @@ Install the package with development requirements: $ poetry install -You can now run an interactive Python session, -or the command-line interface: +You can now run an interactive shell session: .. code:: console - $ poetry run python - $ poetry run laptrack + $ poetry shell .. _Poetry: https://python-poetry.org/ .. _Nox: https://nox.thea.codes/ .. _nox-poetry: https://nox-poetry.readthedocs.io/ +.. _poetry-bumpversion: https://github.com/monim67/poetry-bumpversion How to test the project diff --git a/docs/examples/api_example.ipynb b/docs/examples/api_example.ipynb index 5a577e54..cb4017b1 100644 --- a/docs/examples/api_example.ipynb +++ b/docs/examples/api_example.ipynb @@ -29,6 +29,13 @@ " %pip install -q --upgrade laptrack matplotlib pandas" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Important note** please restart the runtime when you're executing this notebook in Google Colaboratory." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -64,8 +71,10 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "Load the example point coordinates from a CSV file.\n", + "\n", "`spots_df` has columns `[\"frame\", \"position_x\", \"position_y\"]` \n", - "(can be arbitrary, and the column names for trackign will be specified later)" + "(can be arbitrary, and the column names for tracking will be specified later)." ] }, { @@ -111,8 +120,8 @@ " track_dist_metric=\"sqeuclidean\", # The similarity metric for particles. See `scipy.spatial.distance.cdist` for allowed values.\n", " splitting_dist_metric=\"sqeuclidean\",\n", " merging_dist_metric=\"sqeuclidean\",\n", - " track_cost_cutoff=max_distance\n", - " ** 2, # the square of the cutoff distance for the \"sqeuclidean\" metric\n", + " # the square of the cutoff distance for the \"sqeuclidean\" metric\n", + " track_cost_cutoff=max_distance**2,\n", " splitting_cost_cutoff=max_distance**2, # or False for non-splitting case\n", " merging_cost_cutoff=max_distance**2, # or False for non-merging case\n", ")" diff --git a/docs/examples/overlap_tracking.ipynb b/docs/examples/overlap_tracking.ipynb index 36aa5ecf..b7e17aed 100644 --- a/docs/examples/overlap_tracking.ipynb +++ b/docs/examples/overlap_tracking.ipynb @@ -415,7 +415,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Here is the core of this example: we can define arbitrary function as the metric to measure how points are close.\n", + "Here is the core of this example: we can define an arbitrary function as the metric to measure how points are close.\n", "\n", "In this example, `metric` function retrive the pre-computed overlap between the segmented regions, and use\n", "$$\n", diff --git a/docs/reference.rst b/docs/reference.rst index 10dab73a..309f4bf6 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -34,3 +34,10 @@ score calculation utilities .. automodule:: laptrack.scores :members: + +metric utilities +------------------------------- + +.. automodule:: laptrack.metric_utils + :members: + :special-members: __init__ diff --git a/pyproject.toml b/pyproject.toml index f72ac64c..5baba9f9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "laptrack" -version = "0.9.0" +version = "0.9.1" description = "LapTrack" authors = ["Yohsuke Fukai "] license = "BSD-3-Clause" @@ -83,3 +83,5 @@ show_error_context = true [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" + +[tool.poetry_bumpversion.file."src/laptrack/__init__.py"] diff --git a/src/laptrack/__init__.py b/src/laptrack/__init__.py index 3e955602..55db1f06 100644 --- a/src/laptrack/__init__.py +++ b/src/laptrack/__init__.py @@ -15,3 +15,5 @@ "scores", "metric_utils", ] + +__version__ = "0.9.1" diff --git a/src/laptrack/metric_utils.py b/src/laptrack/metric_utils.py index 041147c3..946b3674 100644 --- a/src/laptrack/metric_utils.py +++ b/src/laptrack/metric_utils.py @@ -1,5 +1,7 @@ """Utilities for metric calculation.""" +from typing import List from typing import Tuple +from typing import Union import numpy as np import pandas as pd @@ -32,14 +34,18 @@ def _union_bbox(self, r1, r2): bbox.append((y0, y1)) return bbox - def __init__(self, label_images: IntArray): + def __init__(self, label_images: Union[IntArray, List[IntArray]]): """Summarise the segmentation properties and initialize the object. Parameters ---------- - label_images : IntArray - The labeled images. The first dimension is interpreted as the time dimension. + label_images : Union[IntArray,List[IntArray]] + The labeled images. The first dimension is interpreted as the frame dimension. """ + if not isinstance(label_images, np.ndarray): + label_images = np.array(label_images) + if label_images.ndim < 3: + raise ValueError("label_images dimension must be >=3.") self.label_images = label_images self.ndim = label_images.ndim - 1 dfs = [] diff --git a/tests/test_metric_utils.py b/tests/test_metric_utils.py index 5db713db..15766376 100644 --- a/tests/test_metric_utils.py +++ b/tests/test_metric_utils.py @@ -1,34 +1,44 @@ from itertools import product +from typing import List +from typing import Union import numpy as np +import pytest +from laptrack._typing_utils import IntArray from laptrack.metric_utils import LabelOverlap def test_label_overlap() -> None: - labels = np.array( - [ - [[[0, 1, 1, 1, 0], [0, 1, 2, 2, 2]], [[0, 1, 2, 2, 2], [3, 3, 3, 1, 0]]], - [[[0, 1, 1, 1, 2], [0, 4, 1, 2, 2]], [[0, 4, 4, 4, 4], [0, 4, 4, 4, 4]]], - [[[0, 1, 1, 1, 0], [5, 5, 5, 5, 5]], [[0, 1, 1, 1, 0], [0, 1, 1, 1, 0]]], - ] - ) - lo = LabelOverlap(labels) - frame_labels = [np.unique(label) for label in labels] - frame_labels = [x[x > 0] for x in frame_labels] - for f1, f2 in [(0, 0), (0, 1), (1, 2)]: - for l1, l2 in product(frame_labels[f1], frame_labels[f2]): - b1 = labels[f1] == l1 - b2 = labels[f2] == l2 + labels = [ + [[[0, 1, 1, 1, 0], [0, 1, 2, 2, 2]], [[0, 1, 2, 2, 2], [3, 3, 3, 1, 0]]], + [[[0, 1, 1, 1, 2], [0, 4, 1, 2, 2]], [[0, 4, 4, 4, 4], [0, 4, 4, 4, 4]]], + [[[0, 1, 1, 1, 0], [5, 5, 5, 5, 5]], [[0, 1, 1, 1, 0], [0, 1, 1, 1, 0]]], + ] + labelss: List[Union[IntArray, List[IntArray]]] = [ + np.array(labels), + [np.array(label).astype(np.int64) for label in labels], + ] - intersect = np.sum(b1 & b2) - union = np.sum(b1 | b2) - r1 = np.sum(b1) - r2 = np.sum(b2) - res = lo.calc_overlap(f1, l1, f2, l2) - assert ( - intersect, - (intersect / union), - (intersect / r1), - (intersect / r2), - ) == res + for _labels in labelss: + lo = LabelOverlap(_labels) + frame_labels = [np.unique(label) for label in _labels] + frame_labels = [x[x > 0] for x in frame_labels] + for f1, f2 in [(0, 0), (0, 1), (1, 2)]: + for l1, l2 in product(frame_labels[f1], frame_labels[f2]): + b1 = _labels[f1] == l1 + b2 = _labels[f2] == l2 + + intersect = np.sum(b1 & b2) + union = np.sum(b1 | b2) + r1 = np.sum(b1) + r2 = np.sum(b2) + res = lo.calc_overlap(f1, l1, f2, l2) + assert ( + intersect, + (intersect / union), + (intersect / r1), + (intersect / r2), + ) == res + with pytest.raises(ValueError): + LabelOverlap(np.array([[1, 2], [0, 1]]))