Untitled
unknown
plain_text
a year ago
43 kB
20
Indexable
#pragma once
#include "MonereVision.h"
#include "utils.hpp"
#include "JsiArrayBufferHostObject.h"
#include <chrono> // for timestamp
#include <sstream> // for stringstream
#include <filesystem> // for file operations
#include <memory> // for std::unique_ptr
#define LOG_TAG "MonerePlugin"
#ifdef ANDROID
#include <android/log.h>
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#else
#define LOGI(...) [] {}()
#endif
using namespace monere;
class MonerePlugin : public jsi::HostObject
{
public:
std::string modelPath = "";
std::string parentDir = "";
// Configuration values with defaults
float eyelid_distance_threshold = 0.2;
float eyelid_open_threshold = 0.2;
int eyelid_color_temp_threshold_lower = 4500;
int eyelid_color_temp_threshold_upper = 6600;
int sclera_color_temp_threshold_lower = 5000;
int sclera_color_temp_threshold_upper = 8200;
float edgeSharpnessThreshold = 13.28f;
int lower_illumination_threshold = 95;
int upper_illumination_threshold = 200;
float yellow_sclera_threshold = 0.05;
int yellowLightThreshold = 15;
float warm_light_threshold = 2.7;
int pdm_threshold = 70;
const int yellow_frame_cct_threshold = 5000;
float rednessThreshold = 0.05f;
float sharpness_decrease_step = 1.0f;
float thinness_threshold = 0.2f;
virtual ~MonerePlugin() {}
std::unique_ptr<MonereVision> monereVision;
MonereVisionConfig monereVisionConfig;
std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
int MAX_WAIT_TIME = 1000; // 1 seconds to allow say "hold, taking image. eyelid detected".
std::unique_ptr<std::vector<cv::Mat>> eyelids_array;
bool timer_running = false;
cv::Mat best_eyelid;
float best_eyelid_sharpness = 0.0f;
ImageProcessingResult imgProcessingResult;
ImageProcessingResult bestProcessingResult;
float currentSharpnessThreshold = 0.0f;
float lowerSharpnessThreshold = 10.0f;
float area_ratio_low = 0.03f;
float area_ratio_high = 0.18f;
float eyelid_distance_high = 0.45f;
bool first_frame = true;
cv::Mat best_frameMat;
MonerePlugin(const std::string &modelParentDir)
{
monereVisionConfig.eyelid_model_path = modelParentDir + "/model.onnx";
monereVisionConfig.sclera_model_path = modelParentDir + "/model2.onnx";
eyelids_array = std::make_unique<std::vector<cv::Mat>>();
}
jsi::Value get(jsi::Runtime &runtime, const jsi::PropNameID &name) override
{
auto propName = name.utf8(runtime);
if (propName == "detect")
{
return this->createDetectAndSaveFunction(runtime, std::move(name));
}
if (propName == "processPhoto")
{
return this->createProcessPhotoFunction(runtime, std::move(name));
}
if (propName == "reset") {
return jsi::Function::createFromHostFunction(
runtime,
name,
/*argumentCount=*/0,
[this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value
{
eyelids_array->clear();
first_frame = true;
timer_running = false;
best_eyelid_sharpness = 0.0f;
best_eyelid = cv::Mat();
best_frameMat = cv::Mat();
return jsi::Value(true);
});
}
if (propName == "createBufferWrapper") {
return jsi::Function::createFromHostFunction(
runtime,
name,
/*argumentCount=*/1,
[this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value
{
if (count < 1)
{
throw jsi::JSError(rt, "Expected ArrayBuffer argument.");
}
if (!args[0].isObject() || !args[0].asObject(rt).isArrayBuffer(rt))
{
throw jsi::JSError(rt, "Argument must be ArrayBuffer.");
}
auto arrayBuffer = args[0].asObject(rt).getArrayBuffer(rt);
size_t byteLength = arrayBuffer.size(rt);
auto buffer = createArrayBufferHostObject(byteLength);
std::memcpy(buffer->buffer_->data(), arrayBuffer.data(rt), byteLength);
return jsi::Object::createFromHostObject(rt, buffer);
});
}
if (propName == "setConfig") {
return jsi::Function::createFromHostFunction(
runtime,
name,
/*argumentCount=*/1,
[this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value
{
if (count < 1 || !args[0].isObject()) {
throw jsi::JSError(rt, "Expected configuration object argument.");
}
jsi::Object config = args[0].asObject(rt);
// Parse the configuration object and set the class members
// First try to get the values, and only update if the properties exist
if (config.hasProperty(rt, "eyelid_open_low_perc")) {
eyelid_open_threshold = static_cast<float>(config.getProperty(rt, "eyelid_open_low_perc").asNumber()) / 100.0f;
}
if (config.hasProperty(rt, "eyelid_distance_low_perc")) {
eyelid_distance_threshold = static_cast<float>(config.getProperty(rt, "eyelid_distance_low_perc").asNumber()) / 100.0f;
}
if (config.hasProperty(rt, "eyelid_temp_low")) { // +
eyelid_color_temp_threshold_lower = static_cast<int>(config.getProperty(rt, "eyelid_temp_low").asNumber());
}
if (config.hasProperty(rt, "eyelid_temp_high")) { // +
eyelid_color_temp_threshold_upper = static_cast<int>(config.getProperty(rt, "eyelid_temp_high").asNumber());
}
if (config.hasProperty(rt, "sclera_temp_low")) { // +
sclera_color_temp_threshold_lower = static_cast<int>(config.getProperty(rt, "sclera_temp_low").asNumber());
}
if (config.hasProperty(rt, "sclera_temp_high")) { // +
sclera_color_temp_threshold_upper = static_cast<int>(config.getProperty(rt, "sclera_temp_high").asNumber());
}
if (config.hasProperty(rt, "sharpness_threshold")) {// +
edgeSharpnessThreshold = static_cast<float>(config.getProperty(rt, "sharpness_threshold").asNumber());
if (edgeSharpnessThreshold == 100.0f) {
edgeSharpnessThreshold = 13.28f;
}
this->currentSharpnessThreshold = edgeSharpnessThreshold;
}
if (config.hasProperty(rt, "eyelid_avg_illu_low") ) {// +
lower_illumination_threshold = static_cast<int>(config.getProperty(rt, "eyelid_avg_illu_low").asNumber());
}
if (config.hasProperty(rt, "eyelid_avg_illu_high")) {// +
upper_illumination_threshold = static_cast<int>(config.getProperty(rt, "eyelid_avg_illu_high").asNumber());
}
if (config.hasProperty(rt, "yellow_sclera_perc")) {
yellow_sclera_threshold = static_cast<float>(config.getProperty(rt, "yellow_sclera_perc").asNumber()) / 100.0f;
}
if (config.hasProperty(rt, "pdm_threshold")) {
pdm_threshold = static_cast<int>(config.getProperty(rt, "pdm_threshold").asNumber());
}
if (config.hasProperty(rt, "redness_threshold")) {
rednessThreshold = static_cast<float>(config.getProperty(rt, "redness_threshold").asNumber()) / 100.0f;
}
if (config.hasProperty(rt, "thinness_threshold")) {
thinness_threshold = static_cast<float>(config.getProperty(rt, "thinness_threshold").asNumber()) / 100.0f;
}
if (config.hasProperty(rt, "lower_sharpness_threshold")) {
this->lowerSharpnessThreshold = static_cast<float>(config.getProperty(rt, "lower_sharpness_threshold").asNumber());
}
if (config.hasProperty(rt, "area_ratio_low")) {
area_ratio_low = static_cast<float>(config.getProperty(rt, "area_ratio_low").asNumber()) / 100.0f;
}
if (config.hasProperty(rt, "area_ratio_high")) {
area_ratio_high = static_cast<float>(config.getProperty(rt, "area_ratio_high").asNumber()) / 100.0f;
}
if (config.hasProperty(rt, "eyelid_distance_high_perc")) {
eyelid_distance_high = static_cast<float>(config.getProperty(rt, "eyelid_distance_high_perc").asNumber()) / 100.0f;
}
if (config.hasProperty(rt, "hold_ms")) {
MAX_WAIT_TIME = static_cast<int>(config.getProperty(rt, "hold_ms").asNumber());
}
// populate monereVision Config
monereVisionConfig.eyelid_distance_low = eyelid_distance_threshold;
monereVisionConfig.eyelid_open_threshold_low = eyelid_open_threshold;
monereVisionConfig.eyelid_color_temp_threshold_lower = eyelid_color_temp_threshold_lower;
monereVisionConfig.eyelid_color_temp_threshold_upper = eyelid_color_temp_threshold_upper;
monereVisionConfig.sclera_color_temp_threshold_lower = sclera_color_temp_threshold_lower;
monereVisionConfig.sclera_color_temp_threshold_upper = sclera_color_temp_threshold_upper;
monereVisionConfig.edgeSharpnessThreshold = edgeSharpnessThreshold;
monereVisionConfig.lower_illumination_threshold = lower_illumination_threshold;
monereVisionConfig.upper_illumination_threshold = upper_illumination_threshold;
monereVisionConfig.yellow_sclera_threshold = yellow_sclera_threshold;
monereVisionConfig.pdm_threshold = pdm_threshold;
monereVisionConfig.rednessThreshold = rednessThreshold;
monereVisionConfig.eyelid_thinness_threshold = thinness_threshold;
monereVisionConfig.area_ratio_low = area_ratio_low;
monereVisionConfig.area_ratio_high = area_ratio_high;
monereVisionConfig.eyelid_distance_high = eyelid_distance_high;
monereVision = std::make_unique<MonereVision>(monereVisionConfig);
if (monereVision->initialize()) {
return jsi::Value(true);
}
return jsi::Value(false);
});
}
return jsi::Value::undefined();
}
ImageProcessingResult run_frame_filters(jsi::Runtime &rt, const jsi::Value *args, size_t count,
cv::Mat frameMat, int frameWidth, int frameHeight,
jsi::Object &boxObject){
ImageProcessingResult imgProcessingResult = monereVision->processFrame(frameMat);
// set focus point
float x = (imgProcessingResult.box0 + imgProcessingResult.box2) / 2.0f / static_cast<float>(frameMat.cols);
float y = (imgProcessingResult.box1 + imgProcessingResult.box3) / 2.0f / static_cast<float>(frameMat.rows);
jsi::Object focusPoint(rt);
jsi::Object frameSizeObj(rt);
jsi::Object focusObject(rt);
focusPoint.setProperty(rt, "x", jsi::Value(x));
focusPoint.setProperty(rt, "y", jsi::Value(y));
frameSizeObj.setProperty(rt, "width", jsi::Value(frameWidth));
frameSizeObj.setProperty(rt, "height", jsi::Value(frameHeight));
focusObject.setProperty(rt, "point", focusPoint);
focusObject.setProperty(rt, "frame", frameSizeObj);
if (count > 5 && args[5].isObject())
{
auto callback = args[5].asObject(rt).asFunction(rt);
callback.call(rt, focusObject);
}
if (!imgProcessingResult.image_quality_passed) {
// check yellow light (general light)
// check distance (moveCloser)
// check eyelid open (model inference)
// check focus (eyelid)
// check overexposed (cct)
// check underexposed (cct)
if (imgProcessingResult.message.find("yellow_light") != std::string::npos) {
boxObject.setProperty(rt, "yellow_light", jsi::Value(true));
boxObject.setProperty(rt, "latency_info", jsi::String::createFromUtf8(rt, imgProcessingResult.latency_info));
}
else if (imgProcessingResult.message.find("openEye") != std::string::npos) {
boxObject.setProperty(rt, "openEye", jsi::Value(true));
boxObject.setProperty(rt, "yellow_light", jsi::Value(false));
boxObject.setProperty(rt, "latency_info", jsi::String::createFromUtf8(rt, imgProcessingResult.latency_info));
}
else if (imgProcessingResult.message.find("openEyelidMore") != std::string::npos) {
boxObject.setProperty(rt, "openEyelidMore", jsi::Value(true));
boxObject.setProperty(rt, "yellow_light", jsi::Value(false));
boxObject.setProperty(rt, "latency_info", jsi::String::createFromUtf8(rt, imgProcessingResult.latency_info));
}
else if (imgProcessingResult.message.find("moveCloser") != std::string::npos) {
boxObject.setProperty(rt, "moveCloser", jsi::Value(true));
boxObject.setProperty(rt, "openEye", jsi::Value(false));
boxObject.setProperty(rt, "yellow_light", jsi::Value(false));
boxObject.setProperty(rt, "latency_info", jsi::String::createFromUtf8(rt, imgProcessingResult.latency_info));
}
else if (imgProcessingResult.message.find("moveAway") != std::string::npos) {
boxObject.setProperty(rt, "moveAway", jsi::Value(true));
boxObject.setProperty(rt, "openEye", jsi::Value(false));
boxObject.setProperty(rt, "moveCloser", jsi::Value(false));
boxObject.setProperty(rt, "yellow_light", jsi::Value(false));
boxObject.setProperty(rt, "latency_info", jsi::String::createFromUtf8(rt, imgProcessingResult.latency_info));
}
else if (imgProcessingResult.message.find("lookUp") != std::string::npos) {
boxObject.setProperty(rt, "lookUp", jsi::Value(true));
boxObject.setProperty(rt, "moveCloser", jsi::Value(false));
boxObject.setProperty(rt, "moveAway", jsi::Value(false));
boxObject.setProperty(rt, "openEye", jsi::Value(false));
boxObject.setProperty(rt, "yellow_light", jsi::Value(false));
boxObject.setProperty(rt, "latency_info", jsi::String::createFromUtf8(rt, imgProcessingResult.latency_info));
}
else if (imgProcessingResult.message.find("overexposed") != std::string::npos) {
boxObject.setProperty(rt, "overexposed", jsi::Value(true));
boxObject.setProperty(rt, "moveCloser", jsi::Value(false));
boxObject.setProperty(rt, "moveAway", jsi::Value(false));
boxObject.setProperty(rt, "openEye", jsi::Value(false));
boxObject.setProperty(rt, "yellow_light", jsi::Value(false));
boxObject.setProperty(rt, "lookUp", jsi::Value(false));
boxObject.setProperty(rt, "latency_info", jsi::String::createFromUtf8(rt, imgProcessingResult.latency_info));
}
else if (imgProcessingResult.message.find("underexposed") != std::string::npos) {
boxObject.setProperty(rt, "underexposed", jsi::Value(true));
boxObject.setProperty(rt, "moveCloser", jsi::Value(false));
boxObject.setProperty(rt, "moveAway", jsi::Value(false));
boxObject.setProperty(rt, "openEye", jsi::Value(false));
boxObject.setProperty(rt, "yellow_light", jsi::Value(false));
boxObject.setProperty(rt, "lookUp", jsi::Value(false));
boxObject.setProperty(rt, "latency_info", jsi::String::createFromUtf8(rt, imgProcessingResult.latency_info));
}
else if (imgProcessingResult.message.find("needToFocus") != std::string::npos) {
this->currentSharpnessThreshold -= this->sharpness_decrease_step;
this->monereVisionConfig.edgeSharpnessThreshold = std::max(this->currentSharpnessThreshold, this->lowerSharpnessThreshold);
this->monereVision->setConfig(this->monereVisionConfig);
boxObject.setProperty(rt, "needToFocus", jsi::Value(true));
boxObject.setProperty(rt, "moveCloser", jsi::Value(false));
boxObject.setProperty(rt, "moveAway", jsi::Value(false));
boxObject.setProperty(rt, "openEye", jsi::Value(false));
boxObject.setProperty(rt, "overexposed", jsi::Value(false));
boxObject.setProperty(rt, "underexposed", jsi::Value(false));
boxObject.setProperty(rt, "yellow_light", jsi::Value(false));
boxObject.setProperty(rt, "lookUp", jsi::Value(false));
boxObject.setProperty(rt, "latency_info", jsi::String::createFromUtf8(rt, imgProcessingResult.latency_info));
}
boxObject.setProperty(rt, "illumination", jsi::Value(imgProcessingResult.illumination));
boxObject.setProperty(rt, "colorTemperature", jsi::Value(imgProcessingResult.sclera_cct));
boxObject.setProperty(rt, "colorTemperature2", jsi::Value(imgProcessingResult.eyelid_cct));
if (count > 4 && args[4].isObject()) {
auto callback = args[4].asObject(rt).asFunction(rt);
callback.call(rt, boxObject);
}
return imgProcessingResult;
}
// all filters passed, save and send object back to js
if (count > 4 && args[4].isObject())
{
auto callback = args[4].asObject(rt).asFunction(rt);
boxObject.setProperty(rt, "illumination", jsi::Value(imgProcessingResult.illumination));
boxObject.setProperty(rt, "colorTemperature", jsi::Value(imgProcessingResult.sclera_cct));
boxObject.setProperty(rt, "colorTemperature2", jsi::Value(imgProcessingResult.eyelid_cct));
boxObject.setProperty(rt, "needToFocus", jsi::Value(false));
boxObject.setProperty(rt, "moveCloser", jsi::Value(false));
boxObject.setProperty(rt, "openEye", jsi::Value(false));
boxObject.setProperty(rt, "underexposed", jsi::Value(false));
boxObject.setProperty(rt, "overexposed", jsi::Value(false));
boxObject.setProperty(rt, "yellow_light", jsi::Value(false));
boxObject.setProperty(rt, "good_spot", jsi::Value(true));
boxObject.setProperty(rt, "lookUp", jsi::Value(false));
boxObject.setProperty(rt, "latency_info", jsi::String::createFromUtf8(rt, imgProcessingResult.latency_info));
callback.call(rt, boxObject);
}
return imgProcessingResult;
}
// Implementation of "detectAndSave"
jsi::Value createDetectAndSaveFunction(jsi::Runtime &runtime, const jsi::PropNameID &propName)
{
return jsi::Function::createFromHostFunction(
runtime,
propName,
/*argumentCount=*/6, // [frame, arrayBuffer, cacheDir, rotation, callback, focusCallback]
[this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value
{
if (count < 4)
{
throw jsi::JSError(rt, "Expected arguments: Frame, ArrayBuffer, CacheDir, Rotation");
}
// 1) Retrieve the Frame object
if (!args[0].isObject())
{
throw jsi::JSError(rt, "Argument 0 must be a Frame object");
}
#ifdef ANDROID
auto frame = args[0].asObject(rt).asHostObject<vision::FrameHostObject>(rt);
cv::Mat frameMat = ConvertAHardwareBufferToRGB(frame->getFrame()->getHardwareBuffer());
auto frameWidth = fmin(frame->getFrame()->getWidth(), frame->getFrame()->getHeight());
auto frameHeight = fmax(frame->getFrame()->getWidth(), frame->getFrame()->getHeight());
#else
auto frame = args[0].asObject(rt).asHostObject<FrameHostObject>(rt);
cv::Mat frameMat = ConvertBufferToRGB(frame->getFrame().buffer);
auto frameWidth = fmin(frame->getFrame().width, frame->getFrame().height);
auto frameHeight = fmax(frame->getFrame().height, frame->getFrame().width);
#endif
auto rotation = args[3].asNumber();
if (rotation == 90){
cv::rotate(frameMat, frameMat, cv::ROTATE_90_CLOCKWISE);
}
else if (rotation == 180){
cv::rotate(frameMat, frameMat, cv::ROTATE_180);
}
else if (rotation == 270){
cv::rotate(frameMat, frameMat, cv::ROTATE_90_COUNTERCLOCKWISE);
}
auto now = std::chrono::steady_clock::now();
if (timer_running && now - this->start > std::chrono::milliseconds(MAX_WAIT_TIME)) {
// log how much time has passed
// take last element
if (eyelids_array->size() == 1) {
//print size of array again
if (first_frame) {
first_frame = false;
return jsi::String::createFromUtf8(rt, "");
}
//std::this_thread::sleep_for(std::chrono::milliseconds(1000)); //updated to 1.5 sec from 3 se
}
eyelids_array->back();
eyelids_array->clear();
std::string cacheDir = args[2].asString(rt).utf8(rt);
// ✅ Generate a millisecond timestamp
auto now = std::chrono::system_clock::now();
auto now_ms = std::chrono::time_point_cast<std::chrono::milliseconds>(now);
auto epoch = now_ms.time_since_epoch();
auto timestamp = std::to_string(std::chrono::duration_cast<std::chrono::milliseconds>(epoch).count());
std::string eyelidPathName = "/eyelid_segmented_" + timestamp + ".jpg";
std::string frameFullName = "/frame_full_" + timestamp + ".jpg";
std::string framePath = cacheDir + eyelidPathName;
std::string framePathFull = cacheDir + frameFullName;
//before saving the new image, erase the older file if any
if (std::filesystem::exists(framePathFull)) {
std::filesystem::remove(framePathFull);
}
if (std::filesystem::exists(framePath)) {
std::filesystem::remove(framePath);
}
timer_running = false;
std::vector<int> imwrite_params;
// Specify the parameter key and value
imwrite_params.push_back(cv::IMWRITE_JPEG_QUALITY);
imwrite_params.push_back(100); // Setting the quality to 100
//save original full frame to cacheDir
cv::imwrite(framePathFull, best_frameMat);
// write eyelid to cacheDir
cv::imwrite(framePath, best_eyelid);
// compute base64 out of cropped image
std::vector<uchar> buf;
cv::imencode(".jpg", best_eyelid, buf);
std::string base64 = base64_encode(buf.data(), buf.size());
jsi::Object result(rt);
std::vector<uchar> buf_full;
cv::imencode(".jpg", best_frameMat, buf_full, imwrite_params);
std::string base64_full = base64_encode(buf_full.data(), buf_full.size());
result.setProperty(rt, "base64_full", jsi::String::createFromUtf8(rt, base64_full));
result.setProperty(rt, "base64", jsi::String::createFromUtf8(rt, base64));
result.setProperty(rt, "frameFullPath", jsi::String::createFromUtf8(rt, framePathFull));
result.setProperty(rt, "eyelidPath", jsi::String::createFromUtf8(rt, framePath));
result.setProperty(rt, "timestamp", jsi::String::createFromUtf8(rt, timestamp));
result.setProperty(rt, "illumination", jsi::Value(this->bestProcessingResult.illumination));
result.setProperty(rt, "scleraTemperature", jsi::Value(this->bestProcessingResult.sclera_cct));
result.setProperty(rt, "eyelidTemperature", jsi::Value(this->bestProcessingResult.eyelid_cct));
result.setProperty(rt, "eyelid_sharpness", jsi::Value(this->bestProcessingResult.eyelid_sharpness));
result.setProperty(rt, "frame_pdm", jsi::Value(this->bestProcessingResult.frame_pdm));
result.setProperty(rt, "frame_cct", jsi::Value(this->bestProcessingResult.frame_cct));
result.setProperty(rt, "x", jsi::Value(this->bestProcessingResult.x));
result.setProperty(rt, "y", jsi::Value(this->bestProcessingResult.y));
result.setProperty(rt, "frame_width", jsi::Value(frameWidth));
result.setProperty(rt, "frame_height", jsi::Value(frameHeight));
result.setProperty(rt, "height", jsi::Value(this->bestProcessingResult.height));
result.setProperty(rt, "width", jsi::Value(this->bestProcessingResult.width));
result.setProperty(rt, "box0", jsi::Value(this->bestProcessingResult.box0));
result.setProperty(rt, "box1", jsi::Value(this->bestProcessingResult.box1));
result.setProperty(rt, "box2", jsi::Value(this->bestProcessingResult.box2));
result.setProperty(rt, "box3", jsi::Value(this->bestProcessingResult.box3));
result.setProperty(rt, "box_frame", jsi::Value(640));
result.setProperty(rt, "latency_info", jsi::String::createFromUtf8(rt, this->bestProcessingResult.latency_info));
return result;
}
jsi::Object boxObject(rt);
this->imgProcessingResult = run_frame_filters(rt, args, count, frameMat, frameWidth, frameHeight, boxObject);
if (!this->imgProcessingResult.image_quality_passed) {
return jsi::String::createFromUtf8(rt, "");
}
if (this->imgProcessingResult.eyelid_sharpness > best_eyelid_sharpness) {
best_eyelid_sharpness = this->imgProcessingResult.eyelid_sharpness;
best_eyelid = this->imgProcessingResult.eyelid_cropped;
best_frameMat = frameMat;
this->bestProcessingResult = this->imgProcessingResult;
}
if (!this->imgProcessingResult.image_quality_passed) {
return jsi::String::createFromUtf8(rt, "");
}
// launch timer to try to collect more frames as phrase "good spot" is sent to TTS
if (!timer_running) {
this->start = std::chrono::steady_clock::now();
timer_running = true;
eyelids_array->push_back(this->imgProcessingResult.eyelid_cropped);
return jsi::String::createFromUtf8(rt, "");
}
return jsi::String::createFromUtf8(rt, "");
}
);
}
// Implementation of the new `processPhoto` method
jsi::Value createProcessPhotoFunction(jsi::Runtime &runtime, const jsi::PropNameID &propName)
{
return jsi::Function::createFromHostFunction(
runtime,
propName,
/*argumentCount=*/3, // [photoPath, cacheDir, rotation]
[this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value
{
if (count < 3)
{
throw jsi::JSError(rt, "Expected arguments: photoPath, cacheDir, rotation");
}
// Retrieve arguments
std::string photoPath = args[0].asString(rt).utf8(rt);
std::string cacheDir = args[1].asString(rt).utf8(rt);
auto rotation = args[2].asNumber();
// 1. Read the image from the file path using OpenCV
cv::Mat frameMat = cv::imread(photoPath, cv::IMREAD_COLOR);
if (frameMat.empty())
{
throw jsi::JSError(rt, "Failed to read image from path: " + photoPath);
}
// 2. Apply rotation if needed
if (rotation == 90){
cv::rotate(frameMat, frameMat, cv::ROTATE_90_CLOCKWISE);
}
else if (rotation == 180){
cv::rotate(frameMat, frameMat, cv::ROTATE_180);
}
else if (rotation == 270){
cv::rotate(frameMat, frameMat, cv::ROTATE_90_COUNTERCLOCKWISE);
}
// 3. Process the frame to get the best eyelid image and processing result
// We'll call the core processing logic directly. We need to create a dummy box object and pass it.
jsi::Object boxObject(rt);
ImageProcessingResult photoProcessingResult = monereVision->processFrame(frameMat);
// 4. Create the result object, similar to the `detect` method's final output
jsi::Object result(rt);
std::string timestamp = std::to_string(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count());
std::string eyelidPathName = "/eyelid_segmented_" + timestamp + ".jpg";
std::string frameFullName = "/frame_full_" + timestamp + ".jpg";
std::string framePath = cacheDir + eyelidPathName;
std::string framePathFull = cacheDir + frameFullName;
// Save original full frame to cacheDir
cv::imwrite(framePathFull, frameMat);
// Write eyelid to cacheDir
cv::imwrite(framePath, photoProcessingResult.eyelid_cropped);
// Compute base64 out of cropped image
std::vector<uchar> buf;
cv::imencode(".jpg", photoProcessingResult.eyelid_cropped, buf);
std::string base64 = base64_encode(buf.data(), buf.size());
// Compute base64 out of full frame
std::vector<uchar> buf_full;
cv::imencode(".jpg", frameMat, buf_full);
std::string base64_full = base64_encode(buf_full.data(), buf_full.size());
result.setProperty(rt, "base64_full", jsi::String::createFromUtf8(rt, base64_full));
result.setProperty(rt, "base64", jsi::String::createFromUtf8(rt, base64));
result.setProperty(rt, "frameFullPath", jsi::String::createFromUtf8(rt, framePathFull));
result.setProperty(rt, "eyelidPath", jsi::String::createFromUtf8(rt, framePath));
result.setProperty(rt, "timestamp", jsi::String::createFromUtf8(rt, timestamp));
result.setProperty(rt, "illumination", jsi::Value(photoProcessingResult.illumination));
result.setProperty(rt, "scleraTemperature", jsi::Value(photoProcessingResult.sclera_cct));
result.setProperty(rt, "eyelidTemperature", jsi::Value(photoProcessingResult.eyelid_cct));
result.setProperty(rt, "eyelid_sharpness", jsi::Value(photoProcessingResult.eyelid_sharpness));
result.setProperty(rt, "frame_pdm", jsi::Value(photoProcessingResult.frame_pdm));
result.setProperty(rt, "frame_cct", jsi::Value(photoProcessingResult.frame_cct));
result.setProperty(rt, "x", jsi::Value(photoProcessingResult.x));
result.setProperty(rt, "y", jsi::Value(photoProcessingResult.y));
result.setProperty(rt, "frame_width", jsi::Value(frameMat.cols));
result.setProperty(rt, "frame_height", jsi::Value(frameMat.rows));
result.setProperty(rt, "height", jsi::Value(photoProcessingResult.height));
result.setProperty(rt, "width", jsi::Value(photoProcessingResult.width));
result.setProperty(rt, "box0", jsi::Value(photoProcessingResult.box0));
result.setProperty(rt, "box1", jsi::Value(photoProcessingResult.box1));
result.setProperty(rt, "box2", jsi::Value(photoProcessingResult.box2));
result.setProperty(rt, "box3", jsi::Value(photoProcessingResult.box3));
result.setProperty(rt, "box_frame", jsi::Value(640));
result.setProperty(rt, "latency_info", jsi::String::createFromUtf8(rt, photoProcessingResult.latency_info));
return result;
});
}
jsi::Value createDetectAndSaveFunction(jsi::Runtime &runtime, const jsi::PropNameID &propName)
{
return jsi::Function::createFromHostFunction(
runtime,
propName,
/*argumentCount=*/6, // [frame, arrayBuffer, cacheDir, rotation, callback, focusCallback]
[this](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value
{
if (count < 4)
{
throw jsi::JSError(rt, "Expected arguments: Frame, ArrayBuffer, CacheDir, Rotation");
}
// 1) Retrieve the Frame object
if (!args[0].isObject())
{
throw jsi::JSError(rt, "Argument 0 must be a Frame object");
}
#ifdef ANDROID
auto frame = args[0].asObject(rt).asHostObject<vision::FrameHostObject>(rt);
cv::Mat frameMat = ConvertAHardwareBufferToRGB(frame->getFrame()->getHardwareBuffer());
auto frameWidth = fmin(frame->getFrame()->getWidth(), frame->getFrame()->getHeight());
auto frameHeight = fmax(frame->getFrame()->getWidth(), frame->getFrame()->getHeight());
#else
auto frame = args[0].asObject(rt).asHostObject<FrameHostObject>(rt);
cv::Mat frameMat = ConvertBufferToRGB(frame->getFrame().buffer);
auto frameWidth = fmin(frame->getFrame().width, frame->getFrame().height);
auto frameHeight = fmax(frame->getFrame().height, frame->getFrame().width);
#endif
auto rotation = args[3].asNumber();
if (rotation == 90){
cv::rotate(frameMat, frameMat, cv::ROTATE_90_CLOCKWISE);
}
else if (rotation == 180){
cv::rotate(frameMat, frameMat, cv::ROTATE_180);
}
else if (rotation == 270){
cv::rotate(frameMat, frameMat, cv::ROTATE_90_COUNTERCLOCKWISE);
}
auto now = std::chrono::steady_clock::now();
if (timer_running && now - this->start > std::chrono::milliseconds(MAX_WAIT_TIME)) {
// log how much time has passed
// take last element
if (eyelids_array->size() == 1) {
//print size of array again
if (first_frame) {
first_frame = false;
return jsi::String::createFromUtf8(rt, "");
}
//std::this_thread::sleep_for(std::chrono::milliseconds(1000)); //updated to 1.5 sec from 3 se
}
eyelids_array->back();
eyelids_array->clear();
std::string cacheDir = args[2].asString(rt).utf8(rt);
// ✅ Generate a millisecond timestamp
auto now = std::chrono::system_clock::now();
auto now_ms = std::chrono::time_point_cast<std::chrono::milliseconds>(now);
auto epoch = now_ms.time_since_epoch();
auto timestamp = std::to_string(std::chrono::duration_cast<std::chrono::milliseconds>(epoch).count());
std::string eyelidPathName = "/eyelid_segmented_" + timestamp + ".jpg";
std::string frameFullName = "/frame_full_" + timestamp + ".jpg";
std::string framePath = cacheDir + eyelidPathName;
std::string framePathFull = cacheDir + frameFullName;
//before saving the new image, erase the older file if any
if (std::filesystem::exists(framePathFull)) {
std::filesystem::remove(framePathFull);
}
if (std::filesystem::exists(framePath)) {
std::filesystem::remove(framePath);
}
timer_running = false;
std::vector<int> imwrite_params;
// Specify the parameter key and value
imwrite_params.push_back(cv::IMWRITE_JPEG_QUALITY);
imwrite_params.push_back(100); // Setting the quality to 100
//save original full frame to cacheDir
cv::imwrite(framePathFull, best_frameMat);
// write eyelid to cacheDir
cv::imwrite(framePath, best_eyelid);
// compute base64 out of cropped image
std::vector<uchar> buf;
cv::imencode(".jpg", best_eyelid, buf);
std::string base64 = base64_encode(buf.data(), buf.size());
jsi::Object result(rt);
std::vector<uchar> buf_full;
cv::imencode(".jpg", best_frameMat, buf_full, imwrite_params);
std::string base64_full = base64_encode(buf_full.data(), buf_full.size());
result.setProperty(rt, "base64_full", jsi::String::createFromUtf8(rt, base64_full));
result.setProperty(rt, "base64", jsi::String::createFromUtf8(rt, base64));
result.setProperty(rt, "frameFullPath", jsi::String::createFromUtf8(rt, framePathFull));
result.setProperty(rt, "eyelidPath", jsi::String::createFromUtf8(rt, framePath));
result.setProperty(rt, "timestamp", jsi::String::createFromUtf8(rt, timestamp));
result.setProperty(rt, "illumination", jsi::Value(this->bestProcessingResult.illumination));
result.setProperty(rt, "scleraTemperature", jsi::Value(this->bestProcessingResult.sclera_cct));
result.setProperty(rt, "eyelidTemperature", jsi::Value(this->bestProcessingResult.eyelid_cct));
result.setProperty(rt, "eyelid_sharpness", jsi::Value(this->bestProcessingResult.eyelid_sharpness));
result.setProperty(rt, "frame_pdm", jsi::Value(this->bestProcessingResult.frame_pdm));
result.setProperty(rt, "frame_cct", jsi::Value(this->bestProcessingResult.frame_cct));
result.setProperty(rt, "x", jsi::Value(this->bestProcessingResult.x));
result.setProperty(rt, "y", jsi::Value(this->bestProcessingResult.y));
result.setProperty(rt, "frame_width", jsi::Value(frameWidth));
result.setProperty(rt, "frame_height", jsi::Value(frameHeight));
result.setProperty(rt, "height", jsi::Value(this->bestProcessingResult.height));
result.setProperty(rt, "width", jsi::Value(this->bestProcessingResult.width));
result.setProperty(rt, "box0", jsi::Value(this->bestProcessingResult.box0));
result.setProperty(rt, "box1", jsi::Value(this->bestProcessingResult.box1));
result.setProperty(rt, "box2", jsi::Value(this->bestProcessingResult.box2));
result.setProperty(rt, "box3", jsi::Value(this->bestProcessingResult.box3));
result.setProperty(rt, "box_frame", jsi::Value(640));
result.setProperty(rt, "latency_info", jsi::String::createFromUtf8(rt, this->bestProcessingResult.latency_info));
return result;
}
jsi::Object boxObject(rt);
this->imgProcessingResult = run_frame_filters(rt, args, count, frameMat, frameWidth, frameHeight, boxObject);
if (!this->imgProcessingResult.image_quality_passed) {
return jsi::String::createFromUtf8(rt, "");
}
if (this->imgProcessingResult.eyelid_sharpness > best_eyelid_sharpness) {
best_eyelid_sharpness = this->imgProcessingResult.eyelid_sharpness;
best_eyelid = this->imgProcessingResult.eyelid_cropped;
best_frameMat = frameMat;
this->bestProcessingResult = this->imgProcessingResult;
}
if (!this->imgProcessingResult.image_quality_passed) {
return jsi::String::createFromUtf8(rt, "");
}
// launch timer to try to collect more frames as phrase "good spot" is sent to TTS
if (!timer_running) {
this->start = std::chrono::steady_clock::now();
timer_running = true;
eyelids_array->push_back(this->imgProcessingResult.eyelid_cropped);
return jsi::String::createFromUtf8(rt, "");
}
return jsi::String::createFromUtf8(rt, "");
}
);
}
};Editor is loading...
Leave a Comment