///|
pub(all) struct SvgOptions {
show_labels : Bool
show_grid : Bool
background : Color
} derive(Debug, ToJson)
///|
pub fn SvgOptions::default() -> SvgOptions {
{
show_labels: true,
show_grid: false,
background: Color::rgb(r=15, g=23, b=42),
}
}
///|
pub fn DebugDocument::to_svg(self : DebugDocument) -> String {
self.to_svg_with(SvgOptions::default())
}
///|
pub fn DebugDocument::to_svg_with(
self : DebugDocument,
options : SvgOptions,
) -> String {
let out = StringBuilder()
out.write_string(
"\n")
out.to_string()
}
///|
fn write_grid(out : StringBuilder, image : ImageSpec) -> Unit {
let step = grid_step(image)
out.write_string("\n")
for x = 0; x <= image.width; x = x + step {
out.write_string(
"\n",
)
}
for y = 0; y <= image.height; y = y + step {
out.write_string(
"\n",
)
}
out.write_string("\n")
}
///|
fn grid_step(image : ImageSpec) -> Int {
let long_edge = if image.width > image.height {
image.width
} else {
image.height
}
if long_edge >= 1600 {
160
} else if long_edge >= 800 {
80
} else {
40
}
}
///|
fn write_overlay(
out : StringBuilder,
overlay : Overlay,
index : Int,
options : SvgOptions,
) -> Unit {
match overlay {
BBox(rect~, label~, color~) =>
write_bbox(out, rect, resolve_color(color, index), label, options)
Mask(polygons~, label~, color~) =>
write_mask(out, polygons, resolve_color(color, index), label, options)
Keypoints(points~, color~) =>
write_keypoints(out, points, resolve_color(color, index))
Trajectory(path~, color~) =>
write_trajectory(out, path, resolve_color(color, index))
Heatmap(cells~, low~, high~) =>
write_heatmap(out, cells, resolve_color(low, 2), resolve_color(high, 1))
ErrorRegion(rect~, expected~, actual~, severity~) =>
write_error_region(out, rect, expected, actual, severity)
}
}
///|
fn write_bbox(
out : StringBuilder,
rect : Rect,
color : Color,
label : Label?,
options : SvgOptions,
) -> Unit {
out.write_string(
"\n",
)
if options.show_labels {
match label {
Some(value) =>
write_label(out, Point::new(x=rect.x, y=rect.y), value, color)
None => ()
}
}
}
///|
fn write_mask(
out : StringBuilder,
polygons : Array[Array[Point]],
color : Color,
label : Label?,
options : SvgOptions,
) -> Unit {
for polygon in polygons {
out.write_string(
"\n",
)
}
if options.show_labels && polygons.length() > 0 {
match label {
Some(value) =>
match polygons[0].get(0) {
Some(point) => write_label(out, point, value, color)
None => ()
}
None => ()
}
}
}
///|
fn write_keypoints(
out : StringBuilder,
points : Array[Keypoint],
color : Color,
) -> Unit {
for point in points {
if point.visible {
out.write_string(
"\n",
)
if point.name != "" {
out.write_string(
"\{escape_text(point.name)}\n",
)
}
}
}
}
///|
fn write_trajectory(
out : StringBuilder,
path : Trajectory,
color : Color,
) -> Unit {
if path.points.length() > 1 {
out.write_string(
"\n",
)
}
match path.points.last() {
Some(last) =>
out.write_string(
"\n",
)
None => ()
}
}
///|
fn write_heatmap(
out : StringBuilder,
cells : Array[HeatCell],
low : Color,
high : Color,
) -> Unit {
for cell in cells {
let color = mix_color(low, high, clamp01(cell.value)).with_alpha(0.46)
out.write_string(
"\n",
)
}
}
///|
fn write_error_region(
out : StringBuilder,
rect : Rect,
expected : String,
actual : String,
severity : Double,
) -> Unit {
let color = mix_color(
Color::rgb(r=245, g=158, b=11),
Color::rgb(r=220, g=38, b=38),
clamp01(severity),
)
out.write_string(
"\n",
)
write_label(
out,
Point::new(x=rect.x, y=rect.y),
Label::new(text="expected: \{expected}; actual: \{actual}"),
color,
)
}
///|
fn write_label(
out : StringBuilder,
anchor : Point,
label : Label,
color : Color,
) -> Unit {
let text = match label.score {
Some(score) => "\{label.text} \{score}"
None => label.text
}
let x = anchor.x
let y = if anchor.y < 18.0 { anchor.y + 18.0 } else { anchor.y - 6.0 }
out.write_string(
"\{escape_text(text)}\n",
)
}
///|
fn polygon_points(points : Array[Point]) -> String {
points.map(point => "\{point.x},\{point.y}").join(" ")
}
///|
fn resolve_color(color : Color?, index : Int) -> Color {
match color {
Some(value) => value
None => palette(index)
}
}
///|
fn mix_color(a : Color, b : Color, t : Double) -> Color {
let k = clamp01(t)
Color::rgba(
r=lerp_int(a.r, b.r, k),
g=lerp_int(a.g, b.g, k),
b=lerp_int(a.b, b.b, k),
a=a.a + (b.a - a.a) * k,
)
}
///|
fn lerp_int(a : Int, b : Int, t : Double) -> Int {
(a.to_double() + (b - a).to_double() * t).round().to_int()
}
///|
fn clamp01(value : Double) -> Double {
if value < 0.0 {
0.0
} else if value > 1.0 {
1.0
} else {
value
}
}
///|
fn escape_text(value : String) -> String {
value
.replace(old="&", new="&")
.replace(old="<", new="<")
.replace(old=">", new=">")
}
///|
fn escape_attr(value : String) -> String {
escape_text(value).replace(old="\"", new=""")
}