Spaces:
Running on Zero
Running on Zero
Download multics/grouping/labels.py from hugging-apps/multics-multiplayer-video: direct link, hf CLI and curl.
- Browser
- Download file 6.7 kB
-
https://huggingface.co/spaces/hugging-apps/multics-multiplayer-video/resolve/main/multics/grouping/labels.py
- Command line
-
hf download hf://spaces/hugging-apps/multics-multiplayer-video/multics/grouping/labels.py
-
curl -L -o labels.py https://huggingface.co/spaces/hugging-apps/multics-multiplayer-video/resolve/main/multics/grouping/labels.py
6.7 kB
| """Stable group slots and the compatible 24-tensor label schema.""" | |
| from __future__ import annotations | |
| import numpy as np | |
| from .partition import cohesion, flexible_partition | |
| from .relevance import relevance_periods, threshold_graph | |
| from .budget import resolve | |
| from .state import eligible_players | |
| LATENT_STRIDE = 4 | |
| def latent_frame_count(num_frames: int, stride: int = LATENT_STRIDE) -> int: | |
| return (num_frames - 1) // stride + 1 | |
| def track_labels(seq: list[list[list[int]]], n_slots: int) -> list[list[int]]: | |
| """Match slots by intersection, Jaccard, then stable group/slot indices.""" | |
| out: list[list[int]] = [] | |
| prev: list[tuple[int, set[int]]] = [] # 上一周期的 (编号, 成员集合) | |
| for groups in seq: | |
| sets = [set(g) for g in groups] | |
| cand = [] | |
| for gi, s in enumerate(sets): | |
| for ci, ps in prev: | |
| inter = len(s & ps) | |
| if inter: # 交集大的先配;gi/ci 兜底定序,保证可复现 | |
| cand.append((-inter, -inter / len(s | ps), gi, ci)) | |
| cand.sort() | |
| lab = [-1] * len(groups) | |
| taken: set[int] = set() | |
| for _, _, gi, ci in cand: | |
| if lab[gi] < 0 and ci not in taken: | |
| lab[gi] = ci | |
| taken.add(ci) | |
| free = [c for c in range(n_slots) if c not in taken] | |
| for gi in range(len(groups)): | |
| if lab[gi] < 0: | |
| lab[gi] = free.pop(0) if free else gi % n_slots | |
| taken.add(lab[gi]) | |
| out.append(lab) | |
| prev = [(lab[gi], sets[gi]) for gi in range(len(groups))] | |
| return out | |
| def flex_label_arrays( | |
| groupings, | |
| periods, | |
| keep: np.ndarray, | |
| *, | |
| n_players: int, | |
| max_groups: int, | |
| start_frame: int, | |
| num_frames: int, | |
| threshold: float, | |
| budget, | |
| stride: int = LATENT_STRIDE, | |
| ) -> dict[str, np.ndarray]: | |
| """把逐周期的 `FlexGrouping` 摊成定长 tensor 字典。 | |
| 列序 = CSV 的 `video_path1..P`(world 按 `row.slots` 加载时,world 下标 == 列下标)。 | |
| 组号经 `track_labels` 跨周期跟踪,槽位落在 `0 .. G-1`(G = max_groups)。 | |
| """ | |
| P, G, K = n_players, max_groups, len(groupings) | |
| if K == 0: | |
| raise ValueError("没有更新周期,写不出标签") | |
| labs = track_labels([g.groups for g in groupings], n_slots=max(G, 1)) | |
| p_start = np.zeros(K, np.int32) | |
| p_frames = np.zeros(K, np.int32) | |
| p_mem = np.zeros((K, G, P), bool) | |
| p_go = np.zeros((K, P), bool) | |
| p_anchor = np.zeros((K, P), bool) | |
| p_score = np.full((K, G), -1.0, np.float32) | |
| p_size = np.zeros((K, G), np.int8) | |
| p_coh = np.full((K, G), -1.0, np.float32) | |
| p_pair = np.zeros(K, np.int32) | |
| p_cost = np.full(K, -1.0, np.float32) | |
| eligible = np.zeros(P, bool) | |
| eligible[np.asarray(keep, int)] = True | |
| for k, (gr, per, sid) in enumerate(zip(groupings, periods, labs)): | |
| p_start[k] = int(per.start_frame - start_frame) | |
| p_frames[k] = int(per.num_frames) | |
| W = threshold_graph(per.smooth, threshold) | |
| for gi, g in enumerate(gr.groups): | |
| s = int(sid[gi]) % G | |
| p_size[k, s] = len(g) | |
| p_score[k, s] = gr.scores[gi] if gi < len(gr.scores) else 0.0 | |
| p_coh[k, s] = cohesion(W, g) if len(g) > 1 else 0.0 | |
| for j in g: | |
| p_mem[k, s, int(keep[j])] = True | |
| for j in gr.global_only: | |
| p_go[k, int(keep[j])] = True | |
| for j in gr.shared: | |
| p_anchor[k, int(keep[j])] = True | |
| p_pair[k] = gr.pair_used | |
| # gr.groups 的下标是 keep 内部下标;未进 keep 的 player 在实际 | |
| # membership/global_only 里两者都是 False,不能误算成 global-only。 | |
| p_cost[k] = budget.topology_cost(gr.groups, n_players=len(keep)) | |
| has = p_mem.any(axis=1) | |
| p_lab = np.where(has, p_mem.argmax(axis=1).astype(np.int8), np.int8(-1)).astype( | |
| np.int8 | |
| ) | |
| raw = np.minimum( | |
| np.arange(latent_frame_count(num_frames, stride)) * stride, | |
| max(num_frames - 1, 0), | |
| ) | |
| idx = np.clip(np.searchsorted(p_start, raw, side="right") - 1, 0, K - 1) | |
| n_local = has.sum(axis=1).astype(np.float32) | |
| n_go = p_go.sum(axis=1).astype(np.float32) | |
| n_anchor = p_anchor.sum(axis=1).astype(np.float32) | |
| denom = np.float32(max(P, 1)) | |
| membership_latent = p_mem[idx] | |
| global_only_latent = p_go[idx] | |
| clip_cost = budget.topology_cost_timeline(membership_latent, global_only_latent) | |
| if clip_cost > budget.cost_limit: | |
| raise RuntimeError( | |
| f"整段 dynamic membership 通信超预算:{clip_cost:,.0f} > " | |
| f"{budget.cost_limit:,.0f}" | |
| ) | |
| return { | |
| "membership": membership_latent, | |
| "global_only": global_only_latent, | |
| "eligible": eligible, | |
| "labels": p_lab[idx], | |
| "latent_period": idx.astype(np.int16), | |
| "period_membership": p_mem, | |
| "period_global_only": p_go, | |
| "period_anchor": p_anchor, | |
| "period_labels": p_lab, | |
| "period_start": p_start, | |
| "period_frames": p_frames, | |
| "period_scores": p_score, | |
| "period_sizes": p_size, | |
| "period_cohesion": p_coh, | |
| "period_pair_used": p_pair, | |
| "period_total_cost": p_cost, | |
| "clip_total_cost": np.array(clip_cost, dtype=np.float64), | |
| "cost_limit": np.array(budget.cost_limit, dtype=np.float64), | |
| "coverage": n_local / denom, | |
| "orphan_ratio": n_go / denom, | |
| "anchor_ratio": n_anchor / denom, | |
| "pair_budget": np.array(budget.pair_budget, dtype=np.int32), | |
| "max_groups": np.array(G, dtype=np.int32), | |
| "max_size": np.array(budget.max_size, dtype=np.int32), | |
| } | |
| def generate_labels(state): | |
| """Generate pose-only labels without model state or random-number draws.""" | |
| state.validate() | |
| if state.clip_num_frames != 165: | |
| raise ValueError( | |
| "Automatic grouping currently requires 165 RGB frames per CSV row" | |
| ) | |
| budget = resolve(state.num_players) | |
| span = state.frame_window(state.clip_start_frame, state.clip_num_frames) | |
| keep = np.flatnonzero(eligible_players(state, span)) | |
| if len(keep) < 2: | |
| raise ValueError("Fewer than two eligible players in grouping clip") | |
| periods = relevance_periods(state, span, state.clip_start_frame, keep) | |
| groups = [flexible_partition(p.smooth, budget=budget) for p in periods] | |
| return flex_label_arrays( | |
| groups, | |
| periods, | |
| keep, | |
| n_players=state.num_players, | |
| max_groups=budget.groups, | |
| start_frame=state.clip_start_frame, | |
| num_frames=state.clip_num_frames, | |
| threshold=0.3, | |
| budget=budget, | |
| ) | |