Repository navigation
Expand file tree
/
Copy pathSensor.ts
More file actions
106 lines (91 loc) · 3.02 KB
/
Copy pathSensor.ts
File metadata and controls
106 lines (91 loc) · 3.02 KB
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
// Sensor — a simplified perception front-end. Instead of ground-truth objects,
// the ego only "sees" what is within range and not occluded by a nearer object,
// every measurement is corrupted by Gaussian noise, and a fraction of detections
// randomly drop out. Cars AND pedestrians flow through the same sensor, so the
// downstream tracker/planner must cope with small, slow, laterally-moving,
// sometimes-late (occluded) objects.
import { ObstacleKind } from "../planner/Trajectory.ts";
export interface SensableObject {
x: number;
y: number;
length: number;
width: number;
kind: ObstacleKind;
}
export interface Detection {
x: number;
y: number;
length: number;
width: number;
kind: ObstacleKind;
}
export interface SensorConfig {
range: number;
posNoise: number;
sizeNoise: number;
occlusion: boolean;
dropout: number; // probability [0,1] a given in-range detection is missed
}
export const DEFAULT_SENSOR: SensorConfig = {
range: 95,
posNoise: 0.35,
sizeNoise: 0.15,
occlusion: true,
dropout: 0.04,
};
function randn(): number {
let u = 0;
let v = 0;
while (u === 0) u = Math.random();
while (v === 0) v = Math.random();
return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
}
export class Sensor {
config: SensorConfig;
constructor(config: Partial<SensorConfig> = {}) {
this.config = { ...DEFAULT_SENSOR, ...config };
}
/** Produce noisy detections of the objects the ego can currently perceive. */
sense(egoX: number, egoY: number, objects: SensableObject[]): Detection[] {
const cfg = this.config;
const cand = objects
.map((o) => {
const dx = o.x - egoX;
const dy = o.y - egoY;
return { o, dist: Math.hypot(dx, dy), bearing: Math.atan2(dy, dx) };
})
.filter((c) => c.dist <= cfg.range)
.sort((a, b) => a.dist - b.dist);
const detections: Detection[] = [];
const blockers: { bearing: number; halfAngle: number }[] = [];
for (const c of cand) {
const o = c.o;
// Angular half-width this object subtends at its range.
const half = Math.atan2(Math.max(o.width, o.length) / 2, Math.max(c.dist, 1));
if (cfg.occlusion) {
let occluded = false;
for (const b of blockers) {
let db = Math.abs(c.bearing - b.bearing);
if (db > Math.PI) db = 2 * Math.PI - db;
if (db < b.halfAngle * 0.8) {
occluded = true;
break;
}
}
// Only solid, wide objects (vehicles) meaningfully occlude.
if (o.kind === "car") blockers.push({ bearing: c.bearing, halfAngle: half });
if (occluded) continue;
}
// Random dropout of an otherwise-visible object.
if (Math.random() < cfg.dropout) continue;
detections.push({
x: o.x + randn() * cfg.posNoise,
y: o.y + randn() * cfg.posNoise,
length: Math.max(0.5, o.length + randn() * cfg.sizeNoise),
width: Math.max(0.5, o.width + randn() * cfg.sizeNoise),
kind: o.kind,
});
}
return detections;
}
}