mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-08-20 07:43:48 +08:00
I thought you said weast
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user