Skip to content

Customizing Solutions

Customizing a Solution can be accomplished by editing its Lua code.

All CVEDIA-RT solutions are broken down in different folders and .json files that are reflected in the instance list sidebar:

Placeholder

Example

If we want to change the Crowd Estimation solution, we will be able to find it at solutions/crowd-estimation/.

CVEDIA-RT on Dockers

When running CVEDIA-RT within docker (Linux deployments) solutions are embedded within the docker image. You can run extract_solutions.sh to expose all included solutions. This script will copy all solutions from the docker image to the solutions folder, which is automatically mounted when you start CVEDIA-RT using run.sh.

Solution

Solution Configuration

A solution configuration is set by the base_config.json file present in the solution root folder. This file sets the common properties for all the instances that use that solution. When an instance starts, it will inherit the solution configuration from the base_config.json file and will add or replace its own settings.

Accessing Solution Configuration

As an example, we show how to define a my_var variable in the JSON and retrieve it from the code.

base_config.json or instance.json:

"Global": {
    "my_var": "my var content"
}

And inside the Lua code, we can retrieve it by using:

local lua_var = instance:getConfigValue("Global/my_var")

Solution Lua Code

Every solution must have a solution.lua file. This file is responsible for creating the solution scope interfaces and modules. It also defines the options to expose in the Input Configuration panel and in the Export configuration.

A base template for this file is the following:

dofile(luaroot .. "/api/api.lua")

---@param solution Solution
function onStartup(solution)
    print("Starting solution")
    local detectInst = api.factory.inference.create(solution, "PVADetector")
    detectInst:loadModelFromConfig()
    solution:registerUiCallback("onInputSinkConfig", "onInputSinkConfig", inputSinkConfig)
end

---@param solution Solution
---@param instance rt_instance
function onStartInstance(solution, instance)
    print("Running instance " .. instance:getName())
end

---@param solution Solution
---@param instance rt_instance
function onStopInstance(solution, instance)
    print("Stopping instance " ..  instance:getName())
end

---@param solution Solution
function onShutdown(solution)
    print("Shutting down solution")
    api.factory.inference.delete(solution, "PVADetector")
    solution:unregisterUiCallback("onInputSinkConfig", "onInputSinkConfig")
end

function inputSinkConfig(input)
    local config = {
        {name = "Detector regions", configkey = "DetectorRegions", introtext = "Create custom detection regions to replace the full frame detection", itemprefix = "Region", type = "rectangle"}
    }

    return config
end

Instance script

An instance main code file is called index.lua.

Info

For testing and debugging purpose, it's possible to use a different main file for the instances. It can be set in the JSON file of an instance, or in the base_config.json of the solution, by setting the lua_script property of the Global object:

{
    "Global": {
        "lua_script": "lua/custom_index.lua"
    },
}

index.lua Structure

File imports

The first thing index.lua does is importing other lua files it may need.

dofile(luaroot .. "/api/api.lua")
dofile(project_root .. "classifier_tracker.lua")
dofile(project_root .. "utils.lua")
---(...)

Lua state global vars

Lua state always has the variables luaroot and project_root defined for the main lua API folder inside CVEDIA-RT and the current lua project folder path.

onStartInstance()

Next the onStartInstance function is defined, this function is the first being called when starting the instance and should be where all the workers are defined.

Workers

In CVEDIA-RT a Worker is an instance thread that has its own onInit and onRun functions. This methods allows multithreading for the instances.

function onStartInstance(name, config)
    local instance = api.thread.getCurrentInstance()
    instance:createWorker("onInit", "onRun")
end

onInit()

The onInit() function runs once when starting the Instance.

Initializing modules

Usually a module can be loaded by calling api.factory.[MODULENAME].create(instance, [MODULENAME_ON_JSON]).

[MODULENAME_ON_JSON] is used to read and write the settings of the module in the instance or solution config JSON.

function onInit(instance)

    local inputInst = api.factory.input.create(instance, "Input")
    inputInst:setSourceFromConfig()

    local outputInst = api.factory.output.create(instance, "Output")
    outputInst:loadHandlersFromConfig()

    zoneInst = api.factory.zone.create(instance, "Zone")
    zoneInst:registerKnownZones()

    -- Create other modules (...)

    return true
end

onInit() return value

onInit() must return true, signaling the engine that initialization was completed successfully.

onRun()

The onRun() function runs continuosly and it's where the video analitycs logic is implemented

function OnRun(currentTime)

    local instance = api.thread.getCurrentInstance()

    -- Get next frame. If the instance is paused we must ignore the skip_frame functionality
    local inputFrames = input:readMetaFrames(instance:isPaused())

    if #inputFrames == 0 then
        -- No frames available yet due to FPS syncronization
        instance:sleep(1) -- avoid CPU burn
        return InstanceStepResult.INSTANCE_STEP_INVALID
    end

    local inputimg = inputFrames[1].image
    local metadata = inputFrames[1].metadata -- data received from the REST API or other protocols

    if inputimg == nil or not inputimg:is_valid() then
        instance:sleep(1) -- avoid CPU burn
        return InstanceStepResult.INSTANCE_STEP_INVALID
    end

    -- Run detector
    -- DetectorRegions is the name given in the inputSinkConfig function for storing the detection region
    -- settings, as defined on the solution.lua code
    ---@type Rect[]
    local regions = instance:getConfigValue("DetectorRegions")

    local detectInst = api.factory.inference.get(instance:getSolution(), "PVADetector")

    ---@type Detection[]
    local detections = detectInst:runInference(regions, inputimg)
    if #detections > 1 and #regions > 1 then
        detections = api.utils.NMS(detections, 0.5)
    end

    Instance:writeOutputSink("source", OutputType.Image, inputimg, "Input image")
    Instance:writeOutputSink("detections", OutputType.BBox, detections, "Raw detections (untracked)")
    Instance:flushOutputSink()

    return InstanceStepResult.INSTANCE_STEP_OK
end

Ouptut Sinks

For more information on the Output Sink check here.

onRun() currentTime parameter

The onRun() function always receives a parameter that indicates the real timestamp at which the function was called. This can be used for timing and performance data gathering.