Skip to content

Commit

Permalink
[OV JS] Support Core.setProperty() & Core.getProperty() methods in JS…
Browse files Browse the repository at this point in the history
… API (openvinotoolkit#23338)

### Details:
 - Implement Core.setProperty()
 - Implement Core.getProperty()

### Tickets:
 - 134824

---------

Co-authored-by: Pawel Raasz <[email protected]>
  • Loading branch information
vishniakov-nikolai and praasz authored Mar 26, 2024
1 parent c88847f commit 97d4780
Show file tree
Hide file tree
Showing 6 changed files with 377 additions and 3 deletions.
3 changes: 3 additions & 0 deletions src/bindings/js/node/include/core_wrap.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ class CoreWrap : public Napi::ObjectWrap<CoreWrap> {
*/
Napi::Value compile_model_async(const Napi::CallbackInfo& info);

Napi::Value set_property(const Napi::CallbackInfo& info);
Napi::Value get_property(const Napi::CallbackInfo& info);

protected:
Napi::Value compile_model_sync(const Napi::CallbackInfo& info,
const Napi::Object& model,
Expand Down
6 changes: 6 additions & 0 deletions src/bindings/js/node/include/helper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,9 @@ ov::Tensor value_to_tensor(const Napi::Value& value, ov::InferRequest& infer_req
napi_types napiType(const Napi::Value& val);

bool acceptableType(const Napi::Value& val, const std::vector<napi_types>& acceptable);

Napi::Value any_to_js(const Napi::CallbackInfo& info, ov::Any value);

ov::Any js_to_any(const Napi::CallbackInfo& info, Napi::Value value);

bool is_napi_value_int(const Napi::CallbackInfo& info, Napi::Value& num);
14 changes: 12 additions & 2 deletions src/bindings/js/node/lib/addon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ type elementTypeString =
interface Core {
compileModel(
model: Model,
device: string,
deviceName: string,
config?: { [option: string]: string }
): Promise<CompiledModel>;
compileModelSync(
model: Model,
device: string,
deviceName: string,
config?: { [option: string]: string }
): CompiledModel;
readModel(modelPath: string, weightsPath?: string): Promise<Model>;
Expand All @@ -38,6 +38,16 @@ interface Core {
readModelSync(modelBuffer: Uint8Array, weightsBuffer?: Uint8Array): Model;
importModelSync(modelStream: Buffer, device: string): CompiledModel;
getAvailableDevices(): string[];
setProperty(props: { [key: string]: string | number | boolean }): void;
setProperty(
deviceName: string,
props: { [key: string]: string | number | boolean },
): void;
getProperty(propertyName: string): string | number | boolean,
getProperty(
deviceName: string,
propertyName: string,
): string | number | boolean,
}
interface CoreConstructor {
new(): Core;
Expand Down
82 changes: 81 additions & 1 deletion src/bindings/js/node/src/core_wrap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,44 @@
#include "node/include/model_wrap.hpp"
#include "node/include/read_model_args.hpp"

void validate_set_property_args(const Napi::CallbackInfo& info) {
const size_t args_length = info.Length();
const bool is_device_specified = info[0].IsString();
const bool has_params_obj = info[is_device_specified ? 1 : 0];

if (!has_params_obj)
OPENVINO_THROW("Properties parameter must be an object");

if (args_length > (is_device_specified ? 2 : 1))
OPENVINO_THROW("setProperty applies 1 or 2 arguments only");
}

std::tuple<ov::AnyMap, std::string> try_get_set_property_parameters(const Napi::CallbackInfo& info) {
validate_set_property_args(info);

std::string device_name;
ov::AnyMap properties;

const size_t args_length = info.Length();

if (args_length > 1)
device_name = info[0].ToString();

const size_t parameters_position_index = device_name.empty() ? 0 : 1;
Napi::Object parameters = info[parameters_position_index].ToObject();
const auto& keys = parameters.GetPropertyNames();

for (uint32_t i = 0; i < keys.Length(); ++i) {
auto property_name = static_cast<Napi::Value>(keys[i]).ToString().Utf8Value();

ov::Any any_value = js_to_any(info, parameters.Get(property_name));

properties.insert(std::make_pair(property_name, any_value));
}

return std::make_tuple(properties, device_name);
}

CoreWrap::CoreWrap(const Napi::CallbackInfo& info) : Napi::ObjectWrap<CoreWrap>(info), _core{} {}

Napi::Function CoreWrap::get_class(Napi::Env env) {
Expand All @@ -20,8 +58,11 @@ Napi::Function CoreWrap::get_class(Napi::Env env) {
InstanceMethod("readModel", &CoreWrap::read_model_async),
InstanceMethod("compileModelSync", &CoreWrap::compile_model_sync_dispatch),
InstanceMethod("compileModel", &CoreWrap::compile_model_async),
InstanceMethod("getAvailableDevices", &CoreWrap::get_available_devices),
InstanceMethod("importModelSync", &CoreWrap::import_model),
InstanceMethod("getAvailableDevices", &CoreWrap::get_available_devices)});
InstanceMethod("getAvailableDevices", &CoreWrap::get_available_devices),
InstanceMethod("setProperty", &CoreWrap::set_property),
InstanceMethod("getProperty", &CoreWrap::get_property)});
}

Napi::Value CoreWrap::read_model_sync(const Napi::CallbackInfo& info) {
Expand Down Expand Up @@ -253,3 +294,42 @@ Napi::Value CoreWrap::import_model(const Napi::CallbackInfo& info) {
const auto& compiled = _core.import_model(_stream, std::string(info[1].ToString()));
return CompiledModelWrap::wrap(info.Env(), compiled);
}

Napi::Value CoreWrap::set_property(const Napi::CallbackInfo& info) {
try {
auto args = try_get_set_property_parameters(info);
ov::AnyMap properties = std::get<0>(args);
std::string device_name = std::get<1>(args);

if (device_name.empty()) {
_core.set_property(properties);
} else {
_core.set_property(device_name, properties);
}
} catch (std::runtime_error& err) {
reportError(info.Env(), err.what());
}

return info.Env().Undefined();
}

Napi::Value CoreWrap::get_property(const Napi::CallbackInfo& info) {
const size_t args_length = info.Length();
std::string device_name;

if (!(info[0].IsString() || (args_length == 2 && info[0].IsString() && info[1].IsString()))) {
reportError(info.Env(), "Invalid arguments of get_property function");

return info.Env().Undefined();
}

if (args_length == 2)
device_name = info[0].ToString();

std::string property_name = info[args_length > 1 ? 1 : 0].ToString();

ov::Any value =
device_name.empty() ? _core.get_property(property_name) : _core.get_property(device_name, property_name);

return any_to_js(info, value);
}
222 changes: 222 additions & 0 deletions src/bindings/js/node/src/helper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -318,3 +318,225 @@ ov::Tensor cast_to_tensor(const Napi::TypedArray& typed_array,
}
return tensor;
}

/**
* @brief Template function to convert C++ map into Javascript Object. Map key must be std::string.
* @tparam MapElementType C++ data type of map elements.
* @param info Contains the environment in which to construct a JavaScript object.
* @return Napi::Object.
*/
template <typename MapElementType>
Napi::Object cpp_map_to_js_object(const Napi::CallbackInfo& info, const std::map<std::string, MapElementType>& map) {
Napi::Object obj = Napi::Object::New(info.Env());

for (const auto& [k, v] : map) {
obj.Set(k, v);
}

return obj;
}

/**
* @brief Template function to convert C++ vector type into Javascript Array
* @tparam SourceType C++ data type of vector elements.
* @param info Contains the environment in which to construct a JavaScript object.
* @return Napi::Array.
*/
template <typename SourceType>
Napi::Array cpp_vector_to_js_array(const Napi::CallbackInfo& info, const std::vector<SourceType>& vec) {
auto array = Napi::Array::New(info.Env(), vec.size());

uint32_t i = 0;
for (auto& property : vec) {
auto any = ov::Any(property);
array[i++] = any_to_js(info, any);
}

return array;
}

Napi::Value any_to_js(const Napi::CallbackInfo& info, ov::Any value) {
// Check for std::string
if (value.is<std::string>()) {
return Napi::String::New(info.Env(), value.as<std::string>());
}
// Check for bool
else if (value.is<bool>()) {
return Napi::Boolean::New(info.Env(), value.as<bool>());
}
// Check for ov::PropertyName
else if (value.is<ov::PropertyName>()) {
return Napi::String::New(info.Env(), value.as<std::string>());
}
// Check for ov::device::Type
else if (value.is<ov::device::Type>()) {
return Napi::String::New(info.Env(), value.as<std::string>());
}
// Check for int
else if (value.is<int>()) {
return Napi::Number::New(info.Env(), value.as<int>());
}
// Check for ov::Affinity
else if (value.is<ov::Affinity>()) {
return Napi::String::New(info.Env(), value.as<std::string>());
}
// Check for ov::element::Type
else if (value.is<ov::element::Type>()) {
return Napi::String::New(info.Env(), value.as<std::string>());
}
// Check for ov::hint::PerformanceMode
else if (value.is<ov::hint::PerformanceMode>()) {
return Napi::String::New(info.Env(), value.as<std::string>());
}
// Check for ov::hint::ExecutionMode
else if (value.is<ov::hint::ExecutionMode>()) {
return Napi::String::New(info.Env(), value.as<std::string>());
}
// Check for ov::hint::SchedulingCoreType
else if (value.is<ov::hint::SchedulingCoreType>()) {
return Napi::String::New(info.Env(), value.as<std::string>());
}
// Check for ov::log::Level
else if (value.is<ov::log::Level>()) {
return Napi::String::New(info.Env(), value.as<std::string>());
}
// Check for uint32_t
else if (value.is<uint32_t>()) {
return Napi::Number::New(info.Env(), value.as<uint32_t>());
}
// Check for std::vector<ov::Any>
else if (value.is<const std::vector<ov::Any>>()) {
auto p = value.as<const std::vector<ov::Any>>();

return cpp_vector_to_js_array(info, p);
}
// Check for std::vector<ov::PropertyName>
else if (value.is<const std::vector<ov::PropertyName>>()) {
auto p = value.as<const std::vector<ov::PropertyName>>();

return cpp_vector_to_js_array(info, p);
}
// Check for std::vector<std::string>
else if (value.is<const std::vector<std::string>>()) {
auto p = value.as<const std::vector<std::string>>();

return cpp_vector_to_js_array(info, p);
}
// Check for std::vector<int>
else if (value.is<std::vector<int>>()) {
auto p = value.as<std::vector<int>>();

return cpp_vector_to_js_array(info, p);
}
// Check for std::vector<int64_t>
else if (value.is<std::vector<int64_t>>()) {
auto p = value.as<std::vector<int64_t>>();

return cpp_vector_to_js_array(info, p);
}
// Check for std::vector<unsigned int>
else if (value.is<std::vector<unsigned int>>()) {
auto p = value.as<std::vector<unsigned int>>();

return cpp_vector_to_js_array(info, p);
}
// Check for std::vector<float>
else if (value.is<std::vector<float>>()) {
auto p = value.as<std::vector<float>>();

return cpp_vector_to_js_array(info, p);
}
// Check for std::vector<double>
else if (value.is<std::vector<double>>()) {
auto p = value.as<std::vector<double>>();

return cpp_vector_to_js_array(info, p);
}
// Check for std::tuple<unsigned int, unsigned int>
else if (value.is<std::tuple<unsigned int, unsigned int>>()) {
auto p = value.as<std::tuple<unsigned int, unsigned int>>();
auto [first, second] = p;

Napi::Array array = Napi::Array::New(info.Env(), 2);
uint32_t indexes[] = {0, 1};

array[indexes[0]] = Napi::Number::New(info.Env(), first);
array[indexes[1]] = Napi::Number::New(info.Env(), second);

return array;
}
// Check for std::tuple<unsigned int, unsigned int, unsigned int>
else if (value.is<std::tuple<unsigned int, unsigned int, unsigned int>>()) {
auto p = value.as<std::tuple<unsigned int, unsigned int, unsigned int>>();
auto [first, second, third] = p;

Napi::Array array = Napi::Array::New(info.Env(), 2);
uint32_t indexes[] = {0, 1, 2};

array[indexes[0]] = Napi::Number::New(info.Env(), first);
array[indexes[1]] = Napi::Number::New(info.Env(), second);
array[indexes[2]] = Napi::Number::New(info.Env(), third);

return array;
}
// Check for std::map<std::string, std::string>
else if (value.is<std::map<std::string, std::string>>()) {
auto p = value.as<std::map<std::string, std::string>>();

return cpp_map_to_js_object(info, p);
}
// Check for std::map<std::string, int>
else if (value.is<std::map<std::string, int>>()) {
auto p = value.as<std::map<std::string, int>>();

return cpp_map_to_js_object(info, p);
}
// Check for std::map<std::string, uint64_t>
else if (value.is<std::map<std::string, uint64_t>>()) {
auto p = value.as<std::map<std::string, uint64_t>>();

return cpp_map_to_js_object(info, p);
}

return info.Env().Undefined();
}

ov::Any js_to_any(const Napi::CallbackInfo& info, Napi::Value value) {
if (value.IsString()) {
return ov::Any(value.ToString().Utf8Value());
} else if (value.IsBigInt()) {
Napi::BigInt big_value = value.As<Napi::BigInt>();
bool is_lossless;
int64_t big_num = big_value.Int64Value(&is_lossless);

if (!is_lossless) {
OPENVINO_THROW("Result of BigInt conversion to int64_t results in a loss of precision");
}

return ov::Any(big_num);
} else if (value.IsNumber()) {
Napi::Number num = value.ToNumber();

if (is_napi_value_int(info, value)) {
return ov::Any(num.Int32Value());
} else {
return ov::Any(num.DoubleValue());
}
} else if (value.IsBoolean()) {
return ov::Any(value.ToBoolean());
} else {
OPENVINO_THROW("Cannot convert to ov::Any");
}
}

bool is_napi_value_int(const Napi::CallbackInfo& info, Napi::Value& num) {
return info.Env()
.Global()
.Get("Number")
.ToObject()
.Get("isInteger")
.As<Napi::Function>()
.Call({num})
.ToBoolean()
.Value();
}
Loading

0 comments on commit 97d4780

Please sign in to comment.