[0.1.13] Models load when first needed, and unload after 10 minutes unused
- No model is made at startup (private memory there falls from about 4.3 GB to about 420 MB). ocr_ui_models_ready makes the OCR engine, then the label engine and the list's set, on the OCR thread when a reading, a scan (the button, -scan, or after a capture or an import) needs them; the OCR and scan buttons wait for them, showing "Loading the models...". A like: search asks for the label engine alone (labels_ui_want) and is worked out again once it is up. - ocr_ui_unload_idle_models, each frame and also in the tray: an engine (OCR, labels or segmentation) unused for MODELS_IDLE_UNLOAD_SECONDS (10 min) is destroyed by an UNLOAD task on the OCR thread, never during a scan, a waiting read or scan, or while search jobs hold the label engine. The label list's set stays, so loading again does not score the prompts again. - Choosing another OCR model with none loaded only notes it; -segment no longer waits for the OCR and label engines. Some 1.4 GB stays after an unload, the CUDA runtime it loaded.
This commit is contained in:
parent
e7229343c7
commit
157888d6d8
@ -29,7 +29,7 @@ APP_NAME :: "mcap";
|
||||
APP_FOLDER_NAME :: "mcap";
|
||||
GIT_BRANCH :: "check";
|
||||
GIT_HASH :: "-";
|
||||
VERSION :: "0.1.12";
|
||||
VERSION :: "0.1.13";
|
||||
BUILD_DATE :: "-";
|
||||
DEBUG :: true;
|
||||
COMPILE_TESTS :: true;
|
||||
|
||||
@ -8,7 +8,7 @@ APP_NAME :: "mcap";
|
||||
APP_FOLDER_NAME :: "mcap";
|
||||
APP_BUNDLE_IDENTIFIER :: "com.example.mcap";
|
||||
|
||||
VERSION :: "0.1.12";
|
||||
VERSION :: "0.1.13";
|
||||
// Where everything goes: bin/, or bin-headless/ for builds made with no_crash_dialog, so automated
|
||||
// builds never write over the ones you run by hand.
|
||||
build_directory := "bin";
|
||||
|
||||
@ -17,6 +17,7 @@ app_ui_startup :: () {
|
||||
// Every frame, before input is handed to ImGui. Poll background work here.
|
||||
app_ui_frame_start :: () {
|
||||
screen_capture_frame(); // also while hidden in the tray, where the frame ends before the UI
|
||||
ocr_ui_unload_idle_models(); // models unused for a while, also while in the tray
|
||||
}
|
||||
|
||||
// Once, on the shutdown thread after the window is asked to close (exit_shutdown.jai): save and
|
||||
@ -77,6 +78,7 @@ app_ui_shutdown_status :: () -> string {
|
||||
case .LABELS_CREATE; text = "Waiting for the label model to finish loading.";
|
||||
case .LABEL_SET; text = "Waiting for the label list to finish.";
|
||||
case .SEGMENT; text = "Stopping the segmentation scan: finishing the images it is segmenting.";
|
||||
case .UNLOAD; text = "Waiting for the models unused for a while to unload.";
|
||||
case; text = "Stopping OCR.";
|
||||
}
|
||||
case .SEGMENT; text = "Closing the segmentation model.";
|
||||
|
||||
@ -149,6 +149,7 @@ background_search_start :: () {
|
||||
likes: [..] string;
|
||||
likes.allocator = temp;
|
||||
for request.terms if it.field == .LIKE array_add(*likes, it.text);
|
||||
if likes.count labels_ui_want(); // when it is not up, the search is worked out again once it is
|
||||
if likes.count && labels_ui.engine {
|
||||
request.like_texts = NewArray(likes.count, string);
|
||||
for likes request.like_texts[it_index] = copy_string(it);
|
||||
|
||||
@ -921,7 +921,7 @@ import_frame :: () { using ImGui;
|
||||
importer.scan_pending = importer.read_after && importer.new_indices.count > 0 && !app.import_source;
|
||||
importer.segment_pending = library_folder_segments(importer.dest_name) && importer.new_indices.count > 0 && !app.import_source;
|
||||
}
|
||||
if importer.scan_pending && !ocr_ui.thread_busy && ocr_ui.engine && labels_ui_settled() {
|
||||
if importer.scan_pending && ocr_ui_models_ready(labels_ui.scan_labels) && ocr_ui.engine {
|
||||
importer.scan_pending = false;
|
||||
ocr_scan_start(importer.new_indices);
|
||||
}
|
||||
|
||||
@ -7,9 +7,10 @@
|
||||
// vectors and a hash of it, made on the OCR thread (app/ocr_ui.jai) and never changed after, so a
|
||||
// scan can hold the set it started with while the list is edited.
|
||||
//
|
||||
// The engine is made on the OCR thread too, once, after the OCR engine; it is destroyed only at
|
||||
// shutdown, after the background workers (app/background.jai), which use its text model for like:
|
||||
// searches, have stopped.
|
||||
// The engine is made on the OCR thread too, when a reading, a scan or a like: search first needs it
|
||||
// (ocr_ui_models_ready, labels_ui_want), and destroyed there once it has gone unused for a while
|
||||
// (ocr_ui_unload_idle_models) with no search job holding it; else at shutdown, after the background
|
||||
// workers (app/background.jai), which use its text model for like: searches, have stopped.
|
||||
|
||||
LABELS_WINDOW :: "Labels";
|
||||
LABELS_MODEL :: "siglip2-base-patch16-384"; // the one label model; its chunks are keyed by this
|
||||
@ -29,8 +30,10 @@ labels_ui: struct {
|
||||
// Owned by the OCR thread while ocr_ui.thread_busy, like ocr_ui.engine.
|
||||
engine: *Labels_Engine;
|
||||
engine_error: string;
|
||||
engine_tried: bool;
|
||||
set: *Label_Set; // the newest list's
|
||||
engine_tried: bool; // made, or failed; cleared when it is unloaded
|
||||
last_used: Apollo_Time; // main thread: when a task or a like: search last used it
|
||||
wanted: bool; // main thread: a like: search asked for it
|
||||
set: *Label_Set; // the newest list's
|
||||
retired_sets: [..] *Label_Set; // replaced while a scan might hold them; freed when the OCR thread is idle
|
||||
|
||||
// Main thread.
|
||||
@ -77,11 +80,11 @@ labels_ui_shutdown :: () {
|
||||
free(labels_ui.counted_hash,, context.default_allocator);
|
||||
}
|
||||
|
||||
// Whether a scan can start with labels: the engine has been tried and, when it is up, the list's
|
||||
// set is made.
|
||||
labels_ui_settled :: () -> bool {
|
||||
if ocr_ui.thread_busy || !labels_ui.engine_tried return false;
|
||||
return !labels_ui.engine || (labels_ui.set && labels_ui.set.hash == labels_ui.list_hash);
|
||||
// A like: search needs the engine: it is made when the OCR thread is free (labels_ui_frame), and the
|
||||
// search worked out again once it is up.
|
||||
labels_ui_want :: () {
|
||||
labels_ui.last_used = current_time_monotonic();
|
||||
if !labels_ui.engine_tried labels_ui.wanted = true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- the label list
|
||||
@ -218,8 +221,9 @@ labels_ui_frame :: () { using ImGui;
|
||||
if !ocr_ui.thread_busy {
|
||||
for labels_ui.retired_sets label_set_free(it);
|
||||
array_reset_keeping_memory(*labels_ui.retired_sets);
|
||||
if !labels_ui.engine_tried && ocr_ui.engine_tried { // after the OCR engine
|
||||
ocr_ui_start(.{kind = .LABELS_CREATE});
|
||||
if labels_ui.wanted {
|
||||
labels_ui.wanted = false;
|
||||
if !labels_ui.engine_tried ocr_ui_start(.{kind = .LABELS_CREATE});
|
||||
} else if labels_ui.engine && (!labels_ui.set || labels_ui.set.hash != labels_ui.list_hash) && labels_ui.list.count {
|
||||
ocr_ui_start(.{kind = .LABEL_SET});
|
||||
}
|
||||
@ -272,7 +276,7 @@ labels_window_contents :: () { using ImGui;
|
||||
Text("%: images on %, text on the CPU", labels_ui.engine.model.title, ifx in_use then "the GPU (CUDA)" else "the CPU");
|
||||
if explanation text_with_links(explanation);
|
||||
} else if !labels_ui.engine_tried {
|
||||
TextColored(app_theme.palette.dim, "Starts after OCR.");
|
||||
TextColored(app_theme.palette.dim, "Not loaded: it loads when a capture is read or labelled, or a like: search runs, and unloads after % minutes unused.", MODELS_IDLE_UNLOAD_SECONDS / 60);
|
||||
}
|
||||
|
||||
// How much of the library is labelled, and with which list: counted again only when the catalog
|
||||
|
||||
@ -10,11 +10,15 @@
|
||||
// Making the engines (OCR, then labels), the label list's vectors, reading one capture and the scan
|
||||
// all run on one background thread, one at a time; the UI only looks at what it has finished, and
|
||||
// at the scan's progress counters.
|
||||
//
|
||||
// No engine is made at startup: each is made when something first needs it (ocr_ui_models_ready),
|
||||
// and destroyed again once it has gone MODELS_IDLE_UNLOAD_SECONDS unused (ocr_ui_unload_idle_models).
|
||||
|
||||
OCR_WINDOW :: "OCR";
|
||||
OCR_DEFAULT_MODEL :: "ppocr-v5-mobile";
|
||||
OCR_SCAN_BATCH :: 8; // captures per ocr_process call in a scan
|
||||
OCR_STORE_ATTEMPTS :: 4; // a file the viewer has open at that moment is tried again, 250 ms apart
|
||||
MODELS_IDLE_UNLOAD_SECONDS :: 10 * 60; // an engine unused this long is destroyed, giving its memory back
|
||||
|
||||
ocr_ui: struct {
|
||||
models: [] Ocr_Model_Info;
|
||||
@ -24,7 +28,8 @@ ocr_ui: struct {
|
||||
// Owned by the main thread while thread_busy is false; by the background thread while it is true.
|
||||
engine: *Ocr_Engine;
|
||||
engine_error: string; // why the last engine could not be made
|
||||
engine_tried: bool; // the first engine has been made, or failed: labels start after it
|
||||
engine_tried: bool; // the engine has been made, or failed; cleared when it is unloaded
|
||||
last_used: Apollo_Time; // main thread: when a task last used the engine
|
||||
thread: Thread;
|
||||
thread_started: bool;
|
||||
thread_busy: bool; // set by the main thread, cleared by the background one
|
||||
@ -33,6 +38,10 @@ ocr_ui: struct {
|
||||
read_error: string; // why the last single read failed
|
||||
read_index: s32 = -1;
|
||||
read_seconds: float64;
|
||||
read_wanted: bool; // the OCR button was pressed: it reads once the models are up
|
||||
read_wanted_index: s32;
|
||||
read_wanted_path: string; // heap
|
||||
scan_wanted: bool; // the scan button was pressed: it scans once the models are up
|
||||
|
||||
scan: Ocr_Scan;
|
||||
scan_requested: bool; // -scan has started its scan
|
||||
@ -49,7 +58,7 @@ ocr_ui: struct {
|
||||
toast_shown := false;
|
||||
}
|
||||
|
||||
Ocr_Task_Kind :: enum { NONE; CREATE; READ; SCAN; LABELS_CREATE; LABEL_SET; ERASE; SEGMENT; }
|
||||
Ocr_Task_Kind :: enum { NONE; CREATE; READ; SCAN; LABELS_CREATE; LABEL_SET; ERASE; SEGMENT; UNLOAD; }
|
||||
|
||||
Ocr_Task :: struct {
|
||||
kind: Ocr_Task_Kind;
|
||||
@ -59,6 +68,10 @@ Ocr_Task :: struct {
|
||||
// LABELS_CREATE and LABEL_SET: the label list as it was when the task started (heap).
|
||||
label_names, label_prompts: [] string;
|
||||
label_hash: string;
|
||||
// UNLOAD: the engines to destroy, already taken out of ocr_ui, labels_ui and segment_ui.
|
||||
unload_ocr: *Ocr_Engine;
|
||||
unload_labels: *Labels_Engine;
|
||||
unload_segment: *Segment_Engine;
|
||||
}
|
||||
|
||||
// The library scan. The counters and flags are shared with the background thread.
|
||||
@ -103,7 +116,7 @@ ocr_ui_startup :: () {
|
||||
ocr_ui.models = models;
|
||||
if error ocr_ui.models_error = copy_string(error);
|
||||
ocr_ui.model_index = ocr_model_index(app_user_settings.ocr_model);
|
||||
if ocr_ui.models.count ocr_ui_start(.{kind = .CREATE, model = ocr_ui.models[ocr_ui.model_index].name});
|
||||
// The engine is made when something first needs it: ocr_ui_models_ready.
|
||||
}
|
||||
|
||||
ocr_ui_shutdown :: () {
|
||||
@ -111,6 +124,7 @@ ocr_ui_shutdown :: () {
|
||||
atomic_swap(*ocr_ui.scan.paused, false);
|
||||
if ocr_ui.thread_started thread_is_done(*ocr_ui.thread, -1);
|
||||
ocr_destroy(ocr_ui.engine);
|
||||
free(ocr_ui.read_wanted_path,, context.default_allocator);
|
||||
}
|
||||
|
||||
// The model the settings name, or the default one, or the first; with a log line when the settings
|
||||
@ -135,7 +149,13 @@ ocr_ui_choose_model :: (index: s32, save := true) {
|
||||
if ocr_ui.thread_busy return;
|
||||
ocr_ui.model_index = index;
|
||||
ocr_ui.toast_shown = true; // said once already, or not needed
|
||||
ocr_ui_start(.{kind = .CREATE, model = ocr_ui.models[index].name});
|
||||
if ocr_ui.engine {
|
||||
ocr_ui_start(.{kind = .CREATE, model = ocr_ui.models[index].name});
|
||||
} else { // not loaded, or failed: the next use makes the one chosen
|
||||
ocr_ui.engine_tried = false;
|
||||
free(ocr_ui.engine_error,, context.default_allocator);
|
||||
ocr_ui.engine_error = "";
|
||||
}
|
||||
if save {
|
||||
free(app_user_settings.ocr_model,, context.default_allocator);
|
||||
app_user_settings.ocr_model = copy_string(ocr_ui.models[index].name,, context.default_allocator);
|
||||
@ -160,6 +180,7 @@ ocr_ui_start :: (task: Ocr_Task) -> bool {
|
||||
}
|
||||
ocr_ui.task.label_hash = copy_string(labels_ui.list_hash);
|
||||
}
|
||||
ocr_ui_note_use(task.kind, current_time_monotonic());
|
||||
ocr_ui.thread_busy = true;
|
||||
if ocr_ui.thread_started thread_deinit(*ocr_ui.thread);
|
||||
ocr_ui.thread_started = thread_init(*ocr_ui.thread, ocr_ui_thread_proc, temporary_storage_size = 4 * 1024 * 1024, name = "ocr");
|
||||
@ -168,6 +189,86 @@ ocr_ui_start :: (task: Ocr_Task) -> bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- the models: made when needed, unloaded when idle
|
||||
|
||||
// Whether the OCR engine (and, with labels, the label engine and the list's set) are up, or could
|
||||
// not be made, so a reading or a scan can start. When not, starts making the next of them on the OCR
|
||||
// thread, if it is free. Called each frame by what waits on them. After it returns true, a null
|
||||
// ocr_ui.engine means OCR could not start.
|
||||
ocr_ui_models_ready :: (labels := true) -> bool {
|
||||
if ocr_ui.thread_busy return false;
|
||||
if !ocr_ui.engine_tried {
|
||||
if !ocr_ui.models.count return true; // ocr_ui.models_error says why
|
||||
ocr_ui_start(.{kind = .CREATE, model = ocr_ui.models[ocr_ui.model_index].name});
|
||||
return false;
|
||||
}
|
||||
if !labels return true;
|
||||
if !labels_ui.engine_tried {
|
||||
ocr_ui_start(.{kind = .LABELS_CREATE});
|
||||
return false;
|
||||
}
|
||||
if labels_ui.engine && labels_ui.list.count && (!labels_ui.set || labels_ui.set.hash != labels_ui.list_hash) {
|
||||
ocr_ui_start(.{kind = .LABEL_SET});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Each frame, also while the window is in the tray: notes which engines the OCR thread's task uses,
|
||||
// and destroys those unused for MODELS_IDLE_UNLOAD_SECONDS, on the OCR thread.
|
||||
ocr_ui_unload_idle_models :: () {
|
||||
if app_exit_shutdown_started() return; // the shutdown thread destroys them
|
||||
now := current_time_monotonic();
|
||||
if ocr_ui.thread_busy { ocr_ui_note_use(ocr_ui.task.kind, now); return; }
|
||||
if ocr_ui.scan.running || ocr_ui.read_wanted || ocr_ui.scan_wanted return;
|
||||
|
||||
idle :: (last_used: Apollo_Time, now: Apollo_Time) -> bool {
|
||||
return to_float64_seconds(now - last_used) >= MODELS_IDLE_UNLOAD_SECONDS;
|
||||
}
|
||||
task := Ocr_Task.{kind = .UNLOAD};
|
||||
if ocr_ui.engine && idle(ocr_ui.last_used, now) {
|
||||
task.unload_ocr = ocr_ui.engine;
|
||||
ocr_ui.engine = null;
|
||||
ocr_ui.engine_tried = false;
|
||||
}
|
||||
// A like: search's jobs hold the label engine until they are back.
|
||||
if labels_ui.engine && idle(labels_ui.last_used, now) && !background.jobs_out {
|
||||
task.unload_labels = labels_ui.engine;
|
||||
labels_ui.engine = null;
|
||||
labels_ui.engine_tried = false;
|
||||
labels_ui.search_ready = false; // the list's set stays: the same model makes the same vectors
|
||||
}
|
||||
if segment_ui.engine && idle(segment_ui.last_used, now) {
|
||||
task.unload_segment = segment_ui.engine;
|
||||
segment_ui.engine = null;
|
||||
}
|
||||
if !task.unload_ocr && !task.unload_labels && !task.unload_segment return;
|
||||
|
||||
names: [..] string;
|
||||
names.allocator = temp;
|
||||
if task.unload_ocr array_add(*names, task.unload_ocr.model.name);
|
||||
if task.unload_labels array_add(*names, LABELS_MODEL);
|
||||
if task.unload_segment array_add(*names, SEGMENT_MODEL);
|
||||
log("[Models] Unloading % after % minutes unused.", join(..names, separator = ", ",, temp), MODELS_IDLE_UNLOAD_SECONDS / 60);
|
||||
if !ocr_ui_start(task) { // the thread could not start: destroy them here instead
|
||||
ocr_destroy(task.unload_ocr);
|
||||
labels_destroy(task.unload_labels);
|
||||
segment_destroy(task.unload_segment);
|
||||
}
|
||||
}
|
||||
|
||||
// The engines a task of this kind uses, as used at now: when it starts, and each frame it runs.
|
||||
ocr_ui_note_use :: (kind: Ocr_Task_Kind, now: Apollo_Time) {
|
||||
if kind == {
|
||||
case .CREATE; ocr_ui.last_used = now;
|
||||
case .LABELS_CREATE; labels_ui.last_used = now;
|
||||
case .LABEL_SET; labels_ui.last_used = now;
|
||||
case .READ; ocr_ui.last_used = now; labels_ui.last_used = now;
|
||||
case .SCAN; ocr_ui.last_used = now; if ocr_ui.scan.label_set labels_ui.last_used = now;
|
||||
case .SEGMENT; segment_ui.last_used = now;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- the background thread
|
||||
|
||||
ocr_ui_thread_proc :: (thread: *Thread) -> s64 {
|
||||
@ -229,6 +330,11 @@ ocr_ui_thread_proc :: (thread: *Thread) -> s64 {
|
||||
case .SEGMENT;
|
||||
segment_ui_scan_task(); // app/segment_ui.jai
|
||||
|
||||
case .UNLOAD;
|
||||
ocr_destroy(task.unload_ocr);
|
||||
labels_destroy(task.unload_labels);
|
||||
segment_destroy(task.unload_segment);
|
||||
|
||||
case .ERASE;
|
||||
scan := *ocr_ui.scan;
|
||||
// Not in temporary storage, which is reset after each capture.
|
||||
@ -512,7 +618,8 @@ ocr_ui_scan_and_exit :: (busy: bool) {
|
||||
if ocr_ui.models_error { finish(1, "[OCR scan] %", ocr_ui.models_error); return; }
|
||||
if viewer.scan_error { finish(1, "[OCR scan] %", viewer.scan_error); return; }
|
||||
if !ocr_ui.scan_requested {
|
||||
if busy || !ocr_ui.engine || viewer.catalog_loaded < viewer.captures.count || !labels_ui_settled() return;
|
||||
if busy || viewer.catalog_loaded < viewer.captures.count || !ocr_ui_models_ready(labels_ui.scan_labels) return;
|
||||
if !ocr_ui.engine { finish(1, "[OCR scan] OCR could not start: %", ocr_ui.engine_error); return; }
|
||||
ocr_ui.scan_requested = true;
|
||||
ocr_ui.window_open = true; // its progress bar
|
||||
ocr_scan_start();
|
||||
@ -570,17 +677,18 @@ ocr_scan_ui :: () { using ImGui;
|
||||
catalog_ready := viewer.catalog_loaded >= viewer.captures.count;
|
||||
|
||||
if !scan.running {
|
||||
can_scan := ocr_ui.engine && !busy && catalog_ready;
|
||||
can_scan := !busy && !ocr_ui.scan_wanted && catalog_ready;
|
||||
if !can_scan BeginDisabled();
|
||||
label := ifx viewer.search_terms.count then "Scan the captures shown" else "Scan the library";
|
||||
if Button(temp_c_string(label)) ocr_scan_start();
|
||||
if Button(temp_c_string(label)) ocr_ui.scan_wanted = true; // it starts once the models are up: ocr_ui_frame
|
||||
if !can_scan EndDisabled();
|
||||
SameLine();
|
||||
labels_ready := labels_ui.engine && labels_ui.set && labels_ui.set.hash == labels_ui.list_hash;
|
||||
if !labels_ready BeginDisabled();
|
||||
labels_failed := labels_ui.engine_tried && !labels_ui.engine;
|
||||
if labels_failed BeginDisabled();
|
||||
Checkbox("Labels too", *labels_ui.scan_labels);
|
||||
if !labels_ready EndDisabled();
|
||||
if IsItemHovered(.AllowWhenDisabled) SetTooltip(ifx labels_ready then tprint("Also labels each capture with %, storing its labels and image vector", LABELS_MODEL) else "Labelling is not ready: see the Labels window");
|
||||
if labels_failed EndDisabled();
|
||||
if IsItemHovered(.AllowWhenDisabled) SetTooltip(ifx labels_failed then "Labelling could not start: see the Labels window" else tprint("Also labels each capture with %, storing its labels and image vector", LABELS_MODEL));
|
||||
if ocr_ui.scan_wanted TextColored(app_theme.palette.dim, "Loading the models...");
|
||||
if !catalog_ready TextColored(app_theme.palette.dim, "(once the catalog has loaded)");
|
||||
else TextColored(app_theme.palette.dim, "Reads every capture this model has not read, and labels those not yet labelled with this list. Each is stored as it is read, so a scan can stop and start again.");
|
||||
if scan.total && scan.done {
|
||||
@ -652,6 +760,17 @@ ocr_ui_frame :: () { using ImGui;
|
||||
if app.scan_and_exit ocr_ui_scan_and_exit(busy);
|
||||
if app.erase_and_exit ocr_ui_erase_and_exit(busy);
|
||||
ocr_erase_popup();
|
||||
// The OCR and scan buttons wait for the models they load.
|
||||
if ocr_ui.read_wanted && ocr_ui_models_ready() {
|
||||
ocr_ui.read_wanted = false;
|
||||
ocr_ui_start(.{kind = .READ, path = ocr_ui.read_wanted_path, index = ocr_ui.read_wanted_index});
|
||||
busy = ocr_ui.thread_busy;
|
||||
}
|
||||
if ocr_ui.scan_wanted && ocr_ui_models_ready(labels_ui.scan_labels) {
|
||||
ocr_ui.scan_wanted = false;
|
||||
if ocr_ui.engine ocr_scan_start();
|
||||
busy = ocr_ui.thread_busy;
|
||||
}
|
||||
if !busy && ocr_ui.engine && !ocr_ui.toast_shown {
|
||||
ocr_ui.toast_shown = true;
|
||||
in_use, explanation := ocr_gpu_status(ocr_ui.engine);
|
||||
@ -685,7 +804,11 @@ ocr_ui_frame :: () { using ImGui;
|
||||
} else if ocr_ui.engine_error {
|
||||
TextColored(app_theme.palette.warning, "OCR could not start:");
|
||||
TextWrapped("%", ocr_ui.engine_error);
|
||||
} else if ocr_ui.engine {
|
||||
} else if !ocr_ui.engine {
|
||||
TextColored(app_theme.palette.dim, "Not loaded: it loads when a capture is read or a scan starts, and unloads after % minutes unused.", MODELS_IDLE_UNLOAD_SECONDS / 60);
|
||||
Separator();
|
||||
ocr_scan_ui();
|
||||
} else {
|
||||
in_use, explanation := ocr_gpu_status(ocr_ui.engine);
|
||||
Text("Running on: %", ifx in_use then "the GPU (CUDA)" else "the CPU");
|
||||
SameLine();
|
||||
@ -750,12 +873,21 @@ ocr_ui_capture_controls :: (index: s32, path: string) { using ImGui;
|
||||
model := ifx ocr_ui.models.count then ocr_ui.models[ocr_ui.model_index].name else "";
|
||||
has_reading := entry && catalog_section(entry, model) != null;
|
||||
|
||||
can_read := ocr_ui.engine && !busy;
|
||||
ocr_failed := ocr_ui.engine_tried && !ocr_ui.engine;
|
||||
can_read := ocr_ui.models.count && !busy && !ocr_ui.read_wanted && !ocr_failed;
|
||||
if !can_read BeginDisabled();
|
||||
if SmallButton(temp_c_string(ifx has_reading then "Read again" else "OCR")) ocr_ui_start(.{kind = .READ, path = path, index = index});
|
||||
if SmallButton(temp_c_string(ifx has_reading then "Read again" else "OCR")) { // it reads once the models are up: ocr_ui_frame
|
||||
ocr_ui.read_wanted = true;
|
||||
ocr_ui.read_wanted_index = index;
|
||||
free(ocr_ui.read_wanted_path,, context.default_allocator);
|
||||
ocr_ui.read_wanted_path = copy_string(path,, context.default_allocator);
|
||||
}
|
||||
if !can_read EndDisabled();
|
||||
SameLine();
|
||||
if busy && ocr_ui.task.kind == .READ && ocr_ui.task.index == index {
|
||||
if ocr_ui.read_wanted && ocr_ui.read_wanted_index == index {
|
||||
TextColored(app_theme.palette.dim, "Loading the models...");
|
||||
SameLine();
|
||||
} else if busy && ocr_ui.task.kind == .READ && ocr_ui.task.index == index {
|
||||
TextColored(app_theme.palette.dim, "Reading...");
|
||||
SameLine();
|
||||
} else if !busy && ocr_ui.read_index == index {
|
||||
@ -789,8 +921,8 @@ ocr_ui_capture_controls :: (index: s32, path: string) { using ImGui;
|
||||
}
|
||||
}
|
||||
SameLine();
|
||||
} else if !ocr_ui.engine {
|
||||
TextColored(app_theme.palette.dim, "(OCR is not running; see the OCR window)");
|
||||
} else if ocr_failed {
|
||||
TextColored(app_theme.palette.dim, "(OCR could not start; see the OCR window)");
|
||||
SameLine();
|
||||
}
|
||||
if SmallButton("OCR window") ocr_ui.window_open = true;
|
||||
|
||||
@ -112,8 +112,8 @@ screen_capture_frame :: () {
|
||||
remove job;
|
||||
}
|
||||
|
||||
// As an import's are, once the models are free: OCR and labels, then segmentation.
|
||||
if screen_capture.scan_pending.count && !ocr_ui.thread_busy && !ocr_ui.scan.running && ocr_ui.engine && labels_ui_settled() {
|
||||
// As an import's are, once the models are loaded and free: OCR and labels, then segmentation.
|
||||
if screen_capture.scan_pending.count && !ocr_ui.scan.running && ocr_ui_models_ready(labels_ui.scan_labels) && ocr_ui.engine {
|
||||
ocr_scan_start(screen_capture.scan_pending);
|
||||
array_reset_keeping_memory(*screen_capture.scan_pending);
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
// The scan runs on the OCR thread (app/ocr_ui.jai), as a task of its own, so it shares the OCR scan's
|
||||
// progress, pause and cancel, and one thread uses the GPU at a time. The engine is made by the first
|
||||
// scan, not at startup: it takes some 2.4 GB of GPU memory, and most sessions never segment anything.
|
||||
// It is destroyed again once it has gone unused for a while (ocr_ui_unload_idle_models).
|
||||
|
||||
SEGMENT_WINDOW :: "Segmentation";
|
||||
SEGMENT_MODEL :: "mask2former-swin-base-coco-panoptic"; // the one segmentation model; its chunks are keyed by this
|
||||
@ -15,6 +16,7 @@ segment_ui: struct {
|
||||
// Owned by the OCR thread while ocr_ui.thread_busy, like ocr_ui.engine.
|
||||
engine: *Segment_Engine;
|
||||
engine_error: string;
|
||||
last_used: Apollo_Time; // main thread: when a scan last used the engine (ocr_ui_unload_idle_models)
|
||||
|
||||
// Main thread.
|
||||
window_open: bool;
|
||||
@ -290,8 +292,8 @@ segment_ui_segment_and_exit :: (busy: bool) {
|
||||
}
|
||||
if viewer.scan_error { finish(1, "[Segment scan] %", viewer.scan_error); return; }
|
||||
if !segment_ui.scan_requested {
|
||||
// After the catalog, and after the engines made at startup, which use the same thread.
|
||||
if busy || viewer.catalog_loaded < viewer.captures.count || !ocr_ui.engine_tried || !labels_ui.engine_tried return;
|
||||
// After the catalog.
|
||||
if busy || viewer.catalog_loaded < viewer.captures.count return;
|
||||
segment_ui.scan_requested = true;
|
||||
segment_ui.window_open = true; // its progress bar
|
||||
if !segment_scan_start(quiet = true) finish(0, "[Segment scan] -segment: nothing to segment (no image in a folder set to segment needs it).");
|
||||
|
||||
@ -908,7 +908,7 @@ viewer_ui :: () { using ImGui;
|
||||
engine: *Ocr_Engine;
|
||||
if !ocr_ui.thread_busy engine = ocr_ui.engine; // the background thread owns it while busy
|
||||
if engine SetTooltip("OCR with %, on %", engine.model.name, ifx engine.provider == .CUDA then "the GPU" else "the CPU");
|
||||
else SetTooltip("OCR is starting, or could not start; the OCR window says which");
|
||||
else SetTooltip("OCR is not loaded, is loading, or could not start; the OCR window says which");
|
||||
}
|
||||
|
||||
// A running OCR scan, as a thin bar under the search, so it shows with the OCR window closed.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user