Dit begint ergens op te lijken (prep voor U? @ hof)
This commit is contained in:
parent
3a73806a52
commit
3b9a7c033f
11 changed files with 2058 additions and 147 deletions
|
|
@ -2,7 +2,7 @@
|
||||||
"batch_size": 512,
|
"batch_size": 512,
|
||||||
"grad_clip": 1.0,
|
"grad_clip": 1.0,
|
||||||
"learning_rate_style": "exp",
|
"learning_rate_style": "exp",
|
||||||
"learning_rate": 0.0001,
|
"learning_rate": 0.001,
|
||||||
"min_learning_rate": 1e-05,
|
"min_learning_rate": 1e-05,
|
||||||
"learning_decay_rate": 0.9999,
|
"learning_decay_rate": 0.9999,
|
||||||
"prediction_horizon": 60,
|
"prediction_horizon": 60,
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
776
tests/track_history.ipynb
Normal file
776
tests/track_history.ipynb
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -168,8 +168,8 @@ def calculate_loitering_scores(track: ProjectedTrack, min_duration_to_linger, li
|
||||||
linger_frames +=1
|
linger_frames +=1
|
||||||
else:
|
else:
|
||||||
# decay if moving faster
|
# decay if moving faster
|
||||||
linger_duration = max(linger_duration - 3, 0)
|
linger_duration = max(linger_duration - 1.5, 0)
|
||||||
linger_frames = max(linger_frames - 3, 0)
|
linger_frames = max(linger_frames - 1.5, 0)
|
||||||
|
|
||||||
# Calculate loitering score
|
# Calculate loitering score
|
||||||
if total_frames > 0:
|
if total_frames > 0:
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from collections import defaultdict, deque, namedtuple
|
from collections import defaultdict, deque, namedtuple
|
||||||
from copy import copy
|
from copy import copy
|
||||||
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import pickle
|
||||||
import select
|
import select
|
||||||
import socket
|
import socket
|
||||||
from turtle import update
|
from turtle import update
|
||||||
|
|
@ -219,25 +221,27 @@ class Lidar(Node):
|
||||||
else:
|
else:
|
||||||
self.sequence_generator = read_live_data(self.config.ip, self.config.port, config)
|
self.sequence_generator = read_live_data(self.config.ip, self.config.port, config)
|
||||||
|
|
||||||
self.tracker = BYTETracker(frame_rate=10)
|
self.tracker = BYTETracker(frame_rate=ASSUMED_FPS)
|
||||||
|
|
||||||
self.tracks: DefaultDict[str, Track] = defaultdict(lambda: Track())
|
self.tracks: DefaultDict[str, Track] = defaultdict(lambda: Track())
|
||||||
|
|
||||||
|
|
||||||
self.room_filter = AdaptiveVoxelFilter(
|
# self.room_filter = AdaptiveVoxelFilter(
|
||||||
voxel_size=.5,
|
# voxel_size=.25,
|
||||||
nr_of_frames_to_static=120 * ASSUMED_FPS , # (confidence_threshold * update_interval) = nr of frames needed to count as static
|
# nr_of_frames_to_static=120 * ASSUMED_FPS , # (confidence_threshold * update_interval) = nr of frames needed to count as static
|
||||||
nr_of_frames_to_unstatic=10 * ASSUMED_FPS,
|
# nr_of_frames_to_unstatic=120 * ASSUMED_FPS, # 1/3
|
||||||
update_interval=20, #not every frame needs to impact history
|
# update_interval=20, #not every frame needs to impact history
|
||||||
decay_rate=1
|
# grow_rate=4,
|
||||||
)
|
# decay_rate=1,
|
||||||
# self.room_filter = VoxelOccupancyFilter(
|
|
||||||
# voxel_size=.5,
|
|
||||||
# history_size=80,
|
|
||||||
# history_interval=20, #not every frame needs to impact history
|
|
||||||
# ready_for_action_at=25,
|
|
||||||
# static_threshold=0.3
|
|
||||||
# )
|
# )
|
||||||
|
|
||||||
|
self.room_filter = VoxelOccupancyFilter(
|
||||||
|
voxel_size=.4,
|
||||||
|
history_size=80,
|
||||||
|
history_interval=20, #not every frame needs to impact history
|
||||||
|
ready_for_action_at=25,
|
||||||
|
static_threshold=0.3
|
||||||
|
)
|
||||||
|
|
||||||
self.debug_lines = load_lines_from_svg(self.config.debug_map, 100, '') if self.config.debug_map else []
|
self.debug_lines = load_lines_from_svg(self.config.debug_map, 100, '') if self.config.debug_map else []
|
||||||
|
|
||||||
|
|
@ -442,6 +446,7 @@ class Lidar(Node):
|
||||||
|
|
||||||
# a = time.perf_counter()
|
# a = time.perf_counter()
|
||||||
filtered_pcd, dropped_indexes = self.room_filter.remove_static(current_pcd)
|
filtered_pcd, dropped_indexes = self.room_filter.remove_static(current_pcd)
|
||||||
|
# print(len(filtered_pcd.points), len(dropped_indexes))
|
||||||
|
|
||||||
|
|
||||||
if self.config.viz and len(dropped_indexes):
|
if self.config.viz and len(dropped_indexes):
|
||||||
|
|
@ -480,13 +485,18 @@ class Lidar(Node):
|
||||||
|
|
||||||
|
|
||||||
points_2d = project_to_xy(filtered_pcd)
|
points_2d = project_to_xy(filtered_pcd)
|
||||||
|
|
||||||
|
# with open('/tmp/points.pcl', 'wb') as fp:
|
||||||
|
# pickle.dump(points_2d, fp)
|
||||||
|
|
||||||
clusters = self.cluster_2d(
|
clusters = self.cluster_2d(
|
||||||
points_2d,
|
points_2d,
|
||||||
)
|
)
|
||||||
|
# print(len(clusters))
|
||||||
|
|
||||||
# boxes, centroids = get_cluster_boxes(points_2d, labels, min_area= 0.3*0.3)
|
# boxes, centroids = get_cluster_boxes(points_2d, labels, min_area= 0.3*0.3)
|
||||||
boxes, centroids = get_cluster_boxes(clusters, min_area= self.get_setting('lidar.min_box_area', .1))
|
boxes, centroids = get_cluster_boxes(clusters, min_area= self.get_setting('lidar.min_box_area', .1))
|
||||||
|
# print(centroids)
|
||||||
|
|
||||||
# append confidence and class (placeholders)
|
# append confidence and class (placeholders)
|
||||||
|
|
||||||
|
|
@ -558,8 +568,12 @@ class Lidar(Node):
|
||||||
for line_set in line_sets:
|
for line_set in line_sets:
|
||||||
vis.add_geometry(line_set, False)
|
vis.add_geometry(line_set, False)
|
||||||
elif self.config.viz:
|
elif self.config.viz:
|
||||||
|
print('initializing')
|
||||||
|
if hasattr(self.room_filter, 'scan_counter'):
|
||||||
|
print(self.room_filter.scan_counter, self.room_filter.nr_of_frames_to_init)
|
||||||
line_sets = []
|
line_sets = []
|
||||||
|
filtered_pcd.paint_uniform_color([.5]*3)
|
||||||
|
dropped_pcds.append(filtered_pcd)
|
||||||
live_filtered_pcd.points = o3d.utility.Vector3dVector(np.empty([0,3]))
|
live_filtered_pcd.points = o3d.utility.Vector3dVector(np.empty([0,3]))
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -584,9 +598,9 @@ class Lidar(Node):
|
||||||
live_pcd.points = dropped_pcd.points
|
live_pcd.points = dropped_pcd.points
|
||||||
live_pcd.colors = dropped_pcd.colors
|
live_pcd.colors = dropped_pcd.colors
|
||||||
|
|
||||||
color = [0.5, 0.5, 0.7] if self.room_filter.initialised else [0.7, 0.7, 0.7]
|
color = [0.2, 0.7, 0.2] if self.room_filter.initialised else [0.7, 0.7, 0.7]
|
||||||
# live_pcd.paint_uniform_color(color)
|
# live_pcd.paint_uniform_color(color)
|
||||||
live_filtered_pcd.paint_uniform_color([0.2, 0.7, 0.2])
|
live_filtered_pcd.paint_uniform_color(color)
|
||||||
|
|
||||||
if not geom_added:
|
if not geom_added:
|
||||||
vis.add_geometry(live_filtered_pcd, True)
|
vis.add_geometry(live_filtered_pcd, True)
|
||||||
|
|
@ -693,7 +707,7 @@ class Lidar(Node):
|
||||||
elif method == 'birch':
|
elif method == 'birch':
|
||||||
process_subclusters = self.get_setting('lidar.birch_process_subclusters', True)
|
process_subclusters = self.get_setting('lidar.birch_process_subclusters', True)
|
||||||
labels = Birch(
|
labels = Birch(
|
||||||
max_eps=self.get_setting('lidar.birch_threshold', 1), # "The radius of the subcluster obtained by merging a new sample"
|
threshold=self.get_setting('lidar.birch_threshold', 1), # "The radius of the subcluster obtained by merging a new sample"
|
||||||
branching_factor=self.get_setting('lidar.birch_branching_factor', 50),
|
branching_factor=self.get_setting('lidar.birch_branching_factor', 50),
|
||||||
n_clusters=None if not process_subclusters else DBSCAN(
|
n_clusters=None if not process_subclusters else DBSCAN(
|
||||||
eps=self.get_setting('lidar.eps', .132),
|
eps=self.get_setting('lidar.eps', .132),
|
||||||
|
|
@ -705,9 +719,9 @@ class Lidar(Node):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
# db.labels_
|
|
||||||
clusters = []
|
clusters = []
|
||||||
for label in labels:
|
|
||||||
|
for label in np.unique(labels):
|
||||||
if label == -1:
|
if label == -1:
|
||||||
continue # Skip noise
|
continue # Skip noise
|
||||||
|
|
||||||
|
|
@ -1019,6 +1033,7 @@ class VoxelOccupancyFilter:
|
||||||
filtered_pcd.points = o3d.utility.Vector3dVector(np.array(filtered_points))
|
filtered_pcd.points = o3d.utility.Vector3dVector(np.array(filtered_points))
|
||||||
return filtered_pcd, dropped_indexes
|
return filtered_pcd, dropped_indexes
|
||||||
|
|
||||||
|
AdaptiveVoxelFilterCache = namedtuple("AdaptiveVoxelFilterCache", ("scan_counter", "static_map", "initialised"))
|
||||||
|
|
||||||
class AdaptiveVoxelFilter:
|
class AdaptiveVoxelFilter:
|
||||||
def __init__(
|
def __init__(
|
||||||
|
|
@ -1027,6 +1042,7 @@ class AdaptiveVoxelFilter:
|
||||||
nr_of_frames_to_static=720,
|
nr_of_frames_to_static=720,
|
||||||
nr_of_frames_to_unstatic=720,
|
nr_of_frames_to_unstatic=720,
|
||||||
decay_rate=1,
|
decay_rate=1,
|
||||||
|
grow_rate=3,
|
||||||
update_interval=10,
|
update_interval=10,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
|
|
@ -1039,22 +1055,50 @@ class AdaptiveVoxelFilter:
|
||||||
decay_rate (int): How much to decrement confidence for unoccupied voxels per scan.
|
decay_rate (int): How much to decrement confidence for unoccupied voxels per scan.
|
||||||
update_interval (int): Update static map every N scans.
|
update_interval (int): Update static map every N scans.
|
||||||
"""
|
"""
|
||||||
|
self.nr_of_frames_to_static = nr_of_frames_to_static
|
||||||
|
self.nr_of_frames_to_unstatic = nr_of_frames_to_unstatic
|
||||||
|
self.nr_of_frames_to_init = self.nr_of_frames_to_static + self.nr_of_frames_to_unstatic
|
||||||
self.confidence_threshold = (nr_of_frames_to_static / update_interval) * decay_rate
|
self.confidence_threshold = (nr_of_frames_to_static / update_interval) * decay_rate
|
||||||
self.max_occupancy_score = (nr_of_frames_to_unstatic / update_interval) * decay_rate + self.confidence_threshold
|
self.max_occupancy_score = (nr_of_frames_to_unstatic / update_interval) * decay_rate + self.confidence_threshold
|
||||||
self.voxel_size = voxel_size
|
self.voxel_size = voxel_size
|
||||||
# self.confidence_threshold = confidence_threshold
|
# self.confidence_threshold = confidence_threshold
|
||||||
# self.max_occupancy_score = max_occupancy_score
|
# self.max_occupancy_score = max_occupancy_score
|
||||||
self.decay_rate = decay_rate
|
self.decay_rate = decay_rate
|
||||||
|
self.grow_rate = grow_rate
|
||||||
|
|
||||||
self.update_interval = update_interval
|
self.update_interval = update_interval
|
||||||
|
|
||||||
# Static map: {voxel: confidence}
|
# Static map: {voxel: confidence}
|
||||||
self.static_map: Dict[Tuple[int, int], int] = defaultdict(int)
|
self.static_map: Dict[Tuple[int, int, int], int] = defaultdict(int)
|
||||||
|
|
||||||
# Counter for update interval
|
# Counter for update interval
|
||||||
self.scan_counter = 0
|
self.scan_counter = 0
|
||||||
|
|
||||||
|
self.cache_load_if_exists()
|
||||||
|
|
||||||
self.initialised = False
|
self.initialised = False
|
||||||
|
|
||||||
|
def cache_save(self):
|
||||||
|
data = AdaptiveVoxelFilterCache(self.scan_counter, self.static_map, self.initialised)
|
||||||
|
with self.cache_fname().open('wb') as fp:
|
||||||
|
pickle.dump(data, fp)
|
||||||
|
logger.info(f"Wrote voxel filter to cache {self.cache_fname()}")
|
||||||
|
|
||||||
|
def cache_load_if_exists(self):
|
||||||
|
# data = AdaptiveVoxelFilterCache(self.scan_counter, self.static_map)
|
||||||
|
if self.cache_fname().exists():
|
||||||
|
logger.info(f"Load voxel filter from cache {self.cache_fname()}")
|
||||||
|
with self.cache_fname().open('rb') as fp:
|
||||||
|
data = pickle.load(fp)
|
||||||
|
assert type(data) == AdaptiveVoxelFilterCache
|
||||||
|
self.static_map = data.static_map
|
||||||
|
self.scan_counter = data.scan_counter
|
||||||
|
self.initialised = data.initialised
|
||||||
|
|
||||||
|
|
||||||
|
def cache_fname(self):
|
||||||
|
props = f"{self.voxel_size}-{self.nr_of_frames_to_static}-{self.nr_of_frames_to_unstatic}"
|
||||||
|
return Path(f"/tmp/voxel_filter-{props}.pcl")
|
||||||
|
|
||||||
|
|
||||||
def remove_static(self, current_pcd):
|
def remove_static(self, current_pcd):
|
||||||
|
|
@ -1086,14 +1130,16 @@ class AdaptiveVoxelFilter:
|
||||||
|
|
||||||
# Update static map and confidence
|
# Update static map and confidence
|
||||||
self.scan_counter += 1
|
self.scan_counter += 1
|
||||||
if self.scan_counter % self.update_interval == 0:
|
if not self.initialised or self.scan_counter % self.update_interval == 0:
|
||||||
if not self.initialised and self.scan_counter > self.confidence_threshold:
|
if not self.initialised and self.scan_counter > self.nr_of_frames_to_init:
|
||||||
self.initialised = True
|
self.initialised = True
|
||||||
|
logger.info('Initialised static filter')
|
||||||
|
self.cache_save()
|
||||||
|
|
||||||
# Add new voxels to static map
|
# Add new voxels to static map
|
||||||
for voxel in current_voxel_set:
|
for voxel in current_voxel_set:
|
||||||
if self.static_map[voxel] < self.max_occupancy_score:
|
if self.static_map[voxel] < self.max_occupancy_score:
|
||||||
self.static_map[voxel] += 1
|
self.static_map[voxel] += self.grow_rate
|
||||||
|
|
||||||
# Decay confidence for voxels not in current scan
|
# Decay confidence for voxels not in current scan
|
||||||
for voxel in list(self.static_map.keys()):
|
for voxel in list(self.static_map.keys()):
|
||||||
|
|
|
||||||
402
trap/lines.py
402
trap/lines.py
|
|
@ -22,7 +22,7 @@ import svgpathtools
|
||||||
from noise import snoise2
|
from noise import snoise2
|
||||||
|
|
||||||
from trap import renderable_pb2
|
from trap import renderable_pb2
|
||||||
from trap.utils import exponentialDecay, exponentialDecayRounded, inv_lerp, relativePointToPolar, relativePolarToPoint
|
from trap.utils import easeInOutQuad, exponentialDecay, exponentialDecayRounded, inv_lerp, lerp, relativePointToPolar, relativePolarToPoint
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
@ -57,6 +57,9 @@ class SrgbaColor():
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return math.isclose(self.red, other.red) and math.isclose(self.green, other.green) and math.isclose(self.blue, other.blue) and math.isclose(self.alpha, other.alpha)
|
return math.isclose(self.red, other.red) and math.isclose(self.green, other.green) and math.isclose(self.blue, other.blue) and math.isclose(self.alpha, other.alpha)
|
||||||
|
|
||||||
|
def as_array(self):
|
||||||
|
return [self.red, self.green, self.blue, self.alpha]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|
@ -311,24 +314,43 @@ class AppendableLine(LineGenerator):
|
||||||
# create origin
|
# create origin
|
||||||
self.drawn_points.append(self.target_points[0])
|
self.drawn_points.append(self.target_points[0])
|
||||||
# and drawing head
|
# and drawing head
|
||||||
self.drawn_points.append(self.target_points[0])
|
# self.drawn_points.append(self.target_points[0])
|
||||||
|
|
||||||
|
target_l = shapely.geometry.LineString(self.target_points).length
|
||||||
|
current_l = shapely.geometry.LineString(self.drawn_points).length if len(self.drawn_points) > 1 else 0
|
||||||
|
req_l = exponentialDecayRounded(self.current_l, target_l, self.draw_decay_speed, dt, .05)
|
||||||
|
|
||||||
|
if np.isclose(req_l, target_l):
|
||||||
|
self.drawn_points = self.target_points
|
||||||
|
else:
|
||||||
|
distance_to_do = req_l - current_l
|
||||||
|
|
||||||
|
idx = len(self.drawn_points) - 1
|
||||||
|
while distance_to_do:
|
||||||
|
target = self.target_points[idx]
|
||||||
|
this_distance = np.array(self.drawn_points[-1] ) - np.array(target)
|
||||||
|
if this_distance > distance_to_do:
|
||||||
|
break
|
||||||
|
distance_to_do - this_distance
|
||||||
|
|
||||||
|
idx+=1
|
||||||
|
self.drawn_points.append(target)
|
||||||
|
|
||||||
idx = len(self.drawn_points) - 1
|
|
||||||
target = self.target_points[idx]
|
|
||||||
|
|
||||||
if np.isclose(self.drawn_points[-1], target, atol=.05).all():
|
|
||||||
# TODO: might want to migrate to np.isclose()
|
|
||||||
if len(self.drawn_points) == len(self.target_points):
|
|
||||||
self.ready = True
|
|
||||||
return # done until a new point is added
|
|
||||||
|
|
||||||
# add new point as drawing head
|
|
||||||
self.drawn_points.append(self.drawn_points[-1])
|
|
||||||
self.ready = False
|
|
||||||
|
|
||||||
x = exponentialDecayRounded(self.drawn_points[-1][0], target[0], self.draw_decay_speed, dt, .05)
|
if np.isclose(self.drawn_points[-1], self.target_points[idx], atol=.05).all():
|
||||||
y = exponentialDecayRounded(self.drawn_points[-1][1], target[1], self.draw_decay_speed, dt, .05)
|
# TODO: might want to migrate to np.isclose()
|
||||||
self.drawn_points[-1] = (float(x), float(y))
|
if len(self.drawn_points) == len(self.target_points):
|
||||||
|
self.ready = True
|
||||||
|
return # done until a new point is added
|
||||||
|
|
||||||
|
# add new point as drawing head
|
||||||
|
self.drawn_points.append(self.drawn_points[-1])
|
||||||
|
self.ready = False
|
||||||
|
|
||||||
|
x = exponentialDecayRounded(self.drawn_points[-1][0], target[0], self.draw_decay_speed, dt, .05)
|
||||||
|
y = exponentialDecayRounded(self.drawn_points[-1][1], target[1], self.draw_decay_speed, dt, .05)
|
||||||
|
self.drawn_points[-1] = (float(x), float(y))
|
||||||
|
|
||||||
class ProceduralChain(LineGenerator):
|
class ProceduralChain(LineGenerator):
|
||||||
"""A line that can be 'dragged' to a target. In which
|
"""A line that can be 'dragged' to a target. In which
|
||||||
|
|
@ -640,13 +662,21 @@ class CropLine(LineAnimator):
|
||||||
Crop the line at a max nr of points (thus not actual lenght!)
|
Crop the line at a max nr of points (thus not actual lenght!)
|
||||||
Keeps the tail, removes the start
|
Keeps the tail, removes the start
|
||||||
"""
|
"""
|
||||||
def __init__(self, target_line = None, max_points = 200):
|
def __init__(self, target_line = None, max_points: Optional[int] = None, start_offset: Optional[int] = None):
|
||||||
super().__init__(target_line)
|
super().__init__(target_line)
|
||||||
|
self.start_offset = start_offset
|
||||||
self.max_points = max_points
|
self.max_points = max_points
|
||||||
self.ready = True # static filter, always ready
|
self.ready = True # static filter, always ready
|
||||||
|
|
||||||
def apply(self, target_line: RenderableLine, dt: DeltaT) -> RenderableLine:
|
def apply(self, target_line: RenderableLine, dt: DeltaT) -> RenderableLine:
|
||||||
target_line.points = target_line.points[-1 * self.max_points:]
|
if self.start_offset:
|
||||||
|
if len(target_line) <= self.start_offset:
|
||||||
|
return RenderableLine([])
|
||||||
|
target_line.points = target_line.points[self.start_offset:]
|
||||||
|
|
||||||
|
if self.max_points:
|
||||||
|
target_line.points = target_line.points[-1 * self.max_points:]
|
||||||
|
|
||||||
return target_line
|
return target_line
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -834,7 +864,7 @@ class NoiseLine(LineAnimator):
|
||||||
# noise_y = noise([x * frequency, (y + dt) * frequency]) * amplitude * normal_y
|
# noise_y = noise([x * frequency, (y + dt) * frequency]) * amplitude * normal_y
|
||||||
noise = snoise2(i * frequency, t % 1000, octaves=4)
|
noise = snoise2(i * frequency, t % 1000, octaves=4)
|
||||||
|
|
||||||
use_amp = amplitude
|
use_amp = amplitude[i] if hasattr(amplitude, "__getitem__") else amplitude
|
||||||
if fade_over_n_points > 0:
|
if fade_over_n_points > 0:
|
||||||
rev_step = len(drawable_points) - i
|
rev_step = len(drawable_points) - i
|
||||||
amp_factor = rev_step / fade_over_n_points
|
amp_factor = rev_step / fade_over_n_points
|
||||||
|
|
@ -905,6 +935,19 @@ class SegmentLine(LineAnimator):
|
||||||
return (ls.length * t, ls.length)
|
return (ls.length * t, ls.length)
|
||||||
else:
|
else:
|
||||||
return (0, ls.length * t)
|
return (0, ls.length * t)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def anim_follow_in_front(cls, t: float, ls: shapely.geometry.LineString):
|
||||||
|
keypoints = [
|
||||||
|
(0.0, 0, 0), #
|
||||||
|
(0.07, 1.0, 2.0), #
|
||||||
|
(0.3, 1.5, 2.0), # At t=0.7, value=0.5
|
||||||
|
(0.6, 1, 1.5), # At t=0.7, value=0.5
|
||||||
|
(0.7, 1.2, 1.5), # At t=0.7, value=0.5
|
||||||
|
(0.7, 1, 1.8), # At t=0.7, value=0.5
|
||||||
|
(1.0, .5, ls.length), # At t=1, value=0
|
||||||
|
]
|
||||||
|
return KeyframeAnimator(keypoints, easeInOutQuad).get_value(t)
|
||||||
|
|
||||||
|
|
||||||
def apply(self, target_line: RenderableLine, dt: DeltaT):
|
def apply(self, target_line: RenderableLine, dt: DeltaT):
|
||||||
|
|
@ -925,6 +968,57 @@ class SegmentLine(LineAnimator):
|
||||||
return RenderableLine.from_linestring(line, target_line.points[0].color)
|
return RenderableLine.from_linestring(line, target_line.points[0].color)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class KeyframeAnimator:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
keypoints: List[Tuple[float, float, float]],
|
||||||
|
easing_func: Callable[[float], float] = None,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Initialize the animator with keypoints and an optional easing function.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
keypoints: List of (t_value, value) tuples, where t_value is in [0, 1].
|
||||||
|
easing_func: Optional function to apply easing to `t` (e.g., easeInOutQuad).
|
||||||
|
"""
|
||||||
|
self.keypoints = sorted(keypoints, key=lambda x: x[0])
|
||||||
|
self.easing_func = easing_func
|
||||||
|
|
||||||
|
def get_value(self, t: float) -> float:
|
||||||
|
"""
|
||||||
|
Get the interpolated value at time `t` (0 <= t <= 1).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
t: Normalized time (0 to 1).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Interpolated value at time `t`.
|
||||||
|
"""
|
||||||
|
if self.easing_func:
|
||||||
|
t = self.easing_func(t)
|
||||||
|
|
||||||
|
# Handle edge cases
|
||||||
|
if t <= self.keypoints[0][0]:
|
||||||
|
return self.keypoints[0][1], self.keypoints[0][2]
|
||||||
|
if t >= self.keypoints[-1][0]:
|
||||||
|
return self.keypoints[-1][1], self.keypoints[-1][2]
|
||||||
|
|
||||||
|
# Find the two keypoints surrounding `t`
|
||||||
|
for i in range(len(self.keypoints) - 1):
|
||||||
|
t0, val0, valb0 = self.keypoints[i]
|
||||||
|
t1, val1, valb1 = self.keypoints[i + 1]
|
||||||
|
|
||||||
|
if t0 <= t <= t1:
|
||||||
|
# Normalize `t` between t0 and t1
|
||||||
|
local_t = inv_lerp(t0, t1, t)
|
||||||
|
|
||||||
|
# Interpolate between val0 and val1
|
||||||
|
|
||||||
|
return lerp(val0, val1, local_t), lerp(valb0, valb1, local_t)
|
||||||
|
|
||||||
|
return self.keypoints[-1][1], self.keypoints[-1][2] # fallback
|
||||||
|
|
||||||
|
|
||||||
class DashedLine(LineAnimator):
|
class DashedLine(LineAnimator):
|
||||||
|
|
@ -980,9 +1074,12 @@ class DashedLine(LineAnimator):
|
||||||
if len(target_line) < 2:
|
if len(target_line) < 2:
|
||||||
self.ready = True
|
self.ready = True
|
||||||
return target_line
|
return target_line
|
||||||
|
|
||||||
|
gap_len = gap_len if not callable(self.gap_len) else self.gap_len(dt, self.running_for())
|
||||||
|
dash_len = dash_len if not callable(self.dash_len) else self.dash_len(dt, self.running_for())
|
||||||
|
|
||||||
ls = target_line.as_linestring()
|
ls = target_line.as_linestring()
|
||||||
multilinestring = self.dashed_line(ls, self.dash_len, self.gap_len, self.t_factor * self.running_for(), self.loop_offset)
|
multilinestring = self.dashed_line(ls, dash_len, gap_len, self.t_factor * self.running_for(), self.loop_offset)
|
||||||
|
|
||||||
|
|
||||||
# when looping, it is always ready, otherwise, only if totally gone
|
# when looping, it is always ready, otherwise, only if totally gone
|
||||||
|
|
@ -1019,17 +1116,54 @@ class StartFromClosestPoint(LineAnimator):
|
||||||
distance = math.inf
|
distance = math.inf
|
||||||
idx = 0
|
idx = 0
|
||||||
from_point = np.array(self.from_point)
|
from_point = np.array(self.from_point)
|
||||||
|
# print(from_point)
|
||||||
|
|
||||||
for i, point in enumerate(target_line.points):
|
for i, point in enumerate(target_line.points):
|
||||||
|
if i < 2:
|
||||||
|
continue # skip the first to avoid jitter
|
||||||
|
|
||||||
to_point = np.array(point.position)
|
to_point = np.array(point.position)
|
||||||
new_distance = np.linalg.norm(from_point-to_point)
|
new_distance = np.linalg.norm(from_point-to_point)
|
||||||
if new_distance < distance:
|
if new_distance < distance:
|
||||||
distance = new_distance
|
distance = new_distance
|
||||||
idx = i
|
idx = i+1
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
# print(idx, target_line.points[idx])
|
|
||||||
return RenderableLine(target_line.points[idx:])
|
if idx >= len(target_line.points):
|
||||||
|
logger.warning("Empty line")
|
||||||
|
return RenderableLine([])
|
||||||
|
points = []
|
||||||
|
if idx > 0:
|
||||||
|
p = target_line.points[idx]
|
||||||
|
p.position = self.from_point
|
||||||
|
|
||||||
|
points.append(p)
|
||||||
|
points.extend(target_line.points[idx:])
|
||||||
|
# print(from_point, idx, [p.position for p in target_line.points[max(0,idx-5):idx+5]])
|
||||||
|
return RenderableLine(points)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def find_closest_to_point(cls, point: Tuple[Coordinate], points: List[Coordinate]):
|
||||||
|
from_point = np.array(point)
|
||||||
|
# idx = 0
|
||||||
|
# distance = math.inf
|
||||||
|
if len(points) == 0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# print(points)
|
||||||
|
# print(points- from_point)
|
||||||
|
# print(np.array(points) - from_point)
|
||||||
|
idx = np.argmin(np.linalg.norm(np.array(points) - from_point, axis=1))
|
||||||
|
# for i, to_point in enumerate(points):
|
||||||
|
# to_point = np.array(to_point)
|
||||||
|
# new_distance = np.linalg.norm(from_point-to_point)
|
||||||
|
# if new_distance < distance:
|
||||||
|
# distance = new_distance
|
||||||
|
# idx = i+1
|
||||||
|
# else:
|
||||||
|
# break
|
||||||
|
return idx
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1053,11 +1187,19 @@ class RotatingLine(LineAnimator):
|
||||||
self.ready = True
|
self.ready = True
|
||||||
return target_line
|
return target_line
|
||||||
|
|
||||||
origin = target_line.points[0]
|
|
||||||
|
|
||||||
if len(self.drawn_points) < 1:
|
if len(self.drawn_points) < 1:
|
||||||
self.drawn_points = target_line.points
|
self.drawn_points = target_line.points
|
||||||
|
|
||||||
|
# find closest point to start from:
|
||||||
|
|
||||||
|
origin = target_line.points[0]
|
||||||
|
# closest_idx = StartFromClosestPoint.find_closest_to_point(origin.position, [p.position for p in self.drawn_points])
|
||||||
|
# if closest_idx:
|
||||||
|
# print('cut at', closest_idx)
|
||||||
|
# self.drawn_points = self.drawn_points[closest_idx:] # hard cutof
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
diff_length = len(target_line) - len(self.drawn_points)
|
diff_length = len(target_line) - len(self.drawn_points)
|
||||||
if diff_length < 0: # drawn points is larger
|
if diff_length < 0: # drawn points is larger
|
||||||
self.drawn_points = self.drawn_points[:len(target_line)]
|
self.drawn_points = self.drawn_points[:len(target_line)]
|
||||||
|
|
@ -1095,56 +1237,218 @@ class RotatingLine(LineAnimator):
|
||||||
is_ready.append(np.isclose(drawn_point.position, target_point.position, atol=.05).all())
|
is_ready.append(np.isclose(drawn_point.position, target_point.position, atol=.05).all())
|
||||||
|
|
||||||
self.ready = all(is_ready)
|
self.ready = all(is_ready)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return RenderableLine(self.drawn_points)
|
return RenderableLine(self.drawn_points)
|
||||||
|
|
||||||
|
from scipy.ndimage import gaussian_filter1d
|
||||||
|
from scipy.signal import find_peaks
|
||||||
|
|
||||||
class NoodleWiggler(LineAnimator):
|
class NoodleWiggler(LineAnimator):
|
||||||
"""When a line is 'noodling' don't draw it as a whole, but sway it animated
|
"""When a line is 'noodling' don't draw it as a whole, but sway it animated
|
||||||
|
|
||||||
|
Work in progress
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, target_line = None):
|
def __init__(self, target_line = None):
|
||||||
super().__init__(target_line)
|
super().__init__(target_line)
|
||||||
|
self.range = 10
|
||||||
|
self.threshold = 1.8
|
||||||
|
self.smoothing_sigma = 2
|
||||||
|
self.t = 0
|
||||||
|
self.t_factor = 4
|
||||||
|
|
||||||
def apply(self, target_line, dt):
|
def apply(self, target_line, dt):
|
||||||
return target_line
|
if len(target_line) < 2:
|
||||||
|
return target_line
|
||||||
|
|
||||||
|
self.t += dt
|
||||||
|
|
||||||
|
scores = []
|
||||||
|
distances = [np.linalg.norm(np.array(a.position)- np.array(b.position)) for a,b in zip(target_line.points, target_line.points[1:])]
|
||||||
|
|
||||||
|
# 1) find points with high distance traveled relative to displacement. This is noodling
|
||||||
|
for i, point in enumerate(target_line.points):
|
||||||
|
if i < self.range:
|
||||||
|
scores.append(0)
|
||||||
|
if len(target_line.points) < i + self.range:
|
||||||
|
scores.append(0)
|
||||||
|
|
||||||
|
a = target_line.points[i-self.range]
|
||||||
|
b = target_line.points[i+self.range]
|
||||||
|
|
||||||
|
net_distance = np.linalg.norm(np.array(a.position)- np.array(b.position))
|
||||||
|
gross_distance = sum(distances[i-self.range:i-self.range-1])
|
||||||
|
self.scores.append(max(0, gross_distance/net_distance - self.threshold))
|
||||||
|
|
||||||
|
# 2) smooth the curve
|
||||||
|
smoothed_scores = gaussian_filter1d(scores, sigma=self.smoothing_sigma)
|
||||||
|
|
||||||
|
# 3) find the peaks ; most intense noodling points
|
||||||
|
peak_idxs, _ = find_peaks(smoothed_scores, height=0.5)
|
||||||
|
|
||||||
|
# 4) for peaks, find start-peak-end indexes
|
||||||
|
segments = self.connect_peaks_to_segments(smoothed_scores, peak_idxs)
|
||||||
|
|
||||||
|
points, scores = self.replace_noodling_segments(scores, target_line, segments, 4)
|
||||||
|
|
||||||
|
# 5) apply noise according to score
|
||||||
|
new_points = NoiseLine.apply_perlin_noise_to_line_normal(
|
||||||
|
drawable_points=[p.position for p in points],
|
||||||
|
amplitude=scores,
|
||||||
|
t = self.t * self.t_factor,
|
||||||
|
frequency= 2
|
||||||
|
)
|
||||||
|
|
||||||
|
# 6) rebuild as line
|
||||||
|
points = []
|
||||||
|
for point, pos in zip(target_line.points, new_positions):
|
||||||
|
p = copy.deepcopy(point)
|
||||||
|
p.position = pos
|
||||||
|
points.append(p)
|
||||||
|
|
||||||
|
|
||||||
|
return RenderableLine(points)
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def connect_peaks_to_segments(cls, scores, peaks, threshold=0.3, min_segment_length=1) -> List [Tuple[int, int, int]]:
|
||||||
|
"""
|
||||||
|
returns a list of tuples: (start_idx, peak_idx, end_idx)
|
||||||
|
"""
|
||||||
|
segments = []
|
||||||
|
i = 0
|
||||||
|
n = len(scores)
|
||||||
|
|
||||||
|
while i < len(peaks):
|
||||||
|
peak = peaks[i]
|
||||||
|
start = peak
|
||||||
|
end = peak
|
||||||
|
|
||||||
|
# Expand left
|
||||||
|
while start > 0 and scores[start - 1] > threshold:
|
||||||
|
start -= 1
|
||||||
|
|
||||||
|
# Expand right
|
||||||
|
while end < n - 1 and scores[end + 1] > threshold:
|
||||||
|
end += 1
|
||||||
|
|
||||||
|
# Merge with next peak if close
|
||||||
|
if i < len(peaks) - 1 and peaks[i + 1] - end < min_segment_length:
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
segments.append((start, peak, end))
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
return segments
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def detect_noodling_sections(cls, line, segment_length=0.1, ratio_threshold=2.0):
|
def replace_noodling_segments(cls, scores: List[float], target_line: RenderableLine, segments, num_interpolated_points = 4):
|
||||||
"""
|
"""
|
||||||
Detects noodling sections in a LineString using the ratio of actual length to straight-line distance.
|
Replace noodling segments in target_line with a fixed number of interpolated points,
|
||||||
|
while preserving all non-noodling sections.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
line (LineString): Input LineString.
|
target_line: Object with a `points` attribute (list of point objects).
|
||||||
segment_length (float): Length of each segment as a fraction of the total line length.
|
segments: List of tuples (start_index, end_index) for noodling segments.
|
||||||
ratio_threshold (float): Threshold for the ratio (actual_length / straight_line_distance).
|
num_interpolated_points: Number of intermediate points to insert between start and end.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
list: List of noodling segments (as LineStrings).
|
A new list of points with noodling segments replaced.
|
||||||
"""
|
"""
|
||||||
noodling_sections = []
|
new_points = []
|
||||||
total_length = line.length
|
new_scores = []
|
||||||
segment_start = 0.0
|
i = 0
|
||||||
|
n = len(target_line.points)
|
||||||
|
|
||||||
while segment_start < 1.0:
|
for start, peak, end in segments:
|
||||||
segment_end = min(segment_start + segment_length, 1.0)
|
# Add all points up to the start of the noodling segment
|
||||||
segment = substring(line, segment_start, segment_end, normalized=True)
|
while i < start:
|
||||||
|
new_points.append(target_line.points[i])
|
||||||
|
new_scores.append(scores[i])
|
||||||
|
i += 1
|
||||||
|
|
||||||
# Calculate straight-line distance between start and end points
|
# Skip the noodling segment and add interpolated points
|
||||||
start_point = Point(segment.coords[0])
|
start_point = target_line.points[start]
|
||||||
end_point = Point(segment.coords[-1])
|
peak_point = target_line.points[peak]
|
||||||
straight_line_distance = start_point.distance(end_point)
|
end_point = target_line.points[end]
|
||||||
|
|
||||||
# Calculate actual length of the segment
|
start_score = scores[start]
|
||||||
actual_length = segment.length
|
peak_score = scores[peak]
|
||||||
|
end_score = scores[end]
|
||||||
|
|
||||||
# Check if the ratio exceeds the threshold
|
# Interpolate between start and peak
|
||||||
if straight_line_distance > 0 and (actual_length / straight_line_distance) > ratio_threshold:
|
for j in range(num_interpolated_points + 2): # +2 to include start and peak
|
||||||
noodling_sections.append(segment)
|
t = inv_lerp(0, num_interpolated_points + 1, j)
|
||||||
|
new_x = lerp(start_point.position[0], peak_point.position[0], t)
|
||||||
|
new_y = lerp(start_point.position[1], peak_point.position[1], t)
|
||||||
|
new_score = lerp(start_score, peak_score, t)
|
||||||
|
new_point = RenderablePoint(position=(new_x, new_y), color=start_point.color)
|
||||||
|
new_points.append(new_point)
|
||||||
|
new_scores.append(new_score)
|
||||||
|
|
||||||
segment_start = segment_end
|
# Interpolate between peak and end (skip peak to avoid duplication)
|
||||||
|
for j in range(1, num_interpolated_points + 2): # +2 to include peak and end
|
||||||
|
t = inv_lerp(0, num_interpolated_points + 1, j)
|
||||||
|
new_x = lerp(peak_point.position[0], end_point.position[0], t)
|
||||||
|
new_y = lerp(peak_point.position[1], end_point.position[1], t)
|
||||||
|
new_score = lerp(peak_score, end_score, t)
|
||||||
|
new_point = RenderablePoint(position=(new_x, new_y), color=start_point.color)
|
||||||
|
new_points.append(new_point)
|
||||||
|
new_scores.append(new_score)
|
||||||
|
|
||||||
return noodling_sections
|
i = end + 1 # Skip to the end of the noodling segment
|
||||||
|
|
||||||
|
# Add remaining points after the last noodling segment
|
||||||
|
while i < n:
|
||||||
|
new_points.append(target_line.points[i])
|
||||||
|
new_scores.append(scores[i])
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
return new_points, scores
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# @classmethod
|
||||||
|
# def detect_noodling_sections(cls, line, segment_length=0.1, ratio_threshold=2.0):
|
||||||
|
# """
|
||||||
|
# Detects noodling sections in a LineString using the ratio of actual length to straight-line distance.
|
||||||
|
|
||||||
|
# Args:
|
||||||
|
# line (LineString): Input LineString.
|
||||||
|
# segment_length (float): Length of each segment as a fraction of the total line length.
|
||||||
|
# ratio_threshold (float): Threshold for the ratio (actual_length / straight_line_distance).
|
||||||
|
|
||||||
|
# Returns:
|
||||||
|
# list: List of noodling segments (as LineStrings).
|
||||||
|
# """
|
||||||
|
# noodling_sections = []
|
||||||
|
# total_length = line.length
|
||||||
|
# segment_start = 0.0
|
||||||
|
|
||||||
|
# while segment_start < 1.0:
|
||||||
|
# segment_end = min(segment_start + segment_length, 1.0)
|
||||||
|
# segment = substring(line, segment_start, segment_end, normalized=True)
|
||||||
|
|
||||||
|
# # Calculate straight-line distance between start and end points
|
||||||
|
# start_point = Point(segment.coords[0])
|
||||||
|
# end_point = Point(segment.coords[-1])
|
||||||
|
# straight_line_distance = start_point.distance(end_point)
|
||||||
|
|
||||||
|
# # Calculate actual length of the segment
|
||||||
|
# actual_length = segment.length
|
||||||
|
|
||||||
|
# # Check if the ratio exceeds the threshold
|
||||||
|
# if straight_line_distance > 0 and (actual_length / straight_line_distance) > ratio_threshold:
|
||||||
|
# noodling_sections.append(segment)
|
||||||
|
|
||||||
|
# segment_start = segment_end
|
||||||
|
|
||||||
|
# return noodling_sections
|
||||||
|
|
||||||
# # Example usage:
|
# # Example usage:
|
||||||
# line = LineString([(0, 0), (0.1, 0.1), (0.2, 0.2), (0.3, 0.1), (0.4, 0.2), (1, 1)])
|
# line = LineString([(0, 0), (0.1, 0.1), (0.2, 0.2), (0.3, 0.1), (0.4, 0.2), (1, 1)])
|
||||||
|
|
|
||||||
|
|
@ -449,7 +449,9 @@ class PredictionServer(Node):
|
||||||
# print('preds', len(predictions[0][0]))
|
# print('preds', len(predictions[0][0]))
|
||||||
|
|
||||||
if not len(history) or np.isnan(history[-1]).any():
|
if not len(history) or np.isnan(history[-1]).any():
|
||||||
logger.warning('skip for no history')
|
logger.warning(f'skip for no history @ {ts_key} [{len(prediction_dict)=}, {len(histories_dict)=}, {len(futures_dict)=}]')
|
||||||
|
logger.info(f"{preds=}")
|
||||||
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# response[node.id] = {
|
# response[node.id] = {
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,18 @@ def process_data(src_dir: Path, dst_dir: Path, name: str, smooth_tracks: bool, n
|
||||||
tracks = filter.apply(tracks, camera)
|
tracks = filter.apply(tracks, camera)
|
||||||
print(f"Filtered: {len(tracks)} tracks")
|
print(f"Filtered: {len(tracks)} tracks")
|
||||||
|
|
||||||
|
skip_idxs = []
|
||||||
|
for idx, track in enumerate(tracks):
|
||||||
|
track_history = track.get_projected_history(camera=camera)
|
||||||
|
distances = np.sqrt(np.sum(np.diff(track_history, axis=0)**2, axis=1))
|
||||||
|
# print(trajectory_org)
|
||||||
|
# print(distances)
|
||||||
|
if any(distances > 3):
|
||||||
|
skip_idxs.append(idx)
|
||||||
|
for idx in skip_idxs:
|
||||||
|
tracks.pop(idx)
|
||||||
|
print(f"Filtered {len(skip_idxs)} tracks which contained leaps")
|
||||||
|
|
||||||
total = len(tracks)
|
total = len(tracks)
|
||||||
bar = tqdm.tqdm(total=total)
|
bar = tqdm.tqdm(total=total)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ class Settings(Node):
|
||||||
self.load()
|
self.load()
|
||||||
|
|
||||||
dpg.create_context()
|
dpg.create_context()
|
||||||
dpg.create_viewport(title='Trap settings', width=600, height=200)
|
dpg.create_viewport(title='Trap settings', width=600, height=1200)
|
||||||
dpg.setup_dearpygui()
|
dpg.setup_dearpygui()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -38,10 +38,11 @@ class Settings(Node):
|
||||||
dpg.add_text(f"Settings from {self.config.settings_file}")
|
dpg.add_text(f"Settings from {self.config.settings_file}")
|
||||||
dpg.add_button(label="Save", callback=self.save)
|
dpg.add_button(label="Save", callback=self.save)
|
||||||
|
|
||||||
with dpg.window(label="Stage", pos=(0, 150)):
|
with dpg.window(label="Stage", pos=(150, 0)):
|
||||||
self.register_setting(f'stage.fps', dpg.add_checkbox(label="FPS cap", default_value=self.get_setting(f'stage.fps', 30), callback=self.on_change))
|
self.register_setting(f'stage.fps', dpg.add_slider_int(label="FPS cap", default_value=self.get_setting(f'stage.fps', 30), callback=self.on_change))
|
||||||
|
self.register_setting(f'stage.prediction_interval', dpg.add_slider_int(label="prediction interval", default_value=self.get_setting('stage.prediction_interval', 18), callback=self.on_change))
|
||||||
|
|
||||||
with dpg.window(label="Lidar", pos=(0, 250)):
|
with dpg.window(label="Lidar", pos=(0, 100), autosize=True):
|
||||||
self.register_setting(f'lidar.crop_map_boundaries', dpg.add_checkbox(label="crop_map_boundaries", default_value=self.get_setting(f'lidar.crop_map_boundaries', True), callback=self.on_change))
|
self.register_setting(f'lidar.crop_map_boundaries', dpg.add_checkbox(label="crop_map_boundaries", default_value=self.get_setting(f'lidar.crop_map_boundaries', True), callback=self.on_change))
|
||||||
self.register_setting(f'lidar.viz_cropping', dpg.add_checkbox(label="viz_cropping", default_value=self.get_setting(f'lidar.viz_cropping', True), callback=self.on_change))
|
self.register_setting(f'lidar.viz_cropping', dpg.add_checkbox(label="viz_cropping", default_value=self.get_setting(f'lidar.viz_cropping', True), callback=self.on_change))
|
||||||
self.register_setting(f'lidar.tracking_enabled', dpg.add_checkbox(label="tracking_enabled", default_value=self.get_setting(f'lidar.tracking_enabled', True), callback=self.on_change))
|
self.register_setting(f'lidar.tracking_enabled', dpg.add_checkbox(label="tracking_enabled", default_value=self.get_setting(f'lidar.tracking_enabled', True), callback=self.on_change))
|
||||||
|
|
@ -60,9 +61,9 @@ class Settings(Node):
|
||||||
dpg.add_separator(label="Cluster filter")
|
dpg.add_separator(label="Cluster filter")
|
||||||
self.register_setting(f'lidar.min_box_area', dpg.add_slider_float(label="min_box_area", default_value=self.get_setting(f'lidar.min_box_area', .1), min_value=0, max_value=1, callback=self.on_change))
|
self.register_setting(f'lidar.min_box_area', dpg.add_slider_float(label="min_box_area", default_value=self.get_setting(f'lidar.min_box_area', .1), min_value=0, max_value=1, callback=self.on_change))
|
||||||
|
|
||||||
for lidar in ["192.168.0.16", "192.168.0.10"]:
|
for i, lidar in enumerate(["192.168.0.16", "192.168.0.10"]):
|
||||||
name = lidar.replace(".", "_")
|
name = lidar.replace(".", "_")
|
||||||
with dpg.window(label=f"Lidar {lidar}", pos=(200, 0),autosize=True):
|
with dpg.window(label=f"Lidar {lidar}", pos=(i * 300, 450),autosize=True):
|
||||||
# dpg.add_text("test")
|
# dpg.add_text("test")
|
||||||
# dpg.add_input_text(label="string", default_value="Quick brown fox")
|
# dpg.add_input_text(label="string", default_value="Quick brown fox")
|
||||||
self.register_setting(f'lidar.{name}.enabled', dpg.add_checkbox(label="enabled", default_value=self.get_setting(f'lidar.{name}.enabled', True), callback=self.on_change))
|
self.register_setting(f'lidar.{name}.enabled', dpg.add_checkbox(label="enabled", default_value=self.get_setting(f'lidar.{name}.enabled', True), callback=self.on_change))
|
||||||
|
|
|
||||||
107
trap/stage.py
107
trap/stage.py
|
|
@ -25,6 +25,7 @@ from trap.counter import CounterSender
|
||||||
from trap.lines import AppendableLine, AppendableLineAnimator, Coordinate, CoordinateSpace, CropAnimationLine, CropLine, DashedLine, DeltaT, FadeOutJitterLine, FadeOutLine, FadedEndsLine, FadedTailLine, LineAnimationStack, LineAnimator, NoiseLine, RenderableLayers, RenderableLine, RenderableLines, RotatingLine, SegmentLine, SimplifyLine, SimplifyMethod, SrgbaColor, StartFromClosestPoint, StaticLine, layers_to_message, load_lines_from_svg
|
from trap.lines import AppendableLine, AppendableLineAnimator, Coordinate, CoordinateSpace, CropAnimationLine, CropLine, DashedLine, DeltaT, FadeOutJitterLine, FadeOutLine, FadedEndsLine, FadedTailLine, LineAnimationStack, LineAnimator, NoiseLine, RenderableLayers, RenderableLine, RenderableLines, RotatingLine, SegmentLine, SimplifyLine, SimplifyMethod, SrgbaColor, StartFromClosestPoint, StaticLine, layers_to_message, load_lines_from_svg
|
||||||
from trap.node import Node
|
from trap.node import Node
|
||||||
from trap.track_history import TrackHistory
|
from trap.track_history import TrackHistory
|
||||||
|
from trap.utils import lerp
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger('trap.stage')
|
logger = logging.getLogger('trap.stage')
|
||||||
|
|
@ -39,13 +40,13 @@ TRACK_ASSUMED_FPS = 12
|
||||||
|
|
||||||
|
|
||||||
LOST_FADEOUT = 2 # seconds
|
LOST_FADEOUT = 2 # seconds
|
||||||
PREDICTION_INTERVAL: float|None = 20 # frames
|
PREDICTION_INTERVAL: int|None = int(TRACK_ASSUMED_FPS * 1.2) # frames
|
||||||
PREDICTION_FADE_IN: float = 3
|
PREDICTION_FADE_IN: float = 3
|
||||||
PREDICTION_FADE_SLOPE: float = -10
|
PREDICTION_FADE_SLOPE: float = -10
|
||||||
PREDICTION_FADE_AFTER_DURATION: float = 8 # seconds
|
PREDICTION_FADE_AFTER_DURATION: float = 8 # seconds
|
||||||
PREDICTION_END_FADE = 2 #frames
|
PREDICTION_END_FADE = 2 #frames
|
||||||
# TRACK_MAX_POINTS = 100
|
# TRACK_MAX_POINTS = 100
|
||||||
TRACK_FADE_AFTER_DURATION = 15. # seconds
|
TRACK_FADE_AFTER_DURATION = 9. # seconds
|
||||||
TRACK_END_FADE = 30 # points
|
TRACK_END_FADE = 30 # points
|
||||||
TRACK_FADE_ASSUME_FPS = TRACK_ASSUMED_FPS
|
TRACK_FADE_ASSUME_FPS = TRACK_ASSUMED_FPS
|
||||||
|
|
||||||
|
|
@ -70,15 +71,16 @@ class SceneInfo:
|
||||||
priority: int
|
priority: int
|
||||||
description: str = ""
|
description: str = ""
|
||||||
takeover_possible: bool = False # whether to allow for other scenarios to steal the stage
|
takeover_possible: bool = False # whether to allow for other scenarios to steal the stage
|
||||||
|
takeover_possible_after: float = -1
|
||||||
|
|
||||||
class ScenarioScene(Enum):
|
class ScenarioScene(Enum):
|
||||||
DETECTED = SceneInfo(4, "First detection")
|
DETECTED = SceneInfo(4, "First detection")
|
||||||
SUBSTANTIAL = SceneInfo(6, "Multiple detections")
|
SUBSTANTIAL = SceneInfo(6, "Multiple detections")
|
||||||
FIRST_PREDICTION = SceneInfo(10, "Prediction is ready")
|
FIRST_PREDICTION = SceneInfo(10, "Prediction is ready")
|
||||||
CORRECTED_PREDICTION = SceneInfo(11, "Multiple predictions")
|
CORRECTED_PREDICTION = SceneInfo(11, "Multiple predictions")
|
||||||
LOITERING = SceneInfo(7, "Foundto be loitering", takeover_possible=True) # TODO: create "possible after"
|
LOITERING = SceneInfo(7, "Foundto be loitering", takeover_possible=True, takeover_possible_after=10) # TODO: create "possible after"
|
||||||
PLAY = SceneInfo(7, description="After many predictions; just fooling around", takeover_possible=True)
|
PLAY = SceneInfo(7, description="After many predictions; just fooling around", takeover_possible=True, takeover_possible_after=10)
|
||||||
LOST = SceneInfo(-1, description="Track lost", takeover_possible=True)
|
LOST = SceneInfo(-1, description="Track lost", takeover_possible=True, takeover_possible_after=0)
|
||||||
|
|
||||||
Time = float
|
Time = float
|
||||||
|
|
||||||
|
|
@ -131,8 +133,9 @@ class PrioritySlotItem():
|
||||||
|
|
||||||
|
|
||||||
class Scenario(PrioritySlotItem):
|
class Scenario(PrioritySlotItem):
|
||||||
def __init__(self, track_id):
|
def __init__(self, track_id, stage: Stage):
|
||||||
super().__init__(track_id)
|
super().__init__(track_id)
|
||||||
|
self.stage = stage
|
||||||
self.track_id = track_id
|
self.track_id = track_id
|
||||||
self.scene: ScenarioScene = ScenarioScene.DETECTED
|
self.scene: ScenarioScene = ScenarioScene.DETECTED
|
||||||
|
|
||||||
|
|
@ -161,7 +164,10 @@ class Scenario(PrioritySlotItem):
|
||||||
return self.scene.value.priority
|
return self.scene.value.priority
|
||||||
|
|
||||||
def can_be_taken_over(self):
|
def can_be_taken_over(self):
|
||||||
return self.scene.value.takeover_possible
|
if self.scene.value.takeover_possible:
|
||||||
|
if time.perf_counter() - self.state_change_at > self.scene.value.takeover_possible_after:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def track_age(self):
|
def track_age(self):
|
||||||
if not self.track:
|
if not self.track:
|
||||||
|
|
@ -291,8 +297,8 @@ class Scenario(PrioritySlotItem):
|
||||||
# in case of the unlikely event that prediction was received sooner
|
# in case of the unlikely event that prediction was received sooner
|
||||||
self.recv_track(track)
|
self.recv_track(track)
|
||||||
|
|
||||||
|
interval = self.stage.get_setting('stage.prediction_interval', PREDICTION_INTERVAL)
|
||||||
if PREDICTION_INTERVAL is not None and len(self.prediction_tracks) and (track.frame_index - self.prediction_tracks[-1].frame_index) < PREDICTION_INTERVAL:
|
if interval is not None and len(self.prediction_tracks) and (track.frame_index - self.prediction_tracks[-1].frame_index) < interval:
|
||||||
# just drop tracks if the predictions come to quick
|
# just drop tracks if the predictions come to quick
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -317,7 +323,7 @@ def build_line_others():
|
||||||
line_others = LineAnimationStack(StaticLine([], others_color))
|
line_others = LineAnimationStack(StaticLine([], others_color))
|
||||||
# line_others.add(SegmentLine(line_others.tail, duration=3, anim_f=partial(SegmentLine.anim_grow, in_and_out=True, max_len=5)))
|
# line_others.add(SegmentLine(line_others.tail, duration=3, anim_f=partial(SegmentLine.anim_grow, in_and_out=True, max_len=5)))
|
||||||
line_others.add(SimplifyLine(line_others.tail, 0.001)) # Simplify before effects, so they don't distort
|
line_others.add(SimplifyLine(line_others.tail, 0.001)) # Simplify before effects, so they don't distort
|
||||||
line_others.add(CropAnimationLine(line_others.tail, lambda dt: 10 + math.sin(dt/4) * 70, assume_fps=TRACK_ASSUMED_FPS*2)) # speed up
|
line_others.add(CropAnimationLine(line_others.tail, 70, assume_fps=TRACK_ASSUMED_FPS*2)) # speed up
|
||||||
line_others.add(NoiseLine(line_others.tail, amplitude=0, t_factor=.3))
|
line_others.add(NoiseLine(line_others.tail, amplitude=0, t_factor=.3))
|
||||||
# line_others.add(DashedLine(line_others.tail, t_factor=4, loop_offset=True))
|
# line_others.add(DashedLine(line_others.tail, t_factor=4, loop_offset=True))
|
||||||
# line_others.get(DashedLine).skip = True
|
# line_others.get(DashedLine).skip = True
|
||||||
|
|
@ -336,8 +342,8 @@ class DrawnScenario(Scenario):
|
||||||
MAX_HISTORY = 130 # points of history of trajectory to display (preventing too long lines)
|
MAX_HISTORY = 130 # points of history of trajectory to display (preventing too long lines)
|
||||||
CUT_GAP = 5 # when adding a new prediction, keep the existing prediction until that point + this CUT_GAP margin
|
CUT_GAP = 5 # when adding a new prediction, keep the existing prediction until that point + this CUT_GAP margin
|
||||||
|
|
||||||
def __init__(self, track_id):
|
def __init__(self, track_id, stage: Stage):
|
||||||
super().__init__(track_id)
|
super().__init__(track_id, stage)
|
||||||
self.last_update_t = time.perf_counter()
|
self.last_update_t = time.perf_counter()
|
||||||
self.active_ptrack: Optional[ProjectedTrack] = None
|
self.active_ptrack: Optional[ProjectedTrack] = None
|
||||||
|
|
||||||
|
|
@ -353,14 +359,25 @@ class DrawnScenario(Scenario):
|
||||||
|
|
||||||
self.prediction_color = SrgbaColor(0,1,0,1)
|
self.prediction_color = SrgbaColor(0,1,0,1)
|
||||||
self.line_prediction = LineAnimationStack(StaticLine([], self.prediction_color))
|
self.line_prediction = LineAnimationStack(StaticLine([], self.prediction_color))
|
||||||
|
|
||||||
|
self.line_prediction.add(CropLine(self.line_prediction.tail, start_offset=0))
|
||||||
|
self.line_prediction.add(StartFromClosestPoint(self.line_prediction.tail))
|
||||||
|
self.line_prediction.get(StartFromClosestPoint).skip=True
|
||||||
self.line_prediction.add(RotatingLine(self.line_prediction.tail, decay_speed=16))
|
self.line_prediction.add(RotatingLine(self.line_prediction.tail, decay_speed=16))
|
||||||
self.line_prediction.get(RotatingLine).skip = False
|
self.line_prediction.get(RotatingLine).skip = False
|
||||||
self.line_prediction.add(SegmentLine(self.line_prediction.tail, duration=.5))
|
self.line_prediction.add(SegmentLine(self.line_prediction.tail, duration=7, anim_f=SegmentLine.anim_follow_in_front))
|
||||||
self.line_prediction.get(SegmentLine).skip = True
|
self.line_prediction.get(SegmentLine).skip = False
|
||||||
self.line_prediction.add(SimplifyLine(self.line_prediction.tail, 0.003)) # Simplify before effects, so they don't distort
|
self.line_prediction.add(SimplifyLine(self.line_prediction.tail, 0.003)) # Simplify before effects, so they don't distort
|
||||||
self.line_prediction.add(DashedLine(self.line_prediction.tail, dash_len=.6, gap_len=0.9, t_factor=2, loop_offset=True))
|
GAP_DURATION = 5
|
||||||
self.line_prediction.get(DashedLine).skip = False
|
def dash_len(dt, t):
|
||||||
self.line_prediction.add(StartFromClosestPoint(self.line_prediction.tail))
|
t=min(1, t/GAP_DURATION)
|
||||||
|
return lerp(.99, .6, t)
|
||||||
|
def gap_len(dt, t):
|
||||||
|
t=min(1, t/GAP_DURATION)
|
||||||
|
return lerp(0.01, .9, t)
|
||||||
|
|
||||||
|
self.line_prediction.add(DashedLine(self.line_prediction.tail, dash_len=dash_len, gap_len=gap_len, t_factor=2, loop_offset=True))
|
||||||
|
self.line_prediction.get(DashedLine).skip = True
|
||||||
self.line_prediction.add(FadeOutLine(self.line_prediction.tail))
|
self.line_prediction.add(FadeOutLine(self.line_prediction.tail))
|
||||||
|
|
||||||
# when rendering tracks from others similar/close to the current one
|
# when rendering tracks from others similar/close to the current one
|
||||||
|
|
@ -372,18 +389,24 @@ class DrawnScenario(Scenario):
|
||||||
# self.line_prediction_drawn = self.line_prediction_faded
|
# self.line_prediction_drawn = self.line_prediction_faded
|
||||||
|
|
||||||
|
|
||||||
def update(self, stage: Stage):
|
def update(self):
|
||||||
super().update()
|
super().update()
|
||||||
if self.track:
|
if self.track:
|
||||||
self.line_history.root.points = self.track.projected_history
|
self.line_history.root.points = self.track.projected_history
|
||||||
|
|
||||||
|
lf = self.lost_factor()
|
||||||
|
self.line_history.get(FadeOutJitterLine).set_alpha(1-lf)
|
||||||
|
self.line_prediction.get(FadeOutLine).set_alpha(1-lf)
|
||||||
|
self.line_history.get(NoiseLine).amplitude = lf * 1.8
|
||||||
|
|
||||||
if len(self.prediction_tracks):
|
if len(self.prediction_tracks):
|
||||||
now_p = np.array(self.line_history.root.points[-1])
|
# now_p = np.array(self.line_history.root.points[-1])
|
||||||
prev_p = np.array(self.line_history.root.points[-1 * min(4, len(self.line_history.root.points))])
|
# prev_p = np.array(self.line_history.root.points[-1 * min(4, len(self.line_history.root.points))])
|
||||||
diff = now_p - prev_p
|
# diff = now_p - prev_p
|
||||||
self.line_prediction.get(StartFromClosestPoint).set_point(now_p + diff)
|
self.line_prediction.get(StartFromClosestPoint).set_point(self.line_history.root.points[-1])
|
||||||
# print("set origin", self.line_history.root.points[-1])
|
# print("set origin", self.line_history.root.points[-1])
|
||||||
|
|
||||||
|
|
||||||
# TODO: only when animation is ready for it? or collect lines
|
# TODO: only when animation is ready for it? or collect lines
|
||||||
if not self.active_ptrack:
|
if not self.active_ptrack:
|
||||||
# draw the first prediction
|
# draw the first prediction
|
||||||
|
|
@ -396,8 +419,11 @@ class DrawnScenario(Scenario):
|
||||||
# stale prediction
|
# stale prediction
|
||||||
# switch only if drawing animation is ready
|
# switch only if drawing animation is ready
|
||||||
# if self.line_prediction.is_ready():
|
# if self.line_prediction.is_ready():
|
||||||
self.active_ptrack = self.prediction_tracks[-1]
|
self.active_ptrack = self.prediction_tracks[-1]
|
||||||
self.line_prediction.root.points = self.active_ptrack._track.predictions[0]
|
self.line_prediction.root.points = self.active_ptrack._track.predictions[0]
|
||||||
|
if self.line_prediction.is_ready() and self.line_prediction.get(DashedLine).skip == True:
|
||||||
|
self.line_prediction.get(SegmentLine).skip = True
|
||||||
|
self.line_prediction.get(DashedLine).skip = False
|
||||||
|
|
||||||
self.line_prediction.start() # reset positions
|
self.line_prediction.start() # reset positions
|
||||||
|
|
||||||
|
|
@ -421,7 +447,10 @@ class DrawnScenario(Scenario):
|
||||||
# self.line_prediction.get(SegmentLine).anim_f = partial(SegmentLine.anim_grow, reverse=True)
|
# self.line_prediction.get(SegmentLine).anim_f = partial(SegmentLine.anim_grow, reverse=True)
|
||||||
# self.line_prediction.get(SegmentLine).duration = 2
|
# self.line_prediction.get(SegmentLine).duration = 2
|
||||||
# self.line_prediction.get(SegmentLine).start()
|
# self.line_prediction.get(SegmentLine).start()
|
||||||
|
|
||||||
|
if self.active_ptrack:
|
||||||
|
# TODO: this should crop by distance/lenght
|
||||||
|
self.line_prediction.get(CropLine).start_offset = self.track._track.frame_index - self.active_ptrack._track.frame_index
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -430,9 +459,10 @@ class DrawnScenario(Scenario):
|
||||||
|
|
||||||
|
|
||||||
# special case: LOITERING
|
# special case: LOITERING
|
||||||
if self.scene is ScenarioScene.LOITERING: # or self.state_change_at:
|
if False and self.scene is ScenarioScene.LOITERING: # or self.state_change_at:
|
||||||
# logger.info('loitering')
|
# logger.info('loitering')
|
||||||
transition = min(1, (time.perf_counter() - self.state_change_at)/1.4)
|
transition = min(1, (time.perf_counter() - self.state_change_at)/1.4)
|
||||||
|
# print('loitering factor', transition)
|
||||||
|
|
||||||
|
|
||||||
# TODO: transition fade, using to_alpha(), so it can fade back in again:
|
# TODO: transition fade, using to_alpha(), so it can fade back in again:
|
||||||
|
|
@ -450,21 +480,24 @@ class DrawnScenario(Scenario):
|
||||||
self.tracks_to_self_fetched_at = time.perf_counter()
|
self.tracks_to_self_fetched_at = time.perf_counter()
|
||||||
|
|
||||||
# fetch lines nearby
|
# fetch lines nearby
|
||||||
track_ids = stage.history.get_nearest_tracks(current_position, 30)
|
track_ids = self.stage.history.get_nearest_tracks(current_position, 30)
|
||||||
self.track_ids_to_self = iter(track_ids)
|
self.track_ids_to_self = iter(track_ids)
|
||||||
self.tracks_to_self = stage.history.ids_as_trajectory(track_ids)
|
self.tracks_to_self = self.stage.history.ids_as_trajectory(track_ids)
|
||||||
|
|
||||||
stage.logger.info(f"Fetched similar tracks for {self.track_id}. (Took {time.perf_counter() - self.tracks_to_self_fetched_at}s)")
|
self.stage.logger.info(f"Fetched similar tracks for {self.track_id}. (Took {time.perf_counter() - self.tracks_to_self_fetched_at}s)")
|
||||||
|
|
||||||
# if self.tracks_to_self and not len(self.line_others.root.points):
|
# if self.tracks_to_self and not len(self.line_others.root.points):
|
||||||
if self.tracks_to_self and not self.line_others.is_running():
|
if self.tracks_to_self and not self.line_others.is_running():
|
||||||
try:
|
try:
|
||||||
current_history = next(self.tracks_to_self)
|
current_history = next(self.tracks_to_self)
|
||||||
current_history_id = next(self.track_ids_to_self)
|
current_history_id = next(self.track_ids_to_self)
|
||||||
self.line_others.get(CropAnimationLine).assume_fps += TRACK_ASSUMED_FPS*1.5 # faster each time
|
self.line_others.get(CropAnimationLine).assume_fps = min(
|
||||||
|
self.line_others.get(CropAnimationLine).assume_fps + TRACK_ASSUMED_FPS*1.5 , # faster each time
|
||||||
|
TRACK_ASSUMED_FPS * 6 # capped at 6x
|
||||||
|
)
|
||||||
|
|
||||||
self.line_others.get(NoiseLine).amplitude = .05
|
self.line_others.get(NoiseLine).amplitude = .05
|
||||||
|
|
||||||
|
|
||||||
logger.info(f"play history item: {current_history_id}")
|
logger.info(f"play history item: {current_history_id}")
|
||||||
self.line_others.get(FadeOutLine).set_alpha(1)
|
self.line_others.get(FadeOutLine).set_alpha(1)
|
||||||
|
|
||||||
|
|
@ -496,9 +529,7 @@ class DrawnScenario(Scenario):
|
||||||
# 1) history, fade out when lost
|
# 1) history, fade out when lost
|
||||||
# self.line_history.get(StaticLine).color = SrgbaColor(1, 0, 1-self.anomaly_factor(), 1)
|
# self.line_history.get(StaticLine).color = SrgbaColor(1, 0, 1-self.anomaly_factor(), 1)
|
||||||
|
|
||||||
self.line_history.get(FadeOutJitterLine).set_alpha(1-self.lost_factor())
|
|
||||||
self.line_prediction.get(FadeOutLine).set_alpha(1-self.lost_factor())
|
|
||||||
self.line_history.get(NoiseLine).amplitude = self.lost_factor() * 1.5
|
|
||||||
|
|
||||||
# fade out history after max duration, given in frames
|
# fade out history after max duration, given in frames
|
||||||
track_age_in_frames = self.track_age() * TRACK_ASSUMED_FPS
|
track_age_in_frames = self.track_age() * TRACK_ASSUMED_FPS
|
||||||
|
|
@ -506,8 +537,6 @@ class DrawnScenario(Scenario):
|
||||||
|
|
||||||
t2 = time.perf_counter()
|
t2 = time.perf_counter()
|
||||||
|
|
||||||
# 2) also fade-out when moving into loitering mode.
|
|
||||||
# when fading out is done, start drawing historical data
|
|
||||||
|
|
||||||
history_line = self.line_history.as_renderable_line(dt)
|
history_line = self.line_history.as_renderable_line(dt)
|
||||||
t3 = time.perf_counter()
|
t3 = time.perf_counter()
|
||||||
|
|
@ -576,7 +605,7 @@ class DatasetDrawer():
|
||||||
|
|
||||||
line_color = SrgbaColor(0,1,1,1)
|
line_color = SrgbaColor(0,1,1,1)
|
||||||
self.track_line = LineAnimationStack(StaticLine([], line_color))
|
self.track_line = LineAnimationStack(StaticLine([], line_color))
|
||||||
self.track_line.add(SimplifyLine(self.track_line.tail, 0.003)) # Simplify before cropping, to get less noodling
|
self.track_line.add(SimplifyLine(self.track_line.tail, 0.004)) # Simplify before cropping, to get less noodling
|
||||||
self.track_line.add(CropAnimationLine(self.track_line.tail, 50, assume_fps=TRACK_ASSUMED_FPS*20)) # speed up
|
self.track_line.add(CropAnimationLine(self.track_line.tail, 50, assume_fps=TRACK_ASSUMED_FPS*20)) # speed up
|
||||||
|
|
||||||
# self.track_line.add(DashedLine(self.track_line.tail, t_factor=4, loop_offset=True))
|
# self.track_line.add(DashedLine(self.track_line.tail, t_factor=4, loop_offset=True))
|
||||||
|
|
@ -614,7 +643,7 @@ class Stage(Node):
|
||||||
self.active_scenarios: List[DrawnScenario] = [] # List of currently running Scenario instances
|
self.active_scenarios: List[DrawnScenario] = [] # List of currently running Scenario instances
|
||||||
|
|
||||||
|
|
||||||
self.scenarios: Dict[str, DrawnScenario] = DefaultDictKeyed(lambda key: DrawnScenario(key))
|
self.scenarios: Dict[str, DrawnScenario] = DefaultDictKeyed(lambda key: DrawnScenario(key, self))
|
||||||
self.frame_noimg_sock = self.sub(self.config.zmq_frame_noimg_addr)
|
self.frame_noimg_sock = self.sub(self.config.zmq_frame_noimg_addr)
|
||||||
self.trajectory_sock = self.sub(self.config.zmq_trajectory_addr)
|
self.trajectory_sock = self.sub(self.config.zmq_trajectory_addr)
|
||||||
self.prediction_sock = self.sub(self.config.zmq_prediction_addr)
|
self.prediction_sock = self.sub(self.config.zmq_prediction_addr)
|
||||||
|
|
@ -676,7 +705,7 @@ class Stage(Node):
|
||||||
"""Update active scenarios and handle pauses/completions."""
|
"""Update active scenarios and handle pauses/completions."""
|
||||||
# 1) process timestep for all scenarios
|
# 1) process timestep for all scenarios
|
||||||
for s in self.scenarios.values():
|
for s in self.scenarios.values():
|
||||||
s.update(self)
|
s.update()
|
||||||
|
|
||||||
|
|
||||||
# 2) Remove stale tracks and take-overs
|
# 2) Remove stale tracks and take-overs
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,12 @@ def inv_lerp(a: float, b: float, v: float) -> float:
|
||||||
"""
|
"""
|
||||||
return (v - a) / (b - a)
|
return (v - a) / (b - a)
|
||||||
|
|
||||||
|
def easeInOutQuad(t: float) -> float:
|
||||||
|
"""Quadratic easing in/out - smoothing the transition."""
|
||||||
|
if t < 0.5:
|
||||||
|
return 2 * t * t
|
||||||
|
else:
|
||||||
|
return 1 - np.power(-2 * t + 2, 2) / 2
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue