Skip to content

Commit 1b91955

Browse files
committed
Add an API to fetch all the detections from redis and place markers corresponding to them on the map.
1 parent b42d47d commit 1b91955

4 files changed

Lines changed: 110 additions & 9 deletions

File tree

gcs/react/backend/app/api.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,19 @@ class Vehicle(BaseModel):
124124
velocity: Velocity | None = None
125125

126126

127+
class Detection(BaseModel):
128+
id: str
129+
cls: str
130+
confidence: NonNegativeFloat
131+
longitude: Longitude
132+
latitude: Latitude
133+
x_min: NonNegativeFloat
134+
y_min: NonNegativeFloat
135+
x_max: NonNegativeFloat
136+
y_max: NonNegativeFloat
137+
link: str | None = None
138+
139+
127140
class VehicleConnection(BaseModel):
128141
model_config = ConfigDict(arbitrary_types_allowed=True)
129142
grpc_channel: grpc.aio.Channel
@@ -427,7 +440,9 @@ async def _remote_imagery_broadcaster(vehicle: str):
427440
nparr = np.frombuffer(img_bytes, np.uint8)
428441
img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
429442
maybe_add_bboxes(vehicle, img)
430-
_, img_bytes = cv2.imencode(".jpg", img, [cv2.IMWRITE_JPEG_QUALITY, 90])
443+
_, img_bytes = cv2.imencode(
444+
".jpg", img, [cv2.IMWRITE_JPEG_QUALITY, 90]
445+
)
431446
base64_image = base64.b64encode(img_bytes).decode("ascii")
432447
await connection_manager.broadcast(f"remote_{vehicle}", base64_image)
433448
await asyncio.sleep(0.1)
@@ -553,6 +568,35 @@ async def get_backends() -> list[str]:
553568
return backend_connections.keys()
554569

555570

571+
@app.get("/api/remote/objects")
572+
async def get_objects() -> list[Detection]:
573+
data = []
574+
if backend_key is None:
575+
conn = backend_connections[list(backend_connections)[0]].redis_connection
576+
else:
577+
conn = backend_connections[backend_key].redis_connection
578+
red = conn
579+
for obj in red.zrange("detections", 0, -1):
580+
if len(red.keys(f"objects:{obj}")) > 0:
581+
fields = red.hgetall(f"objects:{obj}")
582+
data.append(
583+
Detection(
584+
id=obj,
585+
cls=fields.get("cls", "unknown"),
586+
confidence=float(fields.get("confidence", 0.0)),
587+
longitude=Longitude(float(fields.get("longitude", 0.0))),
588+
latitude=Latitude(float(fields.get("latitude", 0.0))),
589+
x_min=NonNegativeFloat(float(fields.get("x_min", 0.0))),
590+
y_min=NonNegativeFloat(float(fields.get("y_min", 0.0))),
591+
x_max=NonNegativeFloat(float(fields.get("x_max", 0.0))),
592+
y_max=NonNegativeFloat(float(fields.get("y_max", 0.0))),
593+
link=fields.get("link", ""),
594+
)
595+
)
596+
597+
return data
598+
599+
556600
@app.get("/api/remote/vehicles")
557601
async def get_vehicles() -> list[Vehicle]:
558602
data = []

gcs/react/prime/src/App.jsx

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export function getApiUrl(path) {
4747
function App() {
4848
const appName = "SteelEagle";
4949
const [vehicles, setVehicles] = useState([]);
50+
const [detectedObjects, setDetectedObjects] = useState([]);
5051
const toast = useRef(null);
5152
const [selectedMenu, setSeletectedMenu] = useState('Control');
5253
const [keyPressed, setKeyPressed] = useState(false);
@@ -102,6 +103,33 @@ function App() {
102103
}
103104
}, [lastMessage]);
104105

