- .library-folders in the library (src/app/library_folders.jai): each library folder's options, starting with segment: true. Every folder starts off; unknown keys are kept when it is rewritten. - The Segmentation window (Segments, above the grid): each folder's Segment checkbox, how many of its images are segmented, and Erase per folder; a button to segment the folders set to segment. - The segmentation scan (src/app/segment_ui.jai) runs on the OCR thread as its own task, with the OCR scan's progress, pause and cancel: images only (never videos), not yet segmented, in folders set to segment. The engine loads with the first scan (2.4 GB of GPU memory). Each image's segs and mask are stored in one rewrite and its catalog entry loaded again. - Importing into a folder set to segment segments the new images; the Import window's checkbox is the folder's setting. - -segment segments and exits; -erase segments, and -erase all now includes segments. - The catalog reads each capture's segs (class list hash, segment count). The exit overlay names the segmentation scan and model. segment_mcap_chunks moved into modules/Segment, shared with segment-probe. The quoting helpers of Capture_File's text formats are public. - Tests: .library-folders read and written back (quoted names, unknown keys, versions).
195 lines
8.0 KiB
Plaintext
195 lines
8.0 KiB
Plaintext
// OCR text: the OCR block of an .mcap (version 2), in the style of the Ex4 text formats. One section
|
|
// per model, so a capture read by several models keeps every reading; reading it again with a model
|
|
// replaces only that model's section.
|
|
//
|
|
// @version 2
|
|
// [ocr ppocr-v5-mobile]
|
|
// engine: "onnxruntime 1.30.0, CUDA"
|
|
// image: rendered 1920 1080
|
|
// date: 2026-10-08T21:04:11Z
|
|
// line: 412 96 640 18 412 96 1052 96 1052 114 412 114 0.993 "Assign Footprints - KiCad"
|
|
//
|
|
// line: x y width height, the line's four corners as x y pairs, confidence, "text". All in the pixels
|
|
// of the image named by image: (rendered or original, at that size). x y width height bound the
|
|
// corners; the corners go clockwise from the text's top left, so a turned line says which way it
|
|
// reads. Text is quoted, with "" for a quote; a control character is written as a space. A line that
|
|
// cannot be read is skipped; keys this version does not know are skipped too.
|
|
//
|
|
// Version 1 had no corners (line: x y width height confidence "text"); it is still read, its corners
|
|
// taken from its box.
|
|
|
|
MCAP_OCR_TEXT_VERSION :: 2;
|
|
|
|
Mcap_Ocr_Line :: struct {
|
|
x, y, width, height: s32;
|
|
confidence: float;
|
|
text: string;
|
|
corners: [8] s32; // x0 y0 x1 y1 x2 y2 x3 y3: top left, top right, bottom right, bottom left of the text; all 0 means the box's
|
|
}
|
|
|
|
// The line's corners: its own, or its box's when it has none.
|
|
mcap_ocr_corners :: (line: Mcap_Ocr_Line) -> [8] s32 {
|
|
for line.corners if it return line.corners;
|
|
x0, y0, x1, y1 := line.x, line.y, line.x + line.width, line.y + line.height;
|
|
return .[x0, y0, x1, y0, x1, y1, x0, y1];
|
|
}
|
|
|
|
Mcap_Ocr_Section :: struct {
|
|
model: string; // the OCR model's name, as in its models.config
|
|
engine: string; // what ran it, such as "onnxruntime 1.30.0, CUDA"
|
|
image: string; // "rendered" or "original"
|
|
image_width, image_height: s32;
|
|
date: string; // when it was read, UTC: 2026-10-08T21:04:11Z
|
|
lines: [..] Mcap_Ocr_Line;
|
|
}
|
|
|
|
// The sections of an OCR block; strings and arrays from the context allocator. "" gives none.
|
|
mcap_ocr_parse :: (text: string) -> [..] Mcap_Ocr_Section, ok: bool, error: string {
|
|
sections: [..] Mcap_Ocr_Section;
|
|
if !text.count return sections, true, "";
|
|
section: *Mcap_Ocr_Section;
|
|
version_seen := false;
|
|
version: s64;
|
|
for raw: split(text, "\n",, temp) {
|
|
line := trim(raw);
|
|
if !line.count || line[0] == #char "#" continue;
|
|
if begins_with(line, "@version") {
|
|
rest := trim(advance(line, 8));
|
|
number, ok := string_to_int(rest);
|
|
if !ok || number < 1 || number > MCAP_OCR_TEXT_VERSION return sections, false, tprint("its OCR text is version %, but this build reads up to %", rest, MCAP_OCR_TEXT_VERSION);
|
|
version = number;
|
|
version_seen = true;
|
|
continue;
|
|
}
|
|
if !version_seen return sections, false, "its OCR text does not start with @version";
|
|
|
|
if begins_with(line, "[ocr ") && ends_with(line, "]") {
|
|
section = array_add(*sections);
|
|
section.* = .{};
|
|
section.model = copy_string(trim(slice(line, 5, line.count - 6)));
|
|
continue;
|
|
}
|
|
colon := find_index_from_left(line, #char ":");
|
|
if colon < 0 || !section continue;
|
|
key := trim(slice(line, 0, colon));
|
|
value := trim(advance(line, colon + 1));
|
|
if key == {
|
|
case "engine";
|
|
quoted, _, ok := mcap_read_quoted(value);
|
|
if ok section.engine = copy_string(quoted);
|
|
case "image";
|
|
parts := split(value, " ",, temp);
|
|
if parts.count == 3 {
|
|
section.image = copy_string(parts[0]);
|
|
section.image_width = cast(s32) string_to_int(parts[1]);
|
|
section.image_height = cast(s32) string_to_int(parts[2]);
|
|
}
|
|
case "date";
|
|
section.date = copy_string(value);
|
|
case "line";
|
|
ocr_line, ok := ocr_parse_line(value, version);
|
|
if ok array_add(*section.lines, ocr_line);
|
|
}
|
|
}
|
|
return sections, true, "";
|
|
}
|
|
|
|
// The OCR block for sections. Temp.
|
|
mcap_ocr_text :: (sections: [] Mcap_Ocr_Section) -> string {
|
|
if !sections.count return "";
|
|
builder: String_Builder;
|
|
builder.allocator = temp;
|
|
print_to_builder(*builder, "@version %\n", MCAP_OCR_TEXT_VERSION);
|
|
for sections {
|
|
print_to_builder(*builder, "\n[ocr %]\n", it.model);
|
|
print_to_builder(*builder, "engine: %\n", mcap_quote(it.engine));
|
|
print_to_builder(*builder, "image: % % %\n", it.image, it.image_width, it.image_height);
|
|
print_to_builder(*builder, "date: %\n", it.date);
|
|
for line: it.lines {
|
|
c := mcap_ocr_corners(line);
|
|
print_to_builder(*builder, "line: % % % % % % % % % % % % % %\n", line.x, line.y, line.width, line.height,
|
|
c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], formatFloat(line.confidence, trailing_width = 3, zero_removal = .NO), mcap_quote(line.text));
|
|
}
|
|
}
|
|
return builder_to_string(*builder,, temp);
|
|
}
|
|
|
|
// Stores section in the .mcap at path, in place of any section by the same model, keeping the
|
|
// others, and upgrades the file to the current version. The file is rewritten beside itself and
|
|
// swapped in (mcap_write), so a failure leaves it as it was.
|
|
mcap_store_ocr :: (path: string, section: Mcap_Ocr_Section) -> ok: bool, error: string {
|
|
ok, error := mcap_store(path, *section);
|
|
return ok, error;
|
|
}
|
|
|
|
// The time now as the date: key writes it, UTC. Temp.
|
|
mcap_ocr_now :: () -> string {
|
|
t := to_calendar(current_time_consensus(), .UTC);
|
|
two :: (n: s64) -> string { return tprint("%", formatInt(n, minimum_digits = 2)); }
|
|
return tprint("%-%-%T%:%:%Z", t.year, two(t.month_starting_at_0 + 1), two(t.day_of_month_starting_at_0 + 1), two(t.hour), two(t.minute), two(t.second));
|
|
}
|
|
|
|
// The quoting the text formats share (OCR text, labels, segments), and the app's own text files (.library-folders).
|
|
|
|
// Quoted, a quote written twice, a control character as a space. Temp.
|
|
mcap_quote :: (text: string) -> string {
|
|
builder: String_Builder;
|
|
builder.allocator = temp;
|
|
append(*builder, #char "\"");
|
|
for 0..text.count - 1 {
|
|
c := text[it];
|
|
if c == #char "\"" append(*builder, "\"\"");
|
|
else if c < 0x20 || c == 0x7F append(*builder, #char " ");
|
|
else append(*builder, c);
|
|
}
|
|
append(*builder, #char "\"");
|
|
return builder_to_string(*builder,, temp);
|
|
}
|
|
|
|
// One quoted string ("" for a quote) at the start of s: its text (context allocator) and what follows.
|
|
mcap_read_quoted :: (s: string) -> text: string, rest: string, ok: bool {
|
|
if !s.count || s[0] != #char "\"" return "", s, false;
|
|
builder: String_Builder;
|
|
i := 1;
|
|
while i < s.count {
|
|
if s[i] == #char "\"" {
|
|
if i + 1 < s.count && s[i + 1] == #char "\"" { append(*builder, #char "\""); i += 2; continue; }
|
|
return builder_to_string(*builder), advance(s, i + 1), true;
|
|
}
|
|
append(*builder, s[i]);
|
|
i += 1;
|
|
}
|
|
free_buffers(*builder);
|
|
return "", s, false;
|
|
}
|
|
|
|
#scope_module
|
|
|
|
// Version 2: x y width height x0 y0 x1 y1 x2 y2 x3 y3 confidence "text". Version 1: x y width height confidence "text".
|
|
ocr_parse_line :: (value: string, version: s64) -> Mcap_Ocr_Line, bool {
|
|
result: Mcap_Ocr_Line;
|
|
rest := value;
|
|
all_numbers: [13] float64;
|
|
numbers := array_view(all_numbers, 0, ifx version >= 2 then 13 else 5);
|
|
for * numbers {
|
|
rest = trim_left(rest);
|
|
space := find_index_from_left(rest, #char " ");
|
|
if space < 0 return result, false;
|
|
number, ok := string_to_float64(slice(rest, 0, space));
|
|
if !ok return result, false;
|
|
it.* = number;
|
|
rest = advance(rest, space);
|
|
}
|
|
text, after, ok := mcap_read_quoted(trim_left(rest));
|
|
if !ok || trim(after).count return result, false;
|
|
result.x, result.y = cast(s32) numbers[0], cast(s32) numbers[1];
|
|
result.width, result.height = cast(s32) numbers[2], cast(s32) numbers[3];
|
|
result.confidence = cast(float) numbers[numbers.count - 1];
|
|
result.text = text;
|
|
if numbers.count == 13 for 0..7 result.corners[it] = cast(s32) numbers[4 + it];
|
|
result.corners = mcap_ocr_corners(result);
|
|
return result, true;
|
|
}
|
|
|
|
#import "String";
|