I thought you said weast

This commit is contained in:
firestar5683
2026-08-08 22:30:34 -05:00
parent 5485e275fc
commit 425ba0faf4
4 changed files with 119 additions and 23 deletions
+32 -8
View File
@@ -41,9 +41,14 @@ uniform sampler2D texture0;
uniform sampler2D texture1;
uniform vec2 uCropMin;
uniform vec2 uCropSize;
uniform int uFlipX;
out vec4 fragColor;
void main() {
vec2 uv = uCropMin + fragTexCoord * uCropSize;
vec2 cropCoord = fragTexCoord;
if (uFlipX == 1) {
cropCoord.x = 1.0 - cropCoord.x;
}
vec2 uv = uCropMin + cropCoord * uCropSize;
float y = texture(texture0, uv).r;
vec2 c = texture(texture1, uv).ra - 0.5;
vec3 rgb = vec3(y + 1.402 * c.y, y - 0.344 * c.x - 0.714 * c.y, y + 1.772 * c.x);
@@ -58,6 +63,12 @@ void main() {
"""
UNIFORM_VEC2 = rl.ShaderUniformDataType.SHADER_UNIFORM_VEC2
UNIFORM_INT = rl.ShaderUniformDataType.SHADER_UNIFORM_INT
IMAGE_TO_VEHICLE_SIDE = {
"left": "right",
"right": "left",
}
CONNECTION_RETRY_INTERVAL = 0.2
PARAM_REFRESH_INTERVAL = 2.0
@@ -95,6 +106,8 @@ class PipSideCamera:
self._texture1_loc = rl.get_shader_location(self.shader, "texture1")
self._crop_min_loc = rl.get_shader_location(self.shader, "uCropMin")
self._crop_size_loc = rl.get_shader_location(self.shader, "uCropSize")
self._flip_x_loc = rl.get_shader_location(self.shader, "uFlipX")
self._flip_x_value = rl.ffi.new("int[1]", [1])
self_ref = weakref.ref(self)
@@ -146,7 +159,12 @@ class PipSideCamera:
self._mask = {}
def active_sides(self) -> list[str]:
"""Return the car-side keys ('left'/'right') whose preview bubble should show."""
"""Return vehicle-side keys whose preview bubble should show.
The driver camera is not mirrored in the Galaxy snapshot. Its image-left
side is the vehicle's right side, so translate the image-relative mask
keys before applying vehicle signals and blind-spot state.
"""
if not ui_state.started:
return []
self._refresh_config()
@@ -165,14 +183,19 @@ class PipSideCamera:
right_bsm = bool(car_state.rightBlindspot) or vasm_right
sides = []
if self._mask.get("center_left") and ((self._show_on_blinker and left_blinker) or (self._show_on_bsm and left_bsm)):
sides.append("left")
if self._mask.get("center_right") and ((self._show_on_blinker and right_blinker) or (self._show_on_bsm and right_bsm)):
sides.append("right")
for image_side, vehicle_side, blinker, blindspot in (
("left", IMAGE_TO_VEHICLE_SIDE["left"], right_blinker, right_bsm),
("right", IMAGE_TO_VEHICLE_SIDE["right"], left_blinker, left_bsm),
):
if self._mask.get(f"center_{image_side}") and (
(self._show_on_blinker and blinker) or (self._show_on_bsm and blindspot)
):
sides.append(vehicle_side)
return sides
def _crop_rect(self, side: str) -> rl.Rectangle | None:
center = self._mask.get(f"center_{side}")
def _crop_rect(self, vehicle_side: str) -> rl.Rectangle | None:
image_side = IMAGE_TO_VEHICLE_SIDE[vehicle_side]
center = self._mask.get(f"center_{image_side}")
size = self._mask.get("crop_size")
if not center or len(center) < 2 or not size:
return None
@@ -248,6 +271,7 @@ class PipSideCamera:
rl.begin_shader_mode(self.shader)
rl.set_shader_value(self.shader, self._crop_min_loc, crop_min, UNIFORM_VEC2)
rl.set_shader_value(self.shader, self._crop_size_loc, crop_size, UNIFORM_VEC2)
rl.set_shader_value(self.shader, self._flip_x_loc, self._flip_x_value, UNIFORM_INT)
rl.set_shader_value_texture(self.shader, self._texture1_loc, self.texture_uv)
rl.draw_texture_pro(self.texture_y, src_rect, dst_rect, rl.Vector2(0, 0), 0.0, rl.WHITE)
rl.end_shader_mode()
+28
View File
@@ -0,0 +1,28 @@
from openpilot.selfdrive.ui.onroad.starpilot.pip_sidecam import (
IMAGE_TO_VEHICLE_SIDE,
PIP_FRAGMENT_SHADER,
PipSideCamera,
)
def test_pip_maps_raw_driver_image_sides_to_vehicle_sides():
assert IMAGE_TO_VEHICLE_SIDE == {"left": "right", "right": "left"}
camera = PipSideCamera.__new__(PipSideCamera)
camera._closed = True
camera._mask = {
"center_left": [100, 200],
"center_right": [900, 200],
"crop_size": 100,
}
left_vehicle_crop = camera._crop_rect("left")
right_vehicle_crop = camera._crop_rect("right")
assert (left_vehicle_crop.x, left_vehicle_crop.y) == (850, 150)
assert (right_vehicle_crop.x, right_vehicle_crop.y) == (50, 150)
def test_pip_driver_camera_shader_mirrors_the_crop():
assert "uniform int uFlipX" in PIP_FRAGMENT_SHADER
assert "cropCoord.x = 1.0 - cropCoord.x" in PIP_FRAGMENT_SHADER
@@ -1,6 +1,13 @@
/* PiP Side Camera reuses the V-ASM tool styling. */
@import url("./v_asm.css");
.v-asm-wrapper .v-asm-btn {
height: auto;
min-height: 32px;
text-align: center;
white-space: normal;
}
.pip-zoom-control {
margin-top: 18px;
padding: 14px 18px;
@@ -5,6 +5,18 @@ const CANVAS_H = 480;
const ZOOM_MIN = 60;
const ZOOM_MAX = 640;
const ZOOM_STEP = 5;
const VEHICLE_SIDE_LABELS = {
left: {
canvas: "LEFT SIDE OF VEHICLE - LEFT HERE",
button: "Set Left Side of Vehicle - Left Here",
moveButton: "Move Left Side of Vehicle - Left Here",
},
right: {
canvas: "RIGHT SIDE OF VEHICLE - RIGHT HERE",
button: "Set Right Side of Vehicle - Right Here",
moveButton: "Move Right Side of Vehicle - Right Here",
},
};
let initialLoadTriggered = false;
const state = reactive({
@@ -60,7 +72,20 @@ function redraw() {
const img = _loadedImage;
if (img) {
canvas._img = img;
ctx.save();
ctx.translate(canvas.width, 0);
ctx.scale(-1, 1);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
ctx.restore();
ctx.font = "bold 14px sans-serif";
ctx.textAlign = "center";
ctx.fillStyle = "rgba(0, 0, 0, 0.65)";
ctx.fillRect(0, 0, canvas.width / 2, 34);
ctx.fillRect(canvas.width / 2, 0, canvas.width / 2, 34);
ctx.fillStyle = "#fff";
ctx.fillText(VEHICLE_SIDE_LABELS.left.canvas, canvas.width / 4, 22);
ctx.fillText(VEHICLE_SIDE_LABELS.right.canvas, canvas.width * 3 / 4, 22);
} else {
ctx.fillStyle = "#222";
ctx.fillRect(0, 0, canvas.width, canvas.height);
@@ -73,8 +98,8 @@ function redraw() {
const half = state.zoom / 2;
const sides = [
{ key: "left", center: state.leftCenter, color: "#0d6efd", label: "LEFT OF IMAGE" },
{ key: "right", center: state.rightCenter, color: "#fd7e14", label: "RIGHT OF IMAGE" },
{ key: "left", center: state.leftCenter, color: "#0d6efd", label: VEHICLE_SIDE_LABELS.left.canvas },
{ key: "right", center: state.rightCenter, color: "#fd7e14", label: VEHICLE_SIDE_LABELS.right.canvas },
];
for (const side of sides) {
@@ -117,7 +142,7 @@ function redraw() {
ctx.font = "bold 15px monospace";
ctx.textAlign = "center";
ctx.fillText(
state.armSide === "left" ? "Click to set the LEFT side of the photo (as seen)" : "Click to set the RIGHT side of the photo (as seen)",
state.armSide === "left" ? "Click LEFT HERE for the LEFT SIDE OF VEHICLE" : "Click RIGHT HERE for the RIGHT SIDE OF VEHICLE",
canvas.width / 2,
canvas.height - 18,
);
@@ -195,7 +220,17 @@ function canvasClick(e) {
const rect = canvas.getBoundingClientRect();
const x = Math.round((e.clientX - rect.left) * (canvas.width / rect.width));
const y = Math.round((e.clientY - rect.top) * (canvas.height / rect.height));
if (x < 0 || y < 0) return;
if (x < 0 || y < 0 || x > canvas.width || y > canvas.height) return;
const leftHalf = x < canvas.width / 2;
if ((state.armSide === "left" && !leftHalf) || (state.armSide === "right" && leftHalf)) {
state.error = state.armSide === "left"
? "LEFT SIDE OF VEHICLE is on the LEFT. Click the left half of the preview."
: "RIGHT SIDE OF VEHICLE is on the RIGHT. Click the right half of the preview.";
state.success = "";
requestAnimationFrame(redraw);
return;
}
if (state.armSide === "left") {
state.leftCenter = [x, y];
@@ -249,16 +284,17 @@ async function saveConfig() {
const { cw, ch, nativeW, nativeH } = canvasScale();
// The preview is mirrored for vehicle-side clarity; the daemon still stores raw image coordinates.
function toNative(center) {
if (!center) return [];
return [Math.round(center[0] * nativeW / cw), Math.round(center[1] * nativeH / ch)];
return [Math.round((cw - center[0]) * nativeW / cw), Math.round(center[1] * nativeH / ch)];
}
const config = {
width: nativeW,
height: nativeH,
center_left: toNative(state.leftCenter),
center_right: toNative(state.rightCenter),
center_left: toNative(state.rightCenter),
center_right: toNative(state.leftCenter),
crop_size: Math.round(state.zoom * nativeW / cw),
};
@@ -309,11 +345,11 @@ function applyConfigToCanvas() {
function toCanvas(center) {
if (!Array.isArray(center) || center.length < 2) return null;
return [Math.round(center[0] * cw / nativeW), Math.round(center[1] * ch / nativeH)];
return [Math.round(cw - center[0] * cw / nativeW), Math.round(center[1] * ch / nativeH)];
}
state.leftCenter = toCanvas(config.center_left);
state.rightCenter = toCanvas(config.center_right);
state.leftCenter = toCanvas(config.center_right);
state.rightCenter = toCanvas(config.center_left);
if (Number.isFinite(Number(config.crop_size))) {
state.zoom = Math.round(Number(config.crop_size) * cw / nativeW);
}
@@ -385,8 +421,9 @@ export function PipSideCamera() {
<div class="v-asm-card v-asm-card-danger">
<div class="v-asm-card-title">Setup</div>
<ul class="v-asm-card-list">
<li>Click "Set Left (as seen)" to place a crop on the left half of the photo, and "Set Right (as seen)" for the right half</li>
<li>"As seen" means left/right of the photo you see</li>
<li>This preview is mirrored to match the normal on-road driver-camera view</li>
<li>The LEFT side of the vehicle is always on the LEFT here; the RIGHT side is always on the RIGHT</li>
<li>Click the matching side shown in the large labels. Raw camera-coordinate conversion is automatic</li>
<li>The zoom slider applies to BOTH windows so the preview stays consistent</li>
<li>At least one window center is required to enable the preview</li>
</ul>
@@ -410,11 +447,11 @@ export function PipSideCamera() {
<div class="v-asm-btn-group">
<button class="${state.armSide === "left" ? "v-asm-btn v-asm-btn-left-active" : "v-asm-btn v-asm-btn-outline-left"}"
@click="${setArm}" value="left">
${state.leftCenter ? "Move Left (as seen)" : "Set Left (as seen)"}
${state.leftCenter ? VEHICLE_SIDE_LABELS.left.moveButton : VEHICLE_SIDE_LABELS.left.button}
</button>
<button class="${state.armSide === "right" ? "v-asm-btn v-asm-btn-right-active" : "v-asm-btn v-asm-btn-outline-right"}"
@click="${setArm}" value="right">
${state.rightCenter ? "Move Right (as seen)" : "Set Right (as seen)"}
${state.rightCenter ? VEHICLE_SIDE_LABELS.right.moveButton : VEHICLE_SIDE_LABELS.right.button}
</button>
<button class="v-asm-btn v-asm-btn-primary" @click="${saveConfig}" .disabled="${state.loading || (!state.leftCenter && !state.rightCenter)}">
@@ -428,7 +465,7 @@ export function PipSideCamera() {
</div>
</div>
${state.armSide ? html`<div class="v-asm-mode-banner ${state.armSide === "left" ? "v-asm-mode-left" : "v-asm-mode-right"}"><span>${state.armSide === "left" ? "⬅ Placing Left (as seen)" : "➡ Placing Right (as seen)"}</span><span>Click on the window to place its center point</span></div>` : ""}
${state.armSide ? html`<div class="v-asm-mode-banner ${state.armSide === "left" ? "v-asm-mode-left" : "v-asm-mode-right"}"><span>${state.armSide === "left" ? "⬅ LEFT SIDE OF VEHICLE - LEFT HERE" : "➡ RIGHT SIDE OF VEHICLE - RIGHT HERE"}</span><span>Click the matching side of the mirrored preview</span></div>` : ""}
<div class="v-asm-canvas-wrapper">
<canvas id="pip-sidecam-canvas" @click="${canvasClick}"></canvas>