Skip to content

Commit 4b2b547

Browse files
authored
Merge pull request #30 from yfukai/fix_zero_distance_problem
Fix zero distance problem
2 parents d8c5d90 + 9f92e88 commit 4b2b547

File tree

9 files changed

+289
-51
lines changed

9 files changed

+289
-51
lines changed

codecov.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ coverage:
33
status:
44
project:
55
default:
6-
target: "90"
6+
target: "80"
77
patch:
88
default:
9-
target: "100"
9+
target: "80"

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "laptrack"
3-
version = "0.1.2"
3+
version = "0.1.3"
44
description = "LapTrack"
55
authors = ["Yohsuke Fukai <ysk@yfukai.net>"]
66
license = "BSD-3-Clause"

src/laptrack/_cost_matrix.py

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,25 @@
22
from typing import Union
33

44
import numpy as np
5-
from scipy.sparse import lil_matrix
5+
from scipy.sparse.coo import coo_matrix
66

77
from ._typing_utils import Float
8-
from ._typing_utils import FloatArray
8+
from ._typing_utils import Matrix
9+
from ._utils import coo_matrix_builder
910

1011

1112
def build_frame_cost_matrix(
12-
dist_matrix: FloatArray,
13-
track_cost_cutoff: Float,
13+
dist_matrix: coo_matrix_builder,
14+
*,
1415
track_start_cost: Optional[Float],
1516
track_end_cost: Optional[Float],
16-
) -> lil_matrix:
17+
) -> coo_matrix:
1718
"""Build sparce array for frame-linking cost matrix.
1819
1920
Parameters
2021
----------
21-
dist_matrix : FloatArray
22+
dist_matrix : Matrix or `_utils.coo_matrix_builder`
2223
The distance matrix for points at time t and t+1.
23-
track_cost_cutoff : Float, optional
24-
The distance cutoff for the connected points in the track
2524
track_start_cost : Float, optional
2625
The cost for starting the track (b in Jaqaman et al 2008 NMeth)
2726
track_end_cost : Float, optional
@@ -35,43 +34,43 @@ def build_frame_cost_matrix(
3534
M = dist_matrix.shape[0]
3635
N = dist_matrix.shape[1]
3736

38-
C = lil_matrix((M + N, N + M), dtype=np.float32)
39-
ind = np.where(dist_matrix < track_cost_cutoff)
40-
C[(*ind,)] = dist_matrix[(*ind,)]
37+
C = coo_matrix_builder((M + N, N + M), dtype=np.float32)
38+
C.append_matrix(dist_matrix)
39+
4140
if track_start_cost is None:
42-
track_start_cost = np.concatenate(C.data).max() * 1.05
41+
track_start_cost = np.max(C.data) * 1.05
4342
if track_end_cost is None:
44-
track_end_cost = np.concatenate(C.data).max() * 1.05
43+
track_end_cost = np.max(C.data) * 1.05
44+
4545
C[np.arange(M, M + N), np.arange(N)] = np.ones(N) * track_end_cost
4646
C[np.arange(M), np.arange(N, N + M)] = np.ones(M) * track_start_cost
47-
ind2 = [ind[1] + M, ind[0] + N]
48-
min_val = np.concatenate(C.data).min() if len(C.data) > 0 else 0
49-
C[(*ind2,)] = min_val
47+
min_val = np.min(C.data) if len(C.data) > 0 else 0
48+
C[dist_matrix.col + M, dist_matrix.row + N] = min_val
5049

51-
return C
50+
return C.to_coo_matrix()
5251

5352

5453
def build_segment_cost_matrix(
55-
gap_closing_dist_matrix: Union[lil_matrix, FloatArray],
56-
splitting_dist_matrix: Union[lil_matrix, FloatArray],
57-
merging_dist_matrix: Union[lil_matrix, FloatArray],
54+
gap_closing_dist_matrix: Union[coo_matrix_builder, Matrix],
55+
splitting_dist_matrix: Union[coo_matrix_builder, Matrix],
56+
merging_dist_matrix: Union[coo_matrix_builder, Matrix],
5857
track_start_cost: Optional[Float],
5958
track_end_cost: Optional[Float],
6059
no_splitting_cost: Optional[Float],
6160
no_merging_cost: Optional[Float],
6261
alternative_cost_factor: Float = 1.05,
6362
alternative_cost_percentile: Float = 90,
6463
alternative_cost_percentile_interpolation: str = "lower",
65-
) -> lil_matrix:
64+
) -> coo_matrix:
6665
"""Build sparce array for segment-linking cost matrix.
6766
6867
Parameters
6968
----------
70-
gap_closing_dist_matrix : lil_matrix or FloatArray
69+
gap_closing_dist_matrix : coo_matrix_builder or Matrix
7170
The distance matrix for closing gaps between segment i and j.
72-
splitting_dist_matrix : lil_matrix or FloatArray
71+
splitting_dist_matrix : coo_matrix_builder or Matrix
7372
The distance matrix for splitting between segment i and time/index j
74-
merging_dist_matrix : lil_matrix or FloatArray
73+
merging_dist_matrix : coo_matrix_builder or Matrix
7574
The distance matrix for merging between segment i and time/index j
7675
track_start_cost : Float, optional
7776
The cost for starting the track (b in Jaqaman et al 2008 NMeth)
@@ -92,7 +91,7 @@ def build_segment_cost_matrix(
9291
9392
Returns
9493
-------
95-
cost_matrix : Optional[FloatArray]
94+
cost_matrix : Optional[coo_matrix]
9695
the cost matrix for frame linking, None if not appropriate
9796
"""
9897
M = gap_closing_dist_matrix.shape[0]
@@ -104,14 +103,14 @@ def build_segment_cost_matrix(
104103

105104
S = 2 * M + N1 + N2
106105

107-
C = lil_matrix((S, S), dtype=np.float32)
106+
C = coo_matrix_builder((S, S), dtype=np.float32)
108107

109-
C[:M, :M] = gap_closing_dist_matrix
110-
C[M : M + N1, :M] = splitting_dist_matrix.T
111-
C[:M, M : M + N2] = merging_dist_matrix
108+
C.append_matrix(gap_closing_dist_matrix)
109+
C.append_matrix(splitting_dist_matrix.T, shift=(M, 0))
110+
C.append_matrix(merging_dist_matrix, shift=(0, M))
112111

113-
all_data = np.concatenate(C.data)
114-
if len(all_data) == 0:
112+
upper_left_size = C.size()
113+
if upper_left_size == 0:
115114
return None
116115

117116
# Note:
@@ -133,7 +132,7 @@ def build_segment_cost_matrix(
133132
):
134133
alternative_cost = (
135134
np.percentile(
136-
all_data,
135+
C.data,
137136
alternative_cost_percentile,
138137
interpolation=alternative_cost_percentile_interpolation,
139138
)
@@ -152,11 +151,12 @@ def build_segment_cost_matrix(
152151
C[np.arange(2 * M + N1, S), np.arange(M, M + N2)] = np.ones(N2) * no_merging_cost
153152
C[np.arange(M), np.arange(M + N2, 2 * M + N2)] = np.ones(M) * track_end_cost
154153
C[np.arange(M, M + N1), np.arange(2 * M + N2, S)] = np.ones(N1) * no_splitting_cost
155-
subC = C[: M + N1, : M + N2]
156-
inds = subC.nonzero()
157154
min_val = np.min(
158155
[track_start_cost, track_end_cost, no_splitting_cost, no_merging_cost]
159156
)
160-
C[inds[1] + M + N1, inds[0] + M + N2] = min_val
161157

162-
return C
158+
upper_left_rows = C.row[:upper_left_size]
159+
upper_left_cols = C.col[:upper_left_size]
160+
C[upper_left_cols + M + N1, upper_left_rows + M + N2] = min_val
161+
162+
return C.to_coo_matrix()

src/laptrack/_optimization.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
from typing import Tuple
22

33
import lap
4-
from scipy.sparse import lil_matrix
54
from scipy.sparse.csr import csr_matrix
65

76
from ._typing_utils import FloatArray
87
from ._typing_utils import Int
98
from ._typing_utils import IntArray
9+
from ._typing_utils import Matrix
1010

1111

1212
def __to_lap_sparse(
13-
cost_matrix: lil_matrix,
13+
cost_matrix: Matrix,
1414
) -> Tuple[Int, FloatArray, IntArray, IntArray]:
1515
"""Convert data for lap.lapmod."""
1616
n = cost_matrix.shape[0]
@@ -19,12 +19,12 @@ def __to_lap_sparse(
1919
return n, cost_matrix2.data, cost_matrix2.indptr, cost_matrix2.indices
2020

2121

22-
def lap_optimization(cost_matrix: lil_matrix) -> Tuple[float, IntArray, IntArray]:
22+
def lap_optimization(cost_matrix: Matrix) -> Tuple[float, IntArray, IntArray]:
2323
"""Solves the linear assignment problem for a sparse matrix.
2424
2525
Parameters
2626
----------
27-
cost_matrix : lil_matrix
27+
cost_matrix : lil_matrix or coo_matrix
2828
the cost matrix in scipy.sparse.lil_matrix format
2929
3030
Returns

src/laptrack/_tracking.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@
1818
import numpy as np
1919
import pandas as pd
2020
from scipy.spatial.distance import cdist
21-
from scipy.sparse import lil_matrix
2221

2322
from ._cost_matrix import build_frame_cost_matrix, build_segment_cost_matrix
2423
from ._optimization import lap_optimization
2524
from ._typing_utils import Float
2625
from ._typing_utils import FloatArray
2726
from ._typing_utils import Int
27+
from ._utils import coo_matrix_builder
2828

2929

3030
def laptrack(
@@ -137,9 +137,16 @@ def laptrack(
137137
# linking between frames
138138
for frame, (coord1, coord2) in enumerate(zip(coords[:-1], coords[1:])):
139139
dist_matrix = cdist(coord1, coord2, metric=track_dist_metric)
140+
ind = np.where(dist_matrix < track_cost_cutoff)
141+
dist_matrix = coo_matrix_builder(
142+
dist_matrix.shape,
143+
row=ind[0],
144+
col=ind[1],
145+
data=dist_matrix[(*ind,)],
146+
dtype=dist_matrix.dtype,
147+
)
140148
cost_matrix = build_frame_cost_matrix(
141149
dist_matrix,
142-
track_cost_cutoff=track_cost_cutoff,
143150
track_start_cost=track_start_cost,
144151
track_end_cost=track_end_cost,
145152
)
@@ -203,7 +210,7 @@ def to_gap_closing_candidates(row):
203210
indices2 = np.where(
204211
target_dist_matrix[0] < gap_closing_cost_cutoff
205212
)[0]
206-
return df.index[indices2], target_dist_matrix[0][indices2]
213+
return df.index[indices2].values, target_dist_matrix[0][indices2]
207214
else:
208215
return [], []
209216

@@ -213,12 +220,14 @@ def to_gap_closing_candidates(row):
213220
else:
214221
segments_df["gap_closing_candidates"] = [[]] * len(segments_df)
215222

216-
gap_closing_dist_matrix = lil_matrix((N_segments, N_segments), dtype=np.float32)
223+
gap_closing_dist_matrix = coo_matrix_builder(
224+
(N_segments, N_segments), dtype=np.float32
225+
)
217226
for ind, row in segments_df.iterrows():
218227
candidate_inds = row["gap_closing_candidates"][0]
219228
candidate_costs = row["gap_closing_candidates"][1]
220229
# row ... track end, col ... track start
221-
gap_closing_dist_matrix[(ind, candidate_inds)] = candidate_costs
230+
gap_closing_dist_matrix[(int(ind), candidate_inds)] = candidate_costs
222231

223232
all_candidates = {}
224233
dist_matrices = {}
@@ -262,7 +271,9 @@ def to_candidates(row):
262271
)
263272

264273
N_middle = len(all_candidates[prefix])
265-
dist_matrices[prefix] = lil_matrix((N_segments, N_middle), dtype=np.float32)
274+
dist_matrices[prefix] = coo_matrix_builder(
275+
(N_segments, N_middle), dtype=np.float32
276+
)
266277

267278
all_candidates_dict = {
268279
tuple(val): i for i, val in enumerate(all_candidates[prefix])
@@ -273,7 +284,7 @@ def to_candidates(row):
273284
all_candidates_dict[tuple(fi)] for fi in candidate_frame_indices
274285
]
275286
candidate_costs = row[f"{prefix}_candidates"][1]
276-
dist_matrices[prefix][(ind, candidate_inds)] = candidate_costs
287+
dist_matrices[prefix][(int(ind), candidate_inds)] = candidate_costs
277288

278289
splitting_dist_matrix = dist_matrices["first"]
279290
merging_dist_matrix = dist_matrices["last"]

src/laptrack/_typing_utils.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@
33

44
import numpy as np
55
import numpy.typing as npt
6+
from scipy.sparse import lil_matrix
7+
from scipy.sparse.coo import coo_matrix
68

79
NumArray = npt.NDArray[Any]
810
FloatArray = npt.NDArray[np.float_]
911
IntArray = npt.NDArray[np.int_]
1012

1113
Int = Union[int, np.int_]
1214
Float = Union[float, np.float_]
15+
16+
Matrix = Union[FloatArray, coo_matrix, lil_matrix]

0 commit comments

Comments
 (0)