Agent lambda development

Here is agent lambda development description.

More information about lambda types and differences of agent and others available at lambda types description.

More information about LUNA videoanalytics, in particular, luna-video-manager, video-agents and their interaction see its’ developers manuals.

Agent lambda requirements

Agent lambda has several requirements to addition with basic requirements:

  • Luna Video Manager available by credentials from Luna-Configurator

  • Luna Licenses available by credentials from Luna-Configurator

  • Luna Events available by credentials from Luna-Configurator; it can be disabled using ADDITIONAL_SERVICES_USAGE setting and it this case lambda should provide for work without Luna-Events usage

  • Luna Sender available by credentials from Luna-Configurator; it can be disabled using ADDITIONAL_SERVICES_USAGE setting and it this case lambda should provide for work without Luna-Sender usage

  • Luna Faces/Images Samples Store available by credentials from Luna-Configurator; it can be disabled using ADDITIONAL_SERVICES_USAGE setting and it this case lambda should provide for work without Luna-Image-Store usage

Agent lambda configuration

The agent lambda required several settings from luna-configurator, whose can be separated to several groups:

  • LUNA_LAMBDA_AGENT_UNIT_LOGGER - lambda logger settings

  • luna-services addresses and timeouts settings (for example, LUNA_EVENTS_ADDRESS and LUNA_EVENTS_TIMEOUTS will be used by lambda to make requests to luna-events service)

  • ADDITIONAL_SERVICES_USAGE setting will be used to determine which luna-services can be used by the lambda (the lambda will not check connection to disabled services and will raise an error if user try to make request to such service)

  • LUNA_LAMBDA_AGENT_UNIT_ANALYTICS_SETTINGS - lambda analytics-specific settings, for example, settings of which device must be used by one or another analytics - CPU or GPU

  • LUNA_LAMBDA_AGENT_UNIT_VIDEO_SETTINGS/LUNA_LAMBDA_AGENT_UNIT_RUNTIME_SETTINGS - lambda video processing settings

Note

Take into account that video-agents can be internal and external (see Luna-Video-Manager/Luna-Video-Agent) developers documentation for details. The lambda video-agent represents internal video-agent at the moment, this behaviour can be changed in the future releases.

Agent lambda development

Lambda agent works with analytics, each lambda agent must have at least one video analytics.

It is possible to specify video analytics as separate package which will installed as lambda dependency or include analytics as module into lambda. The lambda_main.py and other configuration files will not differ in this cases.

The description below describes the case when analytics represents itself separate package, for description in the case of analytics is a module in lambda see analytics development chapter.

The agent lambda development in the easiest way consists of it’s configuration by specifying several parameters in lambda_main.py file.

Warning

The names of analytics, features, etc. listed below are fictitious and have nothing in common with the names of real analytics, etc.

The only required variable which must be specified is a list of available analytics, which also must be specified as lambda requirements, for example:

lambda_main.py
    #: list of analytics for agent lambda
    AVAILABLE_ANALYTICS = ["people"]
requirements.txt
    people_analytics==1.2.0

There are some more parameters which can also be relevant:

The ANALYTICS_LICENSE_FEATURES is a map with analytics and their licensing feature names. If analytics not required licensing, it must not be included into this map. If no one analytics must depends on licensing, this map must be empty or may not be included in lambda_main.py.

The ESTIMATORS_TO_INITIALIZE and ESTIMATORS_TARGETS are lists of SDK estimators and their targets which must initialized before analytics usage. If no one analytics not required SDK estimators usage, this lists must be empty or may not be included in lambda_main.py.

Note

In this example lambda has people_analytics package of version 1.2.0 as requirement, which represents video analytics package with analytics named people. The usage of this analytics is regulated by license feature named people_feature. It is requires the estimator from luna-sdkloop package named people and the people target estimator to be initialized before lambda starts.

lambda_main.py
    from sdk_loop.enums import Estimators, LoopEstimations

    #: list of analytics for agent lambda
    AVAILABLE_ANALYTICS = ["people"]
    #: map with analytics and their license features
    ANALYTICS_LICENSE_FEATURES = {"people": "people_feature"}
    #: list of estimators for initialization
    ESTIMATORS_TO_INITIALIZE = [Estimators.people]
    #: list of estimators targets for initialization
    ESTIMATORS_TARGETS = [LoopEstimations.people]
requirements.txt
    people_analytics==1.2.0

There are some more settings which can be used for agent lambda in lambda_main.py file:

lambda_main.py
    # count of video-agent registration attempt in *luna-video-manager*
    REGISTRATION_ATTEMPT_COUNT = 5
    # delay between registration attempts
    REGISTRATION_ATTEMPT_DELAY = 1
    # max size of feedback which can be sent to *luna-video-manager* at one time
    FEEDBACK_MAX_BATCH_COUNT = 1000
    # the code of response for ws-connections when agent shutdown
    WSCODE_GOING_AWAY = 1001
    # the name of lambda video-agent which will be registered in *luna-video-manager*
    AGENT_NAME = "luna-lambda-great-video-agent"
    # the description of lambda video-agent which will be registered in *luna-video-manager*
    AGENT_DESCRIPTION = "luna-lambda-greatest-video-agent"

The are many other possibilities to modify such lambda agent behavior, it’s interaction with analytics and so on, which can be realized in the same way as regular luna-video-agent, for more information, see luna-video-agent developers manual.

Agent video analytics development

It is possible to specify video analytics as separate package which will installed as lambda dependency or include analytics as module into lambda. The description below describes analytics module, which can then be included as a module or package.

Note

The complete for user video analytics development guide is in development and will be included in future releases.

In presented example(s) the poetry tool using for dependencies management, so the pyproject.toml file is filled by user and the poetry.lock file must be generated using poetry tool.

The example of video analytics which detect suits on video/streams and generate events if suits appears.

Note

The example below uses image classification model (“resnet50-v2-7.onnx”) and json file (“imagenet-simple-labels.json”) from github. To make this example works it needs to get this files from github and place into data folder of lambda agent archive.

Suit analytics example file structure itself
  ├──__init__.py
  ├──analytics.py
  ├──classes.py
  ├──common.py
  ├──models.py
  ├──spec.html
  ├──spec.yml
  └──data
     ├──resnet50-v2-7.onnx
     └──imagenet-simple-labels.json
  └──suit_nodes
     ├──__init__.py
     ├──events.py
     ├──suit_estimator.py
     └──suid_nodes.py

Note

The video analytics requires some dependencies which are available on visionlabs pypi for local run. All provided examples use visionlabs pypi specification at tool.poetry.source section of pyproject.toml file which make possible to install dependencies from the above source. It is also required HASP license provided by VisionLabs. For agent lambda creation, all required video analytics dependencies already included in base image (which is using for agent lambda building).

If it needs to build this analytics as separate package, it must include its’ dependencies for example using pyproject.toml/poetry.lock files:

pyproject.toml
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

[tool.poetry]
name = "suit-analytics"
version = "0.0.4"
description = "suit analytics"
authors = [ "VisionLabs", ]

[[tool.poetry.source]]
name = "vlabspypi"
url = "http://pypi.visionlabs.ru/root/public/+simple"
priority = "primary"

[[tool.poetry.source]]
name = "public-pypi"
url = "https://pypi.org/simple"
priority = "supplemental"

[tool.setuptools.package-data]
analytics-docs = ["spec.html"]
onnx = ["data/resnet50-v2-7.onnx"]

[tool.poetry.dependencies]
python = "^3.12"
luna-analytics-manager = {version="*", extras=["sdk"]}
opencv-python-headless = "*"
nvidia-curand-cu11 = "*"
nvidia-cufft-cu11 = "*"
onnxruntime-gpu = "*"
pynvcodec = "^2.6.0"

After dependencies resolving it must be built into a packages (for example, using poetry build).

The *__init__.py* module must contains all presented imported objects and classes to make analytics works.
__init__.py
"""Suit analytics."""

from video_analytics_helpers.luna_wraps import EventMetadata, LunaApiExternalParams, LunaWraps as PostProcessing

from .analytics import AnalyticsCtx, FrameCtx, initialize
from .common import getDocumentation, mandatoryNodes, targetNodes
from .models import Callbacks, Params, Targets
The *analytics.py* module contains analytics initialization, frame and analytics contexts.
analytics.py
"""Analytics template."""

from logging import Logger
from pathlib import Path

import attr
import numpy as np
import onnxruntime as ort
from luna_analytics_manager.base import FrameEnviron
from luna_analytics_manager.templates.sdk import SDKAnalyticsCtx, SDKFrameCtx
from lunavl.sdk.image_utils.image import VLImage
from video_analytics_helpers.onnxruntime_wrapper import OnnxruntimeWrapper

from .classes import AnalyticsRes, FrameRes
from .models import Callbacks, Params, Targets

logger = Logger("luna.suit")


async def initialize(dataPath: str | Path | None = None, **kwargs) -> None:
    if dataPath is None:
        configPrefix = Path(__file__).absolute().parent / "data"
    elif isinstance(dataPath, str):
        configPrefix = Path(dataPath)
    else:
        configPrefix = dataPath

    configPrefix / "imagenet-simple-labels.json"
    runtimeSettings = kwargs.get("runtime_settings", {})
    options = ort.SessionOptions()
    options.intra_op_num_threads = runtimeSettings.get("num_threads", 6)

    backend = (
        [("CUDAExecutionProvider", {"device_id": 0})]
        if runtimeSettings.get("device_class", "cpu") == "gpu"
        else ["CPUExecutionProvider"]
    )

    globalSession = ort.InferenceSession(
        (configPrefix / "resnet50-v2-7.onnx").as_posix(),
        sess_options=options,
        providers=backend,
    )
    globalSession.set_providers(backend)
    onnxWrapper = OnnxruntimeWrapper(options.intra_op_num_threads, globalSession)

    AnalyticsCtx.initialize(onnxWrapper)


@attr.dataclass(slots=True)
class FrameCtx(SDKFrameCtx):
    """Frame processing context."""

    processed: FrameRes | None = None


@attr.dataclass(slots=True)
class AnalyticsCtx(SDKAnalyticsCtx):
    """Analytics context."""

    name: str = "suit"
    params: Params = attr.field(factory=Params)
    targets: Targets = attr.field(factory=Targets)
    callbacks: Callbacks = attr.field(factory=Callbacks)
    aggregated: AnalyticsRes = attr.field(factory=AnalyticsRes)

    @classmethod
    def initialize(cls, onnxWrapper):
        cls.onnxWrapper = onnxWrapper

    async def prepareFrameCtx(self, frame: FrameEnviron[np.ndarray, VLImage]) -> FrameCtx:
        """Prepare frame processing context."""

        return FrameCtx(
            image=frame.frame,
            number=frame.raw.number,
            timestamp=frame.raw.timestamp,
            lastFrame=frame.raw.lastFrame,
        )
The *classes.py* module contains frame and analytics results containers description.
classes.py
"""Analytics structures."""

import attr

from .suit_nodes.events import SuitEvent


@attr.dataclass(slots=True)
class FrameRes:
    """Analytics result on a single frame."""

    events: list[SuitEvent]

    def asDict(self):
        """Build results in API format."""

        return {"events": [event.asDict() for event in self.events]}


@attr.dataclass(slots=True)
class AnalyticsRes:
    """Analytics aggregated result."""

    def asDict(self):
        """Build results in API format."""

        return {}
The *common.py* module contains analytics target, mandatory nodes and `getDocumentation` function which returns raw html analytics documentation for user.
common.py
"""Analytics setup."""

from pathlib import Path

from .models import Params, Targets
from .suit_nodes.suit_nodes import NodeOverview, NodeSuit

NODES = {NodeOverview, NodeSuit}
_DOCS_PATH = Path(__file__).parent / "spec.html"


def targetNodes(targets: Targets):
    """
    Pre-select nodes by user-defined targets.

    Examle:
        nodes = set()
        if {"foo", "foobar") & targets:
            nodes +| NodeFoo
        if "baz" in targets:
            nodes +| NodesBaz
        yield from nodes

    """

    yield from (node for node in NODES if node.name in targets)  # type: ignore


def mandatoryNodes(params: Params, targets: Targets):
    """
    Pre-select mandatory nodes according to configuration parameters.

    Examle:
        nodes = set()
        if params.baz.filters.threshold is not None and "baz" in targets:
            nodes |= {NodeBazFilter}
        yield from nodes

    """

    yield from (NodeSuit,)


def getDocumentation() -> str:
    """Get analytics documentation"""
    with open(_DOCS_PATH) as f:
        return f.read()
The *models.py* module contains analytics parameters models.
models.py
"""Analytics models."""

from typing import Annotated, Literal

from annotated_types import Ge
from luna_analytics_manager.templates.mixins.frame_frequency_mixins import RateFrame, RateTime
from luna_analytics_manager.templates.models import EventPolicy, WrapModelError, WrapUnionError
from luna_analytics_manager.templates.sdk import SDKAnalyticsTargets
from pydantic import Field, Strict
from video_analytics_helpers.models import EventAnalyticCallback, HttpAnalyticCallback, WSAnalyticCallback
from video_analytics_helpers.params import BaseParams
from vlutils.structures.pydantic import BaseModel


class RateFrameDefault(BaseModel):
    period: Annotated[int, Ge(1), Strict()] = 1
    unit: Literal["frame"] = "second"


class OnetimeEventPolicyDefault(BaseModel):
    trigger: Literal["start", "end"] = "start"


class Params(BaseParams):
    """Params of analytic"""

    rate: Annotated[
        RateFrame | RateTime,
        WrapUnionError,
        Field(discriminator="unit", default_factory=RateFrameDefault),
    ]
    droi: None = None
    eventPolicy: Annotated[
        EventPolicy,
        WrapUnionError,
        WrapModelError,
        Field(default_factory=OnetimeEventPolicyDefault),
    ]


class Targets(SDKAnalyticsTargets[Literal["suit"]]):
    """Targets of analytic"""

    _default = {"suit"}


Callbacks = Annotated[
    list[Annotated[HttpAnalyticCallback | WSAnalyticCallback | EventAnalyticCallback, Field(discriminator="type")]],
    Field(max_length=10),
]
The *spec.yml* file contains openapi documentation with analytics parameters description in yaml format.
spec.yml
openapi: 3.0.0
info:
  version: 'v.0.0.1'
  title: 'Suit Analytics'
  description: |
     `Suit Analytics` is intended for determination of:
        - suit_class parameter;
        

     The `suit` analytics decode stream frames taking into account `rate` parameter
     from <a href="#tag/suit-analytics/paths/~1suit_analytics/post">stream creation request analytic parameters</a>
     and performs the following actions:
        - Calculates suit class
        - For each callback check it's conditions and execute callbacks, which fit conditions.

      The schema of data sending by `callback` described as stream creation request callback.

