-
Notifications
You must be signed in to change notification settings - Fork 86
feat(ocr): rcli ocr + SDK 0.20.36 pin — DO NOT MERGE YET
#56
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
sanchitmonga22
wants to merge
1
commit into
main
Choose a base branch
from
feat/ocr-command
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,171 @@ | ||
| /** | ||
| * @file cmd_ocr.cpp | ||
| * @brief `rcli ocr <image.ppm> --model <ref>` — read the text on a page. | ||
| * | ||
| * Mirrors cmd_segment's shape (bootstrap → resolve model → one commons path → render), against | ||
| * the OCR service promoted to `ocr_ops` in ABI v11: | ||
| * | ||
| * rac_ocr_create(model) → rac_ocr_initialize(path) → rac_ocr_read_page(image, &result) | ||
| * → rac_ocr_result_free / _cleanup / _destroy | ||
| * | ||
| * `read_page` and NOT `recognize`. Full-page OCR is two models, and for the family this serves | ||
| * the recognizer's input is a grid-sampled crop of the DETECTOR's feature map rather than a line | ||
| * image — so `recognize` returns NOT_SUPPORTED and there is no line-level verb to expose here. | ||
| * | ||
| * PPM (P6) for the same reason cmd_segment uses it: the dependency-free image format the CLI can | ||
| * decode without linking a codec. | ||
| */ | ||
| #include <cstdio> | ||
| #include <memory> | ||
| #include <string> | ||
| #include <vector> | ||
|
|
||
| #include "commands/commands.h" | ||
| #include "commands/model_setup.h" | ||
| #include "io/image_io.h" | ||
| #include "io/output.h" | ||
| #include "rac/features/ocr/rac_ocr_service.h" | ||
|
|
||
| namespace rcli::commands { | ||
|
|
||
| namespace { | ||
|
|
||
| int resolve_model_path(const GlobalOptions& options, const std::string& ref, | ||
| std::string* out_path) { | ||
| ResolvedModelPaths model; | ||
| const int setup = ensure_model_ready(options, ref, &model); | ||
| if (setup != 0) { | ||
| return setup; | ||
| } | ||
| *out_path = model.primary_path; | ||
| return 0; | ||
| } | ||
|
|
||
| void print_result(const GlobalOptions& options, const std::string& model_ref, | ||
| const rac_ocr_result_t& result) { | ||
| if (options.json) { | ||
| out::JsonWriter json; | ||
| json.begin_object() | ||
| .field("model", model_ref) | ||
| .field("region_count", static_cast<int64_t>(result.num_regions)) | ||
| .field("processing_time_ms", static_cast<int64_t>(result.processing_time_ms)); | ||
| json.begin_array("regions"); | ||
| for (size_t i = 0; i < result.num_regions; ++i) { | ||
| const rac_ocr_region_t& r = result.regions[i]; | ||
| json.begin_array_object() | ||
| .field("text", r.text ? r.text : "") | ||
| .field("confidence", static_cast<double>(r.confidence)); | ||
| // The quad, not just the text. Geometry is half of what OCR | ||
| // produces -- dropping it here would make the JSON strictly less | ||
| // useful than the C API it wraps, and it is what a caller needs to | ||
| // crop, sort into reading order, or draw a box. | ||
| json.begin_array("quad"); | ||
| for (int q = 0; q < 8; ++q) { | ||
| json.value(static_cast<double>(r.quad[q])); | ||
| } | ||
| json.end_array().end_object(); | ||
| } | ||
| json.end_array().end_object(); | ||
| out::result_line(json.str()); | ||
| return; | ||
| } | ||
|
|
||
| if (result.num_regions == 0) { | ||
| out::result_line("(no text found)"); | ||
| } else { | ||
| std::vector<std::vector<std::string>> rows; | ||
| rows.reserve(result.num_regions); | ||
| for (size_t i = 0; i < result.num_regions; ++i) { | ||
| const rac_ocr_region_t& r = result.regions[i]; | ||
| char conf[16]; | ||
| std::snprintf(conf, sizeof(conf), "%.2f", static_cast<double>(r.confidence)); | ||
| rows.push_back({conf, r.text ? r.text : ""}); | ||
| } | ||
| out::table({"conf", "text"}, rows); | ||
| } | ||
| if (options.verbose) { | ||
| out::status_line("(" + std::to_string(result.processing_time_ms) + " ms)"); | ||
| } | ||
| } | ||
|
|
||
| int run_ocr(const GlobalOptions& options, const std::string& image_path, | ||
| const std::string& model_ref) { | ||
| Bootstrapped env; | ||
| if (bootstrap(options, &env) != RAC_SUCCESS) { | ||
| return 1; | ||
| } | ||
| if (model_ref.empty()) { | ||
| out::error_line("--model is required (an OCR model id or on-disk path)"); | ||
| return 2; | ||
| } | ||
|
|
||
| std::string model_path; | ||
| const int resolve = resolve_model_path(options, model_ref, &model_path); | ||
| if (resolve != 0) { | ||
| return resolve; | ||
| } | ||
|
|
||
| image::RgbImage src; | ||
| std::string err; | ||
| if (!image::read_ppm(image_path, &src, &err)) { | ||
| out::error_line(err); | ||
| return 1; | ||
| } | ||
|
|
||
| rac_handle_t handle = nullptr; | ||
| rac_result_t rc = rac_ocr_create(model_path.c_str(), &handle); | ||
| if (rc != RAC_SUCCESS) { | ||
| out::error_line("could not create the OCR service: " + out::describe_result(rc)); | ||
| return 1; | ||
| } | ||
| rc = rac_ocr_initialize(handle, model_path.c_str()); | ||
| if (rc != RAC_SUCCESS) { | ||
| // The manifest refuses a recognizer-only bundle BY NAME, which is what every | ||
| // nemotron-ocr bundle published before nemotron-ocr-v1-full_ANE is. | ||
| out::error_line("could not initialize the OCR model: " + out::describe_result(rc)); | ||
| rac_ocr_destroy(handle); | ||
| return 1; | ||
| } | ||
|
|
||
| rac_ocr_image_t image = {}; | ||
| image.format = RAC_OCR_FORMAT_RGB8; | ||
| image.pixels = src.rgb.data(); | ||
| image.width = src.width; | ||
| image.height = src.height; | ||
|
|
||
| rac_ocr_result_t result = {}; | ||
| rc = rac_ocr_read_page(handle, &image, &result); | ||
| if (rc != RAC_SUCCESS) { | ||
| out::error_line("read_page failed: " + out::describe_result(rc)); | ||
| rac_ocr_result_free(&result); | ||
| rac_ocr_cleanup(handle); | ||
| rac_ocr_destroy(handle); | ||
| return 1; | ||
| } | ||
|
|
||
| print_result(options, model_ref, result); | ||
| rac_ocr_result_free(&result); | ||
| rac_ocr_cleanup(handle); | ||
| rac_ocr_destroy(handle); | ||
| return 0; | ||
| } | ||
|
|
||
| } // namespace | ||
|
|
||
| void register_ocr(CLI::App& app, GlobalOptions& options) { | ||
| CLI::App* cmd = app.add_subcommand("ocr", "Read the text on a page image"); | ||
| auto image_path = std::make_shared<std::string>(); | ||
| auto model = std::make_shared<std::string>(); | ||
| cmd->add_option("image", *image_path, "Input image (binary PPM / P6)") | ||
| ->required() | ||
| ->check(CLI::ExistingFile); | ||
| cmd->add_option("--model,-m", *model, "OCR model id or on-disk path")->required(); | ||
| cmd->callback([&options, image_path, model]() { | ||
| const int exit_code = run_ocr(options, *image_path, *model); | ||
| if (exit_code != 0) { | ||
| throw CLI::RuntimeError(exit_code); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| } // namespace rcli::commands |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Remove or replace the known-broken OCR model.
ocr-aneresolves tonemotron_ocr_v1_full_ane. The PR objective states that this bundle does not correctly read pages. Users can therefore completercli ocrsuccessfully but receive incorrect OCR output.Do not register this entry until a verified v2 bundle is available. Alternatively, repoint this row to that bundle before merge.
🤖 Prompt for AI Agents