Add partial LED side coverage (edge spans) for calibration
Some checks failed
Validate / validate (push) Failing after 8s
Some checks failed
Validate / validate (push) Failing after 8s
Allow LEDs to cover only a fraction of each screen edge via draggable span bars in the calibration UI. Per-edge start/end (0.0-1.0) values control which portion of the screen border is sampled for LED colors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -65,6 +65,15 @@ class CalibrationConfig:
|
||||
leds_right: int = 0
|
||||
leds_bottom: int = 0
|
||||
leds_left: int = 0
|
||||
# Per-edge span: fraction of screen side covered by LEDs (0.0–1.0)
|
||||
span_top_start: float = 0.0
|
||||
span_top_end: float = 1.0
|
||||
span_right_start: float = 0.0
|
||||
span_right_end: float = 1.0
|
||||
span_bottom_start: float = 0.0
|
||||
span_bottom_end: float = 1.0
|
||||
span_left_start: float = 0.0
|
||||
span_left_end: float = 1.0
|
||||
|
||||
def build_segments(self) -> List[CalibrationSegment]:
|
||||
"""Derive segment list from core parameters."""
|
||||
@@ -99,6 +108,13 @@ class CalibrationConfig:
|
||||
"""Get derived segment list."""
|
||||
return self.build_segments()
|
||||
|
||||
def get_edge_span(self, edge: str) -> tuple[float, float]:
|
||||
"""Get span (start, end) for a given edge."""
|
||||
return (
|
||||
getattr(self, f"span_{edge}_start", 0.0),
|
||||
getattr(self, f"span_{edge}_end", 1.0),
|
||||
)
|
||||
|
||||
def validate(self) -> bool:
|
||||
"""Validate calibration configuration.
|
||||
|
||||
@@ -117,6 +133,13 @@ class CalibrationConfig:
|
||||
if count < 0:
|
||||
raise ValueError(f"LED count for {edge} must be non-negative, got {count}")
|
||||
|
||||
for edge in ["top", "right", "bottom", "left"]:
|
||||
start, end = self.get_edge_span(edge)
|
||||
if not (0.0 <= start <= 1.0) or not (0.0 <= end <= 1.0):
|
||||
raise ValueError(f"Span for {edge} must be in [0.0, 1.0], got ({start}, {end})")
|
||||
if end <= start:
|
||||
raise ValueError(f"Span end must be greater than start for {edge}, got ({start}, {end})")
|
||||
|
||||
return True
|
||||
|
||||
def get_total_leds(self) -> int:
|
||||
@@ -202,6 +225,20 @@ class PixelMapper:
|
||||
else: # left
|
||||
edge_pixels = border_pixels.left
|
||||
|
||||
# Slice to span region if not full coverage
|
||||
span_start, span_end = self.calibration.get_edge_span(edge_name)
|
||||
if span_start > 0.0 or span_end < 1.0:
|
||||
if edge_name in ("top", "bottom"):
|
||||
total_w = edge_pixels.shape[1]
|
||||
s = int(span_start * total_w)
|
||||
e = int(span_end * total_w)
|
||||
edge_pixels = edge_pixels[:, s:e, :]
|
||||
else:
|
||||
total_h = edge_pixels.shape[0]
|
||||
s = int(span_start * total_h)
|
||||
e = int(span_end * total_h)
|
||||
edge_pixels = edge_pixels[s:e, :, :]
|
||||
|
||||
# Divide edge into segments matching LED count
|
||||
try:
|
||||
pixel_segments = get_edge_segments(
|
||||
@@ -333,6 +370,14 @@ def calibration_from_dict(data: dict) -> CalibrationConfig:
|
||||
leds_right=data.get("leds_right", 0),
|
||||
leds_bottom=data.get("leds_bottom", 0),
|
||||
leds_left=data.get("leds_left", 0),
|
||||
span_top_start=data.get("span_top_start", 0.0),
|
||||
span_top_end=data.get("span_top_end", 1.0),
|
||||
span_right_start=data.get("span_right_start", 0.0),
|
||||
span_right_end=data.get("span_right_end", 1.0),
|
||||
span_bottom_start=data.get("span_bottom_start", 0.0),
|
||||
span_bottom_end=data.get("span_bottom_end", 1.0),
|
||||
span_left_start=data.get("span_left_start", 0.0),
|
||||
span_left_end=data.get("span_left_end", 1.0),
|
||||
)
|
||||
|
||||
config.validate()
|
||||
@@ -355,7 +400,7 @@ def calibration_to_dict(config: CalibrationConfig) -> dict:
|
||||
Returns:
|
||||
Dictionary representation
|
||||
"""
|
||||
return {
|
||||
result = {
|
||||
"layout": config.layout,
|
||||
"start_position": config.start_position,
|
||||
"offset": config.offset,
|
||||
@@ -364,3 +409,11 @@ def calibration_to_dict(config: CalibrationConfig) -> dict:
|
||||
"leds_bottom": config.leds_bottom,
|
||||
"leds_left": config.leds_left,
|
||||
}
|
||||
# Include span fields only when not default (full coverage)
|
||||
for edge in ["top", "right", "bottom", "left"]:
|
||||
start = getattr(config, f"span_{edge}_start")
|
||||
end = getattr(config, f"span_{edge}_end")
|
||||
if start != 0.0 or end != 1.0:
|
||||
result[f"span_{edge}_start"] = start
|
||||
result[f"span_{edge}_end"] = end
|
||||
return result
|
||||
|
||||
Reference in New Issue
Block a user