Diffusers
Safetensors
File size: 12,721 Bytes
74da989
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
from typing import List
import numpy as np
import torch
import math
from einops import rearrange
from scipy.spatial.transform import Rotation as R_scipy
from hyvideo.utils.retrieval_context import calculate_fov_overlap_similarity

from typing import List, Dict, Tuple
from distributed.parallel_state import get_sp_parallel_rank, get_sp_world_size


def shard_latents_dim_across_sp(latents: torch.Tensor, total_dim) -> torch.Tensor:
    sp_world_size = get_sp_world_size()
    rank_in_sp_group = get_sp_parallel_rank()
    # latents = latents[:, :, :num_latent_t]
    if sp_world_size > 1:
        if total_dim == 5:
            latents = rearrange(
                latents, "b c (n s) h w -> b c n s h w", n=sp_world_size
            ).contiguous()
            latents = latents[:, :, rank_in_sp_group, :, :, :]
        elif total_dim == 4:
            latents = rearrange(
                latents, "b (n s) h w -> b n s h w", n=sp_world_size
            ).contiguous()
            latents = latents[:, rank_in_sp_group, :, :, :]
        elif total_dim == 3:
            latents = rearrange(
                latents, "b (n s) c -> b n s c", n=sp_world_size
            ).contiguous()
            latents = latents[:, rank_in_sp_group, :, :]
        elif total_dim == 2:
            latents = rearrange(
                latents, "b (n s)-> b n s", n=sp_world_size
            ).contiguous()
            latents = latents[:, rank_in_sp_group, :]
        elif total_dim == 1:
            latents = rearrange(latents, "(n s)-> n s", n=sp_world_size).contiguous()
            latents = latents[rank_in_sp_group, :]
        else:
            raise NotImplementedError(f"total_dim {total_dim} not supported")
    return latents


def select_mem_frames_wan(
    w2c_list: List[np.ndarray],
    current_frame_idx: int,
    memory_frames: int,
    temporal_context_size: int,
    pred_latent_size: int,
    pos_weight: float = 1.0,
    ang_weight: float = 1.0,
    device=None,
    points_local=None,
) -> List[int]:
    """
    为给定帧选择记忆帧和上下文帧,基于复杂的四帧片段距离计算。

    参数:
        w2c_list (List[np.ndarray]): 包含所有N个4x4外参矩阵的列表。
        current_frame_idx (int): 当前要处理的帧的索引。
        memory_frames (int): 需要选择的记忆帧总数。
        context_size (int): 需要选择的上下文帧总数。
        pos_weight (float): 空间距离的权重。
        ang_weight (float): 角度距离的权重。

    返回:
        List[int]: 包含选定记忆帧和上下文帧索引的列表。
    """
    num_total_frames = len(w2c_list)
    # 检查当前帧是否能构成一个完整的4帧片段
    if current_frame_idx >= num_total_frames or current_frame_idx < 3:
        raise ValueError("当前帧索引必须在 w2c_list 的有效范围内,且至少为3。")

    # 1. 选择上下文帧 (Context Frames)
    start_context_idx = max(0, current_frame_idx - temporal_context_size)
    context_frames_indices = list(range(start_context_idx, current_frame_idx))

    # 2. 计算记忆帧 (Memory Frames) 的候选池
    candidate_distances = []
    query_clip_indices = list(
        range(
            current_frame_idx,
            (
                current_frame_idx + pred_latent_size
                if current_frame_idx + pred_latent_size <= num_total_frames
                else num_total_frames
            ),
        )
    )

    historical_clip_indices = list(
        range(0, current_frame_idx - temporal_context_size, 4)
    )

    memory_frames_indices = []  # add the first latent frame as context
    memory_frames = memory_frames - temporal_context_size

    for hist_idx in historical_clip_indices:
        total_dist = 0
        hist_w2c_1 = w2c_list[hist_idx]
        hist_w2c_2 = w2c_list[hist_idx + 2]
        for query_idx in query_clip_indices:
            dist_1_for_query_idx = 1.0 - calculate_fov_overlap_similarity(
                w2c_list[query_idx],
                hist_w2c_1,
                fov_h_deg=60.0,
                fov_v_deg=35.0,
                device=device,
                points_local=points_local,
            )
            dist_2_for_query_idx = 1.0 - calculate_fov_overlap_similarity(
                w2c_list[query_idx],
                hist_w2c_2,
                fov_h_deg=60.0,
                fov_v_deg=35.0,
                device=device,
                points_local=points_local,
            )
            dist_for_query_idx = (dist_1_for_query_idx + dist_2_for_query_idx) / 2.0
            total_dist += dist_for_query_idx

        final_clip_distance = total_dist / len(query_clip_indices)
        candidate_distances.append((hist_idx, final_clip_distance))

    candidate_distances.sort(key=lambda x: x[1])

    # 遍历排序后的候选片段,直到收集到足够的记忆帧
    for start_idx, _ in candidate_distances:
        if start_idx not in memory_frames_indices:
            memory_frames_indices.extend(range(start_idx, start_idx + 4))

        # 检查是否已达到 memory_size 的要求
        if len(memory_frames_indices) >= memory_frames:
            break

    # 4. 组合并去重,以确保没有重复的帧
    selected_frames_set = set(context_frames_indices)
    selected_frames_set.update(memory_frames_indices)

    final_selected_frames = sorted(list(selected_frames_set))
    assert len(final_selected_frames) == memory_frames + temporal_context_size
    return final_selected_frames


# --- 核心函数重构 ---
# def select_memory_frames(w2c_list: List[np.ndarray], current_frame_idx: int, memory_size: int,
#                          context_size: int, pos_weight: float = 1.0, ang_weight: float = 1.0) -> List[int]:
#     """
#     为给定帧选择记忆帧和上下文帧,基于复杂的四帧片段距离计算。

#     参数:
#         w2c_list (List[np.ndarray]): 包含所有N个4x4外参矩阵的列表。
#         current_frame_idx (int): 当前要处理的帧的索引。
#         memory_size (int): 需要选择的记忆帧总数。
#         context_size (int): 需要选择的上下文帧总数。
#         pos_weight (float): 空间距离的权重。
#         ang_weight (float): 角度距离的权重。

#     返回:
#         List[int]: 包含选定记忆帧和上下文帧索引的列表。
#     """
#     num_total_frames = len(w2c_list)
#     # 检查当前帧是否能构成一个完整的4帧片段
#     if current_frame_idx >= num_total_frames or current_frame_idx < 3:
#         raise ValueError("当前帧索引必须在 w2c_list 的有效范围内,且至少为3。")

#     # 1. 选择上下文帧 (Context Frames)
#     start_context_idx = max(0, current_frame_idx - context_size)
#     context_frames_indices = list(range(start_context_idx, current_frame_idx))

#     # 2. 计算记忆帧 (Memory Frames) 的候选池
#     candidate_distances = []
#     query_clip_indices = list(range(current_frame_idx, current_frame_idx + 4))

#     # 遍历所有历史片段,将每个片段作为记忆帧候选
#     # 历史片段的起始索引必须是 4 的倍数,且不能与上下文帧重叠
#     for i in range(0, current_frame_idx, 4):
#         historical_clip_indices = list(range(i, i + 4))

#         # 排除上下文帧,如果历史片段与上下文帧有任何交集,就跳过
#         is_context_clip = any(idx in context_frames_indices for idx in historical_clip_indices)
#         if is_context_clip:
#             continue

#         avg_distance = calculate_complex_clip_distance(w2c_list, query_clip_indices, historical_clip_indices,
#                                                        pos_weight, ang_weight)

#         # 存储 (片段起始帧索引, 平均距离)
#         candidate_distances.append((i, avg_distance))

#     # 按平均距离从小到大排序
#     candidate_distances.sort(key=lambda x: x[1])

#     # 3. 选取最相似的 `memory_size` 个帧
#     memory_frames_indices = []

#     # 遍历排序后的候选片段,直到收集到足够的记忆帧
#     for start_idx, _ in candidate_distances:
#         # 将整个片段的帧都添加到记忆帧列表中
#         memory_frames_indices.extend(range(start_idx, start_idx + 4))

#         # 检查是否已达到 memory_size 的要求
#         if len(memory_frames_indices) >= memory_size:
#             break

#     # 4. 组合并去重,以确保没有重复的帧
#     selected_frames_set = set(context_frames_indices)
#     selected_frames_set.update(memory_frames_indices)

#     final_selected_frames = sorted(list(selected_frames_set))

#     return final_selected_frames


