File size: 10,024 Bytes
c8f05dd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# ============================================================
# dataset.py
# ============================================================
#
# ETHUKU CREATE PANNINOM?
# ━━━━━━━━━━━━━━━━━━━━━━
# PyTorch DataLoader-ku puriyara madhiri oru Dataset class venum.
# Rendu velai pannuthu:
#   1. Input taiyaar: 6 camera images + K + E   (sample_loader use)
#   2. TARGET taiyaar: ground-truth boxes -> BEV map format
#      (heatmap, offset, size, rot...) -> loss compare panna
#
# MUNADI FILE ODA CONNECTION:
# ━━━━━━━━━━━━━━━━━━━━━━━━━━
# sample_loader.load_sample() -> input side
# constants.py -> grid size, classes
# training/train.py ithai DataLoader-la wrap pannum.
#
# INNER OPERATIONS:
# ━━━━━━━━━━━━━━━━
# nuScenes boxes (LIDAR frame) -> ego frame -> BEV grid cell (200x200)
# -> antha cell-la gaussian blob varaiyurom (heatmap)
# -> regression values (offset, z, size, yaw, velocity) store pannurom.
#
# INPUT / OUTPUT:
# ━━━━━━━━━━━━━━
# __getitem__(i) -> dict with images/intrinsics/extrinsics + targets
#
# EPADI USE AAGUM:
# ━━━━━━━━━━━━━━━
# Model "intha cell-la car iruku" nu predict pannum.
# Namma target "aama/illa" nu solli, difference = loss.
#
# ============================================================

import numpy as np
import torch
from torch.utils.data import Dataset

from .constants import (
    BEV_H, BEV_W, BEV_RESOLUTION, X_RANGE, Y_RANGE,
    N_CLASSES, NUSCENES_NAME_MAP, CLASS_TO_IDX,
    VAL_SCENES, DATA_ROOT, VERSION,
)
from .sample_loader import load_sample


def draw_gaussian(heatmap: np.ndarray, cx: int, cy: int, radius: int) -> None:
    """
    Heatmap-la oru cell suthi "mellisaana veliccham" (gaussian) varaiyurathu.

    Yaen point mattum illa, blob?
    Object center exactly oru cell-la nikkathu - konjam adjacent cell-um
    "kittathatta correct" thaan. So neighbours-ku konjam score kudukirom.
    Ithu illaina training romba kastam (200x200 = 40000 cell-la 1 mattum
    correct-nu solli model-ai therikka mudiyathu).

    Args:
        heatmap: [H, W] array, in-place modify aagum
        cx, cy : center cell (column, row)
        radius : blob size in cells
    """
    diameter = 2 * radius + 1
    sigma = diameter / 6.0                      # standard CenterNet choice

    # Chinna gaussian patch create pannurom
    y, x = np.ogrid[-radius:radius + 1, -radius:radius + 1]
    g = np.exp(-(x * x + y * y) / (2 * sigma * sigma))   # center=1.0, edge~0
    g[g < np.finfo(g.dtype).eps * g.max()] = 0

    H, W = heatmap.shape
    # Grid edge-la object irundha patch veliya poidum -> clip pannurom
    left, right = min(cx, radius), min(W - cx, radius + 1)
    top, bottom = min(cy, radius), min(H - cy, radius + 1)
    if right <= 0 or bottom <= 0 or left < 0 or top < 0:
        return

    masked_hm = heatmap[cy - top:cy + bottom, cx - left:cx + right]
    masked_g = g[radius - top:radius + bottom, radius - left:radius + right]
    # maximum: rendu object overlap aana, periya value-ai vachikirom
    np.maximum(masked_hm, masked_g, out=masked_hm)


