Visitar URL original
Fix PySide6 stylesheet usage by C-Achard · Pull Request #3371 · DeepLabCut/DeepLabCut · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 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
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
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
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
8c6c722
Add GUI asset helper utilities
C-Achard Jun 15, 2026
32c12e7
Use gui_assets for icons and pixmaps
C-Achard Jun 15, 2026
a7f2643
Use get_style_qss and update qdarkstyle deps
C-Achard Jun 15, 2026
2a59325
Update uv.lock
C-Achard Jun 15, 2026
4dec33a
Fallback to PySide2 qdarkstyle stylesheet
C-Achard Jun 15, 2026
5275139
Remove unused imports and tidy spacing
C-Achard Jul 16, 2026
e6aab6a
Remove outdated comments
C-Achard Jul 17, 2026
b5c2c91
Merge remote-tracking branch 'upstream/main' into cy/fix-pyside6-theme
deruyter92 Jul 17, 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
Add safe_resolve helper for path resolution
  • Loading branch information
deruyter92 committed May 29, 2026
commit fddcbaa46f0f2a3273d332dd2429ebf199a5b6c2
9 changes: 6 additions & 3 deletions deeplabcut/pose_estimation_pytorch/modelzoo/memory_replay.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,11 @@ def prepare_memory_replay_dataset(
device: str | None = None,
):
"""Need to first run inference on the source project train file."""
project_root = loader.project_path.resolve()
source_dataset_folder = Path(source_dataset_folder).resolve()
# Use safe_resolve so that symlinks are followed where possible, but we
# fall back to abspath on Windows 11 SMB drives that produce unusable
# \\?\Volume{GUID}\... paths. See https://github.com/DeepLabCut/DeepLabCut/issues/3348
project_root = af.safe_resolve(loader.project_path)
source_dataset_folder = af.safe_resolve(Path(source_dataset_folder))

# Contains the ground truth annotations for the DeepLabCut project
# .../dlc-models-pytorch/.../...shuffle0/train/memory_replay/annotations/train.json
Expand Down Expand Up @@ -242,7 +245,7 @@ def optimal_match(gts_list, preds_list):
# parse the GT to put the image paths back into OS-specific format
for image in project_gt["images"]:
image_rel_path = image["file_name"].split("/")
image["file_name"] = str(project_root.resolve() / Path(*image_rel_path))
image["file_name"] = str(af.safe_resolve(project_root / Path(*image_rel_path)))

with open(memory_replay_train_file_path, "w") as f:
json.dump(project_gt, f, indent=4)
Expand Down
18 changes: 18 additions & 0 deletions deeplabcut/utils/auxiliaryfunctions.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,24 @@ def create_config_template_3d():
return cfg_file_3d, ruamelFile_3d


def safe_resolve(path: Path) -> Path:
"""Return a resolved Path that is safe to use with str-based I/O.

Prefers Path.resolve() so that symlinks are followed (useful on Linux).
Falls back to Path.absolute() when the resolved path cannot be opened
as a plain string — e.g. on Windows 11 + SMB network drives where
resolve() may return an unusable \\\\?\\Volume{GUID}\\... form.

See https://github.com/DeepLabCut/DeepLabCut/issues/3348
"""
resolved = path.resolve()
try:
open(resolved).close()
return resolved
except OSError:
return path.absolute()


def read_config(configname):
"""Reads structured config file defining a project."""
ruamelFile = YAML()
Expand Down
77 changes: 77 additions & 0 deletions tests/test_auxiliaryfunctions.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
#
# Licensed under GNU Lesser General Public License v3.0
#
import os
from pathlib import Path
from unittest.mock import patch

import pytest

Expand Down Expand Up @@ -269,3 +271,78 @@ def test_get_snapshots_from_folder_none(mock_no_snapshots_folder):
"""Test raises ValueError if no snapshots are found."""
with pytest.raises(FileNotFoundError):
auxiliaryfunctions.get_snapshots_from_folder(mock_no_snapshots_folder)


# ---------------------------------------------------------------------------
# Tests for safe_resolve() and read_config() network-drive path safety
# https://github.com/DeepLabCut/DeepLabCut/issues/3348
# ---------------------------------------------------------------------------


class TestSafeResolve:
"""safe_resolve() must return a Path whose str() representation can be
opened by plain string-based I/O — i.e. it must not return Windows 11 SMB
Volume GUID paths like \\\\?\\Volume{...}\\...
"""

def test_normal_path_is_returned_unchanged(self, tmp_path):
"""On a normal local filesystem, safe_resolve returns the resolved path."""
f = tmp_path / "config.yaml"
f.touch()
result = auxiliaryfunctions.safe_resolve(f)
assert result == f.resolve()
assert result.exists()

def test_fallback_when_resolve_produces_unusable_path(self, tmp_path):
"""When resolve() returns a path that cannot be opened as a string,
safe_resolve must fall back to abspath."""
f = tmp_path / "config.yaml"
f.write_text("project_path: .")

fake_volume_guid = Path(r"\\?\Volume{DEADBEEF-0000-0000-0000-000000000000}\fake")

with patch.object(Path, "resolve", return_value=fake_volume_guid):
result = auxiliaryfunctions.safe_resolve(f)

# Must NOT return the unusable Volume GUID path
assert "Volume{" not in str(result)
# Fallback must be the absolute (non-resolved) path, which exists and is openable
assert result == f.absolute()
open(result).close()


class TestReadConfigProjectPath:
"""read_config() must never persist a \\\\?\\Volume{GUID}\\... path into
project_path, even on Windows 11 SMB network drives."""

def test_project_path_not_written_as_volume_guid(self, tmp_path):
"""If resolve() would produce a Volume GUID path, read_config must fall
back to abspath and write a usable path to config.yaml."""
project_dir = tmp_path / "my_project"
project_dir.mkdir()
config_file = project_dir / "config.yaml"

auxiliaryfunctions.write_config(config_file, {"project_path": str(project_dir)})

fake_volume_guid = Path(r"\\?\Volume{DEADBEEF-0000-0000-0000-000000000000}\my_project")

with patch.object(Path, "resolve", return_value=fake_volume_guid):
cfg = auxiliaryfunctions.read_config(config_file)

assert "Volume{" not in cfg["project_path"]
# The stored value must be openable as a plain string
assert os.path.isdir(cfg["project_path"])

def test_project_path_updated_when_moved(self, tmp_path):
"""read_config() must still update project_path when a project is moved
to a new directory (the original feature that resolve() was meant for)."""
project_dir = tmp_path / "original_location"
project_dir.mkdir()
config_file = project_dir / "config.yaml"

auxiliaryfunctions.write_config(config_file, {"project_path": "/some/old/path/that/no/longer/exists"})

cfg = auxiliaryfunctions.read_config(config_file)

expected = str(project_dir.absolute())
assert cfg["project_path"] == expected
Loading