Honglin Yu | 0c4760a | 2020-04-18 20:53:31 +1000 | [diff] [blame] | 1 | // Copyright 2020 The Chromium OS Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include "ml/handwriting.h" |
| 6 | |
| 7 | #include <string> |
Honglin Yu | cfb1930 | 2020-08-17 17:13:14 +1000 | [diff] [blame] | 8 | #include <utility> |
Honglin Yu | 0c4760a | 2020-04-18 20:53:31 +1000 | [diff] [blame] | 9 | |
Qijiang Fan | 713061e | 2021-03-08 15:45:12 +0900 | [diff] [blame] | 10 | #include <base/check.h> |
| 11 | #include <base/check_op.h> |
Honglin Yu | 0c4760a | 2020-04-18 20:53:31 +1000 | [diff] [blame] | 12 | #include <base/files/file_path.h> |
| 13 | #include <base/logging.h> |
| 14 | #include <base/native_library.h> |
alanlxl | 867da6c | 2021-04-19 10:36:35 +1000 | [diff] [blame] | 15 | #include <base/optional.h> |
Honglin Yu | 0c4760a | 2020-04-18 20:53:31 +1000 | [diff] [blame] | 16 | |
| 17 | namespace ml { |
Honglin Yu | 0c4760a | 2020-04-18 20:53:31 +1000 | [diff] [blame] | 18 | namespace { |
charleszhao | 17777f9 | 2020-04-23 12:53:11 +1000 | [diff] [blame] | 19 | |
Charles Zhao | c882eb0 | 2020-07-27 10:02:35 +1000 | [diff] [blame] | 20 | using chrome_knowledge::HandwritingRecognizerModelPaths; |
| 21 | using chrome_knowledge::HandwritingRecognizerOptions; |
| 22 | using chromeos::machine_learning::mojom::HandwritingRecognizerSpecPtr; |
| 23 | |
| 24 | constexpr char kHandwritingLibraryRelativePath[] = "libhandwriting.so"; |
Charles Zhao | c882eb0 | 2020-07-27 10:02:35 +1000 | [diff] [blame] | 25 | |
| 26 | // A list of supported language code. |
| 27 | constexpr char kLanguageCodeEn[] = "en"; |
| 28 | constexpr char kLanguageCodeGesture[] = "gesture_in_context"; |
| 29 | |
| 30 | // Returns HandwritingRecognizerModelPaths based on the `spec`. |
| 31 | HandwritingRecognizerModelPaths GetModelPaths( |
Honglin Yu | 0f5b21d | 2021-04-06 23:39:04 +1000 | [diff] [blame] | 32 | const std::string& language, const base::FilePath& model_path) { |
Charles Zhao | c882eb0 | 2020-07-27 10:02:35 +1000 | [diff] [blame] | 33 | HandwritingRecognizerModelPaths paths; |
Honglin Yu | 0f5b21d | 2021-04-06 23:39:04 +1000 | [diff] [blame] | 34 | if (language == kLanguageCodeEn) { |
Charles Zhao | 6d467e6 | 2020-08-31 10:02:03 +1000 | [diff] [blame] | 35 | paths.set_reco_model_path(model_path.Append("latin_indy.tflite").value()); |
| 36 | paths.set_seg_model_path( |
| 37 | model_path.Append("latin_indy_seg.tflite").value()); |
| 38 | paths.set_conf_model_path( |
| 39 | model_path.Append("latin_indy_conf.tflite").value()); |
| 40 | paths.set_fst_lm_path(model_path.Append("latin_indy.compact.fst").value()); |
| 41 | paths.set_recospec_path(model_path.Append("latin_indy.pb").value()); |
Charles Zhao | c882eb0 | 2020-07-27 10:02:35 +1000 | [diff] [blame] | 42 | return paths; |
| 43 | } |
| 44 | |
Honglin Yu | 0f5b21d | 2021-04-06 23:39:04 +1000 | [diff] [blame] | 45 | DCHECK_EQ(language, kLanguageCodeGesture); |
Charles Zhao | 6d467e6 | 2020-08-31 10:02:03 +1000 | [diff] [blame] | 46 | paths.set_reco_model_path(model_path.Append("gic.reco_model.tflite").value()); |
| 47 | paths.set_recospec_path(model_path.Append("gic.recospec.pb").value()); |
Charles Zhao | c882eb0 | 2020-07-27 10:02:35 +1000 | [diff] [blame] | 48 | return paths; |
Honglin Yu | 0c4760a | 2020-04-18 20:53:31 +1000 | [diff] [blame] | 49 | } |
| 50 | |
alanlxl | 867da6c | 2021-04-19 10:36:35 +1000 | [diff] [blame] | 51 | class HandwritingLibraryImpl : public HandwritingLibrary { |
| 52 | public: |
| 53 | HandwritingLibraryImpl(); |
| 54 | ~HandwritingLibraryImpl() override = default; |
charleszhao | 17777f9 | 2020-04-23 12:53:11 +1000 | [diff] [blame] | 55 | |
alanlxl | 867da6c | 2021-04-19 10:36:35 +1000 | [diff] [blame] | 56 | Status GetStatus() const override; |
| 57 | HandwritingRecognizer CreateHandwritingRecognizer() const override; |
| 58 | bool LoadHandwritingRecognizer(HandwritingRecognizer recognizer, |
| 59 | const std::string& language) const override; |
| 60 | bool RecognizeHandwriting( |
| 61 | HandwritingRecognizer recognizer, |
| 62 | const chrome_knowledge::HandwritingRecognizerRequest& request, |
| 63 | chrome_knowledge::HandwritingRecognizerResult* result) const override; |
Charles Zhao | 6d467e6 | 2020-08-31 10:02:03 +1000 | [diff] [blame] | 64 | |
alanlxl | 867da6c | 2021-04-19 10:36:35 +1000 | [diff] [blame] | 65 | void DestroyHandwritingRecognizer( |
| 66 | HandwritingRecognizer recognizer) const override; |
| 67 | |
| 68 | private: |
| 69 | friend class base::NoDestructor<HandwritingLibraryImpl>; |
| 70 | FRIEND_TEST(HandwritingLibraryTest, CanLoadLibrary); |
| 71 | |
| 72 | // Initialize the handwriting library. |
| 73 | explicit HandwritingLibraryImpl(const std::string& root_path); |
| 74 | HandwritingLibraryImpl(const HandwritingLibraryImpl&) = delete; |
| 75 | HandwritingLibraryImpl& operator=(const HandwritingLibraryImpl&) = delete; |
| 76 | |
| 77 | base::Optional<base::ScopedNativeLibrary> library_; |
| 78 | Status status_; |
| 79 | const base::FilePath model_path_; |
| 80 | |
| 81 | // Store the interface function pointers. |
| 82 | // TODO(honglinyu) as pointed out by cjmcdonald@, we should group the pointers |
| 83 | // into a single `HandwritingInterface` struct and make it optional, i.e., |
| 84 | // declaring something like |base::Optional<HandwritingInterface> interface_|. |
| 85 | CreateHandwritingRecognizerFn create_handwriting_recognizer_; |
| 86 | LoadHandwritingRecognizerFn load_handwriting_recognizer_; |
| 87 | RecognizeHandwritingFn recognize_handwriting_; |
| 88 | DeleteHandwritingResultDataFn delete_handwriting_result_data_; |
| 89 | DestroyHandwritingRecognizerFn destroy_handwriting_recognizer_; |
| 90 | }; |
| 91 | |
| 92 | HandwritingLibraryImpl::HandwritingLibraryImpl(const std::string& model_path) |
Honglin Yu | 0c4760a | 2020-04-18 20:53:31 +1000 | [diff] [blame] | 93 | : status_(Status::kUninitialized), |
Charles Zhao | 6d467e6 | 2020-08-31 10:02:03 +1000 | [diff] [blame] | 94 | model_path_(model_path), |
Honglin Yu | 0c4760a | 2020-04-18 20:53:31 +1000 | [diff] [blame] | 95 | create_handwriting_recognizer_(nullptr), |
| 96 | load_handwriting_recognizer_(nullptr), |
| 97 | recognize_handwriting_(nullptr), |
| 98 | delete_handwriting_result_data_(nullptr), |
| 99 | destroy_handwriting_recognizer_(nullptr) { |
charleszhao | 17777f9 | 2020-04-23 12:53:11 +1000 | [diff] [blame] | 100 | if (!IsHandwritingLibrarySupported()) { |
| 101 | status_ = Status::kNotSupported; |
| 102 | return; |
| 103 | } |
Honglin Yu | 0c4760a | 2020-04-18 20:53:31 +1000 | [diff] [blame] | 104 | // Load the library with an option preferring own symbols. Otherwise the |
| 105 | // library will try to call, e.g., external tflite, which leads to crash. |
| 106 | base::NativeLibraryOptions native_library_options; |
| 107 | native_library_options.prefer_own_symbols = true; |
Honglin Yu | 0c4760a | 2020-04-18 20:53:31 +1000 | [diff] [blame] | 108 | library_.emplace(base::LoadNativeLibraryWithOptions( |
Charles Zhao | 6d467e6 | 2020-08-31 10:02:03 +1000 | [diff] [blame] | 109 | model_path_.Append(kHandwritingLibraryRelativePath), |
Charles Zhao | c882eb0 | 2020-07-27 10:02:35 +1000 | [diff] [blame] | 110 | native_library_options, nullptr)); |
Honglin Yu | 0c4760a | 2020-04-18 20:53:31 +1000 | [diff] [blame] | 111 | if (!library_->is_valid()) { |
| 112 | status_ = Status::kLoadLibraryFailed; |
| 113 | return; |
| 114 | } |
| 115 | |
| 116 | // Helper macro to look up functions from the library, assuming the function |
| 117 | // pointer type is named as (name+"Fn"), which is the case in |
Honglin Yu | cfb1930 | 2020-08-17 17:13:14 +1000 | [diff] [blame] | 118 | // "libhandwriting/handwriting_interface.h". |
Honglin Yu | 0c4760a | 2020-04-18 20:53:31 +1000 | [diff] [blame] | 119 | #define ML_HANDWRITING_LOOKUP_FUNCTION(function_ptr, name) \ |
| 120 | function_ptr = \ |
| 121 | reinterpret_cast<name##Fn>(library_->GetFunctionPointer(#name)); \ |
| 122 | if (function_ptr == NULL) { \ |
| 123 | status_ = Status::kFunctionLookupFailed; \ |
| 124 | return; \ |
| 125 | } |
| 126 | // Look up the function pointers. |
| 127 | ML_HANDWRITING_LOOKUP_FUNCTION(create_handwriting_recognizer_, |
| 128 | CreateHandwritingRecognizer); |
| 129 | ML_HANDWRITING_LOOKUP_FUNCTION(load_handwriting_recognizer_, |
| 130 | LoadHandwritingRecognizer); |
| 131 | ML_HANDWRITING_LOOKUP_FUNCTION(recognize_handwriting_, RecognizeHandwriting); |
| 132 | ML_HANDWRITING_LOOKUP_FUNCTION(delete_handwriting_result_data_, |
| 133 | DeleteHandwritingResultData); |
| 134 | ML_HANDWRITING_LOOKUP_FUNCTION(destroy_handwriting_recognizer_, |
| 135 | DestroyHandwritingRecognizer); |
| 136 | #undef ML_HANDWRITING_LOOKUP_FUNCTION |
| 137 | |
| 138 | status_ = Status::kOk; |
| 139 | } |
| 140 | |
alanlxl | 867da6c | 2021-04-19 10:36:35 +1000 | [diff] [blame] | 141 | HandwritingLibrary::Status HandwritingLibraryImpl::GetStatus() const { |
Honglin Yu | 0c4760a | 2020-04-18 20:53:31 +1000 | [diff] [blame] | 142 | return status_; |
| 143 | } |
| 144 | |
Honglin Yu | 0c4760a | 2020-04-18 20:53:31 +1000 | [diff] [blame] | 145 | // Proxy functions to the library function pointers. |
alanlxl | 867da6c | 2021-04-19 10:36:35 +1000 | [diff] [blame] | 146 | HandwritingRecognizer HandwritingLibraryImpl::CreateHandwritingRecognizer() |
| 147 | const { |
Honglin Yu | 0c4760a | 2020-04-18 20:53:31 +1000 | [diff] [blame] | 148 | DCHECK(status_ == Status::kOk); |
| 149 | return (*create_handwriting_recognizer_)(); |
| 150 | } |
| 151 | |
alanlxl | 867da6c | 2021-04-19 10:36:35 +1000 | [diff] [blame] | 152 | bool HandwritingLibraryImpl::LoadHandwritingRecognizer( |
Honglin Yu | 0f5b21d | 2021-04-06 23:39:04 +1000 | [diff] [blame] | 153 | HandwritingRecognizer const recognizer, const std::string& language) const { |
Honglin Yu | 0c4760a | 2020-04-18 20:53:31 +1000 | [diff] [blame] | 154 | DCHECK(status_ == Status::kOk); |
Charles Zhao | c882eb0 | 2020-07-27 10:02:35 +1000 | [diff] [blame] | 155 | |
| 156 | // options is not used for now. |
| 157 | const std::string options_pb = |
| 158 | HandwritingRecognizerOptions().SerializeAsString(); |
| 159 | |
| 160 | const std::string paths_pb = |
Honglin Yu | 0f5b21d | 2021-04-06 23:39:04 +1000 | [diff] [blame] | 161 | GetModelPaths(language, model_path_).SerializeAsString(); |
Honglin Yu | 0c4760a | 2020-04-18 20:53:31 +1000 | [diff] [blame] | 162 | return (*load_handwriting_recognizer_)(recognizer, options_pb.data(), |
| 163 | options_pb.size(), paths_pb.data(), |
| 164 | paths_pb.size()); |
| 165 | } |
| 166 | |
alanlxl | 867da6c | 2021-04-19 10:36:35 +1000 | [diff] [blame] | 167 | bool HandwritingLibraryImpl::RecognizeHandwriting( |
Honglin Yu | 0c4760a | 2020-04-18 20:53:31 +1000 | [diff] [blame] | 168 | HandwritingRecognizer const recognizer, |
| 169 | const chrome_knowledge::HandwritingRecognizerRequest& request, |
| 170 | chrome_knowledge::HandwritingRecognizerResult* const result) const { |
| 171 | DCHECK(status_ == Status::kOk); |
| 172 | const std::string request_pb = request.SerializeAsString(); |
| 173 | char* result_data = nullptr; |
| 174 | int result_size = 0; |
| 175 | const bool recognize_result = |
| 176 | (*recognize_handwriting_)(recognizer, request_pb.data(), |
| 177 | request_pb.size(), &result_data, &result_size); |
| 178 | if (recognize_result) { |
| 179 | const bool parse_result_status = |
| 180 | result->ParseFromArray(result_data, result_size); |
| 181 | DCHECK(parse_result_status); |
| 182 | // only need to delete result_data if succeeds. |
| 183 | (*delete_handwriting_result_data_)(result_data); |
| 184 | } |
| 185 | |
| 186 | return recognize_result; |
| 187 | } |
| 188 | |
alanlxl | 867da6c | 2021-04-19 10:36:35 +1000 | [diff] [blame] | 189 | void HandwritingLibraryImpl::DestroyHandwritingRecognizer( |
Honglin Yu | 0c4760a | 2020-04-18 20:53:31 +1000 | [diff] [blame] | 190 | HandwritingRecognizer const recognizer) const { |
| 191 | DCHECK(status_ == Status::kOk); |
| 192 | (*destroy_handwriting_recognizer_)(recognizer); |
| 193 | } |
| 194 | |
alanlxl | 867da6c | 2021-04-19 10:36:35 +1000 | [diff] [blame] | 195 | static HandwritingLibrary* g_fake_handwriting_library = nullptr; |
| 196 | |
| 197 | } // namespace |
| 198 | |
| 199 | constexpr char HandwritingLibrary::kHandwritingDefaultModelDir[]; |
| 200 | |
| 201 | HandwritingLibrary* HandwritingLibrary::GetInstance( |
| 202 | const std::string& model_path) { |
| 203 | if (g_fake_handwriting_library) { |
| 204 | return g_fake_handwriting_library; |
| 205 | } |
| 206 | static base::NoDestructor<HandwritingLibraryImpl> instance(model_path); |
| 207 | return instance.get(); |
| 208 | } |
| 209 | |
| 210 | void HandwritingLibrary::UseFakeHandwritingLibraryForTesting( |
| 211 | HandwritingLibrary* const fake_handwriting_library) { |
| 212 | g_fake_handwriting_library = fake_handwriting_library; |
| 213 | } |
| 214 | |
Honglin Yu | 0c4760a | 2020-04-18 20:53:31 +1000 | [diff] [blame] | 215 | } // namespace ml |