class NuScenesBEVDataset(Dataset):
    """
    nuScenes mini -> BEV detection dataset.

    Args:
        data_root: "data/nuscenes-mini"
        split: "train" or "val"
        nusc: already-loaded NuScenes object (optional, reuse panna)
    """

    def __init__(self, data_root: str = DATA_ROOT, split: str = "train", nusc=None):
        from nuscenes.nuscenes import NuScenes

        self.data_root = data_root
        self.split = split
        # NuScenes DB load pannurathu ~10 sec edukkum, so oru thadava mattum
        self.nusc = nusc if nusc is not None else NuScenes(
            version=VERSION, dataroot=data_root, verbose=False
        )

        # Scene level-la split pannurom (sample level illa).
        # Yaen? Ore scene-la adjacent frames kittathatta same photo.
        # Train-la oru frame, val-la adjacent frame irundha = cheating.
        self.sample_tokens = []
        for scene in self.nusc.scene:
            is_val = scene["name"] in VAL_SCENES
            if (split == "val") != is_val:
                continue
            token = scene["first_sample_token"]
            while token:
                self.sample_tokens.append(token)
                token = self.nusc.get("sample", token)["next"]

    def __len__(self) -> int:
        return len(self.sample_tokens)

    def _boxes_in_ego(self, sample_token: str) -> list:
        """
        Antha sample-oda ellaa annotation box-aiyum EGO CAR frame-la thara.

        Yaen ego frame? Namma camera extrinsics-um camera->ego thaan.
        Rendum ore frame-la irundha thaan match aagum.

        Returns:
            list of dict: {cls, x, y, z, w, l, h, yaw, vx, vy}
            x = pinnadi(-)/munnadi(+) metres, y = valathu(-)/idathu(+) metres
        """
        from pyquaternion import Quaternion

        sample = self.nusc.get("sample", sample_token)
        # LIDAR_TOP sample_data -> ego pose reference-ku use pannurom
        lidar_token = sample["data"]["LIDAR_TOP"]
        sd = self.nusc.get("sample_data", lidar_token)
        ego_pose = self.nusc.get("ego_pose", sd["ego_pose_token"])

        ego_t = np.array(ego_pose["translation"])
        ego_R_inv = Quaternion(ego_pose["rotation"]).inverse

        boxes = []
        for ann_token in sample["anns"]:
            ann = self.nusc.get("sample_annotation", ann_token)

            name = NUSCENES_NAME_MAP.get(ann["category_name"])
            if name is None:          # namma 10 class-la illa -> skip
                continue

            # Global (world) coords -> ego coords
            center = np.array(ann["translation"]) - ego_t
            center = ego_R_inv.rotate(center)

            rot = ego_R_inv * Quaternion(ann["rotation"])
            yaw = rot.yaw_pitch_roll[0]        # top-down la yaw mattum thevai

            w, l, h = ann["size"]              # nuScenes order: width,length,height

            vel = self.nusc.box_velocity(ann_token)   # global m/s, nan varalam
            if np.any(np.isnan(vel)):
                vx, vy = 0.0, 0.0
            else:
                v_ego = ego_R_inv.rotate(vel)
                vx, vy = float(v_ego[0]), float(v_ego[1])

            boxes.append({
                "cls": CLASS_TO_IDX[name],
                "x": float(center[0]), "y": float(center[1]), "z": float(center[2]),
                "w": float(w), "l": float(l), "h": float(h),
                "yaw": float(yaw), "vx": vx, "vy": vy,
            })
        return boxes

    def _build_targets(self, boxes: list) -> dict:
        """
        Box list -> BEV target maps (model output-oda same shape).

        Returns dict of tensors:
          heatmap  [10,200,200]  0..1, center-la 1.0
          mask     [1,200,200]   1 = inga object center iruku
          offset   [2,200,200]   cell-uku ulla exact position (0..1)
          height   [1,200,200]   z metres
          size     [3,200,200]   log(w), log(l), log(h)
          rot      [2,200,200]   sin(yaw), cos(yaw)
          vel      [2,200,200]   vx, vy
        """
        heatmap = np.zeros((N_CLASSES, BEV_H, BEV_W), dtype=np.float32)
        mask = np.zeros((1, BEV_H, BEV_W), dtype=np.float32)
        offset = np.zeros((2, BEV_H, BEV_W), dtype=np.float32)
        height = np.zeros((1, BEV_H, BEV_W), dtype=np.float32)
        size = np.zeros((3, BEV_H, BEV_W), dtype=np.float32)
        rot = np.zeros((2, BEV_H, BEV_W), dtype=np.float32)
        vel = np.zeros((2, BEV_H, BEV_W), dtype=np.float32)

        for b in boxes:
            # metres -> grid cell (float)
            # Example: x = 0m   -> (0 - (-50))/0.5 = 100 = grid center
            #          x = 10m (10m munnadi) -> (10+50)/0.5 = 120
            fx = (b["x"] - X_RANGE[0]) / BEV_RESOLUTION
            fy = (b["y"] - Y_RANGE[0]) / BEV_RESOLUTION
            cx, cy = int(fx), int(fy)

            if not (0 <= cx < BEV_W and 0 <= cy < BEV_H):
                continue                       # 100x100m veliya -> skip

            # Object evlo periyathu-nu paathu blob size decide
            radius = max(2, int(min(b["w"], b["l"]) / BEV_RESOLUTION / 2))
            draw_gaussian(heatmap[b["cls"]], cx, cy, radius)

            mask[0, cy, cx] = 1.0
            # int-la potta appuram missing aana decimal part.
            # Ithu illaina 0.5m varai error (cell size).
            offset[0, cy, cx] = fx - cx
            offset[1, cy, cx] = fy - cy
            height[0, cy, cx] = b["z"]
            # log yaen? size 0.5m to 20m varai varum. log potta range
            # chinnathaagum -> network kathukka easy.
            size[0, cy, cx] = np.log(max(b["w"], 0.1))
            size[1, cy, cx] = np.log(max(b["l"], 0.1))
            size[2, cy, cx] = np.log(max(b["h"], 0.1))
            # yaw-ai neradiya predict panna problem: 0 degree = 360 degree
            # aana number-la romba different. sin/cos-la athu solve aagum.
            rot[0, cy, cx] = np.sin(b["yaw"])
            rot[1, cy, cx] = np.cos(b["yaw"])
            vel[0, cy, cx] = b["vx"]
            vel[1, cy, cx] = b["vy"]

        return {
            "heatmap": torch.from_numpy(heatmap),
            "mask": torch.from_numpy(mask),
            "offset": torch.from_numpy(offset),
            "height": torch.from_numpy(height),
            "size": torch.from_numpy(size),
            "rot": torch.from_numpy(rot),
            "vel": torch.from_numpy(vel),
        }

    def __getitem__(self, idx: int) -> dict:
        token = self.sample_tokens[idx]

        sample = load_sample(self.nusc, token, self.data_root)   # inputs
        targets = self._build_targets(self._boxes_in_ego(token))  # labels

        return {
            "images": sample["images"],            # [6,3,224,400]
            "intrinsics": sample["intrinsics"],    # [6,3,3]
            "extrinsics": sample["extrinsics"],    # [6,4,4]
            "targets": targets,
        }