diff --git a/musicalgestures/_pose.py b/musicalgestures/_pose.py index a7c9d8b8..d5c0bd4e 100644 --- a/musicalgestures/_pose.py +++ b/musicalgestures/_pose.py @@ -486,7 +486,7 @@ def pose( avg_acc = np.zeros((self.height, self.width, 3), dtype=np.float64) if save_average_pose else None avg_n = 0 from collections import deque - _trail = deque(maxlen=int(marker_history)) if marker_history and marker_history > 0 else None + _trail: deque | None = deque(maxlen=int(marker_history)) if marker_history and marker_history > 0 else None while True: # Read frame-by-frame @@ -509,7 +509,7 @@ def pose( H = output.shape[2] W = output.shape[3] - points = [] + points: list[tuple[int, int] | None] = [] for i in range(nPoints): @@ -824,7 +824,7 @@ def _rerender_pose_from_cache(self: "musicalgestures.MgVideo", style='both', ove video_out = None frame_bytes = width * height * 3 from collections import deque - _trail = deque(maxlen=int(marker_history)) if marker_history and marker_history > 0 else None + _trail: deque | None = deque(maxlen=int(marker_history)) if marker_history and marker_history > 0 else None pb = MgProgressbar(total=len(data), prefix='Re-rendering pose (cached):') for ii, row in enumerate(data): @@ -920,7 +920,7 @@ def _ensure_pose_keypoints(self: "musicalgestures.MgVideo", **pose_kwargs): def mg_pose_waterfall(self: "musicalgestures.MgVideo", style: str = 'trajectories', n_samples: int = 40, markers: list | None = None, color_by: str | None = None, cmap: str = 'hsv', dpi: int = 200, elev: float = 20, azim: float = -60, lw: float = 1.0, axes: bool = True, crop: bool = False, - target_name: str | None = None, overwrite: bool = True, **pose_kwargs) -> "MgFigure": + target_name: str | None = None, overwrite: bool = True, **pose_kwargs) -> "MgFigure | None": """ Render a 3D spatio-temporal waterfall of the pose, cascading along the time (depth) axis — a pose-based counterpart to ``silhouette_waterfall()``. Uses cached pose keypoints from a @@ -952,7 +952,7 @@ def mg_pose_waterfall(self: "musicalgestures.MgVideo", style: str = 'trajectorie **pose_kwargs: Forwarded to ``pose()`` if keypoints have to be computed. Returns: - MgFigure: the 3D waterfall figure, or None if there are too few frames. + MgFigure | None: the 3D waterfall figure, or None if there are too few frames. """ from musicalgestures._pose_visualize import render_pose_waterfall @@ -974,7 +974,7 @@ def mg_pose_waterfall(self: "musicalgestures.MgVideo", style: str = 'trajectorie def mg_pose_segments(self: "musicalgestures.MgVideo", segments: list | None = None, n_bins: int = 36, cmap: str = 'viridis', dpi: int = 200, ncols: int = 6, - target_name: str | None = None, overwrite: bool = True, **pose_kwargs) -> "MgFigure": + target_name: str | None = None, overwrite: bool = True, **pose_kwargs) -> "MgFigure | None": """ Circular (polar) motion plots and statistics for each body segment. @@ -998,7 +998,7 @@ def mg_pose_segments(self: "musicalgestures.MgVideo", segments: list | None = No **pose_kwargs: Forwarded to ``pose()`` if keypoints have to be computed. Returns: - MgFigure: the grid of circular plots (per-segment stats in ``.data['stats']``), or None. + MgFigure | None: the grid of circular plots (per-segment stats in ``.data['stats']``), or None. """ from musicalgestures._pose_visualize import render_segment_circular @@ -1018,7 +1018,7 @@ def mg_pose_segments(self: "musicalgestures.MgVideo", segments: list | None = No return mgf -def mg_pose_center(self: "musicalgestures.MgVideo", save_data: bool = True, dpi: int = 200, target_name: str | None = None, overwrite: bool = True, **pose_kwargs) -> "MgFigure": +def mg_pose_center(self: "musicalgestures.MgVideo", save_data: bool = True, dpi: int = 200, target_name: str | None = None, overwrite: bool = True, **pose_kwargs) -> "MgFigure | None": """ Centre the pose data on its global centroid — a 2D port of the MoCap Toolbox ``mccenter``. @@ -1037,7 +1037,8 @@ def mg_pose_center(self: "musicalgestures.MgVideo", save_data: bool = True, dpi: **pose_kwargs: Forwarded to ``pose()`` if keypoints have to be computed. Returns: - MgFigure: the centred-trajectories figure; ``.data['coords']`` holds the (T, n, 2) centred + MgFigure | None: the centred-trajectories figure; ``.data['coords']`` holds the (T, n, 2) centred + None when the figure could not be built, for instance with too few frames. coordinates and ``.data['offset']`` the removed centroid. None if there are too few frames. """ from musicalgestures._pose_visualize import render_pose_center @@ -1068,7 +1069,7 @@ def mg_pose_center(self: "musicalgestures.MgVideo", save_data: bool = True, dpi: return mgf -def mg_pose_distance(self: "musicalgestures.MgVideo", dpi: int = 200, target_name: str | None = None, overwrite: bool = True, **pose_kwargs) -> "MgFigure": +def mg_pose_distance(self: "musicalgestures.MgVideo", dpi: int = 200, target_name: str | None = None, overwrite: bool = True, **pose_kwargs) -> "MgFigure | None": """ Per-marker distance travelled and the average across markers — a 2D port of the MoCap Toolbox ``mccumdist``. @@ -1086,7 +1087,8 @@ def mg_pose_distance(self: "musicalgestures.MgVideo", dpi: int = 200, target_nam **pose_kwargs: Forwarded to ``pose()`` if keypoints have to be computed. Returns: - MgFigure: ``.data['total']`` (per-marker totals), ``.data['average']`` (mean total), and + MgFigure | None: ``.data['total']`` (per-marker totals), ``.data['average']`` (mean total), and + None when the figure could not be built, for instance with too few frames. ``.data['cumulative']`` (per-marker cumulative curves). None if there are too few frames. """ from musicalgestures._pose_visualize import render_pose_distance @@ -1184,7 +1186,7 @@ def _pose_mediapipe( avg_acc = np.zeros((self.height, self.width, 3), dtype=np.float64) if save_average_pose else None avg_n = 0 from collections import deque - _trail = deque(maxlen=int(marker_history)) if marker_history and marker_history > 0 else None + _trail: deque | None = deque(maxlen=int(marker_history)) if marker_history and marker_history > 0 else None estimator = MediaPipePoseEstimator(device=device.lower()) diff --git a/musicalgestures/_pose_visualize.py b/musicalgestures/_pose_visualize.py index 9dd2919d..6163d225 100644 --- a/musicalgestures/_pose_visualize.py +++ b/musicalgestures/_pose_visualize.py @@ -3,7 +3,7 @@ import cv2 import matplotlib import matplotlib.pyplot as plt -from musicalgestures._utils import MgImage, generate_outfilename +from musicalgestures._utils import MgFigure, MgImage, generate_outfilename def _layout_labels(anchors, box_w, box_h, width, height, iterations=400, gap=None): @@ -90,7 +90,7 @@ def _per_marker_stats(coords, fps, fmin=0.2, fmax=8.0): def render_average_pose(data, names, connections, width, height, fps, avg_frame, - target_name, overwrite=True, fmin=0.2, fmax=8.0, style='both'): + target_name, overwrite=True, fmin=0.2, fmax=8.0, style='both') -> "MgImage | None": """ Render the average pose of the whole video, with per-marker quantity of motion (colour + label) and dominant frequency (label) annotated. @@ -191,7 +191,7 @@ def render_average_pose(data, names, connections, width, height, fps, avg_frame, def render_trajectories(data, names, width, height, fps, target_name, overwrite=True, - background='black', labels=False): + background='black', labels=False) -> "MgImage | None": """ Render every marker's spatial trajectory across the whole video. @@ -258,7 +258,7 @@ def render_trajectories(data, names, width, height, fps, target_name, overwrite= def render_pose_waterfall(data, names, width, height, fps, target_name, overwrite=True, style='trajectories', connections=None, n_samples=40, markers=None, color_by=None, cmap='hsv', dpi=200, - elev=20, azim=-60, lw=1.0, axes=True, crop=False): + elev=20, azim=-60, lw=1.0, axes=True, crop=False) -> "MgFigure | None": """ Render a 3D spatio-temporal waterfall of the pose, cascading along the time (depth) axis — a pose-based counterpart to ``silhouette_waterfall()``. @@ -450,7 +450,7 @@ def _segment_angles(coords, a, b): def render_segment_circular(data, names, connections, width, height, fps, target_name, overwrite=True, segments=None, n_bins=36, cmap='viridis', - dpi=200, ncols=6): + dpi=200, ncols=6) -> "MgFigure | None": """ Circular (polar) motion plots and statistics for every body segment. @@ -591,7 +591,7 @@ def pose_center(data, names): def render_pose_center(data, names, width, height, target_name, overwrite=True, - cmap='hsv', dpi=200): + cmap='hsv', dpi=200) -> "MgFigure | None": """ Centre the pose data (see :func:`pose_center`) and plot the centred marker trajectories. @@ -667,7 +667,7 @@ def pose_distance(data, names, width, height): def render_pose_distance(data, names, width, height, fps, target_name, overwrite=True, - cmap='hsv', dpi=200): + cmap='hsv', dpi=200) -> "MgFigure | None": """ Plot per-marker cumulative distance travelled over time plus a ranked total per marker.