dsh ui improve: add brand bar
This commit is contained in:
@@ -13,6 +13,7 @@ __all__ = [
|
||||
"SdkPacket",
|
||||
"depth_map_to_bgr",
|
||||
"draw_arrow_display",
|
||||
"flow_colormap",
|
||||
"hex_to_bgr",
|
||||
"load_fixed_mode",
|
||||
"orisys_importable",
|
||||
@@ -28,6 +29,7 @@ _LAZY_ATTRS = {
|
||||
"orisys_importable": ("orisys_app_shared.acquisition.sdk_worker", "orisys_importable"),
|
||||
"depth_map_to_bgr": ("orisys_app_shared.acquisition.arrows", "depth_map_to_bgr"),
|
||||
"draw_arrow_display": ("orisys_app_shared.acquisition.arrows", "draw_arrow_display"),
|
||||
"flow_colormap": ("orisys_app_shared.acquisition.arrows", "flow_colormap"),
|
||||
"hex_to_bgr": ("orisys_app_shared.acquisition.arrows", "hex_to_bgr"),
|
||||
}
|
||||
|
||||
|
||||
@@ -2,24 +2,26 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import lru_cache
|
||||
from typing import Optional, Sequence
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
from orisys_app_shared.theme import color
|
||||
|
||||
ARROW_BG_BGR = (0x16, 0x13, 0x12)
|
||||
_ARROW_DRAW_KW = dict(
|
||||
threshold=2,
|
||||
grid_spacing=20,
|
||||
arrow_scale=0.5,
|
||||
below_threshold_color=(160, 160, 160),
|
||||
)
|
||||
_FINGER_ARROW_DRAW_KW = dict(
|
||||
threshold=0.4,
|
||||
grid_spacing=10,
|
||||
arrow_scale=1.0,
|
||||
below_threshold_color=(160, 160, 160),
|
||||
)
|
||||
"""Fallback canvas colour (dark theme ``arrow_canvas``)."""
|
||||
|
||||
FLOW_RAMP_FALLBACK = "#12315D,#6E4AD0,#FA02F3,#FF8A6B,#D8FF00"
|
||||
"""Design-spec flow ramp: brand navy, violet, magenta, coral, lime."""
|
||||
|
||||
FLOW_IDLE_HEX = "#8A93A6"
|
||||
"""Brand-neutral tint for the grid dots below the flow threshold."""
|
||||
|
||||
_LUT_STEPS = 256
|
||||
_RAMP_MIN_STOPS = 2
|
||||
_RAMP_MAX_STOPS = 8
|
||||
|
||||
|
||||
def hex_to_bgr(hex_color: str, fallback: tuple[int, int, int] = ARROW_BG_BGR) -> tuple[int, int, int]:
|
||||
@@ -35,6 +37,62 @@ def hex_to_bgr(hex_color: str, fallback: tuple[int, int, int] = ARROW_BG_BGR) ->
|
||||
return (b, g, r)
|
||||
|
||||
|
||||
_ARROW_DRAW_KW = dict(
|
||||
threshold=2,
|
||||
grid_spacing=20,
|
||||
arrow_scale=0.5,
|
||||
below_threshold_color=hex_to_bgr(FLOW_IDLE_HEX),
|
||||
)
|
||||
_FINGER_ARROW_DRAW_KW = dict(
|
||||
threshold=0.4,
|
||||
grid_spacing=10,
|
||||
arrow_scale=1.0,
|
||||
below_threshold_color=hex_to_bgr(FLOW_IDLE_HEX),
|
||||
)
|
||||
|
||||
|
||||
def _parse_ramp(ramp: str) -> list[tuple[int, int, int]]:
|
||||
stops: list[tuple[int, int, int]] = []
|
||||
for token in str(ramp or "").split(","):
|
||||
text = token.strip().lstrip("#")
|
||||
if len(text) != 6:
|
||||
continue
|
||||
try:
|
||||
stops.append((int(text[0:2], 16), int(text[2:4], 16), int(text[4:6], 16)))
|
||||
except ValueError:
|
||||
continue
|
||||
return stops
|
||||
|
||||
|
||||
@lru_cache(maxsize=8)
|
||||
def _lut_from_ramp(ramp: str) -> np.ndarray:
|
||||
"""Build a 256-entry user colour table from ``#RRGGBB`` ramp stops.
|
||||
|
||||
The table is BGR, matching how :func:`cv2.applyColorMap` consumes user
|
||||
colormaps. Invalid input never raises: it falls back to
|
||||
:data:`FLOW_RAMP_FALLBACK`.
|
||||
"""
|
||||
stops = _parse_ramp(ramp)
|
||||
if not _RAMP_MIN_STOPS <= len(stops) <= _RAMP_MAX_STOPS:
|
||||
stops = _parse_ramp(FLOW_RAMP_FALLBACK)
|
||||
positions = np.linspace(0.0, 1.0, _LUT_STEPS)
|
||||
span = np.linspace(0.0, 1.0, len(stops))
|
||||
lut = np.empty((_LUT_STEPS, 1, 3), dtype=np.uint8)
|
||||
for channel in range(3): # source order: R, G, B
|
||||
values = np.interp(positions, span, [stop[channel] for stop in stops])
|
||||
lut[:, 0, channel] = np.clip(np.round(values), 0, 255).astype(np.uint8)
|
||||
return lut[:, :, ::-1].copy()
|
||||
|
||||
|
||||
def flow_colormap(theme: str = "dark") -> np.ndarray:
|
||||
"""Return the branded flow-magnitude colour table for ``theme``.
|
||||
|
||||
Cached on the resolved ramp string rather than the theme name, so a theme
|
||||
switch is picked up as soon as :func:`build_qss` reloads the tokens.
|
||||
"""
|
||||
return _lut_from_ramp(color(theme, "flow_ramp", FLOW_RAMP_FALLBACK))
|
||||
|
||||
|
||||
def _ensure_bgr(img: np.ndarray) -> np.ndarray:
|
||||
if img.ndim == 2:
|
||||
return cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
|
||||
@@ -53,19 +111,22 @@ def draw_arrow_display(
|
||||
canvas_bgr: Sequence[int] | None = None,
|
||||
draw_style: str = "box",
|
||||
valid_mask: Optional[np.ndarray] = None,
|
||||
colormap: Optional[np.ndarray] = None,
|
||||
) -> np.ndarray:
|
||||
"""Draw flow arrows on a solid canvas or over the processed frame."""
|
||||
base = _ensure_bgr(processed_frame)
|
||||
if flow is None:
|
||||
return base
|
||||
draw_kw = _FINGER_ARROW_DRAW_KW if str(draw_style).lower().startswith("f") else _ARROW_DRAW_KW
|
||||
draw_kw = dict(_FINGER_ARROW_DRAW_KW if str(draw_style).lower().startswith("f") else _ARROW_DRAW_KW)
|
||||
if colormap is not None:
|
||||
draw_kw["colormap"] = colormap
|
||||
try:
|
||||
import orisys
|
||||
|
||||
if background == "pure_color":
|
||||
canvas = np.empty_like(base)
|
||||
color = ARROW_BG_BGR if canvas_bgr is None else tuple(int(v) for v in canvas_bgr)
|
||||
canvas[:, :] = color
|
||||
color_bgr = ARROW_BG_BGR if canvas_bgr is None else tuple(int(v) for v in canvas_bgr)
|
||||
canvas[:, :] = color_bgr
|
||||
return orisys.util.draw_arrows(
|
||||
canvas, flow, valid_mask=valid_mask, **draw_kw
|
||||
)
|
||||
@@ -74,8 +135,11 @@ def draw_arrow_display(
|
||||
return base
|
||||
|
||||
|
||||
def depth_map_to_bgr(depth: Optional[np.ndarray]) -> Optional[np.ndarray]:
|
||||
"""Convert a float depth/divergence map to a JET colormap BGR image."""
|
||||
def depth_map_to_bgr(
|
||||
depth: Optional[np.ndarray],
|
||||
colormap: Optional[np.ndarray] = None,
|
||||
) -> Optional[np.ndarray]:
|
||||
"""Convert a float depth/divergence map to a colormap BGR image."""
|
||||
if depth is None:
|
||||
return None
|
||||
arr = np.asarray(depth, dtype=np.float64)
|
||||
@@ -90,4 +154,6 @@ def depth_map_to_bgr(depth: Optional[np.ndarray]) -> Optional[np.ndarray]:
|
||||
normalized = ((arr - lo) / (hi - lo) * 255.0).astype(np.uint8)
|
||||
else:
|
||||
normalized = np.zeros(arr.shape, dtype=np.uint8)
|
||||
return cv2.applyColorMap(normalized, cv2.COLORMAP_JET)
|
||||
return cv2.applyColorMap(
|
||||
normalized, cv2.COLORMAP_JET if colormap is None else colormap
|
||||
)
|
||||
|
||||
@@ -1,45 +1,51 @@
|
||||
{
|
||||
"window_bg": "#121316",
|
||||
"panel_bg": "#1a1c20",
|
||||
"panel_border": "#2a2d33",
|
||||
"text_primary": "#e8eaed",
|
||||
"text_secondary": "#9aa0a6",
|
||||
"text_muted": "#6f757c",
|
||||
"accent": "#4a62c0",
|
||||
"accent_hover": "#5c74d0",
|
||||
"accent_text": "#ffffff",
|
||||
"toolbar_bg": "#1a1c20",
|
||||
"button_bg": "#2a2d33",
|
||||
"button_hover": "#3a3f47",
|
||||
"input_bg": "#0f1012",
|
||||
"input_border": "#3a3f47",
|
||||
"menu_bg": "#1a1c20",
|
||||
"menu_hover": "#2a2d33",
|
||||
"plot_fn": "#8ab4f8",
|
||||
"plot_fx": "#34a853",
|
||||
"plot_fy": "#fbbc04",
|
||||
"plot_bg": "#15171a",
|
||||
"plot_grid": "#2a2d33",
|
||||
"arrow_canvas": "#121316",
|
||||
"danger": "#ea4335",
|
||||
"danger_hover": "#f2554a",
|
||||
"danger_text": "#ffffff",
|
||||
"warning": "#fbbc04",
|
||||
"info": "#8ab4f8",
|
||||
"success": "#34a853",
|
||||
"status_idle": "#9aa0a6",
|
||||
"status_connecting": "#fbbc04",
|
||||
"status_running": "#34a853",
|
||||
"status_error": "#ea4335",
|
||||
"table_header_bg": "#23262b",
|
||||
"table_row_alt": "#1e2126",
|
||||
"table_selected": "#33406b",
|
||||
"scrollbar_bg": "#1a1c20",
|
||||
"scrollbar_handle": "#3a3f47",
|
||||
"scrollbar_handle_hover": "#4a5058",
|
||||
"card_bg": "#1e2126",
|
||||
"card_border": "#32363d",
|
||||
"separator": "#2a2d33",
|
||||
"focus_ring": "#6b82d9",
|
||||
"hint_text": "#7d838a"
|
||||
"window_bg": "#0E1320",
|
||||
"panel_bg": "#161C2A",
|
||||
"panel_border": "#28303F",
|
||||
"toolbar_bg": "#161C2A",
|
||||
"card_bg": "#1B2231",
|
||||
"card_border": "#2E3748",
|
||||
"separator": "#28303F",
|
||||
"input_bg": "#0B0F19",
|
||||
"input_border": "#66739A",
|
||||
"button_bg": "#28303F",
|
||||
"button_hover": "#333D50",
|
||||
"menu_bg": "#161C2A",
|
||||
"menu_hover": "#28303F",
|
||||
"table_header_bg": "#1F2635",
|
||||
"table_row_alt": "#1A2130",
|
||||
"table_selected": "#2C3A63",
|
||||
"scrollbar_bg": "#161C2A",
|
||||
"scrollbar_handle": "#627092",
|
||||
"scrollbar_handle_hover": "#6E7C9E",
|
||||
"plot_bg": "#11161F",
|
||||
"plot_grid": "#28303F",
|
||||
"plot_fn": "#8AA6F2",
|
||||
"plot_fx": "#F45CC0",
|
||||
"plot_fy": "#D8FF00",
|
||||
"text_primary": "#E6EAF2",
|
||||
"text_secondary": "#9AA4B8",
|
||||
"text_muted": "#838FA6",
|
||||
"hint_text": "#838FA6",
|
||||
"accent": "#4D62BE",
|
||||
"accent_hover": "#5770CE",
|
||||
"accent_text": "#FFFFFF",
|
||||
"focus_ring": "#7C92E8",
|
||||
"danger": "#EE6B60",
|
||||
"danger_hover": "#F0796E",
|
||||
"danger_fill": "#CC4438",
|
||||
"danger_fill_hover": "#BE3A31",
|
||||
"danger_text": "#FFFFFF",
|
||||
"warning": "#F0B429",
|
||||
"info": "#8AA6F2",
|
||||
"success": "#4FC08D",
|
||||
"status_idle": "#9AA4B8",
|
||||
"status_connecting": "#F0B429",
|
||||
"status_running": "#4FC08D",
|
||||
"status_error": "#EE6B60",
|
||||
"brand_bar_bg": "#12315D",
|
||||
"brand_bar_fg": "#FFFFFF",
|
||||
"brand_bar_fg_muted": "#B9C8E4",
|
||||
"arrow_canvas": "#0B1830",
|
||||
"flow_ramp": "#0B1830,#6E4AD0,#FA02F3,#FF8A6B,#D8FF00"
|
||||
}
|
||||
|
||||
@@ -1,45 +1,51 @@
|
||||
{
|
||||
"window_bg": "#ECEFF4",
|
||||
"window_bg": "#E1E6EF",
|
||||
"panel_bg": "#FFFFFF",
|
||||
"panel_border": "#D0D5DE",
|
||||
"text_primary": "#1A1C20",
|
||||
"text_secondary": "#5F6368",
|
||||
"text_muted": "#7C838C",
|
||||
"accent": "#003893",
|
||||
"accent_hover": "#2A4FA8",
|
||||
"accent_text": "#ffffff",
|
||||
"toolbar_bg": "#E8EBF0",
|
||||
"button_bg": "#F4F6FA",
|
||||
"button_hover": "#E8ECF5",
|
||||
"panel_border": "#CFD7E4",
|
||||
"toolbar_bg": "#EDF1F8",
|
||||
"card_bg": "#F4F7FC",
|
||||
"card_border": "#D5DCE8",
|
||||
"separator": "#D8DFEA",
|
||||
"input_bg": "#FFFFFF",
|
||||
"input_border": "#C5CAD3",
|
||||
"input_border": "#7C8AA4",
|
||||
"button_bg": "#F6F8FD",
|
||||
"button_hover": "#E7EDF9",
|
||||
"menu_bg": "#FFFFFF",
|
||||
"menu_hover": "#EEF1F7",
|
||||
"menu_hover": "#EDF1F8",
|
||||
"table_header_bg": "#EEF2F9",
|
||||
"table_row_alt": "#F7F9FD",
|
||||
"table_selected": "#D7E2FA",
|
||||
"scrollbar_bg": "#E6EBF3",
|
||||
"scrollbar_handle": "#78839A",
|
||||
"scrollbar_handle_hover": "#6A7590",
|
||||
"plot_bg": "#F8FAFD",
|
||||
"plot_grid": "#DFE5EE",
|
||||
"plot_fn": "#003893",
|
||||
"plot_fx": "#188038",
|
||||
"plot_fy": "#E8710A",
|
||||
"plot_bg": "#FAFBFD",
|
||||
"plot_grid": "#E3E6EC",
|
||||
"arrow_canvas": "#EEF1F7",
|
||||
"danger": "#D93025",
|
||||
"danger_hover": "#B3261C",
|
||||
"danger_text": "#ffffff",
|
||||
"warning": "#F9AB00",
|
||||
"info": "#1A73E8",
|
||||
"success": "#1B8A5A",
|
||||
"status_idle": "#5F6368",
|
||||
"status_connecting": "#F9AB00",
|
||||
"status_running": "#1B8A5A",
|
||||
"status_error": "#D93025",
|
||||
"table_header_bg": "#F1F3F8",
|
||||
"table_row_alt": "#F8F9FC",
|
||||
"table_selected": "#DCE6FA",
|
||||
"scrollbar_bg": "#EDEFF3",
|
||||
"scrollbar_handle": "#C3C9D4",
|
||||
"scrollbar_handle_hover": "#A8B0BE",
|
||||
"card_bg": "#F7F9FC",
|
||||
"card_border": "#DDE2EA",
|
||||
"separator": "#D8DDE6",
|
||||
"focus_ring": "#2A4FA8",
|
||||
"hint_text": "#7C838C"
|
||||
"plot_fx": "#B0178C",
|
||||
"plot_fy": "#4E7A00",
|
||||
"text_primary": "#141A24",
|
||||
"text_secondary": "#4A5566",
|
||||
"text_muted": "#5C6678",
|
||||
"hint_text": "#5C6678",
|
||||
"accent": "#003893",
|
||||
"accent_hover": "#4D62BE",
|
||||
"accent_text": "#FFFFFF",
|
||||
"focus_ring": "#4D62BE",
|
||||
"danger": "#C7362B",
|
||||
"danger_hover": "#B93227",
|
||||
"danger_fill": "#C7362B",
|
||||
"danger_fill_hover": "#B93227",
|
||||
"danger_text": "#FFFFFF",
|
||||
"warning": "#8A5A00",
|
||||
"info": "#3A55B8",
|
||||
"success": "#1F7A4D",
|
||||
"status_idle": "#4A5566",
|
||||
"status_connecting": "#8A5A00",
|
||||
"status_running": "#1F7A4D",
|
||||
"status_error": "#C7362B",
|
||||
"brand_bar_bg": "#12315D",
|
||||
"brand_bar_fg": "#FFFFFF",
|
||||
"brand_bar_fg_muted": "#B9C8E4",
|
||||
"arrow_canvas": "#12315D",
|
||||
"flow_ramp": "#12315D,#6E4AD0,#FA02F3,#FF8A6B,#D8FF00"
|
||||
}
|
||||
|
||||
@@ -146,6 +146,35 @@ QToolTip {{
|
||||
border-radius: 6px;
|
||||
padding: 5px 8px;
|
||||
}}
|
||||
/* ---------- Brand bar ---------- */
|
||||
QFrame#brandBar {{
|
||||
background-color: {c['brand_bar_bg']};
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
}}
|
||||
QLabel#brandName {{
|
||||
color: {c['brand_bar_fg']};
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1.5px;
|
||||
}}
|
||||
QLabel#brandTagline {{
|
||||
color: {c['brand_bar_fg_muted']};
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.5px;
|
||||
}}
|
||||
QLabel#brandProduct {{
|
||||
color: {c['brand_bar_fg']};
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.3px;
|
||||
}}
|
||||
QFrame#brandBar QFrame#sectionSeparator {{
|
||||
background-color: {c['brand_bar_fg_muted']};
|
||||
border: none;
|
||||
max-width: 1px;
|
||||
min-width: 1px;
|
||||
}}
|
||||
/* ---------- Buttons ---------- */
|
||||
QPushButton {{
|
||||
background-color: {c['button_bg']};
|
||||
@@ -157,7 +186,6 @@ QPushButton {{
|
||||
}}
|
||||
QPushButton:hover {{
|
||||
background-color: {c['button_hover']};
|
||||
border-color: {c['accent_hover']};
|
||||
}}
|
||||
QPushButton:pressed {{
|
||||
background-color: {c['menu_hover']};
|
||||
@@ -192,20 +220,20 @@ QPushButton#primaryButton:disabled {{
|
||||
border-color: {c['panel_border']};
|
||||
}}
|
||||
QPushButton#dangerButton {{
|
||||
background-color: {c['danger']};
|
||||
background-color: {c['danger_fill']};
|
||||
color: {c['danger_text']};
|
||||
border: 1px solid {c['danger']};
|
||||
border: 1px solid {c['danger_fill']};
|
||||
font-weight: 600;
|
||||
padding: 7px 18px;
|
||||
}}
|
||||
QPushButton#dangerButton:hover {{
|
||||
background-color: {c['danger_hover']};
|
||||
border-color: {c['danger_hover']};
|
||||
background-color: {c['danger_fill_hover']};
|
||||
border-color: {c['danger_fill_hover']};
|
||||
}}
|
||||
QPushButton#dangerButton:pressed,
|
||||
QPushButton#dangerButton:checked {{
|
||||
background-color: {c['danger_hover']};
|
||||
border-color: {c['danger']};
|
||||
background-color: {c['danger_fill_hover']};
|
||||
border-color: {c['danger_fill']};
|
||||
}}
|
||||
QPushButton#dangerButton:disabled {{
|
||||
background-color: {c['panel_border']};
|
||||
@@ -249,10 +277,10 @@ QComboBox, QLineEdit, QSpinBox, QDoubleSpinBox, QAbstractSpinBox {{
|
||||
selection-color: {accent_text};
|
||||
}}
|
||||
QComboBox:hover, QLineEdit:hover, QSpinBox:hover, QDoubleSpinBox:hover {{
|
||||
border-color: {c['accent_hover']};
|
||||
border-color: {c['focus_ring']};
|
||||
}}
|
||||
QComboBox:focus, QLineEdit:focus, QSpinBox:focus, QDoubleSpinBox:focus {{
|
||||
border: 1px solid {c['accent']};
|
||||
border: 1px solid {c['focus_ring']};
|
||||
}}
|
||||
QComboBox:disabled, QLineEdit:disabled, QSpinBox:disabled, QDoubleSpinBox:disabled {{
|
||||
color: {c['text_muted']};
|
||||
|
||||
Reference in New Issue
Block a user