components:
  schemas:
    error:
      type: object
      properties:
        error_code:
          type: integer
          description: Error code.
        desc:
          type: string
          description: Short error description.
        detail:
          type: string
          description: Error details.
        link:
          type: string
          description: Link to the documentation website with the error description.
      required: [error_code, detail, desc, link]
      example:
        error_code: 1
        detail: internal server error
        desc: internal server error
        link: "https://docs.visionlabs.ai/info/luna/troubleshooting/errors-description/code-1"

    int01:
      type: integer
      enum: [0,1]

    roi_int_coordinates:
      type: integer
      minimum: 0
      default: 0
      maximum: 65536
      example: 3327

    roi_float_percent:
      type: number
      format: float
      minimum: 0
      default: 0.0
      maximum: 100.0
      example: 87.4

    roi_float_percent_size:
      type: number
      format: float
      minimum: 0.00001
      maximum: 100.0
      example: 66.3

    roi_abs:
      type: object
      properties:
        x:
          $ref: '#/components/schemas/roi_int_coordinates'
        y:
          $ref: '#/components/schemas/roi_int_coordinates'
        width:
          $ref: '#/components/schemas/roi_int_coordinates'
        height:
          $ref: '#/components/schemas/roi_int_coordinates'
        mode:
          type: string
          enum: [abs]
          example: "abs"
          description: Coordinates and size are set in pixels.
      required: [x, y, width, height, mode]

    roi_percent:
      type: object
      properties:
        x:
          $ref: '#/components/schemas/roi_float_percent'
        y:
          $ref: '#/components/schemas/roi_float_percent'
        width:
          $ref: '#/components/schemas/roi_float_percent_size'
        height:
          $ref: '#/components/schemas/roi_float_percent_size'
        mode:
          type: string
          enum: [ percent ]
          example: "percent"
          description: Coordinates and size are set in percentage.
      required: [x, y, width, height, mode]

    roi:
      oneOf:
        - $ref: '#/components/schemas/roi_abs'
        - $ref: '#/components/schemas/roi_percent'
      description: |
          Region of interest on a frame. Boundaries of the area are described in `x`, `y` coordinates
          of the top left point and `width`, `height` properties
          **Region must not be any bigger than the original frame**

          The region of interest will be sent to estimator. The smaller the `roi`, the smaller the area the estimator
          will process and, accordingly, work faster.

    number01:
      type: number
      minimum: 0
      maximum: 1

    rate:
      type: object
      description: |
        Analytic rate execution determines on which frames the analytics will be launched.

        Analytic rate execution can be configured in next ways:
        - execute analytic on each Nth frame (`unit` - `frame`)
        - execute analytic on each frame corresponding to the Nth second (`unit` - `second`)
      properties:
        period:
          type: float
          minimum: 0
          default: 1
          description: Period length.
        unit:
          type: string
          default: second
          enum:
            - frame
            - second
          description: Unit for a period calculation (every `n` seconds or every `n` frames).
      required: [ unit, period ]

    callback_base:
      type: object
      properties:
        enable:
          type: integer
          enum: [0, 1]
          default: 1
          description: Whether callback enabled or not.

    callback_ws:
      allOf:
        - $ref: "#/components/schemas/callback_base"
        - type: object
          properties:
            type:
              type: string
              enum: [luna-ws-notification]
              description: Event will be sent with `suit` type.
      required: [type]

    callback_basic_authorization:
      type: object
      properties:
        type:
          type: string
          description: Authorization type.
          enum: [basic]
        login:
          type: string
          maxLength: 128
          description: Login.
        password:
          type: string
          maxLength: 128
          description: Password.
      required: [ type, login, password]
      description: Callback basic authorization parameters.

    callback_http:
      allOf:
        - $ref: "#/components/schemas/callback_base"
        - type: object
          properties:
            type:
              type: string
              enum: [http]
              description: Event will be sent to http url.
            url:
              type: string
              description: Callback url.
            authorization:
              $ref: '#/components/schemas/callback_basic_authorization'
            params:
              type: object
              properties:
                timeout:
                  type: integer
                  default: 60
                  description: Callback request timeout.
                content_type:
                  type: string
                  default: application/json
                  enum: [application/json]
                  description: Callback request content type.
                headers:
                  type: object
                  description: Callback request headers.
                  additionalProperties: true
              description: Callback request parameters
      required: [url, type]

    callback:
      oneOf:
        - $ref: '#/components/schemas/callback_http'
        - $ref: '#/components/schemas/callback_ws'
      discriminator:
        propertyName: type
        mapping:
          http: '#/components/schemas/callback_http'
          luna-ws-notification: '#/components/schemas/callback_ws'

    image_retain_policy:
      type: object
      description: |
        Image retain policy applicable when analytic `overview` target is specified,
        and configures parameters with which will image be saved.
      properties:
        mimetype:
          type: string
          enum:
            - PNG
            - JPEG
          default: JPEG
          description: Image format.
        quality:
          allOf:
            - $ref: '#/components/schemas/number01'
            - default: 1
          description: Image quality, on a scale from 0 (worst) to 1 (best). Has no effect on `PNG`.
        max_size:
          type: integer
          minimum: 0
          default: 640
          description: Image max size, in pxl. Neither the width nor the height will exceed this value.

    period_event_policy:
      type: object
      properties:
        trigger:
          type: string
          enum: [period]
        interval:
          type: float
          description: Event generation period interval.
          minimum: 0.0
          default: 1.0
      required: [trigger]

    start_event_policy:
      type: object
      properties:
        trigger:
          type: string
          enum: [start]
      required: [trigger]

    end_event_policy:
      type: object
      properties:
        trigger:
          type: string
          enum: [end]
      required: [trigger]

    parameters:
      type: object
      description: |
        Analytic parameters for stream processing.

        There are default analytic parameters which will be applied for stream processing if no one specified here.
      properties:
        targets:
          type: array
          default: [suit]
          items:
            type: string
            enum: [suit]
          description: |
            Estimations to perform on the video.

            `suit` will calculate some `suit` value if specified.

            `overview` will add image to events.
        callbacks:
          type: array
          description: |
            Callbacks parameters.

            `http` type callback sends events to the specified url by POST request.

            `luna-ws-notification` type callback sends events to websocket using sender.
          maxItems: 10
          items:
            $ref: '#/components/schemas/callback'

        parameters:
          type: object
          description: Estimation parameters.
          properties:
            event_policy:
              description: |
                Event policy.
                
                When suit appears on frame, the new `track` will starts, when suit disappears, `track` will stops.
                
                - when track starts (`trigger` - `start`)
                - when track ends (`trigger` - `end`)
                - periodically while track exists (`trigger` - `period`)
              type: object
              properties:
              oneOf:
                - $ref: '#/components/schemas/period_event_policy'
                - $ref: '#/components/schemas/start_event_policy'
                - $ref: '#/components/schemas/end_event_policy'
              discriminator:
                propertyName: trigger
                mapping:
                  period: '#/components/schemas/period_event_policy'
                  start: '#/components/schemas/start_event_policy'
                  end: '#/components/schemas/end_event_policy'
              default:
                trigger: start
            image_retain_policy:
              $ref: '#/components/schemas/image_retain_policy'
            probe_count:
              description: |
                A number of consecutive suit estimations with not 'none' class. This parameter is intended to
                prevent false positives from analytics.
              type: integer
              minimum: 0
              default: 3
            roi:
              $ref: '#/components/schemas/roi'
            rate:
              allOf:
                - $ref: '#/components/schemas/rate'
                - default:
                    unit: second
                    period: 1

    stream:
      type: object
      description: |
        Full stream creation schema is available in `Luna-Video-Manager` documentation.

        The presented schema described only analytics parameters.
      properties:
        data:
          type: object
          description: stream data
          additionalProperties: true
        analytics:
          type: array
          description: analytics list
          items:
            type: object
            description: analytics
            properties:
              analytic_name:
                type: string
                maxLength: 36
                pattern: '^[a-zA-Z0-9_\-]{1,36}$'
                description: Analytic name.
                enum: [suit_analytics]
              parameters:
                type: object
                $ref: '#/components/schemas/parameters'
            required: [analytic_name]
      additionalProperties: true
      required: [data, analytics]

    string36_nullable:
      type: string
      maxLength: 36
      nullable: true

    longitude:
      type: number
      minimum: -180
      maximum: 180
      example: 36.616

    latitude:
      type: number
      minimum: -90
      maximum: 90
      example: 55.752

    geo_position:
      type: object
      nullable: true
      description: Geo position specified by geographic coordinates - longitude and latitude.
      properties:
        longitude:
          allOf:
            - $ref: '#/components/schemas/longitude'
            - description: Longitude in degrees.
        latitude:
          allOf:
            - $ref: '#/components/schemas/latitude'
            - description: Latitude in degrees.
      required: [longitude, latitude]
      example:
        longitude: 36.616
        latitude: 55.752

    uuid:
      type: string
      format: uuid
      pattern: '^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$'
      example: "557d54ec-29ad-4f3c-93b4-c9092ef12515"

    video_segment:
      type: object
      properties:
        start_time_offset:
          type: number
          example: 0.123
          description: Start video segment offset(seconds).
        end_time_offset:
          type: number
          example: 1.234
          description: Eng video segment offset(seconds).
      required: [ start_time_offset, end_time_offset ]

    callback_request:
      type: object
      properties:
        event_type:
          type: string
          enum: [suit]
          description: Event type.
        account_id:
          allOf:
            - $ref: '#/components/schemas/uuid'
          description: Account id of event.
        event_create_time:
          type: string
          description: Event creation time.
        event_end_time:
          type: string
          description: Event end time.
        event:
          type: object
          description: Event.
          properties:
            stream_id:
              allOf:
                - $ref: '#/components/schemas/uuid'
              description: Stream ID.
            event_id:
              allOf:
                - $ref: '#/components/schemas/uuid'
              description: Event ID.
            track_id:
              allOf:
                - $ref: '#/components/schemas/uuid'
              description: Track id.
              nullable: true
            video_segment:
              $ref: '#/components/schemas/video_segment'
            overview:
              type: object
              properties:
                time_offset:
                  type: number
                  description: time offset
                image:
                  type: str
                  description: link to image
              required: [time_offset]
            location:
              type: object
              properties:
                city:
                  allOf:
                    - $ref: '#/components/schemas/string36_nullable'
                  example: Moscow
                  description: City that stream belongs.
                area:
                  allOf:
                    - $ref: '#/components/schemas/string36_nullable'
                  example: Central
                  description: Area that stream belongs.
                district:
                  allOf:
                    - $ref: '#/components/schemas/string36_nullable'
                  example: Basmanny
                  description: District that stream belongs.
                street:
                  allOf:
                    - $ref: '#/components/schemas/string36_nullable'
                  example: Podsosensky lane
                  description: Street that stream belongs.
                house_number:
                  allOf:
                    - $ref: '#/components/schemas/string36_nullable'
                  example: 23 bldg.3
                  description: Street that stream belongs.
                geo_position:
                  $ref: '#/components/schemas/geo_position'
              description: |
                Stream location parameters.

                Required callback `location` target to send this value.
          required: [stream_id, event_id, suit_class, track_id]
      required: [event_type, event, account_id, event_create_time, event_end_time]
paths:
  /suit_analytics:
    post:
      tags:
        - suit analytics
      summary: stream creation
      description: |
        Stream creation superficial request with detailed analytics parameters description.
        
        Full request description (exclude for this analytics description) available at `Luna-Video-Manager` documentation.
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/stream'
            example:
              data:
                reference: rtsp://stream_url
                type: stream
                downloadable: false
              analytics:
                - analytic_name: suit_analytics
                  parameters:
                    parameters:
                      probe_count: 2
                      image_retain_policy:
                        mimetype: PNG
                      roi:
                        mode: abs
                        x: 0
                        y: 0
                        width: 1000
                        height: 500
                    callbacks:
                      - url: "http://127.0.0.1:8001"
                        type: http
                      - url: "http://127.0.0.1:8002"
                        type: http
                      - type: luna-ws-notification
                    targets:
                      - suit
                      - overview
      callbacks:
        onData:
          '{$request.body.callbacks.*.url}':
            post:
              requestBody:
                description: subscription payload
                content:
                  application/json:
                    schema:
                      $ref: '#/components/schemas/callback_request'
The *spec.html* file contains openapi documentation with analytics parameters description in html format.
spec.html
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf8" />
  <title>Suit Analytics</title>
  <!-- needed for adaptive design -->
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>
    body {
      padding: 0;
      margin: 0;
    }
  </style>
  <script src="https://cdn.redoc.ly/redoc/v2.0.0/bundles/redoc.standalone.js"></script><style data-styled="true" data-styled-version="5.3.3">.fcjMZs{width:calc(100% - 40%);padding:0 40px;}/*!sc*/
