Skip to content

Guard CoW read-only .to_numpy()/.values mutations under pandas 3 - #3445

Open
AxelNoun wants to merge 9 commits into
DeepLabCut:mainfrom
AxelNoun:fix/pandas3-cow-guards
Open

Guard CoW read-only .to_numpy()/.values mutations under pandas 3#3445
AxelNoun wants to merge 9 commits into
DeepLabCut:mainfrom
AxelNoun:fix/pandas3-cow-guards

Conversation

@AxelNoun

Copy link
Copy Markdown

Recreates #3416, which was closed automatically on 12 Aug when the fork
hosting its branch was detached during an account cleanup on my side —
my mistake, apologies for the noise.

The head commit is byte-identical to the original PR (a3977ff926), so it
still includes your own commits on that branch, @deruyter92 — nothing was
rebased or dropped. The original review thread remains readable at
#3416, where the PR was approved.


Guard in-place mutations of .to_numpy() / .values results (Copy-on-Write, pandas 3.0)

Follow-up to #3360. Related to the tracking issue #3362.

Summary

Under pandas 3.0's Copy-on-Write, .to_numpy() and .values return a
read-only array for single-dtype selections; mutating that array in place
raises ValueError: assignment destination is read-only. This PR guards the five
sites where such an array is mutated, using to_numpy(copy=True).

The pandas<3 upper bound is intentionally left unchanged — this is a
forward-compatibility fix, not the pin removal discussed in #3362. It complements
the preparatory work already merged in #3360 and is safe on the currently pinned
pandas 2.x.

The pattern

arr = df.to_numpy()      # or df.values -> read-only view under CoW (single dtype)
arr[mask] = value        # ValueError: assignment destination is read-only

copy=True returns a writable array and is behavior-preserving on pandas 2.x, so
the change is correct under both major versions.

Changes

to_numpy(copy=True) at the sites that mutate the extracted array in place:

  • pose_estimation_pytorch/data/ctd.py
  • pose_estimation_3d/triangulation.py
  • pose_estimation_3d/plotting3D.py
  • refine_training_dataset/tracklets.py
  • post_processing/filtering.py — spline-filter branch (same interpolation
    pattern as tracklets.py; only the tracklets copy is covered by the current
    tests, so this one was located by a static scan rather than a failing test)

Considered and left unchanged: pose_estimation_tensorflow/core/evaluate_multianimal.py
extracts a mixed-dtype selection (["sample", "y", "x", "bodyparts"]), which
yields a writable object array and is therefore not affected.

Testing

  • Locally against pandas 3.0.3 + PyTables 3.11.1: the CTD-HDF tests pass after
    the fix, and the spline-filter path is smoke-tested (writable arrays, correct
    gap-filling).
  • CI continues to run on pandas 2.x (unchanged pin), where these changes are
    behavior-preserving.

Notes for #3362

  • This is a class of pandas-3 break not covered by Prepare migration to pandas 3.0 #3360 — it only surfaces at
    runtime under pandas 3.0, so it isn't caught by the 2.3 future-mode tooling.
  • While validating against released pandas 3.0.3, the HDF concern described in
    Migration to Pandas 3.0 #3362 ("PyTables cannot serialize a MultiIndex whose levels use extension
    dtypes -> change all dataframes back to object before saving"
    ) does not
    reproduce: pandas 3.0 explicitly whitelists StringDtype in that guard
    (io/pytables.py, write_multi_index:
    isinstance(lev.dtype, ExtensionDtype) and not isinstance(lev.dtype, StringDtype)),
    and DeepLabCut's string index/column levels serialize fine (verified for the
    df_with_missing / tracks / predictions / ass keys). So no to_hdf
    object-conversion wrapper appears necessary on the released 3.0. (The pandas 2.3
    future.infer_string mode does raise there, since the exemption only lands in
    3.0 — so that mode shouldn't be used to validate HDF writes.)

AxelNoun and others added 9 commits July 19, 2026 19:04
Under pandas 3.0 Copy-on-Write, .to_numpy()/.values return read-only
arrays for single-dtype selections; in-place mutation raises
ValueError. Use copy=True at the five affected sites. Pin unchanged.
Follow-up to the pandas 3.0 CoW sweep (DeepLabCut#3362). Three more sites extract a
NumPy array from a pandas object and mutate it in place; under pandas 3 CoW
these can be read-only views, raising "assignment destination is read-only".

- refine_training_dataset/tracklets.py: self.data backs self.xy/self.prob,
  which swap_tracklets and the refine GUI mutate in place (HDF load path;
  flagged in review).
- utils/make_labeled_video.py: coords is a view into xyp, masked in place in
  both the first-frame and per-frame branches.
- pose_estimation_tensorflow/.../pose_multianimal_imgaug.py: a single-row
  float Series is read-only; kpts is masked in place when
  mask_kpts_below_thresh is set.

Co-authored-by: Cursor <cursoragent@cursor.com>
Guards against the in-place write on the next line producing a
read-only view when the intermediate frame becomes single-block.
@C-Achard C-Achard added the dependencies Pull requests that update a dependency file label Aug 13, 2026
@C-Achard
C-Achard self-requested a review August 13, 2026 08:09

@C-Achard C-Achard left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@deruyter92 As mentioned, PR is identical to previously, so I'm re-approving for convenience

@deruyter92 deruyter92 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@AxelNoun thanks for re-opening. Again, great PR, thanks for your contribution!

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

Labels

dependencies Pull requests that update a dependency file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants