Visitar URL original
Fix bone likelihood in analyzebone to use both bodyparts by Denny-Hwang · Pull Request #3401 · DeepLabCut/DeepLabCut · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Migrate to pathlib.Path instead of str (first phase) (#3350)
* fix path resolution on network drives. Replace  Path(..).resolve() with Path(..).absolute()

* Add safe_resolve helper for path resolution

* replace str with pathlib.Path

* fix redundant resolution of resolved path in memory_replay.py

* fix open -> os.stat

* replace os.stat -> open for files, listdir for dirs.

* fix TestReadConfigProjectPath

* Update deeplabcut/utils/auxiliaryfunctions.py

* additional replacements: str ->Path

* Update function signatures str -> str | Path

* Add ruff linting rule PTH

* Open dataset file directly using Path.open

Fix path handling when loading the dataset pickle. The code previously attempted Path(project_path) / file_name even though file_name already included the project path, resulting in an incorrect path. This changes the context manager to call file_name.open('rb') directly, removing the redundant join and preventing path errors when loading the pickle.

* Use context manager when writing pose_cfg.yaml

Replace direct ruamel_file.dump(...open('w')) call with a with-statement to open pose_cfg.yaml. This ensures the file handle is properly closed after writing and avoids potential resource leaks.

* Pass string path to cv2.VideoCapture

Convert video_path to str when constructing cv2.VideoCapture to ensure pathlib.Path objects are handled correctly and avoid type errors when opening videos. This makes VideoReader more robust when given Path instances.

* Use explicit loop to unlink frame files

Replace the list-comprehension side-effect used to delete temporary frame files with an explicit for-loop that checks p.is_file() before calling unlink. This avoids building an unused list, prevents errors from trying to unlink non-file entries (e.g., directories), and improves readability and safety.

* Fix CircleCI failure

* fix Matplotlib Path import shadowing ->MPLPath

* use pathlib consistently in `metrics`

* use safe_resolve in export

* remove redundant defensive block for windows paths on python 3.6

* remove unused `get_immediate_subdirectories`

* use Path in plot_edge_affinity_distributions

* remove PTH enforcement in linting

* use safe_resolve instead of resolve in `training` and `auxiliaryfunctions`

