Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .github/skills/python.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,15 @@ result = client.getScene(scene_id)

### Logging

- Use f-strings for interpolated log messages.
- Do not use printf-style placeholders (for example `%s`, `%d`) in log statements.

```python
from scene_common import log

log.info("Processing started")
log.error("Failed to process")
log.debug("Debug information")
log.error(f"Failed to process scene {scene_id}")
log.debug(f"Debug information: {debug_payload}")
```

## Type Hints
Expand Down
61 changes: 46 additions & 15 deletions controller/src/controller/detections_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import numpy as np

from controller.scene import TripwireEvent
from scene_common import log
from scene_common.earth_lla import convertXYZToLLA, calculateHeading
from scene_common.geometry import DEFAULTZ, Point, Size
from scene_common.timestamp import get_iso_time
Expand Down Expand Up @@ -134,23 +135,53 @@ def computeCameraBounds(scene, aobj, obj_dict):
camera_bounds = {}
for cameraID in obj_dict['visibility']:
bounds = None
if aobj and len(aobj.vectors) > 0 and hasattr(aobj.vectors[0].camera, 'cameraID') \
and cameraID == aobj.vectors[0].camera.cameraID:
projected = False
is_source_camera = (
aobj and len(aobj.vectors) > 0 and hasattr(aobj.vectors[0].camera, 'cameraID')
and cameraID == aobj.vectors[0].camera.cameraID
)

# Prefer source detector pixel bbox when available. This preserves the
# detector-provided 2D box instead of a reprojected estimate.
if is_source_camera:
bounds = getattr(aobj, 'boundingBoxPixels', None)
elif scene:
projected = False
if bounds is None:
log.debug(
f"computeCameraBounds: source camera {cameraID} has no boundingBoxPixels; "
"falling back to projected bounds when possible."
)

# For non-source cameras (or source camera without pixel bbox), project
# 3D/metric object state into each visible camera image plane.
if bounds is None and scene:
camera = scene.cameraWithID(cameraID)
if camera is not None and 'bb_meters' in obj_dict:
obj_translation = None
obj_size = None
if aobj:
obj_translation = aobj.sceneLoc
obj_size = aobj.bbMeters.size
else:
obj_translation = Point(obj_dict['translation'])
obj_size = Size(obj_dict['bb_meters']['width'], obj_dict['bb_meters']['height'])
bounds = camera.pose.projectEstimatedBoundsToCameraPixels(obj_translation,
obj_size)
if camera is None:
log.debug(
f"computeCameraBounds: camera {cameraID} not found in scene; cannot project bounds."
)
continue
elif 'bb_meters' not in obj_dict and (not aobj or not hasattr(aobj, 'bbMeters') or aobj.bbMeters is None):
log.debug(
f"computeCameraBounds: missing bb_meters for camera {cameraID}; cannot project bounds."
)
continue

obj_translation = None
obj_size = None
if aobj and hasattr(aobj, 'bbMeters') and aobj.bbMeters is not None:
obj_translation = aobj.sceneLoc
obj_size = aobj.bbMeters.size
else:
obj_translation = Point(obj_dict['translation'])
obj_size = Size(obj_dict['bb_meters']['width'], obj_dict['bb_meters']['height'])
bounds = camera.pose.projectEstimatedBoundsToCameraPixels(obj_translation,
obj_size)
projected = True

if bounds:
camera_bounds[cameraID] = bounds.asDict
bound_dict = dict(bounds.asDict)
bound_dict['projected'] = projected
camera_bounds[cameraID] = bound_dict
obj_dict['camera_bounds'] = camera_bounds
return
3 changes: 0 additions & 3 deletions controller/src/controller/scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -502,9 +502,6 @@ def _deserializeTrackedObjects(self, serialized_objects):
'confidence': obj.confidence,
}

if 'center_of_mass' in obj_data:
obj.info['center_of_mass'] = obj_data['center_of_mass']

