Visitar URL original
Prepare TensorFlow deprecation - add `deeplabcut.api` by deruyter92 · Pull Request #3382 · DeepLabCut/DeepLabCut · GitHub
Skip to content

Prepare TensorFlow deprecation - add deeplabcut.api - #3382

Open
deruyter92 wants to merge 106 commits into
devfrom
jaap/prepare_tf_deprecation
Open

deruyter92 wants to merge 106 commits into
devfrom
jaap/prepare_tf_deprecation

Conversation

@deruyter92

Copy link
Copy Markdown
Collaborator

Preparation for full TensorFlow deprecation

  1. Remove deeplabcut.compat and split into PyTorch-only deeplabcut.api and TF-only deeplabcut.tensorflow_compat
  2. Add TensorFlow routing and deprecation utilities which warns the user.

Background

TensorFlow support will be dropped soon and a clean preparation is needed to make the transition smooth. Although the pose_estimation code was already cleanly branched in deeplabcut/compat.py, this brought the disadvantage that the main entrypoint for DeepLabCut is actually mixed API, which was not ideal to test and remains a small obstacle to eventually remove TF cleanly.

This PR makes a large step towards clean TF deprecation, by splitting the compat module into our new clean user-facing API (Pytorch-only) with a fallback for TensorFlow instead of mixed API. It introduces some new packages and removes deeplabcut.compat as the entry point for the public API.

New

deeplabcut.api : the new home for user-facing API functions. Currently covers all pose estimation functions (train_network, evaluate_network, analyze_videos, analyze_images, etc.), but can cover other main API as well.

deeplabcut.tensorflow_compat — an isolated compatibility layer that holds the TensorFlow-specific branches, split out of compat.py. This module is explicitly marked as temporary and will be deleted wholesale when TF support is dropped.

deeplabcut.api._tf_routing — a new decorator-based routing system (@with_tensorflow_fallback) that automatically intercepts calls to functions in deeplabcut.api, resolves the engine and delegates to deeplabcut.tensorflow_compat handling TF-only legacy kwargs. A DLCDeprecationWarning is emitted to inform user about the migration.

Next steps

