Untitled

 avatar
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