if 'camera_bounds' in obj_data and obj_data['camera_bounds']:
obj._camera_bounds = obj_data['camera_bounds']
else:
Expand Down
19 changes: 11 additions & 8 deletions controller/src/controller/time_chunking.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ def run(self):
class TimeChunkedIntelLabsTracking(IntelLabsTracking):
"""Time-chunked version of IntelLabsTracking."""

EMPTY_FRAME_CAMERA_ID = "__empty_frame__"

def __init__(self, max_unreliable_time, non_measurement_time_dynamic, non_measurement_time_static, time_chunking_rate_fps, suspended_track_timeout_secs=DEFAULT_SUSPENDED_TRACK_TIMEOUT_SECS, reid_config_data=None):
# Call parent constructor to initialize IntelLabsTracking
super().__init__(max_unreliable_time, non_measurement_time_dynamic, non_measurement_time_static, time_chunking_rate_fps, suspended_track_timeout_secs, reid_config_data)
Expand All @@ -150,18 +152,19 @@ def trackObjects(self, objects, already_tracked_objects, when, categories,
# Create IntelLabs trackers if not already created
self._createIlabsTrackers(categories, max_unreliable_time, non_measurement_time_dynamic, non_measurement_time_static)

if len(objects) == 0:
return

if not categories:
categories = self.trackers.keys()

# Extract camera_id from objects - required for time chunking
try:
camera_id = objects[0].camera.cameraID
except (AttributeError, IndexError):
log.warning("No camera ID found in objects, skipping time chunking processing")
return
if len(objects) > 0:
try:
camera_id = objects[0].camera.cameraID
except (AttributeError, IndexError):
log.warning("No camera ID found in objects, skipping time chunking processing")
return
else:
# Keep retirement moving when a camera/category has no detections.
camera_id = self.EMPTY_FRAME_CAMERA_ID

for category in categories:
# Use time chunking
Expand Down
29 changes: 0 additions & 29 deletions controller/src/schema/metadata.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,32 +90,6 @@
},
"required": ["x", "y", "width", "height"]
},
"center_of_mass": {
"title": "Center of Mass",
"type": "object",
"description": "Center of mass region of a detected object in pixel coordinates of a detected object in an image frame. It is a smaller bounding box that is used for determining average distance from the camera when using RealSense, for example.",
"properties": {
"x": {
"type": "number",
"description": "Number of horizontal pixels from the left side of the image to the center of mass bounding box."
},
"y": {
"type": "number",
"description": "Number of vertical pixels from the top of the image to the center of mass bounding box."
},
"width": {
"type": "number",
"description": "Horizontal width of the center of mass bounding box in pixels.",
"minimum": 0
},
"height": {
"type": "number",
"description": "Vertical height of the center of mass bounding box in pixels.",
"minimum": 0
}
},
"required": ["x", "y", "width", "height"]
},
"semantic_metadata_attribute": {
"type": "object",
"title": "Semantic Metadata Attribute",
Expand Down Expand Up @@ -233,9 +207,6 @@
"$ref": "#/definitions/semantic_metadata",
"description": "Semantic metadata describing what an object is (age, gender, clothing, embedding vectors, etc)."
},
"center_of_mass": {
"$ref": "#/definitions/center_of_mass"
},
"distance": {
"title": "Distance to Object",
"type": "number",
Expand Down
36 changes: 0 additions & 36 deletions controller/tools/tracker/standard-tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -576,12 +576,6 @@
"width": 0.20656122377186442,
"height": 0.3816131083242918
},
"center_of_mass": {
"x": 560,
"y": 75,
"width": 39.333333333333336,
"height": 54.5
},
"bounding_box_px": {
"x": 521,
"y": 21,
Expand Down Expand Up @@ -1685,12 +1679,6 @@
"width": 0.159297214942709,
"height": 0.44638230560868997
},
"center_of_mass": {
"x": 310,
"y": 149,
"width": 30.333333333333332,
"height": 63.75
},
"bounding_box_px": {
"x": 280,
"y": 86,
Expand All @@ -1708,12 +1696,6 @@
"width": 0.13128891341432058,
"height": 0.3728605140966705
},
"center_of_mass": {
"x": 488,
"y": 78,
"width": 25.0,
"height": 53.25
},
"bounding_box_px": {
"x": 463,
"y": 25,
Expand Down Expand Up @@ -2390,12 +2372,6 @@
"width": 0.22931796876368,
"height": 0.4516338621452628
},
"center_of_mass": {
"x": 44,
"y": 84,
"width": 43.666666666666664,
"height": 64.5
},
"bounding_box_px": {
"x": 1,
"y": 20,
Expand Down Expand Up @@ -3048,12 +3024,6 @@
"width": 0.20656122377186437,
"height": 0.5251556536572823
},
"center_of_mass": {
"x": 370,
"y": 203,
"width": 39.333333333333336,
"height": 75.0
},
"reid": "vTGGvSaJeL4ktus9yj24PhxMs77yXuO++DwsP4D5fzsGec49heCovX3fCD6Ffvy+pgiBvkjKHj3Yx6e9xLW0vhjOJj2SXZI+0KZkvrDs871LdMq+maQ/vZMj0b6I1LO+vhugvn8ltT70vlI9kv8PPlTbZL6uyse9VWDwvQY8SD/h0K++/t+Pvl/uzD7oR2+/sVXevXAKtb5EuZs+M4bdvmjjs74NjJK+Lg/RvvZ8Ib+7Enk+kO8Kv3xbBj5hjj69mu8+PmkmvT5JopC+PEapPQcW474gHIe+1IIqvunHJT6Yg4W+asdBPjASPD+zR7o+MsFsPobqTz/xFxc/gPLHPXh9JD3El6w9HA1ePqoRmb5SMt498kgcP3P6Nj+s7sk+17+gvB4oSz7zveu+VsdnvoAB0T5pu8k+e8nZvqS2bT4gwZ0+5AwtPxAK7TzY5C48ga15vnSVaT/N/kO+xhDcPsAALDwm5EU+rVVFP0GxS77cLrI+y6xdv8IzPz9DwpE+ip/YPZyFSj6Gq7W+rULnvrzsYT9Ypug+1ayRPjbXKL9EBFK+htnTvk5ykr6gej2/1x9KP1/sxL73D9++4HqFPP8QHT4/Iv290NtpPhoSXT7UZ7U/3nD2PRO0wb11Yqq+mIUYPzMmgT6yvAY+nskWvxnIpz7up2K+SbKWvsUHkb3dnbQ+sCzAPTtgEr7xPDS+nZHzvgA75zz6LPO+qyOmPlGYG7+KqA8+q2BQP8T26j7AnpY+UmFyPrabJT74iyq/YskaP6QoL78kKqm9bwdIvkL0y76/NCW/rPSdPmTe8T11cPK9zKu7PUzSqz28JUW/qzmxvkHsrr4AVKE+8yMnPzL0PD/ajZI+dy6TvngM5L1SioA+mJk3P3bJNj+9uyy+AfsHvLKxqT54xj4+Ij7svtTxEL8ccmQ/U5cAPyD2hT1h3yc/BTxMvif72b6EiQc+3CPDPmd16z4ek0i+q99KPnimX75tir++lzbAvLJ9UL4g/QI/Y1+4PY4kij7cZXy/HPB+Pthu076BGYG+hEkZPobQB78GrAA/GHXAPGBH0z2SVsq9vEi0PojqID+yZxa9kJ3bPnhUbj3Fima+jMgHPtDAwzwH/yG9x318vgSZNj+xxpu9zH2svgjDqz6S0BA/AqYGv+/N174Nynw+EP+WP7ZX175qwcU+mOrgPbXGXj7s+fe+bHTCvRzZnjzFOPa+juksPaybrTzdJoy+NrAGP8ezGz82U5e9nGC0Pm6zKb6/wTi9Aw3mPoosOT3q8LK+22YnP3M+F71cArg+0c7JvpzOPD+Coxi/XZ6LvqT+Nz+8Hg2/MlMMvyWV2L1utg0/L1qKPsEc6r3/aQ4+zgYyPg==",
"bounding_box_px": {
"x": 331,
Expand All @@ -3072,12 +3042,6 @@
"width": 0.1557961772516604,
"height": 0.3746110329421948
},
"center_of_mass": {
"x": 574,
"y": 62,
"width": 29.666666666666668,
"height": 53.5
},
"reid": "vTGGvSaJeL4ktus9yj24PhxMs77yXuO++DwsP4D5fzsGec49heCovX3fCD6Ffvy+pgiBvkjKHj3Yx6e9xLW0vhjOJj2SXZI+0KZkvrDs871LdMq+maQ/vZMj0b6I1LO+vhugvn8ltT70vlI9kv8PPlTbZL6uyse9VWDwvQY8SD/h0K++/t+Pvl/uzD7oR2+/sVXevXAKtb5EuZs+M4bdvmjjs74NjJK+Lg/RvvZ8Ib+7Enk+kO8Kv3xbBj5hjj69mu8+PmkmvT5JopC+PEapPQcW474gHIe+1IIqvunHJT6Yg4W+asdBPjASPD+zR7o+MsFsPobqTz/xFxc/gPLHPXh9JD3El6w9HA1ePqoRmb5SMt498kgcP3P6Nj+s7sk+17+gvB4oSz7zveu+VsdnvoAB0T5pu8k+e8nZvqS2bT4gwZ0+5AwtPxAK7TzY5C48ga15vnSVaT/N/kO+xhDcPsAALDwm5EU+rVVFP0GxS77cLrI+y6xdv8IzPz9DwpE+ip/YPZyFSj6Gq7W+rULnvrzsYT9Ypug+1ayRPjbXKL9EBFK+htnTvk5ykr6gej2/1x9KP1/sxL73D9++4HqFPP8QHT4/Iv290NtpPhoSXT7UZ7U/3nD2PRO0wb11Yqq+mIUYPzMmgT6yvAY+nskWvxnIpz7up2K+SbKWvsUHkb3dnbQ+sCzAPTtgEr7xPDS+nZHzvgA75zz6LPO+qyOmPlGYG7+KqA8+q2BQP8T26j7AnpY+UmFyPrabJT74iyq/YskaP6QoL78kKqm9bwdIvkL0y76/NCW/rPSdPmTe8T11cPK9zKu7PUzSqz28JUW/qzmxvkHsrr4AVKE+8yMnPzL0PD/ajZI+dy6TvngM5L1SioA+mJk3P3bJNj+9uyy+AfsHvLKxqT54xj4+Ij7svtTxEL8ccmQ/U5cAPyD2hT1h3yc/BTxMvif72b6EiQc+3CPDPmd16z4ek0i+q99KPnimX75tir++lzbAvLJ9UL4g/QI/Y1+4PY4kij7cZXy/HPB+Pthu076BGYG+hEkZPobQB78GrAA/GHXAPGBH0z2SVsq9vEi0PojqID+yZxa9kJ3bPnhUbj3Fima+jMgHPtDAwzwH/yG9x318vgSZNj+xxpu9zH2svgjDqz6S0BA/AqYGv+/N174Nynw+EP+WP7ZX175qwcU+mOrgPbXGXj7s+fe+bHTCvRzZnjzFOPa+juksPaybrTzdJoy+NrAGP8ezGz82U5e9nGC0Pm6zKb6/wTi9Aw3mPoosOT3q8LK+22YnP3M+F71cArg+0c7JvpzOPD+Coxi/XZ6LvqT+Nz+8Hg2/MlMMvyWV2L1utg0/L1qKPsEc6r3/aQ4+zgYyPg==",
"bounding_box_px": {
"x": 545,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,9 @@ def detectionPolicy(pobj, item, fw, fh):
'category': category,
'confidence': detection['confidence']
})
computeObjBoundingBoxParams(pobj, fw, fh, item['x'], item['y'], item['w'],item['h'],
item['detection']['bounding_box']['x_min'],
item['detection']['bounding_box']['y_min'],
item['detection']['bounding_box']['x_max'],
item['detection']['bounding_box']['y_max'])
pobj.update({
'bounding_box_px': {'x': item['x'], 'y': item['y'], 'width': item['w'], 'height': item['h']}
})
return