This PR intentionally scopes to pose estimation only. (I included generate_training_dataset as well first, it's probably better to work in incremental changes, rather than one big PR).

Several other areas still go through compat or have TF branches inline and need similar treatment in follow-up PRs:

  • Dataset creation (generate_training_dataset) — the largest remaining concern.
  • deeplabcut.utils — scattered TF-conditional helpers.
  • deeplabcut.modelzoo — TF-specific download / inference paths.
  • Other smaller callsites in the GUI and 3D/tracking modules.

The goal is that every user-facing function ultimately lives under deeplabcut.api with a clean, PyTorch-only signature, while all TF code is quarantined in deeplabcut.tensorflow_compat and removable in a single future commit.

Test coverage

  • tests/api/test_pose_estimation.py (480 lines) — smoke tests for the new deeplabcut.api pose estimation surface: parameter forwarding, engine routing, legacy kwarg handling.
  • tests/api/test_tf_routing.py (360 lines) — unit tests for the _tf_routing machinery: engine resolution, renamed/dropped param warnings, gputouse normalisation, multi-shuffle validation.
  • tests/core/test_visualization.py (191 lines) — new coverage for deeplabcut.core.visualization helpers.
  • tests/pose_estimation_pytorch/apis/test_apis_utils.py (102 lines) — new coverage for PyTorch API utilities.

@deruyter92

Copy link
Copy Markdown
Collaborator Author

(Needs formatted docstrings and typed configs still)

@deruyter92
deruyter92 force-pushed the jaap/prepare_tf_deprecation branch from c4637d0 to 9b86fef Compare July 16, 2026 17:36
@deruyter92

deruyter92 commented Jul 16, 2026 •

Copy link
Copy Markdown
Collaborator Author
  • Rebased on main with new docstrings, pathlib internals etc.
  • Dissected TF from mixed functions and added to deeplabcut.api: modelzoo inference and dataset creation (create_pretrained_dataset), project creation ( SA and MA), post-processing, refinement
  • Made the machinery for TF routing a bit more robust: no silent assumption that config is first arg, allow custom predicate for routing (e.g. for specific TF models, instead of shuffle/config/engine keywords)
  • Added tests for deeplabcut.api

Will do a self-review tomorrow, and address failing tests. But otherwise nearly done.

@C-Achard
C-Achard self-requested a review July 17, 2026 08:33
@deruyter92
deruyter92 marked this pull request as ready for review July 17, 2026 11:07
Comment thread deeplabcut/api/_tf_routing.py Resolved
Comment thread deeplabcut/api/_tf_routing.py Resolved

@C-Achard C-Achard left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good start,I really like the design overall, I just think some of the current routing might need to be made safer; happy to discuss if needed!

Comment thread deeplabcut/api/_tf_routing.py Resolved
Comment thread deeplabcut/api/_tf_routing.py Resolved
# Default: engine-based routing (from shuffle / config)
route_to_tf = _resolve_engine(unified) == Engine.TF

kwargs.pop("engine", None)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this work on args as well?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes should also be popped on the unified args. fixed now.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But modifying unified does not change the args which are then passed to TF right? And TF should not get engine

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for staying alert. This was not fixed properly indeed for cases where engine is passed as positional arg. I've made it keyword-only now and added a validation step that runs at decoration time.

Note that making engine Keyword only changes the API! In fact I took the liberty to make more parameters keyword-only, since 12 positional args could get confusing easily. Let me know if you agree.

see 4fc2d18

Comment thread deeplabcut/api/_tf_routing.py Resolved
Comment thread deeplabcut/api/_tf_routing.py Resolved
Comment thread tests/api/test_tf_routing.py Resolved
Comment thread tests/api/test_tf_routing.py Resolved
Comment thread tests/api/test_tf_routing.py Resolved
Comment thread deeplabcut/api/pose_estimation.py Resolved
Comment thread tests/api/test_pose_estimation.py Resolved
@deruyter92

Copy link
Copy Markdown
Collaborator Author

@C-Achard thanks for your review. I've made the tf_routing more robust now. I've addressed all your comments, and added more tests.

I will do another iteration next week to see if there are more tests to be added, and try it out in practice a low. It is a high-impact PR, so I agree that it should be well-tested.

@deruyter92
deruyter92 marked this pull request as draft July 28, 2026 18:10
@deruyter92
deruyter92 requested a review from C-Achard August 10, 2026 09:24
@deruyter92
deruyter92 marked this pull request as ready for review August 10, 2026 09:24

@C-Achard C-Achard left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few preliminary comments while going through this again, let's perhaps discuss which areas would benefit from further review/testing as it has grown quite a bit.

Comment thread deeplabcut/core/config/utils.py Resolved
Comment thread deeplabcut/gui/tabs/manage_project.py Resolved
Comment thread deeplabcut/gui/tabs/analyze_videos.py Resolved
@deruyter92
deruyter92 changed the base branch from main to feat/refreshed-api August 12, 2026 14:59
@C-Achard
C-Achard self-requested a review August 13, 2026 13:50

@C-Achard C-Achard left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some more comments!

Let me know if you would like any further review on any other points.


if normalize_gputouse and "gputouse" in kwargs:
# Normalize parameter "gputouse" to torch device string and rename
kwargs["gputouse"] = _normalize_gputouse(kwargs["gputouse"])

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this handle None?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
kwargs["gputouse"] = _normalize_gputouse(kwargs["gputouse"])
if normalize_gputouse and "gputouse" in kwargs:
gpu = kwargs["gputouse"]
if gpu is None:
kwargs.pop("gputouse")
else:
kwargs["gputouse"] = _normalize_gputouse(gpu)
effective_renames["gputouse"] = "device"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch, None was missing from the normalization. I added it in _normalize_gputouse rather than dropping it, so users get feedback for incorrect usage of gputouse (+ device).

Comment thread deeplabcut/api/_tf_routing.py Resolved
Comment thread deeplabcut/api/_tf_routing.py Resolved
Comment thread deeplabcut/api/_tf_routing.py Resolved
Comment thread deeplabcut/__init__.py
"transformer_reID": (".pose_tracking_pytorch", "transformer_reID"),
}

# API exports are lazily loaded from the pose_estimation API facade.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will "break" the API docs in that the direct (still valid) imports like deeplabcut.analyze_videos will no longer be recognized.
I'm mentioning it, I doubt there is any easy clean fix for this, nor is it a big problem in the first place, but maybe worth mentioning.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Dev docs are cleaner with the new version imo. I also checked again that all imports are still supported)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok I agree that this needs to be improved. I implemented manual __getattr__ lazy loading following PEP 562, but that would have required some additions, like improving type checking to find modules before they are actually accessed as attribute.

After some more research, I think that lazy_loader is a good alternative (also used by napari, scikit-image) that keeps everything in one place (a .pyi stub) and works with type-checking. See #3459

@C-Achard C-Achard Aug 19, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More of a discussion point, but is it correct that
deeplabcut.modelzoo.video_inference
is now PT only, whereas

  • deeplabcut.api.modelzoo_inference.video_inference_superanimal
  • deeplabcut.video_inference_superanimal

have TF routing ? Maybe having a private torch only function and a single public TF wrapper would be cleaner ?
At the very least a direct import should warn if possible while keeping the current public call site, e.g.

# deeplabcut/modelzoo/video_inference.py

def _video_inference_superanimal_pytorch(...):
    ...

# deeplabcut/api/modelzoo_inference.py

@with_tensorflow_fallback(
    when=lambda params: params.get("model_name") in _TENSORFLOW_MODELS,
    ...
)
def video_inference_superanimal(...):
    from deeplabcut.modelzoo.video_inference import (
        _video_inference_superanimal_pytorch,
    )

    return _video_inference_superanimal_pytorch(...)
