Miscellaneous imports and code fixes (#3411)
* Fix bone likelihood in analyzebone to use both bodyparts analyzebone() computed each skeleton bone's likelihood as min(bp2.likelihood, bp2.likelihood), stacking the second bodypart twice and discarding bp1. The adjacent comment states the intent is 'keep the smallest of the two likelihoods', so the result should be min(bp1.likelihood, bp2.likelihood). As written, a bone whose first bodypart is occluded (low confidence) but whose second bodypart is confident was reported as fully confident, silently corrupting the likelihood column of every skeleton analysis output. * Fix SORTSkeleton removing the wrong tracker on cleanup In SORTSkeleton.track, the reverse-iteration cleanup loop maintains i as the current tracker's index but removed dead trackers with self.trackers.pop() (removes the last element) instead of self.trackers.pop(i). The sibling classes SORTBox and SORTEllipse correctly use pop(i). With a stale tracker in the middle of the list, pop() deleted a different (often still-live) tracker; the dead one persisted and animalindex[i] alignment broke, corrupting identity/tracklet assignment for the skeleton track method. * Fix CTD tracklet-age reset writing to a non-existent attribute When a frame lost all individuals (NMS mask all-False), the reset branch assigned self._idx_ages = None, but the tracking-age attribute is _ctd_track_ages (initialised in __init__ and used by _ctd_tracking_postprocess). _idx_ages is never read anywhere, so the intended reset silently did nothing and a dead attribute was created. As a result _ctd_track_ages kept its stale values; on the next BU-seeded frame the OKS-NMS ordering was biased toward stale-old indices, retaining the wrong pose/identity, and the bias compounded over subsequent frames. Reset the correct attribute. * Fix destfolder leaking across videos in filter/skeleton loops filterpredictions() and analyzeskeleton() reassigned the destfolder parameter inside their 'for video in Videos' loop when it was None. After the first iteration destfolder stayed pinned to the first video's folder, so videos located in a different folder were looked up in the wrong place: load_analyzed_data raised FileNotFoundError and the video was silently reported as not analyzed (and any output would go to the wrong folder). Use a per-iteration local 'videofolder' instead of mutating the parameter, matching plot_trajectories and extract_outlier_frames. * Fix except clause catching a ValueError instance instead of the type In extract_outlier_frames (outlieralgorithm='list'), the handler was written 'except ValueError():', which evaluates ValueError() to an instance. When the try body raised a ValueError, Python's exception matching hit a non-class handler and raised 'TypeError: catching classes that do not inherit from BaseException is not allowed', masking the real error and the intended message. Catch the ValueError type instead. * Import scipy.ndimage functions directly (drop removed measurements namespace) predict_multianimal.py imported 'from scipy.ndimage import measurements'. The scipy.ndimage.measurements submodule was deprecated in scipy 1.8 and removed in scipy 1.14; since the project pins scipy>=1.9 with no upper bound, a fresh install resolves a scipy where this import raises ImportError at module load. Because the module is imported at the top of the TensorFlow multi-animal inference path, analyze_videos on a TF maDLC project failed immediately on import. Import label and center_of_mass directly from scipy.ndimage (their canonical public location) and call them without the measurements prefix. * Replace removed np.object alias with builtin object test.py used np.zeros(..., dtype=np.object). np.object was a deprecated alias for the builtin object and was removed in numpy 1.24, so it raises AttributeError under the pinned numpy (>=1.18.5,<2 resolves to 1.26). Use dtype=object. * Fix operator precedence in condition NaN/non-positive mask The mask 'conds <= 0 | np.isnan(conds)' parses as 'conds <= (0 | np.isnan(conds))' because bitwise | binds tighter than the <= comparison, so the NaN check was not OR-ed with the non-positive check as intended. Parenthesize to '(conds <= 0) | np.isnan(conds)'. Verified: for conds=[0.5, -0.1, nan] the buggy expression yields [False, True, False] (misses the NaN), while the fixed expression yields [False, True, True]. * Fix scipy.stats.mode usage for keepdims=False default (scipy>=1.11) SORTEllipse.track indexed mode(identities[i])[0][0]. Since scipy 1.11 mode defaults to keepdims=False, so mode(1d)[0] is already a scalar and the second [0] raises IndexError. Pass keepdims=False explicitly and take a single [0], matching the pattern already used in refine_training_dataset/stitch.py. These sites are in the identity-aware branch of SORTEllipse.track, which is currently only reached when 'identities' is passed to track() (no in-tree caller does), so this is a latent fix rather than a live regression. --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Sungjoo(Dennis) Hwang <48212469+Denny-Hwang@users.noreply.github.com>
C
Cyril Achard committed
040db9fb38a489befd0ffdf5d5a4a9cfa73ede6a
Parent: 030f458
Committed by GitHub <noreply@github.com>
on 7/20/2026, 11:39:47 AM