@media print,screen and (max-width:75rem){.fcjMZs{width:100%;padding:40px 40px;}}/*!sc*/
data-styled.g4[id="sc-hKwDye"]{content:"fcjMZs,"}/*!sc*/
.kKqedw{padding:40px 0;}/*!sc*/
.kKqedw:last-child{min-height:calc(100vh + 1px);}/*!sc*/
.sc-eCImPb > .sc-eCImPb:last-child{min-height:initial;}/*!sc*/
@media print,screen and (max-width:75rem){.kKqedw{padding:0;}}/*!sc*/
.hexvPn{padding:40px 0;position:relative;}/*!sc*/
.hexvPn:last-child{min-height:calc(100vh + 1px);}/*!sc*/
.sc-eCImPb > .sc-eCImPb:last-child{min-height:initial;}/*!sc*/
@media print,screen and (max-width:75rem){.hexvPn{padding:0;}}/*!sc*/
.hexvPn:not(:last-of-type):after{position:absolute;bottom:0;width:100%;display:block;content:'';border-bottom:1px solid rgba(0,0,0,0.2);}/*!sc*/
data-styled.g5[id="sc-eCImPb"]{content:"kKqedw,hexvPn,"}/*!sc*/
.vSqXI{width:40%;color:#ffffff;background-color:#263238;padding:0 40px;}/*!sc*/
@media print,screen and (max-width:75rem){.vSqXI{width:100%;padding:40px 40px;}}/*!sc*/
data-styled.g6[id="sc-jRQBWg"]{content:"vSqXI,"}/*!sc*/
.dKDnzb{background-color:#263238;}/*!sc*/
data-styled.g7[id="sc-gKclnd"]{content:"dKDnzb,"}/*!sc*/
.jTXZbd{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;width:100%;padding:0;}/*!sc*/
@media print,screen and (max-width:75rem){.jTXZbd{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;}}/*!sc*/
data-styled.g8[id="sc-iCfMLu"]{content:"jTXZbd,"}/*!sc*/
.kwcwWd{font-family:Montserrat,sans-serif;font-weight:400;font-size:1.85714em;line-height:1.6em;color:#333333;}/*!sc*/
data-styled.g9[id="sc-furwcr"]{content:"kwcwWd,"}/*!sc*/
.QwiBS{font-family:Montserrat,sans-serif;font-weight:400;font-size:1.57143em;line-height:1.6em;color:#333333;margin:0 0 20px;}/*!sc*/
data-styled.g10[id="sc-pVTFL"]{content:"QwiBS,"}/*!sc*/
.hwMGJl{color:#ffffff;}/*!sc*/
data-styled.g12[id="sc-kDTinF"]{content:"hwMGJl,"}/*!sc*/
.kyZwVG{border-bottom:1px solid rgba(38,50,56,0.3);margin:1em 0 1em 0;color:rgba(38,50,56,0.5);font-weight:normal;text-transform:uppercase;font-size:0.929em;line-height:20px;}/*!sc*/
data-styled.g13[id="sc-iqseJM"]{content:"kyZwVG,"}/*!sc*/
.bNUQy{cursor:pointer;margin-left:-20px;padding:0;line-height:1;width:20px;display:inline-block;outline:0;}/*!sc*/
.bNUQy:before{content:'';width:15px;height:15px;background-size:contain;background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMCIgeT0iMCIgd2lkdGg9IjUxMiIgaGVpZ2h0PSI1MTIiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIj48cGF0aCBmaWxsPSIjMDEwMTAxIiBkPSJNNDU5LjcgMjMzLjRsLTkwLjUgOTAuNWMtNTAgNTAtMTMxIDUwLTE4MSAwIC03LjktNy44LTE0LTE2LjctMTkuNC0yNS44bDQyLjEtNDIuMWMyLTIgNC41LTMuMiA2LjgtNC41IDIuOSA5LjkgOCAxOS4zIDE1LjggMjcuMiAyNSAyNSA2NS42IDI0LjkgOTAuNSAwbDkwLjUtOTAuNWMyNS0yNSAyNS02NS42IDAtOTAuNSAtMjQuOS0yNS02NS41LTI1LTkwLjUgMGwtMzIuMiAzMi4yYy0yNi4xLTEwLjItNTQuMi0xMi45LTgxLjYtOC45bDY4LjYtNjguNmM1MC01MCAxMzEtNTAgMTgxIDBDNTA5LjYgMTAyLjMgNTA5LjYgMTgzLjQgNDU5LjcgMjMzLjR6TTIyMC4zIDM4Mi4ybC0zMi4yIDMyLjJjLTI1IDI0LjktNjUuNiAyNC45LTkwLjUgMCAtMjUtMjUtMjUtNjUuNiAwLTkwLjVsOTAuNS05MC41YzI1LTI1IDY1LjUtMjUgOTAuNSAwIDcuOCA3LjggMTIuOSAxNy4yIDE1LjggMjcuMSAyLjQtMS40IDQuOC0yLjUgNi44LTQuNWw0Mi4xLTQyYy01LjQtOS4yLTExLjYtMTgtMTkuNC0yNS44IC01MC01MC0xMzEtNTAtMTgxIDBsLTkwLjUgOTAuNWMtNTAgNTAtNTAgMTMxIDAgMTgxIDUwIDUwIDEzMSA1MCAxODEgMGw2OC42LTY4LjZDMjc0LjYgMzk1LjEgMjQ2LjQgMzkyLjMgMjIwLjMgMzgyLjJ6Ii8+PC9zdmc+Cg==');opacity:0.5;visibility:hidden;display:inline-block;vertical-align:middle;}/*!sc*/
h1:hover > .sc-crHmcD::before,h2:hover > .bNUQy::before,.bNUQy:hover::before{visibility:visible;}/*!sc*/
data-styled.g14[id="sc-crHmcD"]{content:"bNUQy,"}/*!sc*/
.jLPfXH{height:18px;width:18px;min-width:18px;vertical-align:middle;float:right;-webkit-transition:-webkit-transform 0.2s ease-out;-webkit-transition:transform 0.2s ease-out;transition:transform 0.2s ease-out;-webkit-transform:rotateZ(-90deg);-ms-transform:rotateZ(-90deg);transform:rotateZ(-90deg);}/*!sc*/
.cUKGZj{height:18px;width:18px;min-width:18px;vertical-align:middle;-webkit-transition:-webkit-transform 0.2s ease-out;-webkit-transition:transform 0.2s ease-out;transition:transform 0.2s ease-out;-webkit-transform:rotateZ(-90deg);-ms-transform:rotateZ(-90deg);transform:rotateZ(-90deg);}/*!sc*/
.gHElpb{height:1.5em;width:1.5em;min-width:1.5em;vertical-align:middle;float:left;-webkit-transition:-webkit-transform 0.2s ease-out;-webkit-transition:transform 0.2s ease-out;transition:transform 0.2s ease-out;-webkit-transform:rotateZ(-90deg);-ms-transform:rotateZ(-90deg);transform:rotateZ(-90deg);}/*!sc*/
.laPRNF{height:20px;width:20px;min-width:20px;vertical-align:middle;float:right;-webkit-transition:-webkit-transform 0.2s ease-out;-webkit-transition:transform 0.2s ease-out;transition:transform 0.2s ease-out;-webkit-transform:rotateZ(0);-ms-transform:rotateZ(0);transform:rotateZ(0);}/*!sc*/
.laPRNF polygon{fill:white;}/*!sc*/
data-styled.g15[id="sc-egiyK"]{content:"jLPfXH,cUKGZj,gHElpb,laPRNF,"}/*!sc*/
.kCCtqV{border-left:1px solid #7c7cbb;box-sizing:border-box;position:relative;padding:10px 10px 10px 0;}/*!sc*/
@media screen and (max-width:50rem){.kCCtqV{display:block;overflow:hidden;}}/*!sc*/
tr:first-of-type > .sc-hBUSln,tr.last > .kCCtqV{border-left-width:0;background-position:top left;background-repeat:no-repeat;background-size:1px 100%;}/*!sc*/
tr:first-of-type > .sc-hBUSln{background-image:linear-gradient( to bottom, transparent 0%, transparent 22px, #7c7cbb 22px, #7c7cbb 100% );}/*!sc*/
tr.last > .sc-hBUSln{background-image:linear-gradient( to bottom, #7c7cbb 0%, #7c7cbb 22px, transparent 22px, transparent 100% );}/*!sc*/
tr.last + tr > .sc-hBUSln{border-left-color:transparent;}/*!sc*/
tr.last:first-child > .sc-hBUSln{background:none;border-left-color:transparent;}/*!sc*/
data-styled.g18[id="sc-hBUSln"]{content:"kCCtqV,"}/*!sc*/
.fwnkRw{vertical-align:top;line-height:20px;white-space:nowrap;font-size:13px;font-family:Courier,monospace;}/*!sc*/
.fwnkRw.deprecated{-webkit-text-decoration:line-through;text-decoration:line-through;color:#707070;}/*!sc*/
data-styled.g20[id="sc-fFeiMQ"]{content:"fwnkRw,"}/*!sc*/
.fOwWgB{border-bottom:1px solid #9fb4be;padding:10px 0;width:75%;box-sizing:border-box;}/*!sc*/
tr.expanded .sc-bkkeKt{border-bottom:none;}/*!sc*/
@media screen and (max-width:50rem){.fOwWgB{padding:0 20px;border-bottom:none;border-left:1px solid #7c7cbb;}tr.last > .sc-bkkeKt{border-left:none;}}/*!sc*/
data-styled.g21[id="sc-bkkeKt"]{content:"fOwWgB,"}/*!sc*/
.fMdASL{color:#7c7cbb;font-family:Courier,monospace;margin-right:10px;}/*!sc*/
.fMdASL::before{content:'';display:inline-block;vertical-align:middle;width:10px;height:1px;background:#7c7cbb;}/*!sc*/
.fMdASL::after{content:'';display:inline-block;vertical-align:middle;width:1px;background:#7c7cbb;height:7px;}/*!sc*/
data-styled.g22[id="sc-ieecCq"]{content:"fMdASL,"}/*!sc*/
.wbnF{border-collapse:separate;border-radius:3px;font-size:14px;border-spacing:0;width:100%;}/*!sc*/
.wbnF > tr{vertical-align:middle;}/*!sc*/
@media screen and (max-width:50rem){.wbnF{display:block;}.wbnF > tr,.wbnF > tbody > tr{display:block;}}/*!sc*/
@media screen and (max-width:50rem) and (-ms-high-contrast:none){.wbnF td{float:left;width:100%;}}/*!sc*/
.wbnF .sc-dJjYzT,.wbnF .sc-dJjYzT .sc-dJjYzT .sc-dJjYzT,.wbnF .sc-dJjYzT .sc-dJjYzT .sc-dJjYzT .sc-dJjYzT .sc-dJjYzT{margin:1em;margin-right:0;background:#fafafa;}/*!sc*/
.wbnF .sc-dJjYzT .sc-dJjYzT,.wbnF .sc-dJjYzT .sc-dJjYzT .sc-dJjYzT .sc-dJjYzT,.wbnF .sc-dJjYzT .sc-dJjYzT .sc-dJjYzT .sc-dJjYzT .sc-dJjYzT .sc-dJjYzT{background:#ffffff;}/*!sc*/
data-styled.g24[id="sc-hGPBjI"]{content:"wbnF,"}/*!sc*/
.eCLDJY > ul{list-style:none;padding:0;margin:0;margin:0 -5px;}/*!sc*/
.eCLDJY > ul > li{padding:5px 10px;display:inline-block;background-color:#11171a;border-bottom:1px solid rgba(0,0,0,0.5);cursor:pointer;text-align:center;outline:none;color:#ccc;margin:0 5px 5px 5px;border:1px solid #07090b;border-radius:5px;min-width:60px;font-size:0.9em;font-weight:bold;}/*!sc*/
.eCLDJY > ul > li.react-tabs__tab--selected{color:#333333;background:#ffffff;}/*!sc*/
.eCLDJY > ul > li.react-tabs__tab--selected:focus{outline:auto;}/*!sc*/
.eCLDJY > ul > li:only-child{-webkit-flex:none;-ms-flex:none;flex:none;min-width:100px;}/*!sc*/
.eCLDJY > ul > li.tab-success{color:#1d8127;}/*!sc*/
.eCLDJY > ul > li.tab-redirect{color:#ffa500;}/*!sc*/
.eCLDJY > ul > li.tab-info{color:#87ceeb;}/*!sc*/
.eCLDJY > ul > li.tab-error{color:#d41f1c;}/*!sc*/
.eCLDJY > .react-tabs__tab-panel{background:#11171a;}/*!sc*/
.eCLDJY > .react-tabs__tab-panel > div,.eCLDJY > .react-tabs__tab-panel > pre{padding:20px;margin:0;}/*!sc*/
.eCLDJY > .react-tabs__tab-panel > div > pre{padding:0;}/*!sc*/
data-styled.g30[id="sc-cxpSdN"]{content:"eCLDJY,"}/*!sc*/
.hGZPxu code[class*='language-'],.hGZPxu pre[class*='language-']{text-shadow:0 -0.1em 0.2em black;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none;}/*!sc*/
@media print{.hGZPxu code[class*='language-'],.hGZPxu pre[class*='language-']{text-shadow:none;}}/*!sc*/
.hGZPxu pre[class*='language-']{padding:1em;margin:0.5em 0;overflow:auto;}/*!sc*/
.hGZPxu .token.comment,.hGZPxu .token.prolog,.hGZPxu .token.doctype,.hGZPxu .token.cdata{color:hsl(30,20%,50%);}/*!sc*/
.hGZPxu .token.punctuation{opacity:0.7;}/*!sc*/
.hGZPxu .namespace{opacity:0.7;}/*!sc*/
.hGZPxu .token.property,.hGZPxu .token.tag,.hGZPxu .token.number,.hGZPxu .token.constant,.hGZPxu .token.symbol{color:#4a8bb3;}/*!sc*/
.hGZPxu .token.boolean{color:#e64441;}/*!sc*/
.hGZPxu .token.selector,.hGZPxu .token.attr-name,.hGZPxu .token.string,.hGZPxu .token.char,.hGZPxu .token.builtin,.hGZPxu .token.inserted{color:#a0fbaa;}/*!sc*/
.hGZPxu .token.selector + a,.hGZPxu .token.attr-name + a,.hGZPxu .token.string + a,.hGZPxu .token.char + a,.hGZPxu .token.builtin + a,.hGZPxu .token.inserted + a,.hGZPxu .token.selector + a:visited,.hGZPxu .token.attr-name + a:visited,.hGZPxu .token.string + a:visited,.hGZPxu .token.char + a:visited,.hGZPxu .token.builtin + a:visited,.hGZPxu .token.inserted + a:visited{color:#4ed2ba;-webkit-text-decoration:underline;text-decoration:underline;}/*!sc*/
.hGZPxu .token.property.string{color:white;}/*!sc*/
.hGZPxu .token.operator,.hGZPxu .token.entity,.hGZPxu .token.url,.hGZPxu .token.variable{color:hsl(40,90%,60%);}/*!sc*/
.hGZPxu .token.atrule,.hGZPxu .token.attr-value,.hGZPxu .token.keyword{color:hsl(350,40%,70%);}/*!sc*/
.hGZPxu .token.regex,.hGZPxu .token.important{color:#e90;}/*!sc*/
.hGZPxu .token.important,.hGZPxu .token.bold{font-weight:bold;}/*!sc*/
.hGZPxu .token.italic{font-style:italic;}/*!sc*/
.hGZPxu .token.entity{cursor:help;}/*!sc*/
.hGZPxu .token.deleted{color:red;}/*!sc*/
data-styled.g32[id="sc-iJKOTD"]{content:"hGZPxu,"}/*!sc*/
.gjxyHD{opacity:0.7;-webkit-transition:opacity 0.3s ease;transition:opacity 0.3s ease;text-align:right;}/*!sc*/
.gjxyHD:focus-within{opacity:1;}/*!sc*/
.gjxyHD > button{background-color:transparent;border:0;color:inherit;padding:2px 10px;font-family:Roboto,sans-serif;font-size:14px;line-height:1.5em;cursor:pointer;outline:0;}/*!sc*/
.gjxyHD > button:hover,.gjxyHD > button:focus{background:rgba(255,255,255,0.1);}/*!sc*/
data-styled.g33[id="sc-giYglK"]{content:"gjxyHD,"}/*!sc*/
.eqZXtE{position:relative;}/*!sc*/
data-styled.g38[id="sc-ikJyIC"]{content:"eqZXtE,"}/*!sc*/
.hgRRjA{margin-left:10px;text-transform:none;font-size:0.929em;color:black;}/*!sc*/
data-styled.g42[id="sc-cCcXHH"]{content:"hgRRjA,"}/*!sc*/
.fYaHea{font-family:Roboto,sans-serif;font-weight:400;line-height:1.5em;}/*!sc*/
.fYaHea p:last-child{margin-bottom:0;}/*!sc*/
.fYaHea h1{font-family:Montserrat,sans-serif;font-weight:400;font-size:1.85714em;line-height:1.6em;color:#32329f;margin-top:0;}/*!sc*/
.fYaHea h2{font-family:Montserrat,sans-serif;font-weight:400;font-size:1.57143em;line-height:1.6em;color:#333333;}/*!sc*/
.fYaHea code{color:#e53935;background-color:rgba(38,50,56,0.05);font-family:Courier,monospace;border-radius:2px;border:1px solid rgba(38,50,56,0.1);padding:0 5px;font-size:13px;font-weight:400;word-break:break-word;}/*!sc*/
.fYaHea pre{font-family:Courier,monospace;white-space:pre;background-color:#11171a;color:white;padding:20px;overflow-x:auto;line-height:normal;border-radius:0px;border:1px solid rgba(38,50,56,0.1);}/*!sc*/
.fYaHea pre code{background-color:transparent;color:white;padding:0;}/*!sc*/
.fYaHea pre code:before,.fYaHea pre code:after{content:none;}/*!sc*/
.fYaHea blockquote{margin:0;margin-bottom:1em;padding:0 15px;color:#777;border-left:4px solid #ddd;}/*!sc*/
.fYaHea img{max-width:100%;box-sizing:content-box;}/*!sc*/
.fYaHea ul,.fYaHea ol{padding-left:2em;margin:0;margin-bottom:1em;}/*!sc*/
.fYaHea ul ul,.fYaHea ol ul,.fYaHea ul ol,.fYaHea ol ol{margin-bottom:0;margin-top:0;}/*!sc*/
.fYaHea table{display:block;width:100%;overflow:auto;word-break:normal;word-break:keep-all;border-collapse:collapse;border-spacing:0;margin-top:1.5em;margin-bottom:1.5em;}/*!sc*/
.fYaHea table tr{background-color:#fff;border-top:1px solid #ccc;}/*!sc*/
.fYaHea table tr:nth-child(2n){background-color:#fafafa;}/*!sc*/
.fYaHea table th,.fYaHea table td{padding:6px 13px;border:1px solid #ddd;}/*!sc*/
.fYaHea table th{text-align:left;font-weight:bold;}/*!sc*/
.fYaHea .share-link{cursor:pointer;margin-left:-20px;padding:0;line-height:1;width:20px;display:inline-block;outline:0;}/*!sc*/
.fYaHea .share-link:before{content:'';width:15px;height:15px;background-size:contain;background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMCIgeT0iMCIgd2lkdGg9IjUxMiIgaGVpZ2h0PSI1MTIiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIj48cGF0aCBmaWxsPSIjMDEwMTAxIiBkPSJNNDU5LjcgMjMzLjRsLTkwLjUgOTAuNWMtNTAgNTAtMTMxIDUwLTE4MSAwIC03LjktNy44LTE0LTE2LjctMTkuNC0yNS44bDQyLjEtNDIuMWMyLTIgNC41LTMuMiA2LjgtNC41IDIuOSA5LjkgOCAxOS4zIDE1LjggMjcuMiAyNSAyNSA2NS42IDI0LjkgOTAuNSAwbDkwLjUtOTAuNWMyNS0yNSAyNS02NS42IDAtOTAuNSAtMjQuOS0yNS02NS41LTI1LTkwLjUgMGwtMzIuMiAzMi4yYy0yNi4xLTEwLjItNTQuMi0xMi45LTgxLjYtOC45bDY4LjYtNjguNmM1MC01MCAxMzEtNTAgMTgxIDBDNTA5LjYgMTAyLjMgNTA5LjYgMTgzLjQgNDU5LjcgMjMzLjR6TTIyMC4zIDM4Mi4ybC0zMi4yIDMyLjJjLTI1IDI0LjktNjUuNiAyNC45LTkwLjUgMCAtMjUtMjUtMjUtNjUuNiAwLTkwLjVsOTAuNS05MC41YzI1LTI1IDY1LjUtMjUgOTAuNSAwIDcuOCA3LjggMTIuOSAxNy4yIDE1LjggMjcuMSAyLjQtMS40IDQuOC0yLjUgNi44LTQuNWw0Mi4xLTQyYy01LjQtOS4yLTExLjYtMTgtMTkuNC0yNS44IC01MC01MC0xMzEtNTAtMTgxIDBsLTkwLjUgOTAuNWMtNTAgNTAtNTAgMTMxIDAgMTgxIDUwIDUwIDEzMSA1MCAxODEgMGw2OC42LTY4LjZDMjc0LjYgMzk1LjEgMjQ2LjQgMzkyLjMgMjIwLjMgMzgyLjJ6Ii8+PC9zdmc+Cg==');opacity:0.5;visibility:hidden;display:inline-block;vertical-align:middle;}/*!sc*/
.fYaHea h1:hover > .share-link::before,.fYaHea h2:hover > .share-link::before,.fYaHea .share-link:hover::before{visibility:visible;}/*!sc*/
.fYaHea a{-webkit-text-decoration:auto;text-decoration:auto;color:#32329f;}/*!sc*/
.fYaHea a:visited{color:#32329f;}/*!sc*/
.fYaHea a:hover{color:#6868cf;-webkit-text-decoration:auto;text-decoration:auto;}/*!sc*/
.fhPxtY{font-family:Roboto,sans-serif;font-weight:400;line-height:1.5em;}/*!sc*/
.fhPxtY p:last-child{margin-bottom:0;}/*!sc*/
.fhPxtY p:first-child{margin-top:0;}/*!sc*/
.fhPxtY p:last-child{margin-bottom:0;}/*!sc*/
.fhPxtY h1{font-family:Montserrat,sans-serif;font-weight:400;font-size:1.85714em;line-height:1.6em;color:#32329f;margin-top:0;}/*!sc*/
.fhPxtY h2{font-family:Montserrat,sans-serif;font-weight:400;font-size:1.57143em;line-height:1.6em;color:#333333;}/*!sc*/
.fhPxtY code{color:#e53935;background-color:rgba(38,50,56,0.05);font-family:Courier,monospace;border-radius:2px;border:1px solid rgba(38,50,56,0.1);padding:0 5px;font-size:13px;font-weight:400;word-break:break-word;}/*!sc*/
.fhPxtY pre{font-family:Courier,monospace;white-space:pre;background-color:#11171a;color:white;padding:20px;overflow-x:auto;line-height:normal;border-radius:0px;border:1px solid rgba(38,50,56,0.1);}/*!sc*/
.fhPxtY pre code{background-color:transparent;color:white;padding:0;}/*!sc*/
.fhPxtY pre code:before,.fhPxtY pre code:after{content:none;}/*!sc*/
.fhPxtY blockquote{margin:0;margin-bottom:1em;padding:0 15px;color:#777;border-left:4px solid #ddd;}/*!sc*/
.fhPxtY img{max-width:100%;box-sizing:content-box;}/*!sc*/
.fhPxtY ul,.fhPxtY ol{padding-left:2em;margin:0;margin-bottom:1em;}/*!sc*/
.fhPxtY ul ul,.fhPxtY ol ul,.fhPxtY ul ol,.fhPxtY ol ol{margin-bottom:0;margin-top:0;}/*!sc*/
.fhPxtY table{display:block;width:100%;overflow:auto;word-break:normal;word-break:keep-all;border-collapse:collapse;border-spacing:0;margin-top:1.5em;margin-bottom:1.5em;}/*!sc*/
.fhPxtY table tr{background-color:#fff;border-top:1px solid #ccc;}/*!sc*/
.fhPxtY table tr:nth-child(2n){background-color:#fafafa;}/*!sc*/
.fhPxtY table th,.fhPxtY table td{padding:6px 13px;border:1px solid #ddd;}/*!sc*/
.fhPxtY table th{text-align:left;font-weight:bold;}/*!sc*/
.fhPxtY .share-link{cursor:pointer;margin-left:-20px;padding:0;line-height:1;width:20px;display:inline-block;outline:0;}/*!sc*/
.fhPxtY .share-link:before{content:'';width:15px;height:15px;background-size:contain;background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMCIgeT0iMCIgd2lkdGg9IjUxMiIgaGVpZ2h0PSI1MTIiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIj48cGF0aCBmaWxsPSIjMDEwMTAxIiBkPSJNNDU5LjcgMjMzLjRsLTkwLjUgOTAuNWMtNTAgNTAtMTMxIDUwLTE4MSAwIC03LjktNy44LTE0LTE2LjctMTkuNC0yNS44bDQyLjEtNDIuMWMyLTIgNC41LTMuMiA2LjgtNC41IDIuOSA5LjkgOCAxOS4zIDE1LjggMjcuMiAyNSAyNSA2NS42IDI0LjkgOTAuNSAwbDkwLjUtOTAuNWMyNS0yNSAyNS02NS42IDAtOTAuNSAtMjQuOS0yNS02NS41LTI1LTkwLjUgMGwtMzIuMiAzMi4yYy0yNi4xLTEwLjItNTQuMi0xMi45LTgxLjYtOC45bDY4LjYtNjguNmM1MC01MCAxMzEtNTAgMTgxIDBDNTA5LjYgMTAyLjMgNTA5LjYgMTgzLjQgNDU5LjcgMjMzLjR6TTIyMC4zIDM4Mi4ybC0zMi4yIDMyLjJjLTI1IDI0LjktNjUuNiAyNC45LTkwLjUgMCAtMjUtMjUtMjUtNjUuNiAwLTkwLjVsOTAuNS05MC41YzI1LTI1IDY1LjUtMjUgOTAuNSAwIDcuOCA3LjggMTIuOSAxNy4yIDE1LjggMjcuMSAyLjQtMS40IDQuOC0yLjUgNi44LTQuNWw0Mi4xLTQyYy01LjQtOS4yLTExLjYtMTgtMTkuNC0yNS44IC01MC01MC0xMzEtNTAtMTgxIDBsLTkwLjUgOTAuNWMtNTAgNTAtNTAgMTMxIDAgMTgxIDUwIDUwIDEzMSA1MCAxODEgMGw2OC42LTY4LjZDMjc0LjYgMzk1LjEgMjQ2LjQgMzkyLjMgMjIwLjMgMzgyLjJ6Ii8+PC9zdmc+Cg==');opacity:0.5;visibility:hidden;display:inline-block;vertical-align:middle;}/*!sc*/
.fhPxtY h1:hover > .share-link::before,.fhPxtY h2:hover > .share-link::before,.fhPxtY .share-link:hover::before{visibility:visible;}/*!sc*/
.fhPxtY a{-webkit-text-decoration:auto;text-decoration:auto;color:#32329f;}/*!sc*/
.fhPxtY a:visited{color:#32329f;}/*!sc*/
.fhPxtY a:hover{color:#6868cf;-webkit-text-decoration:auto;text-decoration:auto;}/*!sc*/
data-styled.g43[id="sc-cidDSM"]{content:"fYaHea,fhPxtY,"}/*!sc*/
.ddxnzs{position:relative;}/*!sc*/
data-styled.g45[id="sc-caiLqq"]{content:"ddxnzs,"}/*!sc*/
.jOBPNj:hover > .sc-giYglK{opacity:1;}/*!sc*/
data-styled.g50[id="sc-jObWnj"]{content:"jOBPNj,"}/*!sc*/
.sRoPx{font-family:Courier,monospace;font-size:13px;white-space:pre;contain:content;overflow-x:auto;}/*!sc*/
.sRoPx .redoc-json code > .collapser{display:none;pointer-events:none;}/*!sc*/
.sRoPx .callback-function{color:gray;}/*!sc*/
.sRoPx .collapser:after{content:'-';cursor:pointer;}/*!sc*/
.sRoPx .collapsed > .collapser:after{content:'+';cursor:pointer;}/*!sc*/
.sRoPx .ellipsis:after{content:' … ';}/*!sc*/
.sRoPx .collapsible{margin-left:2em;}/*!sc*/
.sRoPx .hoverable{padding-top:1px;padding-bottom:1px;padding-left:2px;padding-right:2px;border-radius:2px;}/*!sc*/
.sRoPx .hovered{background-color:rgba(235,238,249,1);}/*!sc*/
.sRoPx .collapser{background-color:transparent;border:0;color:#fff;font-family:Courier,monospace;font-size:13px;padding-right:6px;padding-left:6px;padding-top:0;padding-bottom:0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;width:15px;height:15px;position:absolute;top:4px;left:-1.5em;cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-user-select:none;padding:2px;}/*!sc*/
.sRoPx .collapser:focus{outline-color:#fff;outline-style:dotted;outline-width:1px;}/*!sc*/
.sRoPx ul{list-style-type:none;padding:0px;margin:0px 0px 0px 26px;}/*!sc*/
.sRoPx li{position:relative;display:block;}/*!sc*/
.sRoPx .hoverable{display:inline-block;}/*!sc*/
.sRoPx .selected{outline-style:solid;outline-width:1px;outline-style:dotted;}/*!sc*/
.sRoPx .collapsed > .collapsible{display:none;}/*!sc*/
.sRoPx .ellipsis{display:none;}/*!sc*/
.sRoPx .collapsed > .ellipsis{display:inherit;}/*!sc*/
data-styled.g51[id="sc-dPiLbb"]{content:"sRoPx,"}/*!sc*/
.eidhyg{padding:0.9em;background-color:rgba(38,50,56,0.4);margin:0 0 10px 0;display:block;font-family:Montserrat,sans-serif;font-size:0.929em;line-height:1.5em;}/*!sc*/
data-styled.g52[id="sc-bBHHxi"]{content:"eidhyg,"}/*!sc*/
.iYcnri{font-family:Montserrat,sans-serif;font-size:12px;position:absolute;z-index:1;top:-11px;left:12px;font-weight:600;color:rgba(255,255,255,0.7);}/*!sc*/
data-styled.g53[id="sc-cNKqjZ"]{content:"iYcnri,"}/*!sc*/
.bcpfGN{position:relative;}/*!sc*/
data-styled.g54[id="sc-AjmGg"]{content:"bcpfGN,"}/*!sc*/
.iUheaL{margin-top:15px;}/*!sc*/
data-styled.g57[id="sc-jgrJph"]{content:"iUheaL,"}/*!sc*/
.iFIEVq button{background-color:transparent;border:0;outline:0;font-size:13px;font-family:Courier,monospace;cursor:pointer;padding:0;color:#333333;}/*!sc*/
.iFIEVq button:focus{font-weight:600;}/*!sc*/
.iFIEVq .sc-egiyK{height:1.1em;width:1.1em;}/*!sc*/
.iFIEVq .sc-egiyK polygon{fill:#666;}/*!sc*/
data-styled.g58[id="sc-gSQFLo"]{content:"iFIEVq,"}/*!sc*/
.gHbDui{vertical-align:middle;font-size:13px;line-height:20px;}/*!sc*/
data-styled.g59[id="sc-lbhJGD"]{content:"gHbDui,"}/*!sc*/
.eLMLxg{color:rgba(102,102,102,0.9);}/*!sc*/
data-styled.g60[id="sc-iNGGcK"]{content:"eLMLxg,"}/*!sc*/
.ifsMbX{color:#666;}/*!sc*/
data-styled.g61[id="sc-jeraig"]{content:"ifsMbX,"}/*!sc*/
.eyZzuM{vertical-align:middle;font-size:13px;line-height:20px;}/*!sc*/
data-styled.g63[id="sc-nVkyK"]{content:"eyZzuM,"}/*!sc*/
.eSyGZd{color:#d41f1c;font-size:0.9em;font-weight:normal;margin-left:20px;line-height:1;}/*!sc*/
data-styled.g64[id="sc-hiwPVj"]{content:"eSyGZd,"}/*!sc*/
.JLmOC{color:#6868cf;}/*!sc*/
data-styled.g65[id="sc-ehCJOs"]{content:"JLmOC,"}/*!sc*/
.dezuum{margin-top:0;margin-bottom:0.5em;}/*!sc*/
data-styled.g93[id="sc-dFtzxp"]{content:"dezuum,"}/*!sc*/
.cfBNBf{border:1px solid #32329f;color:#32329f;font-weight:normal;margin-left:0.5em;padding:4px 8px 4px;display:inline-block;-webkit-text-decoration:none;text-decoration:none;cursor:pointer;}/*!sc*/
data-styled.g94[id="sc-brSvTw"]{content:"cfBNBf,"}/*!sc*/
.eXEyiA{width:9ex;display:inline-block;height:13px;line-height:13px;background-color:#333;border-radius:3px;background-repeat:no-repeat;background-position:6px 4px;font-size:7px;font-family:Verdana,sans-serif;color:white;text-transform:uppercase;text-align:center;font-weight:bold;vertical-align:middle;margin-right:6px;margin-top:2px;}/*!sc*/
.eXEyiA.get{background-color:#2F8132;}/*!sc*/
.eXEyiA.post{background-color:#186FAF;}/*!sc*/
.eXEyiA.put{background-color:#95507c;}/*!sc*/
.eXEyiA.options{background-color:#947014;}/*!sc*/
.eXEyiA.patch{background-color:#bf581d;}/*!sc*/
.eXEyiA.delete{background-color:#cc3333;}/*!sc*/
.eXEyiA.basic{background-color:#707070;}/*!sc*/
.eXEyiA.link{background-color:#07818F;}/*!sc*/
.eXEyiA.head{background-color:#A23DAD;}/*!sc*/
.eXEyiA.hook{background-color:#32329f;}/*!sc*/
data-styled.g101[id="sc-ilfuhL"]{content:"eXEyiA,"}/*!sc*/
.cBjpjq{margin:0;padding:0;}/*!sc*/
.cBjpjq:first-child{padding-bottom:32px;}/*!sc*/
.sc-uojGG .sc-uojGG{font-size:0.929em;}/*!sc*/
.fZvBef{margin:0;padding:0;display:none;}/*!sc*/
.fZvBef:first-child{padding-bottom:32px;}/*!sc*/
.sc-uojGG .sc-uojGG{font-size:0.929em;}/*!sc*/
data-styled.g102[id="sc-uojGG"]{content:"cBjpjq,fZvBef,"}/*!sc*/
.iVJVQY{list-style:none inside none;overflow:hidden;text-overflow:ellipsis;padding:0;}/*!sc*/
data-styled.g103[id="sc-bilyIR"]{content:"iVJVQY,"}/*!sc*/
.cbGskd{cursor:pointer;color:#333333;margin:0;padding:12.5px 20px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;font-family:Montserrat,sans-serif;font-size:0.929em;text-transform:none;background-color:#fafafa;}/*!sc*/
.cbGskd:hover{color:#32329f;background-color:#e1e1e1;}/*!sc*/
.cbGskd .sc-egiyK{height:1.5em;width:1.5em;}/*!sc*/
.cbGskd .sc-egiyK polygon{fill:#333333;}/*!sc*/
.jDuioL{cursor:pointer;color:#333333;margin:0;padding:12.5px 20px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;font-family:Montserrat,sans-serif;background-color:#fafafa;}/*!sc*/
.jDuioL:hover{color:#32329f;background-color:#ededed;}/*!sc*/
.jDuioL .sc-egiyK{height:1.5em;width:1.5em;}/*!sc*/
.jDuioL .sc-egiyK polygon{fill:#333333;}/*!sc*/
data-styled.g104[id="sc-eGPXGI"]{content:"cbGskd,jDuioL,"}/*!sc*/
.hTIqli{display:inline-block;vertical-align:middle;width:auto;overflow:hidden;text-overflow:ellipsis;}/*!sc*/
.gPtqKT{display:inline-block;vertical-align:middle;width:calc(100% - 38px);overflow:hidden;text-overflow:ellipsis;}/*!sc*/
data-styled.g105[id="sc-hAcGzb"]{content:"hTIqli,gPtqKT,"}/*!sc*/
.hjTfvW{font-size:0.8em;margin-top:10px;text-align:center;position:fixed;width:260px;bottom:0;background:#fafafa;}/*!sc*/
.hjTfvW a,.hjTfvW a:visited,.hjTfvW a:hover{color:#333333 !important;padding:5px 0;border-top:1px solid #e1e1e1;-webkit-text-decoration:none;text-decoration:none;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;}/*!sc*/
.hjTfvW img{width:15px;margin-right:5px;}/*!sc*/
@media screen and (max-width:50rem){.hjTfvW{width:100%;}}/*!sc*/
data-styled.g106[id="sc-kYHfwS"]{content:"hjTfvW,"}/*!sc*/
.hrnNnQ{border:0;width:100%;text-align:left;}/*!sc*/
.hrnNnQ > *{vertical-align:middle;}/*!sc*/
.hrnNnQ .sc-egiyK polygon{fill:#c2c2c2;}/*!sc*/
data-styled.g107[id="sc-xiLah"]{content:"hrnNnQ,"}/*!sc*/
.ixwUsq{-webkit-text-decoration:none;text-decoration:none;margin-right:8px;}/*!sc*/
data-styled.g108[id="sc-dVNjXY"]{content:"ixwUsq,"}/*!sc*/
.iMeny{margin:0 5px 0 0;}/*!sc*/
data-styled.g109[id="sc-jHkVzv"]{content:"iMeny,"}/*!sc*/
.fuWZWu{padding:10px;border-radius:2px;margin-bottom:4px;line-height:1.5em;background-color:#F5F5F5;cursor:pointer;outline-color:#c2c2c2;}/*!sc*/
data-styled.g110[id="sc-bQtKYq"]{content:"fuWZWu,"}/*!sc*/
.jYAhkx{cursor:pointer;position:relative;margin-bottom:5px;}/*!sc*/
data-styled.g112[id="sc-FNXRL"]{content:"jYAhkx,"}/*!sc*/
.hxZHzL{font-family:Courier,monospace;margin-left:10px;-webkit-flex:1;-ms-flex:1;flex:1;overflow-x:hidden;text-overflow:ellipsis;}/*!sc*/
data-styled.g113[id="sc-jWUzzU"]{content:"hxZHzL,"}/*!sc*/
.bxamQ{outline:0;color:inherit;width:100%;text-align:left;cursor:pointer;padding:10px 30px 10px 20px;border-radius:4px 4px 0 0;background-color:#11171a;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;white-space:nowrap;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;border:1px solid transparent;border-bottom:0;-webkit-transition:border-color 0.25s ease;transition:border-color 0.25s ease;}/*!sc*/
.bxamQ ..sc-jWUzzU{color:#ffffff;}/*!sc*/
.bxamQ:focus{box-shadow:inset 0 2px 2px rgba(0,0,0,0.45),0 2px 0 rgba(128,128,128,0.25);}/*!sc*/
data-styled.g114[id="sc-eFegNN"]{content:"bxamQ,"}/*!sc*/
.huMmnt{font-size:0.929em;line-height:20px;background-color:#186FAF;color:#ffffff;padding:3px 10px;text-transform:uppercase;font-family:Montserrat,sans-serif;margin:0;}/*!sc*/
data-styled.g115[id="sc-fmBCVi"]{content:"huMmnt,"}/*!sc*/
.fuqHqW{position:absolute;width:100%;z-index:100;background:#fafafa;color:#263238;box-sizing:border-box;box-shadow:0px 0px 6px rgba(0,0,0,0.33);overflow:hidden;border-bottom-left-radius:4px;border-bottom-right-radius:4px;-webkit-transition:all 0.25s ease;transition:all 0.25s ease;visibility:hidden;-webkit-transform:translateY(-50%) scaleY(0);-ms-transform:translateY(-50%) scaleY(0);transform:translateY(-50%) scaleY(0);}/*!sc*/
data-styled.g116[id="sc-lkgTHX"]{content:"fuqHqW,"}/*!sc*/
.fUpXCf{padding:10px;}/*!sc*/
data-styled.g117[id="sc-jlRLRk"]{content:"fUpXCf,"}/*!sc*/
.ggmziU{padding:5px;border:1px solid #ccc;background:#fff;word-break:break-all;color:#32329f;}/*!sc*/
.ggmziU > span{color:#333333;}/*!sc*/
data-styled.g118[id="sc-dUbtfd"]{content:"ggmziU,"}/*!sc*/
.bCWegr{font-size:1.3em;padding:0.2em 0;margin:3em 0 1.1em;color:#333333;font-weight:normal;}/*!sc*/
data-styled.g126[id="sc-gjNHFA"]{content:"bCWegr,"}/*!sc*/
.mgliX{margin-top:15px;}/*!sc*/
data-styled.g127[id="sc-fmciRz"]{content:"mgliX,"}/*!sc*/
.hNivrs{background:#11171a;padding:20px;}/*!sc*/
data-styled.g128[id="sc-eXlEPa"]{content:"hNivrs,"}/*!sc*/
.ilWCwE{margin-bottom:30px;}/*!sc*/
data-styled.g129[id="sc-iFMAIt"]{content:"ilWCwE,"}/*!sc*/
.lptroy{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;width:20px;height:20px;-webkit-align-self:center;-ms-flex-item-align:center;align-self:center;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;color:#32329f;}/*!sc*/
data-styled.g130[id="sc-iqVWFU"]{content:"lptroy,"}/*!sc*/
.iFeLMl{width:260px;background-color:#fafafa;overflow:hidden;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-backface-visibility:hidden;backface-visibility:hidden;height:100vh;position:-webkit-sticky;position:sticky;position:-webkit-sticky;top:0;}/*!sc*/
@media screen and (max-width:50rem){.iFeLMl{position:fixed;z-index:20;width:100%;background:#fafafa;display:none;}}/*!sc*/
@media print{.iFeLMl{display:none;}}/*!sc*/
data-styled.g131[id="sc-eWfVMQ"]{content:"iFeLMl,"}/*!sc*/
.cxHKpm{outline:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:#f2f2f2;color:#32329f;display:none;cursor:pointer;position:fixed;right:20px;z-index:100;border-radius:50%;box-shadow:0 0 20px rgba(0,0,0,0.3);bottom:44px;width:60px;height:60px;padding:0 20px;}/*!sc*/
@media screen and (max-width:50rem){.cxHKpm{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;}}/*!sc*/
.cxHKpm svg{color:#0065FB;}/*!sc*/
@media print{.cxHKpm{display:none;}}/*!sc*/
data-styled.g132[id="sc-kTLmzF"]{content:"cxHKpm,"}/*!sc*/
.cjkacg{font-family:Roboto,sans-serif;font-size:14px;font-weight:400;line-height:1.5em;color:#333333;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;position:relative;text-align:left;-webkit-font-smoothing:antialiased;font-smoothing:antialiased;text-rendering:optimizeSpeed !important;tap-highlight-color:rgba(0,0,0,0);-webkit-text-size-adjust:100%;text-size-adjust:100%;}/*!sc*/
.cjkacg *{box-sizing:border-box;-webkit-tap-highlight-color:rgba(255,255,255,0);}/*!sc*/
data-styled.g133[id="sc-dwsnSq"]{content:"cjkacg,"}/*!sc*/
.jxeukX{z-index:1;position:relative;overflow:hidden;width:calc(100% - 260px);contain:layout;}/*!sc*/
@media print,screen and (max-width:50rem){.jxeukX{width:100%;}}/*!sc*/
data-styled.g134[id="sc-jtXEFf"]{content:"jxeukX,"}/*!sc*/
.bLydqy{background:#263238;position:absolute;top:0;bottom:0;right:0;width:calc((100% - 260px) * 0.4);}/*!sc*/
@media print,screen and (max-width:75rem){.bLydqy{display:none;}}/*!sc*/
data-styled.g135[id="sc-eldieg"]{content:"bLydqy,"}/*!sc*/
.qyDlP{padding:5px 0;}/*!sc*/
data-styled.g136[id="sc-kiIyQV"]{content:"qyDlP,"}/*!sc*/
.KrPzq{width:calc(100% - 40px);box-sizing:border-box;margin:0 20px;padding:5px 10px 5px 20px;border:0;border-bottom:1px solid #e1e1e1;font-family:Roboto,sans-serif;font-weight:bold;font-size:13px;color:#333333;background-color:transparent;outline:none;}/*!sc*/
data-styled.g137[id="sc-cLpAjG"]{content:"KrPzq,"}/*!sc*/
.liFvtQ{position:absolute;left:20px;height:1.8em;width:0.9em;}/*!sc*/
.liFvtQ path{fill:#333333;}/*!sc*/
data-styled.g138[id="sc-iIUQWv"]{content:"liFvtQ,"}/*!sc*/
</style>
  <link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
</head>

<body>
  
      <div id="redoc"><div class="sc-dwsnSq cjkacg redoc-wrap"><div class="sc-eWfVMQ iFeLMl menu-content" style="top:0px;height:calc(100vh - 0px)"><div role="search" class="sc-kiIyQV qyDlP"><svg class="sc-iIUQWv liFvtQ search-icon" version="1.1" viewBox="0 0 1000 1000" x="0px" xmlns="http://www.w3.org/2000/svg" y="0px"><path d="M968.2,849.4L667.3,549c83.9-136.5,66.7-317.4-51.7-435.6C477.1-25,252.5-25,113.9,113.4c-138.5,138.3-138.5,362.6,0,501C219.2,730.1,413.2,743,547.6,666.5l301.9,301.4c43.6,43.6,76.9,14.9,104.2-12.4C981,928.3,1011.8,893,968.2,849.4z M524.5,522c-88.9,88.7-233,88.7-321.8,0c-88.9-88.7-88.9-232.6,0-321.3c88.9-88.7,233-88.7,321.8,0C613.4,289.4,613.4,433.3,524.5,522z"></path></svg><input type="text" value="" placeholder="Search..." aria-label="Search" class="sc-cLpAjG KrPzq search-input"/></div><div class="sc-ikJyIC eqZXtE scrollbar-container undefined"><ul class="sc-uojGG cBjpjq" role="menu"><li data-item-id="tag/suit-analytics" class="sc-bilyIR iVJVQY"><label type="tag" role="menuitem" class="sc-eGPXGI cbGskd -depth1"><span title="suit analytics" class="sc-hAcGzb hTIqli">suit analytics</span><svg class="sc-egiyK jLPfXH" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></label><ul class="sc-uojGG fZvBef"><li data-item-id="tag/suit-analytics/paths/~1suit_analytics/post" class="sc-bilyIR iVJVQY"><label role="menuitem" class="sc-eGPXGI jDuioL -depth2"><span type="post" class="sc-ilfuhL eXEyiA operation-type post">post</span><span width="calc(100% - 38px)" class="sc-hAcGzb gPtqKT">stream creation</span></label></li></ul></li></ul><div class="sc-kYHfwS hjTfvW"><a target="_blank" rel="noopener noreferrer" href="https://redocly.com/redoc/">API docs by Redocly</a></div></div></div><div class="sc-kTLmzF cxHKpm"><div class="sc-iqVWFU lptroy"><svg class="" style="transform:translate(2px, -4px) rotate(180deg);transition:transform 0.2s ease" viewBox="0 0 926.23699 573.74994" version="1.1" x="0px" y="0px" width="15" height="15"><g transform="translate(904.92214,-879.1482)"><path d="
          m -673.67664,1221.6502 -231.2455,-231.24803 55.6165,
          -55.627 c 30.5891,-30.59485 56.1806,-55.627 56.8701,-55.627 0.6894,
          0 79.8637,78.60862 175.9427,174.68583 l 174.6892,174.6858 174.6892,
          -174.6858 c 96.079,-96.07721 175.253196,-174.68583 175.942696,
          -174.68583 0.6895,0 26.281,25.03215 56.8701,
          55.627 l 55.6165,55.627 -231.245496,231.24803 c -127.185,127.1864
          -231.5279,231.248 -231.873,231.248 -0.3451,0 -104.688,
          -104.0616 -231.873,-231.248 z
        " fill="currentColor"></path></g></svg><svg class="" style="transform:translate(2px, 4px);transition:transform 0.2s ease" viewBox="0 0 926.23699 573.74994" version="1.1" x="0px" y="0px" width="15" height="15"><g transform="translate(904.92214,-879.1482)"><path d="
          m -673.67664,1221.6502 -231.2455,-231.24803 55.6165,
          -55.627 c 30.5891,-30.59485 56.1806,-55.627 56.8701,-55.627 0.6894,
          0 79.8637,78.60862 175.9427,174.68583 l 174.6892,174.6858 174.6892,
          -174.6858 c 96.079,-96.07721 175.253196,-174.68583 175.942696,
          -174.68583 0.6895,0 26.281,25.03215 56.8701,
          55.627 l 55.6165,55.627 -231.245496,231.24803 c -127.185,127.1864
          -231.5279,231.248 -231.873,231.248 -0.3451,0 -104.688,
          -104.0616 -231.873,-231.248 z
        " fill="currentColor"></path></g></svg></div></div><div class="sc-jtXEFf jxeukX api-content"><div class="sc-eCImPb kKqedw"><div class="sc-iCfMLu jTXZbd"><div class="sc-hKwDye fcjMZs api-info"><h1 class="sc-furwcr sc-dFtzxp kwcwWd dezuum">Suit Analytics<!-- --> <span>(<!-- -->v.0.0.1<!-- -->)</span></h1><p>Download OpenAPI specification<!-- -->:<a download="openapi.json" target="_blank" class="sc-brSvTw cfBNBf">Download</a></p><div class="sc-iJKOTD sc-cidDSM hGZPxu fYaHea"></div><div class="sc-iJKOTD sc-cidDSM hGZPxu fYaHea" data-role="redoc-summary"></div><div class="sc-iJKOTD sc-cidDSM hGZPxu fYaHea" data-role="redoc-description"><p><code>Suit Analytics</code> is intended for determination of:</p>
<ul>
<li>suit_class parameter;</li>
</ul>
<p>The <code>suit</code> analytics decode stream frames taking into account <code>rate</code> parameter
from <a href="#tag/suit-analytics/paths/~1suit_analytics/post">stream creation request analytic parameters</a>
and performs the following actions:</p>
<ul>
<li>Calculates suit class</li>
<li>For each callback check it&#39;s conditions and execute callbacks, which fit conditions.</li>
</ul>
<p> The schema of data sending by <code>callback</code> described as stream creation request callback.</p>
</div></div></div></div><div id="tag/suit-analytics" data-section-id="tag/suit-analytics" class="sc-eCImPb kKqedw"><div class="sc-iCfMLu jTXZbd"><div class="sc-hKwDye fcjMZs"><h1 class="sc-furwcr kwcwWd"><a class="sc-crHmcD bNUQy" href="#tag/suit-analytics" aria-label="tag/suit-analytics"></a>suit analytics</h1></div></div></div><div id="tag/suit-analytics/paths/~1suit_analytics/post" data-section-id="tag/suit-analytics/paths/~1suit_analytics/post" class="sc-eCImPb hexvPn"><div class="sc-iCfMLu jTXZbd"><div class="sc-hKwDye fcjMZs"><h2 class="sc-pVTFL QwiBS"><a class="sc-crHmcD bNUQy" href="#tag/suit-analytics/paths/~1suit_analytics/post" aria-label="tag/suit-analytics/paths/~1suit_analytics/post"></a>stream creation<!-- --> </h2><div class="sc-iFMAIt ilWCwE"><div class="sc-iJKOTD sc-cidDSM hGZPxu fYaHea"><p>Stream creation superficial request with detailed analytics parameters description.</p>
<p>Full request description (exclude for this analytics description) available at <code>Luna-Video-Manager</code> documentation.</p>
</div></div><h5 class="sc-iqseJM kyZwVG">Request Body schema: <span class="sc-cCcXHH hgRRjA">application/json</span></h5><div class="sc-iJKOTD sc-cidDSM hGZPxu fYaHea"></div><table class="sc-hGPBjI wbnF"><tbody><tr class=""><td class="sc-hBUSln sc-fFeiMQ sc-gSQFLo kCCtqV fwnkRw iFIEVq" kind="field" title="data"><span class="sc-ieecCq fMdASL"></span><button aria-label="expand properties"><span class="property-name">data</span><svg class="sc-egiyK cUKGZj" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></button><div class="sc-nVkyK sc-hiwPVj eyZzuM eSyGZd">required</div></td><td class="sc-bkkeKt fOwWgB"><div><div><span class="sc-lbhJGD sc-iNGGcK gHbDui eLMLxg"></span><span class="sc-lbhJGD sc-jeraig gHbDui ifsMbX">object</span></div> <div><div class="sc-iJKOTD sc-cidDSM hGZPxu fhPxtY"><p>stream data</p>
</div></div></div></td></tr><tr class=""><td class="sc-hBUSln sc-fFeiMQ sc-gSQFLo kCCtqV fwnkRw iFIEVq" kind="field" title="analytics"><span class="sc-ieecCq fMdASL"></span><button aria-label="expand properties"><span class="property-name">analytics</span><svg class="sc-egiyK cUKGZj" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></button><div class="sc-nVkyK sc-hiwPVj eyZzuM eSyGZd">required</div></td><td class="sc-bkkeKt fOwWgB"><div><div><span class="sc-lbhJGD sc-iNGGcK gHbDui eLMLxg">Array of </span><span class="sc-lbhJGD sc-jeraig gHbDui ifsMbX">objects</span></div> <div><div class="sc-iJKOTD sc-cidDSM hGZPxu fhPxtY"><p>analytics list</p>
</div></div></div></td></tr><tr class="last "><td class="sc-hBUSln sc-fFeiMQ kCCtqV fwnkRw" kind="additionalProperties" title="property name*"><span class="sc-ieecCq fMdASL"></span><span class="property-name">property name*</span><div class="sc-nVkyK sc-hiwPVj sc-ehCJOs eyZzuM eSyGZd JLmOC">additional property</div></td><td class="sc-bkkeKt fOwWgB"><div><div><span class="sc-lbhJGD sc-iNGGcK gHbDui eLMLxg"></span><span class="sc-lbhJGD sc-jeraig gHbDui ifsMbX">any</span></div> <div><div class="sc-iJKOTD sc-cidDSM hGZPxu fhPxtY"></div></div></div></td></tr></tbody></table><div><h3 class="sc-gjNHFA bCWegr"> Callbacks </h3><button class="sc-xiLah hrnNnQ sc-bQtKYq fuWZWu"><span type="post" class="sc-ilfuhL sc-jHkVzv eXEyiA iMeny operation-type post">post</span><svg class="sc-egiyK gHElpb" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><span class="sc-dVNjXY ixwUsq">{$request.body.callbacks.*.url}</span></button></div></div><div class="sc-jRQBWg sc-gKclnd vSqXI dKDnzb"><div class="sc-FNXRL jYAhkx"><button class="sc-eFegNN bxamQ"><span type="post" class="sc-fmBCVi huMmnt http-verb post">post</span><span class="sc-jWUzzU hxZHzL">/suit_analytics</span><svg class="sc-egiyK laPRNF" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></button><div aria-hidden="true" class="sc-lkgTHX fuqHqW"><div class="sc-jlRLRk fUpXCf"><div class="sc-iJKOTD sc-cidDSM hGZPxu fhPxtY"></div><div tabindex="0" role="button"><div class="sc-dUbtfd ggmziU"><span></span>/suit_analytics</div></div></div></div></div><div><h3 class="sc-kDTinF hwMGJl"> <!-- -->Request samples<!-- --> </h3><div class="sc-cxpSdN eCLDJY" data-rttabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="react-tabs__tab react-tabs__tab--selected" role="tab" id="react-tabs-0" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-1" tabindex="0" data-rttab="true">Payload</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-1" aria-labelledby="react-tabs-0"><div><div class="sc-AjmGg bcpfGN"><span class="sc-cNKqjZ iYcnri">Content type</span><div class="sc-bBHHxi eidhyg">application/json</div></div><div class="sc-jgrJph iUheaL"><div class="sc-jObWnj jOBPNj"><div class="sc-giYglK gjxyHD"><button><div class="sc-caiLqq ddxnzs">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class="sc-iJKOTD hGZPxu sc-dPiLbb sRoPx"><div class="redoc-json"><code><button class="collapser" aria-label="collapse"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"data"</span>: <button class="collapser" aria-label="collapse"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"reference"</span>: <span class="token string">&quot;rtsp://stream_url&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"type"</span>: <span class="token string">&quot;stream&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"downloadable"</span>: <span class="token boolean">false</span></div></li></ul><span class="token punctuation">}</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"analytics"</span>: <button class="collapser" aria-label="collapse"></button><span class="token punctuation">[</span><span class="ellipsis"></span><ul class="array collapsible"><li><div class="hoverable collapsed"><button class="collapser" aria-label="expand"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"analytic_name"</span>: <span class="token string">&quot;suit_analytics&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"parameters"</span>: <button class="collapser" aria-label="expand"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"parameters"</span>: <button class="collapser" aria-label="expand"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"probe_count"</span>: <span class="token number">2</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"image_retain_policy"</span>: <button class="collapser" aria-label="expand"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"mimetype"</span>: <span class="token string">&quot;PNG&quot;</span></div></li></ul><span class="token punctuation">}</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"roi"</span>: <button class="collapser" aria-label="expand"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"mode"</span>: <span class="token string">&quot;abs&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"x"</span>: <span class="token number">0</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"y"</span>: <span class="token number">0</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"width"</span>: <span class="token number">1000</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"height"</span>: <span class="token number">500</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">}</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"callbacks"</span>: <button class="collapser" aria-label="expand"></button><span class="token punctuation">[</span><span class="ellipsis"></span><ul class="array collapsible"><li><div class="hoverable collapsed"><button class="collapser" aria-label="expand"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"url"</span>: <span class="token string">&quot;</span><a href="http://127.0.0.1:8001">http://127.0.0.1:8001</a><span class="token string">&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"type"</span>: <span class="token string">&quot;http&quot;</span></div></li></ul><span class="token punctuation">}</span>,</div></li><li><div class="hoverable collapsed"><button class="collapser" aria-label="expand"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"url"</span>: <span class="token string">&quot;</span><a href="http://127.0.0.1:8002">http://127.0.0.1:8002</a><span class="token string">&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"type"</span>: <span class="token string">&quot;http&quot;</span></div></li></ul><span class="token punctuation">}</span>,</div></li><li><div class="hoverable collapsed"><button class="collapser" aria-label="expand"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"type"</span>: <span class="token string">&quot;luna-ws-notification&quot;</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">]</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"targets"</span>: <button class="collapser" aria-label="expand"></button><span class="token punctuation">[</span><span class="ellipsis"></span><ul class="array collapsible"><li><div class="hoverable collapsed"><span class="token string">&quot;suit&quot;</span>,</div></li><li><div class="hoverable collapsed"><span class="token string">&quot;overview&quot;</span></div></li></ul><span class="token punctuation">]</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">]</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div></div><div><h3 class="sc-kDTinF hwMGJl"> Callback payload samples </h3><div class="sc-eXlEPa hNivrs"><div class="sc-AjmGg bcpfGN"><span class="sc-cNKqjZ iYcnri">Callback</span><div class="sc-bBHHxi eidhyg">POST: {$request.body.callbacks.*.url}</div></div><div class="sc-fmciRz mgliX"><div class="sc-AjmGg bcpfGN"><span class="sc-cNKqjZ iYcnri">Content type</span><div class="sc-bBHHxi eidhyg">application/json</div></div><div class="sc-jgrJph iUheaL"><div class="sc-jObWnj jOBPNj"><div class="sc-giYglK gjxyHD"><button><div class="sc-caiLqq ddxnzs">Copy</div></button><button> Expand all </button><button> Collapse all </button></div><div class="sc-iJKOTD hGZPxu sc-dPiLbb sRoPx"><div class="redoc-json"><code><button class="collapser" aria-label="collapse"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"event_type"</span>: <span class="token string">&quot;suit&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"account_id"</span>: <span class="token string">&quot;557d54ec-29ad-4f3c-93b4-c9092ef12515&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"event_create_time"</span>: <span class="token string">&quot;string&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"event_end_time"</span>: <span class="token string">&quot;string&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"event"</span>: <button class="collapser" aria-label="collapse"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"stream_id"</span>: <span class="token string">&quot;557d54ec-29ad-4f3c-93b4-c9092ef12515&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"event_id"</span>: <span class="token string">&quot;557d54ec-29ad-4f3c-93b4-c9092ef12515&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"track_id"</span>: <span class="token string">&quot;557d54ec-29ad-4f3c-93b4-c9092ef12515&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"video_segment"</span>: <button class="collapser" aria-label="expand"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"start_time_offset"</span>: <span class="token number">0.123</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"end_time_offset"</span>: <span class="token number">1.234</span></div></li></ul><span class="token punctuation">}</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"overview"</span>: <button class="collapser" aria-label="expand"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"time_offset"</span>: <span class="token number">0</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"image"</span>: <span class="token keyword">null</span></div></li></ul><span class="token punctuation">}</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"location"</span>: <button class="collapser" aria-label="expand"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"city"</span>: <span class="token string">&quot;Moscow&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"area"</span>: <span class="token string">&quot;Central&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"district"</span>: <span class="token string">&quot;Basmanny&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"street"</span>: <span class="token string">&quot;Podsosensky lane&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"house_number"</span>: <span class="token string">&quot;23 bldg.3&quot;</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"geo_position"</span>: <button class="collapser" aria-label="expand"></button><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"longitude"</span>: <span class="token number">36.616</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"latitude"</span>: <span class="token number">55.752</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div></div></div></div></div><div class="sc-eldieg bLydqy"></div></div></div>
      <script>
      const __redoc_state = {"menu":{"activeItemIdx":-1},"spec":{"data":{"openapi":"3.0.0","info":{"version":"v.0.0.1","title":"Suit Analytics","description":"`Suit Analytics` is intended for determination of:\n   - suit_class parameter;\n   \n\nThe `suit` analytics decode stream frames taking into account `rate` parameter\nfrom <a href=\"#tag/suit-analytics/paths/~1suit_analytics/post\">stream creation request analytic parameters</a>\nand performs the following actions:\n   - Calculates suit class\n   - For each callback check it's conditions and execute callbacks, which fit conditions.\n\n The schema of data sending by `callback` described as stream creation request callback.\n"},"components":{"schemas":{"error":{"type":"object","properties":{"error_code":{"type":"integer","description":"Error code."},"desc":{"type":"string","description":"Short error description."},"detail":{"type":"string","description":"Error details."},"link":{"type":"string","description":"Link to the documentation website with the error description."}},"required":["error_code","detail","desc","link"],"example":{"error_code":1,"detail":"internal server error","desc":"internal server error","link":"https://docs.visionlabs.ai/info/luna/troubleshooting/errors-description/code-1"}},"int01":{"type":"integer","enum":[0,1]},"roi_int_coordinates":{"type":"integer","minimum":0,"default":0,"maximum":65536,"example":3327},"roi_float_percent":{"type":"number","format":"float","minimum":0,"default":0,"maximum":100,"example":87.4},"roi_float_percent_size":{"type":"number","format":"float","minimum":0.00001,"maximum":100,"example":66.3},"roi_abs":{"type":"object","properties":{"x":{"$ref":"#/components/schemas/roi_int_coordinates"},"y":{"$ref":"#/components/schemas/roi_int_coordinates"},"width":{"$ref":"#/components/schemas/roi_int_coordinates"},"height":{"$ref":"#/components/schemas/roi_int_coordinates"},"mode":{"type":"string","enum":["abs"],"example":"abs","description":"Coordinates and size are set in pixels."}},"required":["x","y","width","height","mode"]},"roi_percent":{"type":"object","properties":{"x":{"$ref":"#/components/schemas/roi_float_percent"},"y":{"$ref":"#/components/schemas/roi_float_percent"},"width":{"$ref":"#/components/schemas/roi_float_percent_size"},"height":{"$ref":"#/components/schemas/roi_float_percent_size"},"mode":{"type":"string","enum":["percent"],"example":"percent","description":"Coordinates and size are set in percentage."}},"required":["x","y","width","height","mode"]},"roi":{"oneOf":[{"$ref":"#/components/schemas/roi_abs"},{"$ref":"#/components/schemas/roi_percent"}],"description":"Region of interest on a frame. Boundaries of the area are described in `x`, `y` coordinates\nof the top left point and `width`, `height` properties\n**Region must not be any bigger than the original frame**\n\nThe region of interest will be sent to estimator. The smaller the `roi`, the smaller the area the estimator\nwill process and, accordingly, work faster.\n"},"number01":{"type":"number","minimum":0,"maximum":1},"rate":{"type":"object","description":"Analytic rate execution determines on which frames the analytics will be launched.\n\nAnalytic rate execution can be configured in next ways:\n- execute analytic on each Nth frame (`unit` - `frame`)\n- execute analytic on each frame corresponding to the Nth second (`unit` - `second`)\n","properties":{"period":{"type":"float","minimum":0,"default":1,"description":"Period length."},"unit":{"type":"string","default":"second","enum":["frame","second"],"description":"Unit for a period calculation (every `n` seconds or every `n` frames)."}},"required":["unit","period"]},"callback_base":{"type":"object","properties":{"enable":{"type":"integer","enum":[0,1],"default":1,"description":"Whether callback enabled or not."}}},"callback_ws":{"allOf":[{"$ref":"#/components/schemas/callback_base"},{"type":"object","properties":{"type":{"type":"string","enum":["luna-ws-notification"],"description":"Event will be sent with `suit` type."}}}],"required":["type"]},"callback_basic_authorization":{"type":"object","properties":{"type":{"type":"string","description":"Authorization type.","enum":["basic"]},"login":{"type":"string","maxLength":128,"description":"Login."},"password":{"type":"string","maxLength":128,"description":"Password."}},"required":["type","login","password"],"description":"Callback basic authorization parameters."},"callback_http":{"allOf":[{"$ref":"#/components/schemas/callback_base"},{"type":"object","properties":{"type":{"type":"string","enum":["http"],"description":"Event will be sent to http url."},"url":{"type":"string","description":"Callback url."},"authorization":{"$ref":"#/components/schemas/callback_basic_authorization"},"params":{"type":"object","properties":{"timeout":{"type":"integer","default":60,"description":"Callback request timeout."},"content_type":{"type":"string","default":"application/json","enum":["application/json"],"description":"Callback request content type."},"headers":{"type":"object","description":"Callback request headers.","additionalProperties":true}},"description":"Callback request parameters"}}}],"required":["url","type"]},"callback":{"oneOf":[{"$ref":"#/components/schemas/callback_http"},{"$ref":"#/components/schemas/callback_ws"}],"discriminator":{"propertyName":"type","mapping":{"http":"#/components/schemas/callback_http","luna-ws-notification":"#/components/schemas/callback_ws"}}},"image_retain_policy":{"type":"object","description":"Image retain policy applicable when analytic `overview` target is specified,\nand configures parameters with which will image be saved.\n","properties":{"mimetype":{"type":"string","enum":["PNG","JPEG"],"default":"JPEG","description":"Image format."},"quality":{"allOf":[{"$ref":"#/components/schemas/number01"},{"default":1}],"description":"Image quality, on a scale from 0 (worst) to 1 (best). Has no effect on `PNG`."},"max_size":{"type":"integer","minimum":0,"default":640,"description":"Image max size, in pxl. Neither the width nor the height will exceed this value."}}},"period_event_policy":{"type":"object","properties":{"trigger":{"type":"string","enum":["period"]},"interval":{"type":"float","description":"Event generation period interval.","minimum":0,"default":1}},"required":["trigger"]},"start_event_policy":{"type":"object","properties":{"trigger":{"type":"string","enum":["start"]}},"required":["trigger"]},"end_event_policy":{"type":"object","properties":{"trigger":{"type":"string","enum":["end"]}},"required":["trigger"]},"parameters":{"type":"object","description":"Analytic parameters for stream processing.\n\nThere are default analytic parameters which will be applied for stream processing if no one specified here.\n","properties":{"targets":{"type":"array","default":["suit"],"items":{"type":"string","enum":["suit"]},"description":"Estimations to perform on the video.\n\n`suit` will calculate some `suit` value if specified.\n\n`overview` will add image to events.\n"},"callbacks":{"type":"array","description":"Callbacks parameters.\n\n`http` type callback sends events to the specified url by POST request.\n\n`luna-ws-notification` type callback sends events to websocket using sender.\n","maxItems":10,"items":{"$ref":"#/components/schemas/callback"}},"parameters":{"type":"object","description":"Estimation parameters.","properties":{"event_policy":{"description":"Event policy.\n\nWhen suit appears on frame, the new `track` will starts, when suit disappears, `track` will stops.\n\n- when track starts (`trigger` - `start`)\n- when track ends (`trigger` - `end`)\n- periodically while track exists (`trigger` - `period`)\n","type":"object","properties":null,"oneOf":[{"$ref":"#/components/schemas/period_event_policy"},{"$ref":"#/components/schemas/start_event_policy"},{"$ref":"#/components/schemas/end_event_policy"}],"discriminator":{"propertyName":"trigger","mapping":{"period":"#/components/schemas/period_event_policy","start":"#/components/schemas/start_event_policy","end":"#/components/schemas/end_event_policy"}},"default":{"trigger":"start"}},"image_retain_policy":{"$ref":"#/components/schemas/image_retain_policy"},"probe_count":{"description":"A number of consecutive suit estimations with not 'none' class. This parameter is intended to\nprevent false positives from analytics.\n","type":"integer","minimum":0,"default":3},"roi":{"$ref":"#/components/schemas/roi"},"rate":{"allOf":[{"$ref":"#/components/schemas/rate"},{"default":{"unit":"second","period":1}}]}}}}},"stream":{"type":"object","description":"Full stream creation schema is available in `Luna-Video-Manager` documentation.\n\nThe presented schema described only analytics parameters.\n","properties":{"data":{"type":"object","description":"stream data","additionalProperties":true},"analytics":{"type":"array","description":"analytics list","items":{"type":"object","description":"analytics","properties":{"analytic_name":{"type":"string","maxLength":36,"pattern":"^[a-zA-Z0-9_\\-]{1,36}$","description":"Analytic name.","enum":["suit_analytics"]},"parameters":{"type":"object","$ref":"#/components/schemas/parameters"}},"required":["analytic_name"]}}},"additionalProperties":true,"required":["data","analytics"]},"string36_nullable":{"type":"string","maxLength":36,"nullable":true},"longitude":{"type":"number","minimum":-180,"maximum":180,"example":36.616},"latitude":{"type":"number","minimum":-90,"maximum":90,"example":55.752},"geo_position":{"type":"object","nullable":true,"description":"Geo position specified by geographic coordinates - longitude and latitude.","properties":{"longitude":{"allOf":[{"$ref":"#/components/schemas/longitude"},{"description":"Longitude in degrees."}]},"latitude":{"allOf":[{"$ref":"#/components/schemas/latitude"},{"description":"Latitude in degrees."}]}},"required":["longitude","latitude"],"example":{"longitude":36.616,"latitude":55.752}},"uuid":{"type":"string","format":"uuid","pattern":"^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$","example":"557d54ec-29ad-4f3c-93b4-c9092ef12515"},"video_segment":{"type":"object","properties":{"start_time_offset":{"type":"number","example":0.123,"description":"Start video segment offset(seconds)."},"end_time_offset":{"type":"number","example":1.234,"description":"Eng video segment offset(seconds)."}},"required":["start_time_offset","end_time_offset"]},"callback_request":{"type":"object","properties":{"event_type":{"type":"string","enum":["suit"],"description":"Event type."},"account_id":{"allOf":[{"$ref":"#/components/schemas/uuid"}],"description":"Account id of event."},"event_create_time":{"type":"string","description":"Event creation time."},"event_end_time":{"type":"string","description":"Event end time."},"event":{"type":"object","description":"Event.","properties":{"stream_id":{"allOf":[{"$ref":"#/components/schemas/uuid"}],"description":"Stream ID."},"event_id":{"allOf":[{"$ref":"#/components/schemas/uuid"}],"description":"Event ID."},"track_id":{"allOf":[{"$ref":"#/components/schemas/uuid"}],"description":"Track id.","nullable":true},"video_segment":{"$ref":"#/components/schemas/video_segment"},"overview":{"type":"object","properties":{"time_offset":{"type":"number","description":"time offset"},"image":{"type":"str","description":"link to image"}},"required":["time_offset"]},"location":{"type":"object","properties":{"city":{"allOf":[{"$ref":"#/components/schemas/string36_nullable"}],"example":"Moscow","description":"City that stream belongs."},"area":{"allOf":[{"$ref":"#/components/schemas/string36_nullable"}],"example":"Central","description":"Area that stream belongs."},"district":{"allOf":[{"$ref":"#/components/schemas/string36_nullable"}],"example":"Basmanny","description":"District that stream belongs."},"street":{"allOf":[{"$ref":"#/components/schemas/string36_nullable"}],"example":"Podsosensky lane","description":"Street that stream belongs."},"house_number":{"allOf":[{"$ref":"#/components/schemas/string36_nullable"}],"example":"23 bldg.3","description":"Street that stream belongs."},"geo_position":{"$ref":"#/components/schemas/geo_position"}},"description":"Stream location parameters.\n\nRequired callback `location` target to send this value.\n"}},"required":["stream_id","event_id","suit_class","track_id"]}},"required":["event_type","event","account_id","event_create_time","event_end_time"]}}},"paths":{"/suit_analytics":{"post":{"tags":["suit analytics"],"summary":"stream creation","description":"Stream creation superficial request with detailed analytics parameters description.\n\nFull request description (exclude for this analytics description) available at `Luna-Video-Manager` documentation.\n","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/stream"},"example":{"data":{"reference":"rtsp://stream_url","type":"stream","downloadable":false},"analytics":[{"analytic_name":"suit_analytics","parameters":{"parameters":{"probe_count":2,"image_retain_policy":{"mimetype":"PNG"},"roi":{"mode":"abs","x":0,"y":0,"width":1000,"height":500}},"callbacks":[{"url":"http://127.0.0.1:8001","type":"http"},{"url":"http://127.0.0.1:8002","type":"http"},{"type":"luna-ws-notification"}],"targets":["suit","overview"]}}]}}}},"callbacks":{"onData":{"{$request.body.callbacks.*.url}":{"post":{"requestBody":{"description":"subscription payload","content":{"application/json":{"schema":{"$ref":"#/components/schemas/callback_request"}}}}}}}}}}}}},"searchIndex":{"store":["tag/suit-analytics","tag/suit-analytics/paths/~1suit_analytics/post"],"index":{"version":"2.3.9","fields":["title","description"],"fieldVectors":[["title/0",[0,0.693,1,0.182]],["description/0",[]],["title/1",[2,0.182,3,0.182]],["description/1",[1,0.196,2,0.129,3,0.129,4,0.492,5,0.744,6,0.492,7,0.492,8,0.897,9,0.492,10,0.492,11,0.492,12,0.492,13,0.492,14,0.492]]],"invertedIndex":[["analyt",{"_index":1,"title":{"0":{}},"description":{"1":{}}}],["avail",{"_index":11,"title":{},"description":{"1":{}}}],["creation",{"_index":3,"title":{"1":{}},"description":{"1":{}}}],["descript",{"_index":8,"title":{},"description":{"1":{}}}],["detail",{"_index":6,"title":{},"description":{"1":{}}}],["document",{"_index":13,"title":{},"description":{"1":{}}}],["exclud",{"_index":10,"title":{},"description":{"1":{}}}],["full",{"_index":9,"title":{},"description":{"1":{}}}],["luna-video-manag",{"_index":12,"title":{},"description":{"1":{}}}],["paramet",{"_index":7,"title":{},"description":{"1":{}}}],["request",{"_index":5,"title":{},"description":{"1":{}}}],["stream",{"_index":2,"title":{"1":{}},"description":{"1":{}}}],["suit",{"_index":0,"title":{"0":{}},"description":{}}],["suit_analyt",{"_index":14,"title":{},"description":{"1":{}}}],["superfici",{"_index":4,"title":{},"description":{"1":{}}}]],"pipeline":[]}},"options":{}};

      var container = document.getElementById('redoc');
      Redoc.hydrate(__redoc_state, container);

      </script>
</body>

</html>
The *__init__.py* module in *suit_nodes* path is empty.
__init__.py

The *events.py* module in *suit_nodes* path contains events and track structures.
events.py
import attr
from video_analytics_helpers.containers import (
    AggregatedEvent as BaseAggregatedEvent,
    Event as BaseEvent,
    Overview,
    Track as BaseTrack,
)


@attr.dataclass(slots=True)
class Suit:
    """Template event frame result."""

    name: str = ""

    def asDict(self):
        """Build results in API format."""
        return {"name": self.name}


@attr.dataclass(slots=True)
class AggregatedSuitEvent(BaseAggregatedEvent[Overview]):
    """Aggregated suit event."""

    eventType: str = "suit"
    suit: Suit | None = None

    def asDict(self):
        """Build results in API format."""

        result = super().asDict()
        if self.suit is not None:
            result.update(self.suit.asDict())
        return result


@attr.dataclass(slots=True)
class SuitEvent(BaseEvent[AggregatedSuitEvent]):
    """Suit event result."""

    eventType: str = "suit"
    suit: Suit | None = None

    def asDict(self):
        """Build event data in API format."""
        res = super().asDict()
        if self.suit is not None:
            res.update(self.suit.asDict())
        return res


class SuitTrack(BaseTrack[SuitEvent, AggregatedSuitEvent]):
    """Analytics track."""
The *suit_estimator.py* module in *suit_nodes* path contains suit estimator responsible for image preprocessing, processing and neural model results postprocessing.
suit_estimator.py
from pathlib import Path

import cv2
import numpy as np
import ujson
from video_analytics_helpers.onnxruntime_wrapper import OnnxruntimeWrapper


class SuitClassifier(object):
    def __init__(
        self,
        onnxWrapper: OnnxruntimeWrapper,
        crop_size: int = 224,
        crop_type: str = "full_frame",
    ):

        self.onnxWrapper = onnxWrapper
        self.onnx_input_keys = [t.name for t in self.onnxWrapper.onnxSession.get_inputs()]

        self.is_new_id = dict()

        self.crop_size = crop_size
        self.crop_type = crop_type

        self.suit_id = dict()
        self._labels = None

    async def crop_and_scale(self, img_rgb):
        assert img_rgb.shape[2] == 3
        img_shape = img_rgb.shape[:2]
        if self.crop_type == "center_crop":
            min_side = min(img_shape[0], img_shape[1])
            y_0, x_0 = (img_shape[0] - min_side) // 2, (img_shape[1] - min_side) // 2
            y_1, x_1 = y_0 + min_side, x_0 + min_side
            crop = img_rgb[y_0:y_1, x_0:x_1]
        elif self.crop_type == "full_frame":
            crop = img_rgb.copy()
        else:
            raise Exception(f"Unknown type of crop: {self.crop_type}")

        crop_resized = await self.onnxWrapper.runAsync(cv2.resize, crop, (self.crop_size, self.crop_size))
        return crop_resized

    async def preproc(self, img_rgb):
        crop_prepared = await self.crop_and_scale(img_rgb)
        crop_prepared = crop_prepared.astype(np.float32) / 255.0
        crop_prepared = (crop_prepared - 0.5) / 0.5
        batch = crop_prepared.reshape((1,) + crop_prepared.shape)
        batch = batch.transpose(0, 3, 1, 2)
        return np.ascontiguousarray(batch)

    async def forward(self, img_rgb):
        batch_numpy = await self.preproc(img_rgb)
        input_tensors_list = {self.onnx_input_keys[0]: batch_numpy}

        net_outs = await self.onnxWrapper.forward(input_tensors_list)
        return net_outs[0][0]

    def get_labels(self):
        if self._labels is None:
            with open(Path(__file__).parent.parent / "data" / "imagenet-simple-labels.json") as f:
                data = ujson.load(f)
            self._labels = np.asarray(data)
        return self._labels

    async def run(self, img_rgb, cam_id):
        if not cam_id in self.suit_id:
            self.suit_id[cam_id] = 0
            self.is_new_id[cam_id] = True

        preds = await self.forward(img_rgb)
        idx = np.argmax(preds)
        return self.get_labels()[idx]
The *suit_nodes.py* module in *suit_nodes* path contains suit nodes structures.
suit_nodes.py
import uuid

from luna_analytics_manager import Node
from luna_analytics_manager.sync import Syncer2 as Syncer
from luna_analytics_manager.templates.image_helpers.roi import getROIRect
from luna_analytics_manager.templates.models import EventStatus, VideoSegment
from video_analytics_helpers.frame_base_analytics import BaseFrameAnalyticsNode
from video_analytics_helpers.node_collections import NodeOverview as NodeOverviewBase

from ..analytics import AnalyticsCtx, FrameCtx
from ..classes import FrameRes
from .events import AggregatedSuitEvent, Overview, Suit, SuitEvent, SuitTrack
from .suit_estimator import SuitClassifier


class TrackFrame:
    bestScore: float | None
    bestTimestamp: float | None


class NodeSuit(
    BaseFrameAnalyticsNode[
        FrameCtx,
        AnalyticsCtx,
        SuitEvent,
        AggregatedSuitEvent,
        FrameRes,
        Suit,
        SuitTrack,
    ]
):
    """Suit Analytics node."""

    name = "suit"
    requires: list[Node] = []
    frameResCls = FrameRes
    # class for tracks
    trackCls = SuitTrack

    def __init__(self):
        super().__init__()
        self.frameSyncer = Syncer()
        self.track = None
        self.trackLength = 0

        self.suitEstimator = SuitClassifier(onnxWrapper=AnalyticsCtx.onnxWrapper)

    def buildEvent(self, suit: Suit, frameCtx: FrameCtx, ctx: AnalyticsCtx) -> tuple[SuitEvent, AggregatedSuitEvent]:
        """Generate template current event and aggregated event."""

        event = SuitEvent(suit=suit, timeOffset=frameCtx.timestamp)
        aggregatedEvent = AggregatedSuitEvent(
            videoSegment=VideoSegment(frameCtx.timestamp, frameCtx.timestamp),
            suit=suit,
            overview=Overview(frameCtx.timestamp, frameCtx.image),
        )
        event.eventId = aggregatedEvent.eventId = str(uuid.uuid4())
        return event, aggregatedEvent

    def updateEvent(self, suit: Suit, frameCtx: FrameCtx, ctx: AnalyticsCtx):
        """Update current event with new frame."""

        event = self.track.currentEvent
        event.suit = suit
        event.timeOffset = frameCtx.timestamp
        event.eventStatus = EventStatus.inProcess

    def updateAggregatedEvent(self, suit: Suit, frameCtx: FrameCtx, ctx: AnalyticsCtx):
        """Update current aggregated event with new frame."""

        aggregatedEvent = self.track.currentAggregatedEvent
        if aggregatedEvent.videoSegment is None:
            aggregatedEvent.videoSegment = VideoSegment(frameCtx.timestamp, frameCtx.timestamp)
        else:
            aggregatedEvent.videoSegment.endTimeOffset = frameCtx.timestamp

        if aggregatedEvent.suit is None:
            aggregatedEvent.suit = suit

    async def estimate(self, frameCtx: FrameCtx, ctx: AnalyticsCtx) -> Suit | None:
        img_rgb = frameCtx.image.asNPArray()  # check if it is really RGB format

        if ctx.params.roi:
            roi = getROIRect(frameCtx.image.rect, ctx.params.roi)
            img_rgb = img_rgb[roi.top : roi.bottom, roi.left : roi.right]

        camera_id = "0"
        detectedObj = await self.suitEstimator.run(img_rgb, camera_id)
        return Suit(name=detectedObj)

    def isTrackNeedEnd(self, suit: Suit, frameCtx: FrameCtx, ctx: AnalyticsCtx):
        """
        Determinate that event is needed to end or not after current frame processing.

        If function return `True`, track will be stopped. If function return `False` track will be started or continue.
        """
        return suit.name != "suit"


class NodeOverview(NodeOverviewBase[FrameCtx, AnalyticsCtx]):
    """Overview node."""

    requires = [NodeSuit]

Agent lambda examples

  • Here is an example of agent lambda with suit video analytics:

    To include video analytics presented in previous chapter into lambda agent as package it needs the following lambda archive file structure:

    Lambda agent with suit analytics example file structure (the case when analytics includes in lambda as package)
      ├──pyproject.toml
      ├──poetry.lock
      └──lambda_main.py
    

    The lambda_main.py module:

    lambda_main.py
    AVAILABLE_ANALYTICS = ["suit_analytics"]
    

    It this case, the dependencies of lambda agent must include only analytics as dependency, not analytics dependencies:

    *pyproject.toml*
    pyproject.toml
    [build-system]
    requires = ["poetry-core"]
    build-backend = "poetry.core.masonry.api"
    
    [tool.poetry]
    name = "lambda-agent"
    version = "0.0.1"
    description = "lambda-agent"
    authors = [ "VisionLabs", ]
    
    [[tool.poetry.source]]
    name = "vlabspypi"
    url = "http://pypi.visionlabs.ru/root/public/+simple"
    priority = "primary"
    
    [[tool.poetry.source]]
    name = "public-pypi"
    url = "https://pypi.org/simple"
    priority = "supplemental"
    
    [tool.poetry.dependencies]
    python = "^3.12"
    suit-analytics = "0.0.4"
    

    To include presented analytics into lambda agent as module it needs the following lambda archive file structure:

    Lambda agent with suit analytics example file structure (the case when analytics includes in lambda as module)
      ├──pyproject.toml
      ├──poetry.lock
      ├──lambda_main.py
      └──suit_analytics
          ├──__init__.py
          ├──analytics.py
          ├──classes.py
          ├──common.py
          ├──models.py
          ├──spec.html
          ├──spec.yml
          └──data
             ├──resnet50-v2-7.onnx
             └──imagenet-simple-labels.json
          └──suit_nodes
             ├──__init__.py
             ├──events.py
             ├──suit_estimator.py
             └──suid_nodes.py
    

    In this case, the analytics dependencies must be included into lambda dependencies. The lambda dependencies described as pyproject.toml file:

    *pyproject.toml*
    pyproject.toml
    [build-system]
    requires = ["poetry-core"]
    build-backend = "poetry.core.masonry.api"
    
    [tool.poetry]
    name = "lambda agent"
    version = "0.0.1"
    description = "lambda agent"
    authors = [ "VisionLabs", ]
    
    [[tool.poetry.source]]
    name = "vlabspypi"
    url = "http://pypi.visionlabs.ru/root/public/+simple"
    priority = "primary"
    
    [[tool.poetry.source]]
    name = "public-pypi"
    url = "https://pypi.org/simple"
    priority = "supplemental"
    
    [tool.poetry.dependencies]
    python = "^3.12"
    luna-analytics-manager = {version="*", extras=["sdk"]}
    opencv-python-headless = "*"
    nvidia-curand-cu11 = "*"
    nvidia-cufft-cu11 = "*"
    onnxruntime-gpu = "*"
    pynvcodec = "^2.6.0"