Skip to content

Commit da15236

Browse files
committed
Wire IMU web stats to session rankings
Include supporting changes in TubStatistics, Tub, and web API handler for IMU segment stats to use session rankings consistently.
1 parent 9a10850 commit da15236

File tree

3 files changed

+89
-2
lines changed

3 files changed

+89
-2
lines changed

donkeycar/parts/tub_statistics.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ class FieldAggregationSpec:
129129
output_key: str # e.g., 'gyro_z_agg'
130130
index: Optional[int] = None # Vector index (None for scalars)
131131
transform: Optional[Callable] = None # Transform function
132-
aggregation: str = 'avg' # avg, sum, min, max, median
132+
aggregation: str = 'avg' # avg, sum, min, max, median, delta
133133

134134
def extract(self, record: dict) -> Optional[float]:
135135
"""Extract and transform value from record."""
@@ -170,6 +170,9 @@ def compute(self) -> Optional[float]:
170170
sorted_vals = sorted(self.values)
171171
mid = len(sorted_vals) // 2
172172
return sorted_vals[mid]
173+
elif self.method == 'delta':
174+
# Delta: last value - first value
175+
return self.values[-1] - self.values[0]
173176
else:
174177
raise ValueError(f'Unknown aggregation method: {self.method}')
175178

donkeycar/parts/tub_v2.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,12 @@ def __init__(self, base_path, inputs=[], types=[], metadata=[],
2525
self.manifest = Manifest(base_path, inputs=inputs, types=types,
2626
metadata=metadata, max_len=max_catalog_len,
2727
read_only=read_only)
28-
self.input_types = dict(zip(inputs, types))
28+
# Use manifest inputs/types if parameters were not provided
29+
if not inputs and not types:
30+
self.input_types = dict(zip(self.manifest.inputs,
31+
self.manifest.types))
32+
else:
33+
self.input_types = dict(zip(inputs, types))
2934
# Create images folder if necessary
3035
if not os.path.exists(self.images_base_path):
3136
os.makedirs(self.images_base_path, exist_ok=True)

donkeycar/parts/web_controller/web.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ def __init__(self, port=8887, mode='user'):
137137
(r"/wsTest", WsTest),
138138
(r"/imupath", IMUPathHandler),
139139
(r"/api/imupath/data", IMUPathDataAPI),
140+
(r"/api/imupath/fields", IMUPathFieldsAPI),
141+
(r"/api/imupath/stats", IMUPathStatsAPI),
140142
(r"/api/imupath/shutdown", IMUPathShutdownAPI),
141143

142144
(r"/static/(.*)", StaticFileHandler,
@@ -410,6 +412,83 @@ async def get(self):
410412
await self.render("templates/imupath.html", **data)
411413

412414

415+
class IMUPathFieldsAPI(RequestHandler):
416+
"""
417+
API endpoint for getting available fields from tub data.
418+
Returns list of fields with metadata (type, dimensions, etc.)
419+
"""
420+
421+
async def get(self):
422+
# Check if data is available
423+
if self.application.imupath_builder is None:
424+
self.set_status(404)
425+
self.write({'error': 'No IMU path data loaded'})
426+
return
427+
428+
try:
429+
fields = self.application.imupath_builder.get_available_fields()
430+
self.set_header('Content-Type', 'application/json')
431+
self.write(json.dumps({'fields': fields}, ensure_ascii=False,
432+
separators=(',', ':')))
433+
except Exception as e:
434+
logger.error(f"Error getting available fields: {e}",
435+
exc_info=True)
436+
self.set_status(500)
437+
self.write({'error': str(e)})
438+
439+
440+
class IMUPathStatsAPI(RequestHandler):
441+
"""
442+
API endpoint for computing segment statistics on-demand.
443+
Query parameters:
444+
- field: Field name (required)
445+
- method: Aggregation method (required)
446+
- dimension: Vector component index (optional, for vector fields)
447+
"""
448+
449+
async def get(self):
450+
# Check if data is available
451+
if self.application.imupath_builder is None:
452+
self.set_status(404)
453+
self.write({'error': 'No IMU path data loaded'})
454+
return
455+
456+
# Get query parameters
457+
field_name = self.get_argument('field', default=None)
458+
method = self.get_argument('method', default=None)
459+
dimension_str = self.get_argument('dimension', default=None)
460+
461+
# Validate required parameters
462+
if not field_name or not method:
463+
self.set_status(400)
464+
self.write({'error': 'Missing required parameters: field, method'})
465+
return
466+
467+
# Parse dimension parameter
468+
dimension = None
469+
if dimension_str is not None and dimension_str != 'null' and dimension_str != '':
470+
try:
471+
dimension = int(dimension_str)
472+
except ValueError:
473+
self.set_status(400)
474+
self.write({'error': 'Invalid dimension parameter: must be integer'})
475+
return
476+
477+
try:
478+
# Compute segment statistics
479+
rankings = self.application.imupath_builder.compute_segment_statistics(
480+
field_name, method, dimension)
481+
482+
self.set_header('Content-Type', 'application/json')
483+
self.write(json.dumps({'rankings': rankings}, ensure_ascii=False,
484+
separators=(',', ':')))
485+
except Exception as e:
486+
logger.error(f"Error computing segment statistics: {e}",
487+
exc_info=True)
488+
self.set_status(500)
489+
self.write({'error': str(e)})
490+
491+
413492
class IMUPathShutdownAPI(RequestHandler):
414493
"""
415494
API endpoint to shutdown the server.

0 commit comments

Comments
 (0)