| 333 | return self.metadata["num_joints"] |
| 334 | |
| 335 | def calibrate(self, train_data_file): |
| 336 | df = pd.read_hdf(train_data_file) |
| 337 | try: |
| 338 | df.drop("single", level="individuals", axis=1, inplace=True) |
| 339 | except KeyError: |
| 340 | pass |
| 341 | n_bpts = len(df.columns.get_level_values("bodyparts").unique()) |
| 342 | if n_bpts == 1: |
| 343 | warnings.warn("There is only one keypoint; skipping calibration...", stacklevel=2) |
| 344 | return |
| 345 | |
| 346 | xy = df.to_numpy().reshape((-1, n_bpts, 2)) |
| 347 | frac_valid = np.mean(~np.isnan(xy), axis=(1, 2)) |
| 348 | # Only keeps skeletons that are more than 90% complete |
| 349 | xy = xy[frac_valid >= 0.9] |
| 350 | if not xy.size: |
| 351 | warnings.warn("No complete poses were found. Skipping calibration...", stacklevel=2) |
| 352 | return |
| 353 | |
| 354 | # TODO Normalize dists by longest length? |
| 355 | # TODO Smarter imputation technique (Bayesian? Grassmann averages?) |
| 356 | dists = np.vstack([pdist(data, "sqeuclidean") for data in xy]) |
| 357 | mu = np.nanmean(dists, axis=0) |
| 358 | missing = np.isnan(dists) |
| 359 | dists = np.where(missing, mu, dists) |
| 360 | try: |
| 361 | kde = gaussian_kde(dists.T) |
| 362 | kde.mean = mu |
| 363 | self._kde = kde |
| 364 | self.safe_edge = True |
| 365 | except np.linalg.LinAlgError: |
| 366 | # Covariance matrix estimation fails due to numerical singularities |
| 367 | warnings.warn("The assembler could not be robustly calibrated. Continuing without it...", stacklevel=2) |
| 368 | |
| 369 | def calc_assembly_mahalanobis_dist(self, assembly, return_proba=False, nan_policy="little"): |
| 370 | if self._kde is None: |