Dynamic Python Blocks - Roboflow Inference (original) (raw)

When the syntax for Workflow definitions was outlined, one key aspect was not covered: the ability to define blocks directly within the Workflow definition itself. This section can include the manifest and Python code for blocks defined in-place, which are dynamically interpreted by the Execution Engine. These in-place blocks function similarly to those statically defined in plugins, yet provide much more flexibility.

Warning

Dynamic blocks only work in your local deployment of inference and are not supported on the Roboflow hosted platform.

If you wish to disable the functionality, export ALLOW_CUSTOM_PYTHON_EXECUTION_IN_WORKFLOWS=False

Theory

The high-level overview of Dynamic Python blocks functionality:

Example

Let's take a look and discuss example workflow with dynamic Python blocks.

Workflow with dynamic block

{ "version": "1.0", "inputs": [ { "type": "WorkflowImage", "name": "image" } ], "dynamic_blocks_definitions": [ { "type": "DynamicBlockDefinition", "manifest": { "type": "ManifestDescription", "block_type": "OverlapMeasurement", "inputs": { "predictions": { "type": "DynamicInputDefinition", "selector_types": [ "step_output" ] }, "class_x": { "type": "DynamicInputDefinition", "value_types": [ "string" ] }, "class_y": { "type": "DynamicInputDefinition", "value_types": [ "string" ] } }, "outputs": { "overlap": { "type": "DynamicOutputDefinition", "kind": [] } } }, "code": { "type": "PythonCode", "run_function_code": "\ndef run(self, predictions: sv.Detections, class_x: str, class_y: str) -> BlockResult:\n bboxes_class_x = predictions[predictions.data[\"class_name\"] == class_x]\n bboxes_class_y = predictions[predictions.data[\"class_name\"] == class_y]\n overlap = []\n for bbox_x in bboxes_class_x:\n bbox_x_coords = bbox_x[0]\n bbox_overlaps = []\n for bbox_y in bboxes_class_y:\n if bbox_y[-1][\"detection_id\"] == bbox_x[-1][\"detection_id\"]:\n continue\n bbox_y_coords = bbox_y[0]\n x_min = max(bbox_x_coords[0], bbox_y_coords[0])\n y_min = max(bbox_x_coords[1], bbox_y_coords[1])\n x_max = min(bbox_x_coords[2], bbox_y_coords[2])\n y_max = min(bbox_x_coords[3], bbox_y_coords[3])\n # compute the area of intersection rectangle\n intersection_area = max(0, x_max - x_min + 1) * max(0, y_max - y_min + 1)\n box_x_area = (bbox_x_coords[2] - bbox_x_coords[0] + 1) * (bbox_x_coords[3] - bbox_x_coords[1] + 1)\n local_overlap = intersection_area / (box_x_area + 1e-5)\n bbox_overlaps.append(local_overlap)\n overlap.append(bbox_overlaps)\n return {\"overlap\": overlap}\n" } }, { "type": "DynamicBlockDefinition", "manifest": { "type": "ManifestDescription", "block_type": "MaximumOverlap", "inputs": { "overlaps": { "type": "DynamicInputDefinition", "selector_types": [ "step_output" ] } }, "outputs": { "max_value": { "type": "DynamicOutputDefinition", "kind": [] } } }, "code": { "type": "PythonCode", "run_function_code": "\ndef run(self, overlaps: List[List[float]]) -> BlockResult:\n max_value = -1\n for overlap in overlaps:\n for overlap_value in overlap:\n if not max_value:\n max_value = overlap_value\n else:\n max_value = max(max_value, overlap_value)\n return {\"max_value\": max_value}\n" } } ], "steps": [ { "type": "RoboflowObjectDetectionModel", "name": "model", "image": "$inputs.image", "model_id": "yolov8n-640" }, { "type": "OverlapMeasurement", "name": "overlap_measurement", "predictions": "$steps.model.predictions", "class_x": "dog", "class_y": "dog" }, { "type": "ContinueIf", "name": "continue_if", "condition_statement": { "type": "StatementGroup", "statements": [ { "type": "BinaryStatement", "left_operand": { "type": "DynamicOperand", "operand_name": "overlaps", "operations": [ { "type": "SequenceLength" } ] }, "comparator": { "type": "(Number) >=" }, "right_operand": { "type": "StaticOperand", "value": 1 } } ] }, "evaluation_parameters": { "overlaps": "$steps.overlap_measurement.overlap" }, "next_steps": [ "$steps.maximum_overlap" ] }, { "type": "MaximumOverlap", "name": "maximum_overlap", "overlaps": "$steps.overlap_measurement.overlap" } ], "outputs": [ { "type": "JsonField", "name": "overlaps", "selector": "$steps.overlap_measurement.overlap" }, { "type": "JsonField", "name": "max_overlap", "selector": "$steps.maximum_overlap.max_value" } ] }

Let's start the analysis from dynamic_blocks_definitions - this is the part of Workflow Definition that provides a list of dynamic blocks. Each block contains two sections:

Definition of block manifest

Manifest definition contains several fields, including:

In any doubt, refer to blocks development guide, as the dynamic blocks replicates standard blocs capabilities.

Definition of dynamic input

Dynamic inputs define fields of dynamically created block manifest. In other words, this is definition based on which BlockManifest class will be created in runtime.

Each input may define the following properties:

Definition of dynamic output

Definitions of outputs are quite simple, hold optional list of kinds declared for given output.

Definition of Python code

Python code is shipped in JSON document with the following fields:

How to create run(...) method?

You must know the following:

from typing import Any, List, Dict, Set, Optional import supervision as sv import numpy as np import math import time import json import os import requests import cv2 import shapely from inference.core.workflows.execution_engine.entities.base import Batch, WorkflowImageData from inference.core.workflows.prototypes.block import BlockResult

So example function may look like the following (for clarity, we provide here Python code formatted nicely, but you must stringify the code to place it in definition):

def run(self, predictions: sv.Detections, class_x: str, class_y: str) -> BlockResult: bboxes_class_x = predictions[predictions.data["class_name"] == class_x] bboxes_class_y = predictions[predictions.data["class_name"] == class_y] overlap = [] for bbox_x in bboxes_class_x: bbox_x_coords = bbox_x[0] bbox_overlaps = [] for bbox_y in bboxes_class_y: if bbox_y[-1]["detection_id"] == bbox_x[-1]["detection_id"]: continue bbox_y_coords = bbox_y[0] x_min = max(bbox_x_coords[0], bbox_y_coords[0]) y_min = max(bbox_x_coords[1], bbox_y_coords[1]) x_max = min(bbox_x_coords[2], bbox_y_coords[2]) y_max = min(bbox_x_coords[3], bbox_y_coords[3]) # compute the area of intersection rectangle intersection_area = max(0, x_max - x_min + 1) * max(0, y_max - y_min + 1) box_x_area = (bbox_x_coords[2] - bbox_x_coords[0] + 1) * (bbox_x_coords[3] - bbox_x_coords[1] + 1) local_overlap = intersection_area / (box_x_area + 1e-5) bbox_overlaps.append(local_overlap) overlap.append(bbox_overlaps) return {"overlap": overlap}

How to create init(...) method?

Init function is supposed to build self._init_results dictionary.

Example:

def my_init() -> Dict[str, Any]: return {"some": "value"}

Usage of Dynamic Python block as step

As shown in example Workflow definition, you may simply use the block as if that was normal block exposed through static plugin:

{ "type": "OverlapMeasurement", "name": "overlap_measurement", "predictions": "$steps.model.predictions", "class_x": "dog", "class_y": "dog" }