88#
99# Licensed under GNU Lesser General Public License v3.0
1010#
11+
12+
13+ # NOTE - DUPLICATED @C-Achard 2026-01-26: Copied from the original DeepLabCut codebase
14+ # from deeplabcut/core/inferenceutils.py
1115from __future__ import annotations
1216
1317import heapq
1721import pickle
1822import warnings
1923from collections import defaultdict
24+ from collections .abc import Iterable
2025from dataclasses import dataclass
2126from math import erf , sqrt
22- from typing import Any , Iterable , Tuple
27+ from typing import Any
2328
2429import networkx as nx
2530import numpy as np
@@ -41,7 +46,7 @@ def _conv_square_to_condensed_indices(ind_row, ind_col, n):
4146 return n * ind_col - ind_col * (ind_col + 1 ) // 2 + ind_row - 1 - ind_col
4247
4348
44- Position = Tuple [float , float ]
49+ Position = tuple [float , float ]
4550
4651
4752@dataclass (frozen = True )
@@ -155,7 +160,7 @@ def soft_identity(self):
155160 unq , idx , cnt = np .unique (data [:, 3 ], return_inverse = True , return_counts = True )
156161 avg = np .bincount (idx , weights = data [:, 2 ]) / cnt
157162 soft = softmax (avg )
158- return dict (zip (unq .astype (int ), soft ))
163+ return dict (zip (unq .astype (int ), soft , strict = False ))
159164
160165 @property
161166 def affinity (self ):
@@ -262,7 +267,8 @@ def __init__(
262267 self ._has_identity = "identity" in self [0 ]
263268 if identity_only and not self ._has_identity :
264269 warnings .warn (
265- "The network was not trained with identity; setting `identity_only` to False."
270+ "The network was not trained with identity; setting `identity_only` to False." ,
271+ stacklevel = 2 ,
266272 )
267273 self .identity_only = identity_only & self ._has_identity
268274 self .nan_policy = nan_policy
@@ -344,15 +350,19 @@ def calibrate(self, train_data_file):
344350 pass
345351 n_bpts = len (df .columns .get_level_values ("bodyparts" ).unique ())
346352 if n_bpts == 1 :
347- warnings .warn ("There is only one keypoint; skipping calibration..." )
353+ warnings .warn (
354+ "There is only one keypoint; skipping calibration..." , stacklevel = 2
355+ )
348356 return
349357
350358 xy = df .to_numpy ().reshape ((- 1 , n_bpts , 2 ))
351359 frac_valid = np .mean (~ np .isnan (xy ), axis = (1 , 2 ))
352360 # Only keeps skeletons that are more than 90% complete
353361 xy = xy [frac_valid >= 0.9 ]
354362 if not xy .size :
355- warnings .warn ("No complete poses were found. Skipping calibration..." )
363+ warnings .warn (
364+ "No complete poses were found. Skipping calibration..." , stacklevel = 2
365+ )
356366 return
357367
358368 # TODO Normalize dists by longest length?
@@ -369,7 +379,8 @@ def calibrate(self, train_data_file):
369379 except np .linalg .LinAlgError :
370380 # Covariance matrix estimation fails due to numerical singularities
371381 warnings .warn (
372- "The assembler could not be robustly calibrated. Continuing without it..."
382+ "The assembler could not be robustly calibrated. Continuing without it..." ,
383+ stacklevel = 2 ,
373384 )
374385
375386 def calc_assembly_mahalanobis_dist (
@@ -428,10 +439,12 @@ def _flatten_detections(data_dict):
428439 ids = [np .ones (len (arr ), dtype = int ) * - 1 for arr in confidence ]
429440 else :
430441 ids = [arr .argmax (axis = 1 ) for arr in ids ]
431- for i , (coords , conf , id_ ) in enumerate (zip (coordinates , confidence , ids )):
442+ for i , (coords , conf , id_ ) in enumerate (
443+ zip (coordinates , confidence , ids , strict = False )
444+ ):
432445 if not np .any (coords ):
433446 continue
434- for xy , p , g in zip (coords , conf , id_ ):
447+ for xy , p , g in zip (coords , conf , id_ , strict = False ):
435448 joint = Joint (tuple (xy ), p .item (), i , ind , g )
436449 ind += 1
437450 yield joint
@@ -474,13 +487,13 @@ def extract_best_links(self, joints_dict, costs, trees=None):
474487 (conf >= self .pcutoff * self .pcutoff ) & (aff >= self .min_affinity )
475488 )
476489 candidates = sorted (
477- zip (rows , cols , aff [rows , cols ], lengths [rows , cols ]),
490+ zip (rows , cols , aff [rows , cols ], lengths [rows , cols ], strict = False ),
478491 key = lambda x : x [2 ],
479492 reverse = True ,
480493 )
481494 i_seen = set ()
482495 j_seen = set ()
483- for i , j , w , l in candidates :
496+ for i , j , w , _l in candidates :
484497 if i not in i_seen and j not in j_seen :
485498 i_seen .add (i )
486499 j_seen .add (j )
@@ -502,7 +515,7 @@ def extract_best_links(self, joints_dict, costs, trees=None):
502515 ]
503516 aff = aff [np .ix_ (keep_s , keep_t )]
504517 rows , cols = linear_sum_assignment (aff , maximize = True )
505- for row , col in zip (rows , cols ):
518+ for row , col in zip (rows , cols , strict = False ):
506519 w = aff [row , col ]
507520 if w >= self .min_affinity :
508521 links .append (Link (dets_s [keep_s [row ]], dets_t [keep_t [col ]], w ))
@@ -548,9 +561,9 @@ def push_to_stack(i):
548561 d = self .calc_assembly_mahalanobis_dist (assembly , nan_policy = nan_policy )
549562 if d < d_old :
550563 push_to_stack (new_ind )
551- if tabu :
552- _ , _ , link = heapq .heappop (tabu )
553- heapq .heappush (stack , (- link .affinity , next (counter ), link ))
564+ if tabu :
565+ _ , _ , link = heapq .heappop (tabu )
566+ heapq .heappush (stack , (- link .affinity , next (counter ), link ))
554567 else :
555568 heapq .heappush (tabu , (d - d_old , next (counter ), best ))
556569 assembly .__dict__ .update (assembly ._dict )
@@ -665,7 +678,7 @@ def build_assemblies(self, links):
665678 for idx in store [j ]._idx :
666679 store [idx ] = store [i ]
667680 except KeyError :
668- # Some links may reference indices that were never added to `store`;
681+ # Some links may reference indices that were never added to `store`;
669682 # in that case we intentionally skip merging for this link
670683 pass
671684
@@ -791,7 +804,7 @@ def _assemble(self, data_dict, ind_frame):
791804 ]
792805 else :
793806 scores = [ass ._affinity for ass in assemblies ]
794- lst = list (zip (scores , assemblies ))
807+ lst = list (zip (scores , assemblies , strict = False ))
795808 assemblies = []
796809 while lst :
797810 temp = max (lst , key = lambda x : x [0 ])
@@ -1074,7 +1087,7 @@ def match_assemblies(
10741087 if ~ np .isnan (oks ):
10751088 mat [i , j ] = oks
10761089 rows , cols = linear_sum_assignment (mat , maximize = True )
1077- for row , col in zip (rows , cols ):
1090+ for row , col in zip (rows , cols , strict = False ):
10781091 matched [row ].ground_truth = ground_truth [col ]
10791092 matched [row ].oks = mat [row , col ]
10801093 _ = inds_true .remove (col )
@@ -1087,7 +1100,7 @@ def parse_ground_truth_data_file(h5_file):
10871100 try :
10881101 df .drop ("single" , axis = 1 , level = "individuals" , inplace = True )
10891102 except KeyError :
1090- # Ignore if the "single" individual column is absent
1103+ # Ignore if the "single" individual column is absent
10911104 pass
10921105 # Cast columns of dtype 'object' to float to avoid TypeError
10931106 # further down in _parse_ground_truth_data.
@@ -1128,7 +1141,7 @@ def find_outlier_assemblies(dict_of_assemblies, criterion="area", qs=(5, 95)):
11281141 for frame_ind , assemblies in dict_of_assemblies .items ():
11291142 for assembly in assemblies :
11301143 tuples .append ((frame_ind , getattr (assembly , criterion )))
1131- frame_inds , vals = zip (* tuples )
1144+ frame_inds , vals = zip (* tuples , strict = False )
11321145 vals = np .asarray (vals )
11331146 lo , up = np .percentile (vals , qs , interpolation = "nearest" )
11341147 inds = np .flatnonzero ((vals < lo ) | (vals > up )).tolist ()
@@ -1246,12 +1259,14 @@ def evaluate_assembly(
12461259 ass_pred_dict ,
12471260 ass_true_dict ,
12481261 oks_sigma = 0.072 ,
1249- oks_thresholds = np . linspace ( 0.5 , 0.95 , 10 ) ,
1262+ oks_thresholds = None ,
12501263 margin = 0 ,
12511264 symmetric_kpts = None ,
12521265 greedy_matching = False ,
12531266 with_tqdm : bool = True ,
12541267):
1268+ if oks_thresholds is None :
1269+ oks_thresholds = np .linspace (0.5 , 0.95 , 10 )
12551270 if greedy_matching :
12561271 return evaluate_assembly_greedy (
12571272 ass_true_dict ,
0 commit comments