* Update GUI assets import (#3370)

* Add GUI asset helper utilities

Introduce deeplabcut.gui.gui_assets module providing utilities to load bundled GUI assets. It locates the package assets directory via importlib.resources, and exposes resource_bytes, resource_text, get_assets_dir, get_style_qss, pixmap_from_resource and icon_from_resource helpers (using PySide6 QPixmap/QIcon). Includes error handling when image data cannot be loaded and uses type annotations for clarity.

* Use gui_assets for icons and pixmaps

Replace direct filesystem asset loading (BASE_DIR/assets and manual Path lookups) with centralized gui_assets helpers (icon_from_resource, pixmap_from_resource, get_style_qss/get_assets_dir) across multiple GUI modules. Updated imports and calls in components, launch_script, create_project, modelzoo, open_project, train_network, and window to use the new resource functions and remove redundant logo path handling, improving resource access and packaging robustness.

* fix path concatenation in `make_labeled_images_from_dataframe`

---------

Co-authored-by: Jaap de Ruyter van Steveninck <32810691+deruyter92@users.noreply.github.com>

* Adjust config path tests for Path objects

Update `test_auxiliaryfunctions.py` to assert `project_path` as a `pathlib.Path` instead of a string. The tests now cast to `str` only for the `"Volume{"` check, use `.exists()` for filesystem validation, and compare against `project_dir.absolute()` directly.

* Fix os import in safe_resolve

Import `os` inside `safe_resolve` before calling `os.listdir` so directory resolution no longer risks a `NameError`. This also removes an unused `os` import in `test_auxiliaryfunctions.py` and a stray blank line in `video_processor.py`.

* address remaining `.resolve()` cases: prefer `.absolute()`, or otherwise `safe_resolve()`

* update `safe_resolve`: add logging, use os.stat (more efficient)

* update test safe_resolve: allow fallback and test open/closing instead

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* fix remaining Path.resolve()

* fix reintroduced pathlib `resolve()` occurences (see #3349)

* use pathlib Path in GUI codebase

* use pathlib Path in memory replay

* GUI rename field `config` to `config_path`

* GUI use os.fspath for str conversions where required

* use pathlib Path in create_project

* use pathlib Path across pose_estimation_pytorch internally

* use centralized yaml dumper (which normalizes Path -> str)

* fix materialize.py downstream old str.split -> Path

* fix testsscripts str usage

* fix old str usage in  trainingsetmanipulation

* allow str type at TF boundary

* allow str formatting in tensorflow code

* fix config update in train_from_coco.py

PoseConfig.model.backbone is currently a dict-type but freeze_bn_stats were set as attribute. This commit fixes that and treats backbone correctly as dict.

---------

Co-authored-by: Cyril Achard <cyril.achard@epfl.ch>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
  • Loading branch information
3 people authored Jul 15, 2026
commit 30a8155d4d27984b272cedcf4a7b0a45b5951a42
13 changes: 7 additions & 6 deletions deeplabcut/benchmark/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@
import json
import os
from collections.abc import Container
from pathlib import Path
from typing import Literal

from deeplabcut.benchmark.base import Benchmark, Result, ResultCollection

DATA_ROOT = os.path.join(os.getcwd(), "data")
CACHE = os.path.join(os.getcwd(), ".results")
DATA_ROOT = Path.cwd() / "data"
CACHE = Path.cwd() / ".results"

__registry = []

Expand Down Expand Up @@ -99,20 +100,20 @@ def evaluate(


def get_filepath(basename: str):
return os.path.join(DATA_ROOT, basename)
return DATA_ROOT / basename


def savecache(results: ResultCollection):
with open(CACHE, "w") as fh:
with Path(CACHE).open("w") as fh:
json.dump(results.todicts(), fh, indent=2)


def loadcache(cache=CACHE, on_missing: Literal["raise", "ignore"] = "ignore") -> ResultCollection:
if not os.path.exists(cache):
if not Path(cache).exists():
if on_missing == "raise":
raise FileNotFoundError(cache)
return ResultCollection()
with open(cache) as fh:
with Path(cache).open() as fh:
try:
data = json.load(fh)
except json.decoder.JSONDecodeError as e:
Expand Down
43 changes: 19 additions & 24 deletions deeplabcut/benchmark/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@

"""Evaluation metrics for the DeepLabCut benchmark."""

import os
import pickle
from collections import defaultdict
from pathlib import Path

import numpy as np
import pandas as pd
Expand All @@ -33,7 +33,7 @@ def _format_gt_data(h5file: str, test_indices: list[int] | None = None):
except KeyError:
n_unique = 0
guarantee_multiindex_rows(df)
file_paths = [os.path.join(*row) for row in df.index.to_list()]
file_paths = [Path(*row) for row in df.index.to_list()]
temp = (
df.stack("individuals", dropna=False)
.reindex(animals, level="individuals")
Expand Down Expand Up @@ -98,19 +98,18 @@ def calc_prediction_errors(preds, gt):
return errors


def _map(strings, substrings):
def _map(paths: list[Path], subpaths: list[Path]) -> dict[Path, Path]:
"""Map image paths from predicted data to GT as the first are typically absolute
whereas the latter are relative to the project path."""

lookup = dict()
strings_ = strings.copy()
substrings_ = substrings.copy()
while strings_:
string = strings_.pop()
for s in substrings_:
if string.endswith(s):
lookup[string] = s
substrings_.remove(s)
lookup = {}
paths_ = paths.copy()
subpaths_ = subpaths.copy()
while paths_:
path = paths_.pop()
for s in subpaths_:
if path.parts[-len(s.parts) :] == s.parts:
lookup[path] = s
subpaths_.remove(s)
break
return lookup

Expand Down Expand Up @@ -146,6 +145,7 @@ def calc_map_from_obj(
drop_kpts=None,
):
"""Calculate mean average precision (mAP) based on predictions."""
eval_results = {Path(k): v for k, v in eval_results_obj.items()}
df = pd.read_hdf(h5_file)
try:
df.drop("single", level="individuals", axis=1, inplace=True)
Expand All @@ -157,7 +157,7 @@ def calc_map_from_obj(
test_indices = _load_test_indices(metadata_file)
df_test = df.iloc[test_indices]
test_images = load_test_images(h5_file, metadata_file)
missing_images = set(test_images) - set(eval_results_obj.keys())
missing_images = set(test_images) - set(eval_results)
if len(missing_images) > 0:
raise ValueError(
f"Failed to compute the test mAP: there are test images missing from theprediction object: {missing_images}"
Expand All @@ -184,7 +184,7 @@ def calc_map_from_obj(
for ind in sorted(drop_kpts, reverse=True):
kpts.pop(ind)

assemblies_pred = conv_obj_to_assemblies(eval_results_obj, kpts)
assemblies_pred = conv_obj_to_assemblies(eval_results, kpts)
with deeplabcut.benchmark.utils.DisableOutput():
oks = inferenceutils.evaluate_assembly(
assemblies_pred,
Expand Down Expand Up @@ -213,7 +213,7 @@ def calc_rmse_from_obj(
for ind in sorted(drop_kpts, reverse=True):
kpts.pop(ind)

test_objects = {k: v for k, v in eval_results_obj.items() if k in gt["annotations"].keys()}
test_objects = {Path(k): v for k, v in eval_results_obj.items() if Path(k) in gt["annotations"]}
if len(gt["annotations"]) != len(test_objects):
gt_images = list(gt["annotations"].keys())
missing_images = [img for img in gt_images if img not in test_objects]
Expand All @@ -239,22 +239,17 @@ def calc_rmse_from_obj(
return np.nanmean(errors[..., 0])


def load_test_images(h5file: str, metadata: str) -> list[str]:
def load_test_images(h5file: str, metadata: str) -> list[Path]:
"""Returns the names of the test images for the benchmark, in the order
corresponding to the test indices."""
df = pd.read_hdf(h5file)
test_indices = _load_test_indices(metadata)
df_test = df.iloc[test_indices]
test_images = []
for img_path in df_test.index:
if not isinstance(img_path, str):
img_path = os.path.join(*img_path)
test_images.append(img_path)
return test_images
return [Path(img_path) if isinstance(img_path, str) else Path(*img_path) for img_path in df_test.index]


def _load_test_indices(shuffle_metadata_path: str) -> list[int]:
"""Returns the indices of test images in the training dataset dataframe."""
with open(shuffle_metadata_path, "rb") as f:
with Path(shuffle_metadata_path).open("rb") as f:
test_indices = set([int(i) for i in pickle.load(f)[2]])
return list(sorted(test_indices))
3 changes: 2 additions & 1 deletion deeplabcut/benchmark/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import os
import pkgutil
import sys
from pathlib import Path


class RedirectStdStreams:
Expand Down Expand Up @@ -46,7 +47,7 @@ def __exit__(self, exc_type, exc_value, traceback):

class DisableOutput(RedirectStdStreams):
def __init__(self):
devnull = open(os.devnull, "w")
devnull = Path(os.devnull).open("w")
super().__init__(stdout=devnull, stderr=devnull)


Expand Down
Loading
Loading