def detection3DPolicy(pobj, item, fw, fh):
Expand All @@ -27,14 +25,8 @@ def detection3DPolicy(pobj, item, fw, fh):
'confidence': item['detection']['confidence'],
})

if 'extra_params' in item:
computeObjBoundingBoxParams3D(pobj, item)
else:
computeObjBoundingBoxParams(pobj, fw, fh, item['x'], item['y'], item['w'],item['h'],
item['detection']['bounding_box']['x_min'],
item['detection']['bounding_box']['y_min'],
item['detection']['bounding_box']['x_max'],
item['detection']['bounding_box']['y_max'])
computeObjBoundingBoxParams3D(pobj, item)

if not ('bounding_box_px' in pobj or 'rotation' in pobj):
print(f"Warning: No bounding box or rotation data found in item {item}")
return
Expand Down Expand Up @@ -91,53 +83,29 @@ def ocrPolicy(pobj, item, fw, fh):

## Utility functions

def computeObjBoundingBoxParams(pobj, fw, fh, x, y, w, h, xminnorm=None, yminnorm=None, xmaxnorm=None, ymaxnorm=None):
# use normalized bounding box for calculating center of mass
xmax, xmin = int(xmaxnorm * fw), int(xminnorm * fw)
ymax, ymin = int(ymaxnorm * fh), int(yminnorm * fh)
comw, comh = (xmax - xmin) / 3, (ymax - ymin) / 4

