Visitar URL original
Migrate to `pathlib.Path` instead of `str` (first phase) by deruyter92 · Pull Request #3350 · DeepLabCut/DeepLabCut · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
3b8c44a
fix path resolution on network drives. Replace Path(..).resolve() wi…
deruyter92 May 29, 2026
fddcbaa
Add safe_resolve helper for path resolution
deruyter92 May 29, 2026
2403b72
replace str with pathlib.Path
deruyter92 May 29, 2026
65f8dfb
fix redundant resolution of resolved path in memory_replay.py
deruyter92 May 29, 2026
6bbe26d
fix open -> os.stat
deruyter92 May 29, 2026
3f93a76
replace os.stat -> open for files, listdir for dirs.
deruyter92 May 29, 2026
87cd6aa
fix TestReadConfigProjectPath
deruyter92 May 29, 2026
8be1c26
Update deeplabcut/utils/auxiliaryfunctions.py
deruyter92 May 29, 2026
f2bb35d
additional replacements: str ->Path
deruyter92 Jun 3, 2026
810406a
Update function signatures str -> str | Path
deruyter92 Jun 4, 2026
c7835bc
Add ruff linting rule PTH
deruyter92 Jun 4, 2026
5419546
Open dataset file directly using Path.open
C-Achard Jun 15, 2026
156c25f
Use context manager when writing pose_cfg.yaml
C-Achard Jun 15, 2026
d854aa5
Pass string path to cv2.VideoCapture
C-Achard Jun 15, 2026
a99f18b
Use explicit loop to unlink frame files
C-Achard Jun 15, 2026
e896398
Fix CircleCI failure
C-Achard Jun 15, 2026
132b882
fix Matplotlib Path import shadowing ->MPLPath
deruyter92 Jun 25, 2026
ee77f84
use pathlib consistently in `metrics`
deruyter92 Jun 25, 2026
0f2d32e
use safe_resolve in export
deruyter92 Jun 25, 2026
421bdc8
remove redundant defensive block for windows paths on python 3.6
deruyter92 Jun 25, 2026
f80c780
remove unused `get_immediate_subdirectories`
deruyter92 Jun 25, 2026
61165bc
use Path in plot_edge_affinity_distributions
deruyter92 Jun 25, 2026
693e945
remove PTH enforcement in linting
deruyter92 Jun 25, 2026
8f3008b
use safe_resolve instead of resolve in `training` and `auxiliaryfunct…
deruyter92 Jun 25, 2026
107a29a
Update GUI assets import (#3370)
C-Achard Jun 25, 2026
5ec1023
Merge remote-tracking branch 'upstream/main' into jaap/safe_resolve_p…
deruyter92 Jun 26, 2026
f422dcc
Merge branch 'main' into jaap/safe_resolve_paths
C-Achard Jun 29, 2026
9d2732b
Adjust config path tests for Path objects
C-Achard Jun 29, 2026
b33ae3e
Merge main in #3350
C-Achard Jun 29, 2026
ad1ae3e
Merge branch 'jaap/safe_resolve_paths' into jaap/pathlib_path_policy
C-Achard Jun 29, 2026
3ab3bd9
Fix os import in safe_resolve
C-Achard Jun 29, 2026
bb23a34
address remaining `.resolve()` cases: prefer `.absolute()`, or otherw…
deruyter92 Jul 6, 2026
831cb1d
update `safe_resolve`: add logging, use os.stat (more efficient)
deruyter92 Jul 6, 2026
77bf7f0
update test safe_resolve: allow fallback and test open/closing instead
deruyter92 Jul 6, 2026
ca63582
Merge remote-tracking branch 'upstream/main' into jaap/safe_resolve_p…
deruyter92 Jul 6, 2026
205b550
fix remaining Path.resolve()
deruyter92 Jul 7, 2026
f1e9d97
Merge remote-tracking branch 'upstream/jaap/safe_resolve_paths'
deruyter92 Jul 7, 2026
c51a25a
fix reintroduced pathlib `resolve()` occurences (see #3349)
deruyter92 Jul 7, 2026
a3b9f12
use pathlib Path in GUI codebase
deruyter92 Jul 7, 2026
09b578c
use pathlib Path in memory replay
deruyter92 Jul 7, 2026
58bc2a4
GUI rename field `config` to `config_path`
deruyter92 Jul 7, 2026
af613f3
GUI use os.fspath for str conversions where required
deruyter92 Jul 7, 2026
1586e2a
use pathlib Path in create_project
deruyter92 Jul 7, 2026
c49d392
use pathlib Path across pose_estimation_pytorch internally
deruyter92 Jul 7, 2026
6a85abb
use centralized yaml dumper (which normalizes Path -> str)
deruyter92 Jul 9, 2026
33c7b58
fix materialize.py downstream old str.split -> Path
deruyter92 Jul 9, 2026
efebf8d
fix testsscripts str usage
deruyter92 Jul 9, 2026
971f381
fix old str usage in trainingsetmanipulation
deruyter92 Jul 9, 2026
292b251
allow str type at TF boundary
deruyter92 Jul 9, 2026
1330eab
allow str formatting in tensorflow code
deruyter92 Jul 9, 2026
84b5c83
fix config update in train_from_coco.py
deruyter92 Jul 9, 2026
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
additional replacements: str ->Path
  • Loading branch information
deruyter92 committed Jun 3, 2026
commit f2bb35d3d9ba32a0405bc7438243023b63cd27c0
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
8 changes: 4 additions & 4 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 = (
Comment thread
deruyter92 marked this conversation as resolved.
Resolved
df.stack("individuals", dropna=False)
.reindex(animals, level="individuals")
Expand Down Expand Up @@ -248,13 +248,13 @@ def load_test_images(h5file: str, metadata: str) -> list[str]:
test_images = []
for img_path in df_test.index:
if not isinstance(img_path, str):
img_path = os.path.join(*img_path)
img_path = str(Path(*img_path))
test_images.append(img_path)
return test_images


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
2 changes: 1 addition & 1 deletion deeplabcut/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -2000,7 +2000,7 @@ def _load_config(config: str) -> dict:
if not config_path.exists():
raise FileNotFoundError(f"Config {config} is not found. Please make sure that the file exists.")

with open(config) as f:
with config_path.open() as f:
project_config = YAML(typ="safe", pure=True).load(f)

return project_config
4 changes: 2 additions & 2 deletions deeplabcut/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def read_config_as_dict(config_path: str | Path) -> dict:
Returns:
The configuration file with pure Python classes
"""
with open(config_path) as f:
with Path(config_path).open() as f:
cfg = YAML(typ="safe", pure=True).load(f)

return cfg
Expand All @@ -46,7 +46,7 @@ def write_config(config_path: str | Path, config: dict, overwrite: bool = True)
if not overwrite and Path(config_path).exists():
raise FileExistsError(f"Cannot write to {config_path} - set overwrite=True to force")

with open(config_path, "w") as file:
with Path(config_path).open("w") as file:
YAML().dump(config, file)


Expand Down
21 changes: 10 additions & 11 deletions deeplabcut/core/crossvalutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@
#


import os
import pickle
import shutil
from collections import defaultdict
from copy import deepcopy
from pathlib import Path

import networkx as nx
import numpy as np
Expand Down Expand Up @@ -44,9 +44,10 @@ def _set_up_evaluation(data):


def _form_original_path(path):
root, filename = os.path.split(path)
base, ext = os.path.splitext(filename)
return os.path.join(root, filename.split("c")[0] + ext)
p = Path(path)
ext = p.suffix
filename = p.name
return str(p.parent / (filename.split("c")[0] + ext))


def _unsorted_unique(array):
Expand Down Expand Up @@ -395,9 +396,9 @@ def cross_validate_paf_graphs(
inf_cfg_temp = inf_cfg.copy()
inf_cfg_temp["pcutoff"] = pcutoff

with open(full_data_file, "rb") as file:
with Path(full_data_file).open("rb") as file:
data = pickle.load(file)
with open(metadata_file, "rb") as file:
with Path(metadata_file).open("rb") as file:
metadata = pickle.load(file)

params = _set_up_evaluation(data)
Expand All @@ -415,10 +416,8 @@ def cross_validate_paf_graphs(

if calibrate:
trainingsetfolder = auxiliaryfunctions.get_training_set_folder(cfg)
calibration_file = os.path.join(
cfg["project_path"],
str(trainingsetfolder),
"CollectedData_" + cfg["scorer"] + ".h5",
calibration_file = str(
Path(cfg["project_path"]) / str(trainingsetfolder) / ("CollectedData_" + cfg["scorer"] + ".h5")
)
else:
calibration_file = ""
Expand Down Expand Up @@ -448,7 +447,7 @@ def cross_validate_paf_graphs(
inds = list(paf_inds[size_opt])
auxiliaryfunctions.edit_config(pose_config, {"paf_best": [int(ind) for ind in inds]})
if output_name:
with open(output_name, "wb") as file:
with Path(output_name).open("wb") as file:
pickle.dump([results], file)
return results[:3], paf_scores, results[3][size_opt]

Expand Down
5 changes: 3 additions & 2 deletions deeplabcut/core/inferenceutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from collections.abc import Iterable
from dataclasses import dataclass
from math import erf, sqrt
from pathlib import Path
from typing import Any

import networkx as nx
Expand Down Expand Up @@ -828,7 +829,7 @@ def wrapped(i):
pbar.update()

def from_pickle(self, pickle_path):
with open(pickle_path, "rb") as file:
with Path(pickle_path).open("rb") as file:
data = pickle.load(file)
self.unique = data.pop("single", {})
self.assemblies = data
Expand Down Expand Up @@ -876,7 +877,7 @@ def to_pickle(self, output_name):
data[ind] = [ass.data for ass in assemblies]
if self.unique:
data["single"] = self.unique
with open(output_name, "wb") as file:
with Path(output_name).open("wb") as file:
pickle.dump(data, file, pickle.HIGHEST_PROTOCOL)


Expand Down
4 changes: 2 additions & 2 deletions deeplabcut/create_project/add.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def add_new_videos(config, videos, copy_videos=False, coords=None, extract_frame
try:
src = str(src)
dst = str(dst)
os.symlink(src, dst)
Path(dst).symlink_to(src)
print(f"Created the symlink of {src} to {dst}")
except OSError:
try:
Expand All @@ -117,7 +117,7 @@ def add_new_videos(config, videos, copy_videos=False, coords=None, extract_frame
video_path = str(Path.resolve(Path(video)))
# video_path = os.path.realpath(video)
except Exception:
video_path = os.readlink(video)
video_path = str(Path(video).readlink())
Comment thread
deruyter92 marked this conversation as resolved.
Resolved

vid = VideoReader(video_path)
if coords is not None:
Expand Down
5 changes: 2 additions & 3 deletions deeplabcut/create_project/demo_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
# Licensed under GNU Lesser General Public License v3.0
#

import os
from pathlib import Path

import deeplabcut
Expand Down Expand Up @@ -61,9 +60,9 @@ def transform_data(config):

cfg["project_path"] = project_path
if "Reaching" in project_path:
video_file = os.path.join(project_path, "videos", "reachingvideo1.avi")
video_file = Path(project_path) / "videos" / "reachingvideo1.avi"
elif "openfield" in project_path:
video_file = os.path.join(project_path, "videos", "m4s1.mp4")
video_file = Path(project_path) / "videos" / "m4s1.mp4"
else:
print("This is not an official demo dataset.")

Expand Down
54 changes: 25 additions & 29 deletions deeplabcut/create_project/modelzoo.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@


def MakeTrain_pose_yaml(itemstochange, saveasconfigfile, defaultconfigfile):
raw = open(defaultconfigfile).read()
raw = Path(defaultconfigfile).open().read()
docs = []
for raw_doc in raw.split("\n---"):
try:
Expand All @@ -57,7 +57,7 @@ def MakeTrain_pose_yaml(itemstochange, saveasconfigfile, defaultconfigfile):
for key in itemstochange.keys():
docs[0][key] = itemstochange[key]
docs[0]["max_input_size"] = 1500
with open(saveasconfigfile, "w") as f:
with Path(saveasconfigfile).open("w") as f:
yaml.dump(docs[0], f)
return docs[0]

Expand Down Expand Up @@ -561,7 +561,7 @@ def create_pretrained_project_tensorflow(
model = "full_human"

if model in MODELOPTIONS:
cwd = os.getcwd()
cwd = Path.cwd()

cfg = deeplabcut.create_new_project(
project, experimenter, videos, working_directory, copy_videos, video_extensions=video_extensions
Expand Down Expand Up @@ -610,31 +610,27 @@ def create_pretrained_project_tensorflow(
auxiliaryfunctions.write_config(cfg, config)
config = auxiliaryfunctions.read_config(cfg)

train_dir = Path(
os.path.join(
config["project_path"],
str(
auxiliaryfunctions.get_model_folder(
trainFraction=config["TrainingFraction"][0],
shuffle=1,
cfg=config,
)
),
"train",
train_dir = (
Path(config["project_path"])
/ str(
auxiliaryfunctions.get_model_folder(
trainFraction=config["TrainingFraction"][0],
shuffle=1,
cfg=config,
)
)
/ "train"
)
test_dir = Path(
os.path.join(
config["project_path"],
str(
auxiliaryfunctions.get_model_folder(
trainFraction=config["TrainingFraction"][0],
shuffle=1,
cfg=config,
)
),
"test",
test_dir = (
Path(config["project_path"])
/ str(
auxiliaryfunctions.get_model_folder(
trainFraction=config["TrainingFraction"][0],
shuffle=1,
cfg=config,
)
)
/ "test"
)

# Create the model directory
Expand All @@ -644,8 +640,8 @@ def create_pretrained_project_tensorflow(
modelfoldername = auxiliaryfunctions.get_model_folder(
trainFraction=config["TrainingFraction"][0], shuffle=1, cfg=config
)
path_train_config = str(os.path.join(config["project_path"], Path(modelfoldername), "train", "pose_cfg.yaml"))
path_test_config = str(os.path.join(config["project_path"], Path(modelfoldername), "test", "pose_cfg.yaml"))
path_train_config = str(Path(config["project_path"]) / Path(modelfoldername) / "train" / "pose_cfg.yaml")
path_test_config = str(Path(config["project_path"]) / Path(modelfoldername) / "test" / "pose_cfg.yaml")

# Download the weights and put then in appropriate directory
print("Downloading weights...")
Expand All @@ -668,9 +664,9 @@ def create_pretrained_project_tensorflow(
# model_path = auxfun_models.check_for_weights(pose_cfg['net_type'], parent_path)

# Updating training and test pose_cfg:
snapshotname = [fn for fn in os.listdir(train_dir) if ".meta" in fn][0].split(".meta")[0]
snapshotname = [p.name for p in Path(train_dir).iterdir() if ".meta" in p.name][0].split(".meta")[0]
dict2change = {
"init_weights": str(os.path.join(train_dir, snapshotname)),
"init_weights": str(Path(train_dir) / snapshotname),
"project_path": str(config["project_path"]),
}

Expand Down
6 changes: 3 additions & 3 deletions deeplabcut/create_project/new.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ def create_new_project(
try:
src = str(src)
dst = str(dst)
os.symlink(src, dst)
Path(dst).symlink_to(src)
print(f"Created the symlink of {src} to {dst}")
except OSError:
try:
Expand All @@ -223,14 +223,14 @@ def create_new_project(
# video. [old: rel_video_path = os.path.realpath(video)]
rel_video_path = str(Path.resolve(Path(video)))
except Exception:
rel_video_path = os.readlink(str(video))
rel_video_path = str(Path(str(video)).readlink())
Comment thread
deruyter92 marked this conversation as resolved.
Resolved

try:
vid = VideoReader(rel_video_path)
video_sets[rel_video_path] = {"crop": ", ".join(map(str, vid.get_bbox()))}
except OSError:
warnings.warn("Cannot open the video file! Skipping to the next one...", stacklevel=2)
os.remove(video) # Removing the video or link from the project
Path(video).unlink() # Removing the video or link from the project

if not len(video_sets):
# Silently sweep the files that were already written.
Expand Down
Loading