106+
useEffect(() => {
107+
const fetchData = async () => {
108+
try {
109+
let response = "";
110+
if (!useLocalVehicle) {
111+
response = await fetch(getApiUrl('/api/remote/objects'));
112+
}
113+
else {
114+
return;
115+
}
116+
if (!response.ok) {
117+
throw new Error(`HTTP error! status: ${response.status}`);
118+
}
119+
const result = await response.json();
120+
setDetectedObjects(result);
121+
122+
} catch (error) {
123+
setError(error);
124+
}
125+
};
126+
127+
fetchData();
128+
129+
const intervalId = setInterval(fetchData, 500);
130+
return () => clearInterval(intervalId);
131+
}, [useLocalVehicle]);
132+
105133
useEffect(() => {
106134
const fetchData = async () => {
107135
try {
@@ -472,7 +500,7 @@ function App() {
472500
manualControl={manualControl} setManualControl={setManualControl} squadList={squadList} setSquadList={setSquadList} basePlanarVelocity={basePlanarVelocity} setBasePlanarVelocity={setBasePlanarVelocity}
473501
baseAngularVelocity={baseAngularVelocity} setBaseAngularVelocity={setBaseAngularVelocity} gamepadDeadzone={gamepadDeadzone} setGamepadDeadzone={setGamepadDeadzone}
474502
takeOffAltitude={takeOffAltitude} setTakeOffAltitude={setTakeOffAltitude} showDetections={showDetections} onToggleDetections={onToggleDetections} gimbalVelocity={gimbalVelocity} setGimbalVelocity={setGimbalVelocity} />}
475-
{selectedMenu == "Monitor" && <MonitorPage vehicles={vehicles} />}
503+
{selectedMenu == "Monitor" && <MonitorPage vehicles={vehicles} detectedObjects={detectedObjects} />}
476504
{selectedMenu == "Plan" && <PlanPage />}
477505
<Toast ref={toast} />
478506
</>

gcs/react/prime/src/Mapbox.jsx

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import 'mapbox-gl/dist/mapbox-gl.css';
55
import { MAPBOX_TOKEN } from './config.js';
66
import ColorHash from 'color-hash'
77

8-
function Mapbox({ selectedVehicle, vehicles, mapPanelSize, tracking }) {
8+
function Mapbox({ selectedVehicle, vehicles, mapPanelSize, tracking, detectedObjects, mapHeight }) {
99
const mapRef = useRef()
1010
const mapContainerRef = useRef()
1111
const [currentLoc, setCurrentLoc] = useState(null);
@@ -78,7 +78,7 @@ function Mapbox({ selectedVehicle, vehicles, mapPanelSize, tracking }) {
7878
vehicles.forEach(v => {
7979
let marker = new mapboxgl.Marker({ "color": colorHash.hex(v.name), rotation: v.bearing, rotationAlignment: 'map' })
8080
.setLngLat([v.current.long, v.current.lat])
81-
.setPopup(new mapboxgl.Popup({focusAfterOpen: false}).setHTML(`<strong style="color:black">${v.name} (${v.current.alt.toFixed(2)} m)</strong>`)) // add popup
81+
.setPopup(new mapboxgl.Popup({ focusAfterOpen: false }).setHTML(`<strong style="color:black">${v.name} (${v.current.alt.toFixed(2)} m)</strong>`))
8282
.addTo(mapRef.current);
8383
marker.togglePopup();
8484
const markerDiv = marker.getElement();
@@ -96,7 +96,37 @@ function Mapbox({ selectedVehicle, vehicles, mapPanelSize, tracking }) {
9696
markerRefs.current.push(marker);
9797
});
9898

99-
}, [vehicles]);
99+
if (detectedObjects != null) {
100+
detectedObjects.forEach(d => {
101+
// Create the SVG element
102+
const el = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
103+
el.setAttribute('width', '16');
104+
el.setAttribute('height', '16');
105+
el.setAttribute('viewBox', '0 0 16 16');
106+
107+
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
108+
circle.setAttribute('cx', '8');
109+
circle.setAttribute('cy', '8');
110+
circle.setAttribute('r', '7');
111+
circle.setAttribute('fill', colorHash.hex(d.cls));
112+
circle.setAttribute('stroke', '#fff');
113+
circle.setAttribute('stroke-width', '2');
114+
115+
el.appendChild(circle);
116+
117+
let marker = new mapboxgl.Marker({ element: el })
118+
.setLngLat([d.longitude, d.latitude])
119+
.setPopup(new mapboxgl.Popup({ focusAfterOpen: false }).setHTML(`<strong style="color:black">${d.id} (${d.confidence.toFixed(2) * 100}%)</strong><img src="${d.link}" style="width:100%;height:auto;margin-top:5px;" />`))
120+
.addTo(mapRef.current);
121+
const markerDiv = marker.getElement();
122+
123+
markerDiv.addEventListener('mouseenter', () => marker.togglePopup());
124+
markerDiv.addEventListener('mouseleave', () => marker.togglePopup());
125+
markerRefs.current.push(marker);
126+
});
127+
}
128+
129+
}, [vehicles, detectedObjects]);
100130

101131
useEffect(() => {
102132
let v = vehicles.find(v => v.name === selectedVehicle);
@@ -112,7 +142,7 @@ function Mapbox({ selectedVehicle, vehicles, mapPanelSize, tracking }) {
112142
}, [selectedVehicle]);
113143

114144
return (
115-
<div id='map-container' ref={mapContainerRef} style={{ width: '100%', height: '20rem' }} />
145+
<div id='map-container' ref={mapContainerRef} style={{ width: '100%', height: mapHeight || '20rem' }} />
116146
)
117147
}
118148

gcs/react/prime/src/MonitorPage.jsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import Status from './Status.jsx';
22
import Mapbox from './Mapbox.jsx';
33
import { Panel } from 'primereact/panel';
44

5-
function MonitorPage({ vehicles }) {
6-
const list = vehicles.map((v) => <Status vehicle={v} />);
5+
function MonitorPage({ vehicles, detectedObjects }) {
76

87

98
return (
@@ -12,7 +11,7 @@ function MonitorPage({ vehicles }) {
1211
<Panel header="Monitor" className="h-full" >
1312
<div className="grid m-0">
1413
<div className="col-12 lg:col-6 p-2"></div>
15-
<Mapbox selectedVehicle={null} tracking={false} mapPanelSize={0} vehicles={vehicles} />
14+
<Mapbox selectedVehicle={null} tracking={false} mapPanelSize={0} vehicles={vehicles} detectedObjects={detectedObjects} mapHeight="30rem" />
1615
{vehicles.map((v) => <div className="col-12 lg:col-3 p-2"> <Status vehicle={v} /> </div>)}
1716
</div>
1817
</Panel >

0 commit comments

Comments
 (0)