feat: HSAP platform v2 — modular navigation, quality review, audit log, world model simulation
Major changes: - New frontend (platform/web/): Vite + React 18 + TypeScript + Tailwind - 4-module navigation: 数据送标 / 模型管理 / 车队管理 / 系统管理 - Data catalog with charts (DMS/ADAS/Lane 3-tab view) - Quality review workflow (标注质检): Good/Fine/Bad scoring with auto-advance - Audit enhancements: batch operations, rejection categories, Feishu notifications - Operation audit log (操作日志) - World model simulation studio (仿真工坊) - Dataset version management with snapshots and diff - ADAS 7-class dataset integration (138K images organized + compressed) - User management with Feishu integration and pagination - CRUD/search/filter on all pages, card layout redesign - PIL-optimized image overlay rendering - Auto-snapshot on build, in_review workflow stage - Removed embedded algorithm code (now in workspace)
This commit is contained in:
11
algorithms/lane_ufld/code.embedded.bak/UFLD/data/constant.py
Executable file
11
algorithms/lane_ufld/code.embedded.bak/UFLD/data/constant.py
Executable file
@@ -0,0 +1,11 @@
|
||||
# row anchors are a series of pre-defined coordinates in image height to detect lanes
|
||||
# the row anchors are defined according to the evaluation protocol of CULane and Tusimple
|
||||
# since our method will resize the image to 288x800 for training, the row anchors are defined with the height of 288
|
||||
# you can modify these row anchors according to your training image resolution
|
||||
|
||||
tusimple_row_anchor = [ 64, 68, 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112,
|
||||
116, 120, 124, 128, 132, 136, 140, 144, 148, 152, 156, 160, 164,
|
||||
168, 172, 176, 180, 184, 188, 192, 196, 200, 204, 208, 212, 216,
|
||||
220, 224, 228, 232, 236, 240, 244, 248, 252, 256, 260, 264, 268,
|
||||
272, 276, 280, 284]
|
||||
culane_row_anchor = [121, 131, 141, 150, 160, 170, 180, 189, 199, 209, 219, 228, 238, 248, 258, 267, 277, 287]
|
||||
118
algorithms/lane_ufld/code.embedded.bak/UFLD/data/dataloader.py
Executable file
118
algorithms/lane_ufld/code.embedded.bak/UFLD/data/dataloader.py
Executable file
@@ -0,0 +1,118 @@
|
||||
import torch, os
|
||||
import numpy as np
|
||||
|
||||
import torchvision.transforms as transforms
|
||||
import data.mytransforms as mytransforms
|
||||
from data.constant import tusimple_row_anchor, culane_row_anchor
|
||||
from data.dataset import LaneClsDataset, LaneTestDataset
|
||||
|
||||
|
||||
def get_train_loader(batch_size, data_root, griding_num, dataset, use_aux, distributed, num_lanes,
|
||||
train_list='list/train_gt.txt', num_workers=8):
|
||||
target_transform = transforms.Compose([
|
||||
mytransforms.FreeScaleMask((288, 800)),
|
||||
mytransforms.MaskToTensor(),
|
||||
])
|
||||
segment_transform = transforms.Compose([
|
||||
mytransforms.FreeScaleMask((36, 100)),
|
||||
mytransforms.MaskToTensor(),
|
||||
])
|
||||
img_transform = transforms.Compose([
|
||||
transforms.Resize((288, 800)),
|
||||
transforms.ToTensor(),
|
||||
# transforms.Normalize((0.723, 0.704, 0.726), (0.191, 0.178, 0.186)),
|
||||
transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)),
|
||||
])
|
||||
simu_transform = mytransforms.Compose2([
|
||||
mytransforms.RandomRotate(6),
|
||||
mytransforms.RandomUDoffsetLABEL(100),
|
||||
mytransforms.RandomLROffsetLABEL(200)
|
||||
])
|
||||
if dataset == 'CULane':
|
||||
train_dataset = LaneClsDataset(data_root,
|
||||
os.path.join(data_root, train_list),
|
||||
img_transform=img_transform, target_transform=target_transform,
|
||||
simu_transform =simu_transform,
|
||||
segment_transform=segment_transform,
|
||||
row_anchor=culane_row_anchor,
|
||||
griding_num=griding_num, use_aux=use_aux, num_lanes=num_lanes)
|
||||
cls_num_per_lane = 18
|
||||
|
||||
elif dataset == 'Tusimple':
|
||||
train_dataset = LaneClsDataset(data_root,
|
||||
os.path.join(data_root, 'train_val_gt.txt'),
|
||||
img_transform=img_transform, target_transform=target_transform,
|
||||
simu_transform =simu_transform,
|
||||
# simu_transform=None,
|
||||
griding_num=griding_num,
|
||||
row_anchor =tusimple_row_anchor,
|
||||
segment_transform=segment_transform, use_aux=use_aux, num_lanes=num_lanes)
|
||||
cls_num_per_lane = 56
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
if distributed:
|
||||
sampler = torch.utils.data.distributed.DistributedSampler(train_dataset)
|
||||
else:
|
||||
sampler = torch.utils.data.RandomSampler(train_dataset)
|
||||
train_loader = torch.utils.data.DataLoader(
|
||||
train_dataset, batch_size=batch_size, sampler=sampler, num_workers=num_workers,
|
||||
)
|
||||
|
||||
return train_loader, cls_num_per_lane
|
||||
|
||||
|
||||
def get_test_loader(batch_size, data_root, dataset, distributed, test_list=None):
|
||||
img_transforms = transforms.Compose([
|
||||
transforms.Resize((288, 800)),
|
||||
transforms.ToTensor(),
|
||||
transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)),
|
||||
])
|
||||
if dataset == 'CULane':
|
||||
if test_list is None:
|
||||
test_list = 'list/test.txt'
|
||||
test_dataset = LaneTestDataset(data_root, os.path.join(data_root, test_list), img_transform=img_transforms)
|
||||
cls_num_per_lane = 18
|
||||
elif dataset == 'Tusimple':
|
||||
if test_list is None:
|
||||
test_list = 'list/test_gt.txt'
|
||||
test_dataset = LaneTestDataset(data_root, os.path.join(data_root, test_list), img_transform=img_transforms)
|
||||
cls_num_per_lane = 56
|
||||
|
||||
if distributed:
|
||||
sampler = SeqDistributedSampler(test_dataset, shuffle=False)
|
||||
else:
|
||||
sampler = torch.utils.data.SequentialSampler(test_dataset)
|
||||
loader = torch.utils.data.DataLoader(test_dataset, batch_size=batch_size, sampler=sampler, num_workers=8)
|
||||
return loader
|
||||
|
||||
|
||||
class SeqDistributedSampler(torch.utils.data.distributed.DistributedSampler):
|
||||
'''
|
||||
Change the behavior of DistributedSampler to sequential distributed sampling.
|
||||
The sequential sampling helps the stability of multi-thread testing, which needs multi-thread file io.
|
||||
Without sequentially sampling, the file io on thread may interfere other threads.
|
||||
'''
|
||||
def __init__(self, dataset, num_replicas=None, rank=None, shuffle=False):
|
||||
super().__init__(dataset, num_replicas, rank, shuffle)
|
||||
|
||||
def __iter__(self):
|
||||
g = torch.Generator()
|
||||
g.manual_seed(self.epoch)
|
||||
if self.shuffle:
|
||||
indices = torch.randperm(len(self.dataset), generator=g).tolist()
|
||||
else:
|
||||
indices = list(range(len(self.dataset)))
|
||||
|
||||
# add extra samples to make it evenly divisible
|
||||
indices += indices[:(self.total_size - len(indices))]
|
||||
assert len(indices) == self.total_size
|
||||
|
||||
num_per_rank = int(self.total_size // self.num_replicas)
|
||||
|
||||
# sequential sampling
|
||||
indices = indices[num_per_rank * self.rank : num_per_rank * (self.rank + 1)]
|
||||
|
||||
assert len(indices) == self.num_samples
|
||||
|
||||
return iter(indices)
|
||||
179
algorithms/lane_ufld/code.embedded.bak/UFLD/data/dataset.py
Executable file
179
algorithms/lane_ufld/code.embedded.bak/UFLD/data/dataset.py
Executable file
@@ -0,0 +1,179 @@
|
||||
import torch
|
||||
from PIL import Image
|
||||
import os
|
||||
import pdb
|
||||
import numpy as np
|
||||
import cv2
|
||||
from data.mytransforms import find_start_pos
|
||||
|
||||
|
||||
def loader_func(path):
|
||||
return Image.open(path)
|
||||
|
||||
|
||||
class LaneTestDataset(torch.utils.data.Dataset):
|
||||
def __init__(self, path, list_path, img_transform=None):
|
||||
super(LaneTestDataset, self).__init__()
|
||||
self.path = path
|
||||
self.img_transform = img_transform
|
||||
with open(list_path, 'r') as f:
|
||||
self.list = f.readlines()
|
||||
self.list = [l[1:] if l[0] == '/' else l for l in self.list] # exclude the incorrect path prefix '/' of CULane
|
||||
|
||||
def __getitem__(self, index):
|
||||
name = self.list[index].split()[0]
|
||||
img_path = os.path.join(self.path, name)
|
||||
img = loader_func(img_path).convert('RGB')
|
||||
|
||||
if self.img_transform is not None:
|
||||
img = self.img_transform(img)
|
||||
|
||||
return img, name
|
||||
|
||||
def __len__(self):
|
||||
return len(self.list)
|
||||
|
||||
|
||||
class LaneClsDataset(torch.utils.data.Dataset):
|
||||
def __init__(self, path, list_path, img_transform=None, target_transform=None, simu_transform=None, griding_num=50, load_name=False,
|
||||
row_anchor=None, use_aux=False, segment_transform=None, num_lanes=2):
|
||||
super(LaneClsDataset, self).__init__()
|
||||
self.img_transform = img_transform
|
||||
self.target_transform = target_transform
|
||||
self.segment_transform = segment_transform
|
||||
self.simu_transform = simu_transform
|
||||
self.path = path
|
||||
self.griding_num = griding_num
|
||||
self.load_name = load_name
|
||||
self.use_aux = use_aux
|
||||
self.num_lanes = num_lanes
|
||||
|
||||
with open(list_path, 'r') as f:
|
||||
self.list = f.readlines()
|
||||
|
||||
self.row_anchor = row_anchor
|
||||
self.row_anchor.sort()
|
||||
|
||||
def _normalize_seg_label(self, label):
|
||||
"""MUFLD masks use 0,2,3,4,5; aux CE needs contiguous 0..num_lanes."""
|
||||
arr = np.array(label, dtype=np.uint8)
|
||||
if arr.max() <= self.num_lanes:
|
||||
return label
|
||||
out = np.zeros_like(arr, dtype=np.uint8)
|
||||
for lane_idx in range(1, self.num_lanes + 1):
|
||||
out[arr == (lane_idx + 1)] = lane_idx
|
||||
return Image.fromarray(out, mode='L')
|
||||
|
||||
def __getitem__(self, index):
|
||||
l = self.list[index]
|
||||
l_info = l.split()
|
||||
img_name, label_name = l_info[0], l_info[1]
|
||||
# print(img_name, label_name)
|
||||
if img_name[0] == '/':
|
||||
img_name = img_name[1:]
|
||||
label_name = label_name[1:]
|
||||
|
||||
label_path = os.path.join(self.path, label_name)
|
||||
label = loader_func(label_path).convert('L')
|
||||
|
||||
img_path = os.path.join(self.path, img_name)
|
||||
img = loader_func(img_path).convert('RGB')
|
||||
|
||||
# print('---------------', img_path, label_path)
|
||||
if self.simu_transform is not None:
|
||||
img, label = self.simu_transform(img, label)
|
||||
# print(',,,,,,,,,,,,,,,,', img.size, label.size)
|
||||
lane_pts = self._get_index(label)
|
||||
# get the coordinates of lanes at row anchors
|
||||
|
||||
|
||||
|
||||
w, h = img.size
|
||||
cls_label = self._grid_pts(lane_pts, self.griding_num, w)
|
||||
# make the coordinates to classification label
|
||||
if self.use_aux:
|
||||
assert self.segment_transform is not None
|
||||
seg_label = self.segment_transform(self._normalize_seg_label(label))
|
||||
|
||||
if self.img_transform is not None:
|
||||
img = self.img_transform(img)
|
||||
|
||||
if self.use_aux:
|
||||
return img, cls_label, seg_label
|
||||
if self.load_name:
|
||||
return img, cls_label, img_name
|
||||
return img, cls_label
|
||||
|
||||
def __len__(self):
|
||||
return len(self.list)
|
||||
|
||||
def _grid_pts(self, pts, num_cols, w):
|
||||
# pts : numlane,n,2
|
||||
num_lane, n, n2 = pts.shape
|
||||
col_sample = np.linspace(0, w - 1, num_cols)
|
||||
|
||||
assert n2 == 2
|
||||
to_pts = np.zeros((n, num_lane))
|
||||
for i in range(num_lane):
|
||||
pti = pts[i, :, 1]
|
||||
to_pts[:, i] = np.asarray(
|
||||
[int(pt // (col_sample[1] - col_sample[0])) if pt != -1 else num_cols for pt in pti])
|
||||
return to_pts.astype(int)
|
||||
|
||||
def _get_index(self, label):
|
||||
w, h = label.size
|
||||
|
||||
if h != 288:
|
||||
scale_f = lambda x : int((x * 1.0/288) * h)
|
||||
sample_tmp = list(map(scale_f,self.row_anchor))
|
||||
|
||||
all_idx = np.zeros((self.num_lanes,len(sample_tmp),2))
|
||||
for i,r in enumerate(sample_tmp):
|
||||
label_r = np.asarray(label)[int(round(r))]
|
||||
for lane_idx in range(1, self.num_lanes + 1):
|
||||
if np.max(label_r) == 2:
|
||||
pos = np.where(label_r == lane_idx)[0]
|
||||
else:
|
||||
pos = np.where(label_r == (lane_idx + 1))[0]
|
||||
if len(pos) == 0:
|
||||
all_idx[lane_idx - 1, i, 0] = r
|
||||
all_idx[lane_idx - 1, i, 1] = -1
|
||||
continue
|
||||
pos = np.mean(pos)
|
||||
all_idx[lane_idx - 1, i, 0] = r
|
||||
all_idx[lane_idx - 1, i, 1] = pos
|
||||
|
||||
# data augmentation: extend the lane to the boundary of image
|
||||
|
||||
all_idx_cp = all_idx.copy()
|
||||
for i in range(self.num_lanes):
|
||||
if np.all(all_idx_cp[i,:,1] == -1):
|
||||
continue
|
||||
# if there is no lane
|
||||
|
||||
valid = all_idx_cp[i,:,1] != -1
|
||||
# get all valid lane points' index
|
||||
valid_idx = all_idx_cp[i,valid,:]
|
||||
# get all valid lane points
|
||||
if valid_idx[-1,0] == all_idx_cp[0,-1,0]:
|
||||
# if the last valid lane point's y-coordinate is already the last y-coordinate of all rows
|
||||
# this means this lane has reached the bottom boundary of the image
|
||||
# so we skip
|
||||
continue
|
||||
if len(valid_idx) < 6:
|
||||
continue
|
||||
# if the lane is too short to extend
|
||||
|
||||
valid_idx_half = valid_idx[len(valid_idx) // 2:,:]
|
||||
p = np.polyfit(valid_idx_half[:,0], valid_idx_half[:,1],deg = 1)
|
||||
start_line = valid_idx_half[-1,0]
|
||||
pos = find_start_pos(all_idx_cp[i,:,0],start_line) + 1
|
||||
|
||||
fitted = np.polyval(p, all_idx_cp[i, pos:, 0])
|
||||
fitted = np.array([-1 if y < 0 or y > w-1 else y for y in fitted])
|
||||
|
||||
assert np.all(all_idx_cp[i,pos:,1] == -1)
|
||||
all_idx_cp[i,pos:,1] = fitted
|
||||
if -1 in all_idx[:, :, 0]:
|
||||
pdb.set_trace()
|
||||
return all_idx_cp
|
||||
187
algorithms/lane_ufld/code.embedded.bak/UFLD/data/mytransforms.py
Executable file
187
algorithms/lane_ufld/code.embedded.bak/UFLD/data/mytransforms.py
Executable file
@@ -0,0 +1,187 @@
|
||||
import numbers
|
||||
import random
|
||||
import numpy as np
|
||||
from PIL import Image, ImageOps, ImageFilter
|
||||
#from config import cfg
|
||||
import torch
|
||||
import pdb
|
||||
import cv2
|
||||
|
||||
# ===============================img tranforms============================
|
||||
|
||||
class Compose2(object):
|
||||
def __init__(self, transforms):
|
||||
self.transforms = transforms
|
||||
|
||||
def __call__(self, img, mask, bbx=None):
|
||||
if bbx is None:
|
||||
for t in self.transforms:
|
||||
# print(t)
|
||||
# print('\\: ', img.size, mask.size)
|
||||
img, mask = t(img, mask)
|
||||
# print('//: ', img.size, mask.size)
|
||||
return img, mask
|
||||
for t in self.transforms:
|
||||
img, mask, bbx = t(img, mask, bbx)
|
||||
return img, mask, bbx
|
||||
|
||||
class FreeScale(object):
|
||||
def __init__(self, size):
|
||||
self.size = size # (h, w)
|
||||
|
||||
def __call__(self, img, mask):
|
||||
return img.resize((self.size[1], self.size[0]), Image.BILINEAR), mask.resize((self.size[1], self.size[0]), Image.NEAREST)
|
||||
|
||||
class FreeScaleMask(object):
|
||||
def __init__(self,size):
|
||||
self.size = size
|
||||
def __call__(self,mask):
|
||||
return mask.resize((self.size[1], self.size[0]), Image.NEAREST)
|
||||
|
||||
class Scale(object):
|
||||
def __init__(self, size):
|
||||
self.size = size
|
||||
|
||||
def __call__(self, img, mask):
|
||||
# if img.size != mask.size:
|
||||
# print(img.size)
|
||||
# print(mask.size)
|
||||
assert img.size == mask.size
|
||||
w, h = img.size
|
||||
if (w <= h and w == self.size) or (h <= w and h == self.size):
|
||||
return img, mask
|
||||
if w < h:
|
||||
ow = self.size
|
||||
oh = int(self.size * h / w)
|
||||
return img.resize((ow, oh), Image.BILINEAR), mask.resize((ow, oh), Image.NEAREST)
|
||||
else:
|
||||
oh = self.size
|
||||
ow = int(self.size * w / h)
|
||||
return img.resize((ow, oh), Image.BILINEAR), mask.resize((ow, oh), Image.NEAREST)
|
||||
|
||||
|
||||
class RandomRotate(object):
|
||||
"""Crops the given PIL.Image at a random location to have a region of
|
||||
the given size. size can be a tuple (target_height, target_width)
|
||||
or an integer, in which case the target will be of a square shape (size, size)
|
||||
"""
|
||||
|
||||
def __init__(self, angle):
|
||||
self.angle = angle
|
||||
|
||||
def __call__(self, image, label):
|
||||
# w, h = image.size
|
||||
# if w != 1280 or h != 720:
|
||||
# image = image.resize(image, (1280, 720))
|
||||
# print(w, h)
|
||||
assert label is None or image.size == label.size
|
||||
|
||||
angle = random.randint(0, self.angle * 2) - self.angle
|
||||
|
||||
label = label.rotate(angle, resample=Image.NEAREST)
|
||||
image = image.rotate(angle, resample=Image.BILINEAR)
|
||||
|
||||
return image, label
|
||||
|
||||
|
||||
|
||||
# ===============================label tranforms============================
|
||||
|
||||
class DeNormalize(object):
|
||||
def __init__(self, mean, std):
|
||||
self.mean = mean
|
||||
self.std = std
|
||||
|
||||
def __call__(self, tensor):
|
||||
for t, m, s in zip(tensor, self.mean, self.std):
|
||||
t.mul_(s).add_(m)
|
||||
return tensor
|
||||
|
||||
|
||||
class MaskToTensor(object):
|
||||
def __call__(self, img):
|
||||
return torch.from_numpy(np.array(img, dtype=np.int32)).long()
|
||||
|
||||
|
||||
def find_start_pos(row_sample,start_line):
|
||||
# row_sample = row_sample.sort()
|
||||
# for i,r in enumerate(row_sample):
|
||||
# if r >= start_line:
|
||||
# return i
|
||||
l,r = 0,len(row_sample)-1
|
||||
while True:
|
||||
mid = int((l+r)/2)
|
||||
if r - l == 1:
|
||||
return r
|
||||
if row_sample[mid] < start_line:
|
||||
l = mid
|
||||
if row_sample[mid] > start_line:
|
||||
r = mid
|
||||
if row_sample[mid] == start_line:
|
||||
return mid
|
||||
|
||||
class RandomLROffsetLABEL(object):
|
||||
def __init__(self,max_offset):
|
||||
self.max_offset = max_offset
|
||||
def __call__(self,img,label):
|
||||
offset = np.random.randint(-self.max_offset,self.max_offset)
|
||||
w, h = img.size
|
||||
# print('max_offset:', self.max_offset, 'ro_offset: ', offset)
|
||||
img = np.array(img)
|
||||
if offset > 0:
|
||||
img[:,offset:,:] = img[:,0:w-offset,:]
|
||||
img[:,:offset,:] = 0
|
||||
if offset < 0:
|
||||
real_offset = -offset
|
||||
img[:,0:w-real_offset,:] = img[:,real_offset:,:]
|
||||
img[:,w-real_offset:,:] = 0
|
||||
|
||||
label = np.array(label)
|
||||
if offset > 0:
|
||||
label[:,offset:] = label[:,0:w-offset]
|
||||
label[:,:offset] = 0
|
||||
if offset < 0:
|
||||
offset = -offset
|
||||
label[:,0:w-offset] = label[:,offset:]
|
||||
label[:,w-offset:] = 0
|
||||
return Image.fromarray(img),Image.fromarray(label)
|
||||
|
||||
class RandomUDoffsetLABEL(object):
|
||||
def __init__(self,max_offset):
|
||||
self.max_offset = max_offset
|
||||
def __call__(self,img,label):
|
||||
offset = np.random.randint(-self.max_offset,self.max_offset)
|
||||
# offset = np.random.randint(0, self.max_offset)
|
||||
# offset = 17
|
||||
# print('max_offset:', self.max_offset, 'do_offset: ', offset)
|
||||
w, h = img.size
|
||||
# if w != 1280 or h != 720:
|
||||
# img = img.resize(img, (1280, 720))
|
||||
# print(w, h)
|
||||
img = np.array(img)
|
||||
if offset > 0:
|
||||
# print('dim > 0:', h - offset, offset)
|
||||
# print(img[offset:,:,:].shape, img[0:h-offset,:,:].shape)
|
||||
img[offset:,:,:] = img[0:h-offset,:,:]
|
||||
img[:offset,:,:] = 0
|
||||
if offset < 0:
|
||||
real_offset = -offset
|
||||
# print('dim < 0:', h - real_offset, real_offset)
|
||||
img[0:h-real_offset,:,:] = img[real_offset:,:,:]
|
||||
img[h-real_offset:,:,:] = 0
|
||||
|
||||
label = np.array(label)
|
||||
if offset > 0:
|
||||
# print('dla > 0:', h - offset, offset)
|
||||
# # print(label[0:h-offset,:].shape, label[offset:,:].shape)
|
||||
# if label[0:h-offset,:].shape == label[offset:,:].shape:
|
||||
label[offset:,:] = label[0:h-offset,:]
|
||||
label[:offset,:] = 0
|
||||
# else:
|
||||
# print(label[0:h - offset, :].shape, label[offset:, :].shape, '/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/')
|
||||
if offset < 0:
|
||||
offset = -offset
|
||||
# print('dla < 0:', h - offset, offset)
|
||||
label[0:h-offset,:] = label[offset:,:]
|
||||
label[h-offset:,:] = 0
|
||||
return Image.fromarray(img),Image.fromarray(label)
|
||||
Reference in New Issue
Block a user