# deeplabcut/modelzoo/video_inference.py

def video_inference_superanimal(*args, **kwargs):
    warnings.warn(
        "Import video_inference_superanimal from deeplabcut "
        "or deeplabcut.api instead.",
        DLCDeprecationWarning,
        stacklevel=2,
    )

    from deeplabcut.api.modelzoo_inference import (
        video_inference_superanimal as public_api,
    )

    return public_api(*args, **kwargs)

Also is dlcrnet the only TF model ?
Since the old implementation selected the backend through models_to_framework.json, while the new router appears to select TensorFlow only when model_name == "dlcrnet".

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, and I think in a broader sense we should streamline this for all functions. Currently pose_estimation_pytorch is a clear exported module on its own, which is re-exported in deeplabcut.api + at the top-level now (including the tf-routing decorator). Docstrings are largely duplicated.

I think after the full stack is merged we should revisit this and see if we need to restructure the exports / module locations a bit. "

For now I've addressed your comment directly in 5cae12f, but keeping this comment open for later discussion.

# ---------------------------------------------------------------------------
# _resolve_legacy_kwargs
# ---------------------------------------------------------------------------

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to be sure, this tests _resolve_legacy_kwargs, which is torch only right? Could we add tests that route to TF while both a legacy and current name are provided?



# ---------------------------------------------------------------------------
# TensorFlow-engine dispatch tests

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add one or two integration tests using mocked read_config and get_shuffle_engine, for example analyze_videos and export_model(cfg_path=...)?
To catch issues with real API funcs.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good suggestion. See 58bda12

Comment thread tests/api/test_tf_routing.py Resolved

@C-Achard C-Achard Aug 19, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add where relevant:

import deeplabcut.api

@pytest.mark.parametrize("name", deeplabcut.api.__all__)
def test_migrated_export_available_from_public_facades(name):
    import deeplabcut
    import deeplabcut.api

    assert name in deeplabcut.__all__
    assert name in deeplabcut.api.__all__
    assert getattr(deeplabcut, name) is getattr(deeplabcut.api, name)

@deruyter92

Copy link
Copy Markdown
Collaborator Author

merged #3442 and #3444 into this base branch

@deruyter92 deruyter92 added the 3.1 label Aug 28, 2026
@deruyter92
deruyter92 marked this pull request as draft August 28, 2026 15:39
@C-Achard

Copy link
Copy Markdown
Member

@deruyter92 While testing the GUI with TF, I had a small issue when switching backends that cause the Create training dataset menu to not switch properly to the TF models list despite confirming. Changing again seemed to solve it.
I haven't had time to find the exact source, let me know if you'd like a more thorough report

deruyter92 and others added 13 commits September 1, 2026 17:09
…abCut_UserGuide.md`

These files are now fully reworked in `user_guide.md`
add test keeping version literals out of deprecation markers
Walks the shipped package and fails on any since= or removed_in= literal, so
DeprecatedSince stays the single source of truth. deprecation.py is exempt:
its docstrings illustrate the API with literal versions.
Also checks that every constant parses as a version.
Every @deprecated and @renamed_parameter marker spelled its version inline, so
a round of deprecations could only be retargeted by grepping for version
strings across the codebase. That is how the 3.1 -> 3.0.1 sweep in #3422 missed
analyze_time_lapse_frames, and it makes it easy to move an unrelated API while
retargeting one round.
DeprecatedSince now holds one constant per round, named after the migration
rather than its version so a retargeted round keeps its name, and annotated
with the PR that introduced it. All 67 markers reference it.
Fix incorrect deprecation description PRETRAINED_PROJECT_MIGRATION, which listed the wrong version for `since`. Merge into more descriptive name, relating to the TF deprecation (which is the main motivation).
…SING

A more descriptive name was needed to avoid ambiguity with next follow-up parameter consistency migrations. This refers to the initial PR that introduced the aliasing system and DeprecationWarnings
- drop the read_config patches from the router tests
- assert the config is forwarded to get_shuffle_engine untouched
- Parametrize the forwarding test over str, Path, dict and ProjectConfig
- fix renamed_parameter(since=...) -> deprecation_round=..
@deruyter92
deruyter92 force-pushed the jaap/prepare_tf_deprecation branch from 0aded65 to a175d75 Compare September 1, 2026 15:15
@C-Achard C-Achard mentioned this pull request Sep 3, 2026
5 tasks done
@deruyter92
deruyter92 changed the base branch from feat/refreshed-api to dev September 4, 2026 06:41
@deruyter92
deruyter92 marked this pull request as draft September 4, 2026 06:41
@deruyter92
deruyter92 marked this pull request as ready for review September 4, 2026 06:42
deruyter92 and others added 4 commits September 4, 2026 11:23
…kers

update deprecation marker versions (TF deprecation since 3.1, removed in 3.2)
test routing only and patch the deprecation warning, it is tested elsewhere anyway

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants