File size: 5,860 Bytes
a1833c8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: OpenMDW-1.1

from collections.abc import Iterable
from typing import Any

import torch

from cosmos_framework.utils.generator.image_resize import get_vision_data_resolution as get_vision_data_resolution


def read_positive_int_metadata(
    data_batch: dict[str, Any],
    key: str,
    expected_count: int,
) -> list[int] | None:
    """Normalize positive integer metadata from common dataloader representations.

    Metadata produced by the dataset can reach consumers in several equivalent
    forms: a tensor containing one value per sample after ``default_collate``, a
    Python integer for an unbatched sample, or a list of scalar integers/tensors.
    Iterative and joint dataloaders may additionally wrap each scalar entry in
    one or more single-element lists. This helper normalizes all of those forms
    into one integer per expected sample while validating the metadata contract.

    Args:
        data_batch: Batch containing the metadata.
        key: Metadata key to read.
        expected_count: Required number of scalar values after normalization.

    Returns:
        A list of positive integers, or ``None`` when ``key`` is absent.

    Raises:
        TypeError: If the metadata contains an unsupported value type.
        ValueError: If a list wrapper is not a singleton, the number of values
            differs from ``expected_count``, or any value is not positive.
    """
    raw_value = data_batch.get(key)
    if raw_value is None:
        return None

    if isinstance(raw_value, torch.Tensor):
        flattened = raw_value.reshape(-1)  # [N]
        entries: list[Any] = list(flattened)
    elif isinstance(raw_value, list):
        entries = raw_value
    elif isinstance(raw_value, int):
        entries = [raw_value]
    else:
        raise TypeError(f"{key} must be a tensor, integer, or list, got {type(raw_value).__name__}.")

    values: list[int] = []
    for entry in entries:
        while isinstance(entry, list):
            if len(entry) != 1:
                raise ValueError(f"{key} entries must contain one value, got {entry}.")
            entry = entry[0]
        if isinstance(entry, torch.Tensor):
            try:
                value = int(entry.item())
            except RuntimeError as error:
                raise ValueError(f"{key} entries must be scalar, got shape {tuple(entry.shape)}.") from error
        elif isinstance(entry, int):
            value = entry
        else:
            raise TypeError(f"{key} entries must be tensors or integers, got {type(entry).__name__}.")
        values.append(value)

    if len(values) != expected_count:
        raise ValueError(f"{key} must have {expected_count} values, got {len(values)}.")
    if any(value <= 0 for value in values):
        raise ValueError(f"{key} values must be positive, got {values}.")
    return values


def slice_data_batch(
    data_batch: dict[str, Any],
    start: int,
    limit: int,
    multi_item_fields: Iterable[str] = ("image", "images", "video", "videos", "image_size"),
) -> dict[str, Any]:
    """Slice a data batch based on the start and limit indices.

    For most fields, the slice ``[start:limit]`` is applied directly along the
    sample dimension. For fields listed in ``multi_item_fields`` (e.g. ``image``,
    ``images``, ``video``, and ``videos``), each sample may contribute multiple visual items that are
    concatenated in flat order. In that case, when
    ``num_vision_items_per_sample`` is present in ``data_batch``, the slice is
    expanded to cover all visual items belonging to the requested samples.

    Example:
        ``num_vision_items_per_sample = [2, 2]`` and
        ``video = [v1_s1, v2_s1, v1_s2, v2_s2]``. Slicing with
        ``start=0, limit=1`` returns ``video = [v1_s1, v2_s1]``.

    The ``lidar`` field is flattened the same way and follows
    ``num_lidar_items_per_sample``, since a sample's range clips need not be as
    many as its camera clips.

    Args:
        data_batch: The data batch to slice.
        start: The start sample index (inclusive).
        limit: The end sample index (exclusive).
        multi_item_fields: Field names whose values store multiple visual
            items per sample concatenated in flat order. Only used when
            ``data_batch`` contains ``num_vision_items_per_sample``.

    Returns:
        The sliced data batch.
    """
    assert start >= 0 and limit > 0, "Start and limit must be positive"
    assert start < limit, "Start must be less than limit"

    def flat_range(counts: Any) -> tuple[int, int]:
        counts_list = counts.tolist() if isinstance(counts, torch.Tensor) else list(counts)
        return sum(counts_list[:start]), sum(counts_list[:limit])

    num_items = data_batch.get("num_vision_items_per_sample")
    flat_start, flat_limit = flat_range(num_items) if num_items is not None else (start, limit)
    # The LiDAR stream is flattened the same way, and counts its own items.
    num_lidar_items = data_batch.get("num_lidar_items_per_sample")
    lidar_start, lidar_limit = flat_range(num_lidar_items) if num_lidar_items is not None else (start, limit)

    multi_item_fields = set(multi_item_fields)

    sliced_batch = {}
    for key, value in data_batch.items():
        if key in multi_item_fields and num_items is not None:
            s, e = flat_start, flat_limit
        elif key == "lidar" and num_lidar_items is not None:
            s, e = lidar_start, lidar_limit
        else:
            s, e = start, limit
        if isinstance(value, torch.Tensor):
            sliced_batch[key] = value[s:e]
        elif isinstance(value, list):
            sliced_batch[key] = value[s:e]
        else:
            sliced_batch[key] = value
    return sliced_batch