def calculate_complex_clip_distance(
    w2c_list: List[np.ndarray],
    query_clip_indices: List[int],
    historical_clip_indices: List[int],
    pos_weight: float = 1.0,
    ang_weight: float = 1.0,
) -> float:
    """
    计算查询片段与历史片段之间的复杂姿态距离。

    该距离是基于查询片段的第二帧和第四帧与历史片段的每一帧的平均距离。
    """
    if len(query_clip_indices) < 4 or len(historical_clip_indices) < 4:
        raise ValueError("片段索引列表必须包含4个元素。")

    # 提取查询片段的第二帧和第四帧的w2c矩阵
    query_2nd_w2c = w2c_list[query_clip_indices[1]]  # 第二帧
    query_4th_w2c = w2c_list[query_clip_indices[3]]  # 第四帧

    # 1. 计算查询片段第二帧与历史片段每帧的平均距离
    dists_from_2nd = []
    for hist_idx in historical_clip_indices:
        hist_w2c = w2c_list[hist_idx]
        dist = calculate_pose_distance_from_w2c(
            query_2nd_w2c, hist_w2c, pos_weight, ang_weight
        )
        dists_from_2nd.append(dist)
    avg_dist_from_2nd = np.mean(dists_from_2nd)

    # 2. 计算查询片段第四帧与历史片段每帧的平均距离
    dists_from_4th = []
    for hist_idx in historical_clip_indices:
        hist_w2c = w2c_list[hist_idx]
        dist = calculate_pose_distance_from_w2c(
            query_4th_w2c, hist_w2c, pos_weight, ang_weight
        )
        dists_from_4th.append(dist)
    avg_dist_from_4th = np.mean(dists_from_4th)

    # 3. 将两个平均值再次取平均,得到最终的片段距离
    final_clip_distance = (avg_dist_from_2nd + avg_dist_from_4th) / 2.0

    return final_clip_distance


def calculate_pose_distance_from_w2c(
    w2c_1: np.ndarray,
    w2c_2: np.ndarray,
    pos_weight: float = 1.0,
    ang_weight: float = 1.0,
) -> float:
    """
    根据两个 4x4 W2C (World-to-Camera) 矩阵计算它们之间的综合姿态距离。

    该距离量化了两个相机姿态的相似度,类似其 FOV 的重叠程度。

    参数:
        w2c_1 (np.ndarray): 第一个相机的 4x4 World-to-Camera 矩阵。
        w2c_2 (np.ndarray): 第二个相机的 4x4 World-to-Camera 矩阵。
        pos_weight (float): 空间距离的权重。
        ang_weight (float): 角度距离的权重。

    返回:
        float: 两个姿态之间的综合距离。
    """

    def w2c_to_6d_pose(w2c_matrix: np.ndarray) -> np.ndarray:
        """
        将 4x4 World-to-Camera (W2C) 矩阵转换为 6D 姿态。

        6D 姿态元组为 (x, y, z, pitch, yaw, roll)。
        """
        # 提取旋转矩阵 R 和平移向量 t
        R_cw = w2c_matrix[:3, :3]
        t_cw = w2c_matrix[:3, 3]

        # 计算相机在世界坐标系下的位置 C_world
        # C_world = -R_cw.T @ t_cw
        C_world = -np.dot(R_cw.T, t_cw)

        # 将旋转矩阵转换为欧拉角 (pitch, yaw, roll)
        # 注意: scipy 默认的欧拉角顺序是 ZYX,对应 yaw, pitch, roll
        # 为了与常见的 (pitch, yaw, roll) 顺序匹配,我们手动转换
        r = R_scipy.from_matrix(R_cw)
        pitch, yaw, roll = r.as_euler("yxz", degrees=True)

        return np.array([C_world[0], C_world[1], C_world[2], pitch, yaw, roll])

    # 1. 将两个 W2C 矩阵转换为 6D 姿态
    pose1_6d = w2c_to_6d_pose(w2c_1)
    pose2_6d = w2c_to_6d_pose(w2c_2)

    # 2. 计算空间距离 (欧几里得距离)
    pos1 = pose1_6d[:3]
    pos2 = pose2_6d[:3]
    spatial_distance = np.linalg.norm(pos1 - pos2)

    # 3. 计算角度距离 (考虑圆周特性)
    angles1 = pose1_6d[3:]
    angles2 = pose2_6d[3:]

    angle_diff = np.abs(angles1 - angles2)
    # 修正角度差,确保是最小的圆周距离
    angular_distance_vector = np.minimum(angle_diff, 360 - angle_diff)
    # 使用欧几里得范数作为综合角度距离
    angular_distance = np.linalg.norm(angular_distance_vector)

    # 4. 结合两种距离得到综合姿态距离
    total_distance = pos_weight * spatial_distance + ang_weight * angular_distance

    return total_distance