Skip to content

Commit 8d2fb49

Browse files
alexarjeclaude
andcommitted
pose(): quiet MediaPipe native logs; route GPU-less fallback to MediaPipe CPU
Addresses noisy/erroring MediaPipe runs: - The no-CUDA auto-fallback now uses the MediaPipe CPU (XNNPACK) delegate, which is fast and reliable, instead of the fragile OpenGL-ES GPU delegate (which on Linux targets the integrated GPU, not an NVIDIA card, and can emit "Tensors are designed for single writes" errors). - New quiet=True (default) suppresses MediaPipe's native C++/GL stderr logs (EGL init, absl INFO/WARNING) via fd-level redirection; quiet=False to debug. - Explicit device='gpu' for MediaPipe still uses the GPU delegate but now prints a one-line caveat recommending device='cpu' if glitches occur, and sets GLOG_minloglevel to reduce log spam. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent bd1c252 commit 8d2fb49

2 files changed

Lines changed: 57 additions & 6 deletions

File tree

musicalgestures/_pose.py

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,35 @@
4242
}
4343

4444

45+
import contextlib
46+
47+
48+
@contextlib.contextmanager
49+
def _suppress_native_stderr(active=True):
50+
"""
51+
Temporarily redirect the process's stderr (fd 2) to /dev/null.
52+
53+
MediaPipe logs from its C++/GL layer (EGL init, absl INFO/WARNING) go straight to
54+
the stderr file descriptor and can't be silenced from Python logging. This redirects
55+
fd 2 around the MediaPipe run so those messages don't clutter notebooks. No-op when
56+
``active`` is False (use ``quiet=False`` to see the native logs for debugging).
57+
"""
58+
if not active:
59+
yield
60+
return
61+
import sys
62+
sys.stderr.flush()
63+
devnull = os.open(os.devnull, os.O_WRONLY)
64+
saved = os.dup(2)
65+
try:
66+
os.dup2(devnull, 2)
67+
yield
68+
finally:
69+
os.dup2(saved, 2)
70+
os.close(devnull)
71+
os.close(saved)
72+
73+
4574
def _pose_canvas_and_colors(frame, overlay, background):
4675
"""Return (canvas, line_color, marker_color) in BGR for drawing the pose."""
4776
if overlay:
@@ -64,6 +93,7 @@ def pose(
6493
overlay=True,
6594
background='black',
6695
convert=True,
96+
quiet=True,
6797
save_average_pose=True,
6898
save_trajectories=True,
6999
transparent_trajectories=None,
@@ -119,6 +149,9 @@ def pose(
119149
convert (bool, optional): If True (default), non-AVI input is first converted to an all-intra
120150
MJPEG `.avi` (cached as ``self.as_avi``) for frame-accurate decoding. Set to False to read the
121151
source file directly — faster and no extra file, suitable when your mp4 decodes reliably.
152+
quiet (bool, optional): MediaPipe only. If True (default), suppress MediaPipe's native C++/GL
153+
console logs (EGL init, absl INFO/WARNING, GPU-delegate messages) during inference. Set to
154+
False to see them for debugging.
122155
target_name_video (str, optional): Target output name for the video. Defaults to None (which
123156
assumes that the input filename with the suffix "_pose" should be used).
124157
save_average_pose (bool, optional): Whether to also render an image of the average pose over
@@ -155,23 +188,28 @@ def pose(
155188

156189
# --- MediaPipe backend ---------------------------------------------------
157190
# Explicit MediaPipe request, or auto-preference: when GPU is requested for an
158-
# OpenPose model but OpenCV has no CUDA backend, prefer MediaPipe (which can use
159-
# its own GPU delegate) instead of silently dropping to CPU OpenPose.
191+
# OpenPose model but OpenCV has no CUDA backend, fall back to the (fast, reliable)
192+
# MediaPipe pose backend instead of CPU OpenPose. We use the CPU delegate here:
193+
# MediaPipe's GPU delegate is the OpenGL-ES path (the integrated GPU on Linux, not
194+
# an NVIDIA card) and is fragile, so it is reserved for explicit model='mediapipe',
195+
# device='gpu' requests.
160196
use_mediapipe = model.lower() == 'mediapipe'
197+
mediapipe_device = device
161198
if not use_mediapipe and device.lower() == 'gpu' and not in_colab() and get_cuda_device_count() <= 0:
162199
if _mediapipe_available():
163200
print(
164201
f"GPU requested but OpenCV has no CUDA backend; switching from '{model}' to the "
165-
"MediaPipe pose backend for GPU acceleration (33 landmarks).\n "
202+
"MediaPipe pose backend (CPU/XNNPACK — fast and reliable).\n "
166203
+ cuda_unavailable_reason()
167204
)
168205
use_mediapipe = True
206+
mediapipe_device = 'cpu'
169207
# else: fall through to the OpenPose path, which will warn and use CPU.
170208

171209
if use_mediapipe:
172210
return _pose_mediapipe(
173211
self,
174-
device=device,
212+
device=mediapipe_device,
175213
threshold=threshold,
176214
save_data=save_data,
177215
data_format=data_format,
@@ -180,6 +218,7 @@ def pose(
180218
overlay=overlay,
181219
background=background,
182220
convert=convert,
221+
quiet=quiet,
183222
save_average_pose=save_average_pose,
184223
save_trajectories=save_trajectories,
185224
transparent_trajectories=transparent_trajectories,
@@ -581,6 +620,7 @@ def _pose_mediapipe(
581620
overlay=True,
582621
background='black',
583622
convert=True,
623+
quiet=True,
584624
save_average_pose=True,
585625
save_trajectories=True,
586626
transparent_trajectories=None,
@@ -651,7 +691,8 @@ def _pose_mediapipe(
651691
avg_acc += frame
652692
avg_n += 1
653693

654-
result = estimator.predict_frame(frame)
694+
with _suppress_native_stderr(quiet):
695+
result = estimator.predict_frame(frame)
655696
keypoints = result.keypoints # shape (33, 3): x, y, visibility
656697

657698
# Collect data row: time + normalised (x, y) for every landmark.
@@ -703,7 +744,8 @@ def _pose_mediapipe(
703744
pb.progress(ii)
704745
ii += 1
705746

706-
estimator.close()
747+
with _suppress_native_stderr(quiet):
748+
estimator.close()
707749

708750
if save_video:
709751
video_out.stdin.close()

musicalgestures/_pose_estimator.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import abc
2727
import logging
28+
import os
2829
from pathlib import Path
2930
from typing import Any
3031

@@ -294,6 +295,8 @@ def _get_model_path(self) -> Path:
294295
def _ensure_initialized(self) -> None:
295296
if self._landmarker is not None:
296297
return
298+
# Quieten MediaPipe/absl/glog INFO+WARNING spam (must be set before import).
299+
os.environ.setdefault("GLOG_minloglevel", "2")
297300
try:
298301
import mediapipe as mp
299302
except ImportError as exc:
@@ -322,6 +325,12 @@ def _make_landmarker(delegate):
322325
if want_gpu:
323326
try:
324327
self._landmarker = _make_landmarker(BaseOptions.Delegate.GPU)
328+
print(
329+
"Note: MediaPipe's GPU delegate uses OpenGL-ES (the integrated GPU on Linux, "
330+
"not a CUDA/NVIDIA card) and can emit synchronization warnings or glitch on some "
331+
"drivers. If you see 'Tensors are designed for single writes' errors or odd output, "
332+
"use device='cpu' (fast and reliable)."
333+
)
325334
logger.debug("MediaPipe PoseLandmarker initialised on GPU (complexity=%d)", self.model_complexity)
326335
return
327336
except Exception as exc:

0 commit comments

Comments
 (0)