pobj.update({
'center_of_mass': {'x': int(xmin + comw), 'y': int(ymin + comh), 'width': comw, 'height': comh},
'bounding_box_px': {'x': x, 'y': y, 'width': w, 'height': h}
})
return

def computeObjBoundingBoxParams3D(pobj, item):
pobj.update({
'translation': item['extra_params']['translation'],
'rotation': item['extra_params']['rotation'],
'size': item['extra_params']['dimension']
})
if 'extra_params' in item and all(k in item['extra_params'] for k in ['translation', 'rotation', 'dimension']):
pobj.update({
'translation': item['extra_params']['translation'],
'rotation': item['extra_params']['rotation'],
'size': item['extra_params']['dimension']
})

x_min, y_min, z_min = pobj['translation']
x_size, y_size, z_size = pobj['size']
x_max, y_max, z_max = x_min + x_size, y_min + y_size, z_min + z_size

bbox_width = x_max - x_min
bbox_height = y_max - y_min
bbox_depth = z_max - z_min

pobj['bounding_box_3D'] = {
'x': x_min,
'y': y_min,
'z': z_min,
'width': bbox_width,
'height': bbox_height,
'depth': bbox_depth
}

x_min, y_min, z_min = pobj['translation']
x_size, y_size, z_size = pobj['size']
x_max, y_max, z_max = x_min + x_size, y_min + y_size, z_min + z_size

bbox_width = x_max - x_min
bbox_height = y_max - y_min
bbox_depth = z_max - z_min

com_w, com_h, com_d = bbox_width / 3, bbox_height / 4, bbox_depth / 3

com_x = int(x_min + com_w)
com_y = int(y_min + com_h)
com_z = int(z_min + com_d)

pobj['bounding_box_3D'] = {
'x': x_min,
'y': y_min,
'z': z_min,
'width': bbox_width,
'height': bbox_height,
'depth': bbox_depth
}
pobj['center_of_mass'] = {
'x': com_x,
'y': com_y,
'z': com_z,
'width': com_w,
'height': com_h,
'depth': com_d
}
return
Loading
Loading