diff --git a/.clang-format b/.clang-format index 44abc84e..0edf4cae 100644 --- a/.clang-format +++ b/.clang-format @@ -145,22 +145,18 @@ IfMacros: - KJ_IF_MAYBE IncludeBlocks: Regroup IncludeCategories: - - Regex: '^' - Priority: 2 - SortPriority: 0 - CaseSensitive: false - - Regex: '^<.*\.h>' + - Regex: '^<[a-z][a-z0-9]*>' Priority: 1 SortPriority: 0 - CaseSensitive: false - - Regex: '^<.*' + CaseSensitive: true + - Regex: '^<.*>' Priority: 2 SortPriority: 0 - CaseSensitive: false + CaseSensitive: true - Regex: '.*' Priority: 3 SortPriority: 0 - CaseSensitive: false + CaseSensitive: true IncludeIsMainRegex: '([-_](test|unittest))?$' IncludeIsMainSourceRegex: '' IndentAccessModifiers: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 19b0b652..3df87908 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,12 +27,20 @@ jobs: restore-keys: | ${{ runner.os }}-bazel-cache- - # Install dependencies (project has additional setup requirements) + # Install dependencies - name: Install Dependencies run: | sudo apt-get update sudo apt-get install -y libxml2-dev ocl-icd-opencl-dev gdb + - name: Setup model server + run: | + curl -fsSL https://ollama.com/install.sh | sh + ollama serve & + sleep 5 + ollama pull llama3:8b + curl -s http://localhost:11434/api/tags || exit 1 + # Checks-out your repository under $GITHUB_WORKSPACE, which is the CWD for # the rest of the steps - uses: actions/checkout@v4 diff --git a/.github/workflows/ui.yml b/.github/workflows/ui.yml new file mode 100644 index 00000000..a4c16ebc --- /dev/null +++ b/.github/workflows/ui.yml @@ -0,0 +1,52 @@ +name: UI CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: subosito/flutter-action@v2 + with: + flutter-version: '3.29.2' + channel: 'stable' + cache: true + + - name: Install dependencies + run: | + cd ui + sudo apt-get update + sudo apt-get install -y lcov libgtk-3-dev libblkid-dev liblzma-dev + flutter pub get + + - name: Verify formatting + run: | + cd ui + dart format --set-exit-if-changed . + + - name: Analyze project source + run: | + cd ui + flutter analyze + + - name: Build linux + run: | + cd ui + flutter build linux + + - name: Build web + run: | + cd ui + flutter build web + + - name: Run tests with coverage + run: | + cd ui + flutter test --coverage + genhtml coverage/lcov.info -o coverage/html \ No newline at end of file diff --git a/BUILD b/BUILD index 96dd380f..927a646a 100644 --- a/BUILD +++ b/BUILD @@ -2,6 +2,7 @@ cc_shared_library( name = "openvx", deps = [":corevx"], + visibility = ["//visibility:public"], ) # Build library for the main project diff --git a/framework/include/vx_internal.h b/framework/include/vx_internal.h index f6e8b202..de10ed0e 100644 --- a/framework/include/vx_internal.h +++ b/framework/include/vx_internal.h @@ -675,7 +675,8 @@ enum vx_ext_target_type_e { /*! \brief The priority list of targets. * \ingroup group_int_target */ -enum vx_target_priority_e { +enum vx_target_priority_e +{ #if defined(EXPERIMENTAL_USE_OPENCL) /*! \brief Defines the priority of the OpenCL Target */ VX_TARGET_PRIORITY_OPENCL, @@ -693,7 +694,7 @@ enum vx_target_priority_e { /*! \brief Defines the priority of the ORT target */ VX_TARGET_PRIORITY_ORT, /*! \brief Defines the maximum priority */ - VX_TARGET_PRIORITY_MAX, + VX_TARGET_PRIORITY_MAX = 10, }; /*! \brief Defines the number of targets in the sample implementation. diff --git a/framework/src/vx_context.cpp b/framework/src/vx_context.cpp index 21577fbc..1953f73c 100644 --- a/framework/src/vx_context.cpp +++ b/framework/src/vx_context.cpp @@ -32,11 +32,7 @@ vx_char targetModules[][VX_MAX_TARGET_NAME] = { #if defined(EXPERIMENTAL_USE_VENUM) "openvx-venum", #endif - "openvx-c_model", - "openvx-onnxRT", - "openvx-ai-server", - "openvx-liteRT", - "openvx-torch", + "openvx-c_model", "openvx-onnxRT", "openvx-ai_server", "openvx-liteRT", "openvx-torch", }; const vx_char extensions[] = @@ -63,7 +59,7 @@ static vx_sem_t global_lock; /*****************************************************************************/ Context::Context() : Reference(nullptr, VX_TYPE_CONTEXT, nullptr), - p_global_lock(nullptr), + p_global_lock(&global_lock), reftable(), num_references(0), modules(), @@ -93,9 +89,9 @@ Context::Context() queues(), #endif imm_border(), - imm_border_policy(), + imm_border_policy(VX_BORDER_POLICY_DEFAULT_TO_UNDEFINED), next_dynamic_user_kernel_id(0), - next_dynamic_user_library_id(0), + next_dynamic_user_library_id(1), imm_target_enum(), imm_target_string(), #ifdef OPENVX_USE_OPENCL_INTEROP @@ -108,6 +104,7 @@ Context::Context() graph_queue(), numGraphsQueued(0ul) { + imm_border.mode = VX_BORDER_UNDEFINED; } Context::~Context() @@ -758,17 +755,6 @@ VX_API_ENTRY vx_context VX_API_CALL vxCreateContext(void) if (context) { vx_uint32 p = 0u, p2 = 0u, t = 0u, m = 0u; -#ifdef OPENVX_USE_OPENCL_INTEROP - context->opencl_context = nullptr; - context->opencl_command_queue = nullptr; -#endif - context->p_global_lock = &global_lock; - context->imm_border.mode = VX_BORDER_UNDEFINED; - context->imm_border_policy = VX_BORDER_POLICY_DEFAULT_TO_UNDEFINED; - context->next_dynamic_user_kernel_id = 0; - context->next_dynamic_user_library_id = 1; - context->perf_enabled = vx_false_e; - #if !DISABLE_ICD_COMPATIBILITY context->platform = platform; #endif diff --git a/framework/src/vx_image.cpp b/framework/src/vx_image.cpp index 1f23be43..dc1f5c24 100644 --- a/framework/src/vx_image.cpp +++ b/framework/src/vx_image.cpp @@ -367,7 +367,7 @@ vx_bool Image::isSupportedFourcc(vx_df_image code) case VX_DF_IMAGE_VIRT: return vx_true_e; default: - VX_PRINT(VX_ZONE_ERROR, "Format 0x%08x is not supported\n", code); + VX_PRINT(VX_ZONE_ERROR, "Format %d is not supported\n", code); if (code != 0) DEBUG_BREAK(); return vx_false_e; } diff --git a/framework/src/vx_reference.cpp b/framework/src/vx_reference.cpp index efe444f5..06dae9e3 100644 --- a/framework/src/vx_reference.cpp +++ b/framework/src/vx_reference.cpp @@ -78,6 +78,10 @@ vx_bool Reference::isValidReference(vx_reference ref) { VX_PRINT(VX_ZONE_ERROR, "%p has already been released and garbage collected!\n", ref); } + else if ((Context::isValidType(ref->type) == vx_false_e)) + { + VX_PRINT(VX_ZONE_ERROR, "%p is not a valid typw!\n", ref); + } else if (ref->type != VX_TYPE_CONTEXT) { printReference(ref); diff --git a/framework/src/vx_type_pairs.cpp b/framework/src/vx_type_pairs.cpp index 5f68909b..6e65d1f4 100644 --- a/framework/src/vx_type_pairs.cpp +++ b/framework/src/vx_type_pairs.cpp @@ -19,41 +19,43 @@ #include "vx_type_pairs.h" const vx_enum_string_t type_pairs[] = { - {VX_STRINGERIZE(VX_TYPE_INVALID),0}, + {VX_STRINGERIZE(VX_TYPE_INVALID), 0}, /* scalar objects */ - {VX_STRINGERIZE(VX_TYPE_CHAR),sizeof(vx_char)*2}, - {VX_STRINGERIZE(VX_TYPE_UINT8),sizeof(vx_uint8)*2}, - {VX_STRINGERIZE(VX_TYPE_UINT16),sizeof(vx_uint16)*2}, - {VX_STRINGERIZE(VX_TYPE_UINT32),sizeof(vx_uint32)*2}, - {VX_STRINGERIZE(VX_TYPE_UINT64),sizeof(vx_uint64)*2}, - {VX_STRINGERIZE(VX_TYPE_INT8),sizeof(vx_int8)*2}, - {VX_STRINGERIZE(VX_TYPE_INT16),sizeof(vx_int16)*2}, - {VX_STRINGERIZE(VX_TYPE_INT32),sizeof(vx_int32)*2}, - {VX_STRINGERIZE(VX_TYPE_INT64),sizeof(vx_int64)*2}, - {VX_STRINGERIZE(VX_TYPE_FLOAT32),sizeof(vx_float32)*2}, - {VX_STRINGERIZE(VX_TYPE_FLOAT64),sizeof(vx_float64)*2}, - {VX_STRINGERIZE(VX_TYPE_SIZE),sizeof(vx_size)*2}, - {VX_STRINGERIZE(VX_TYPE_DF_IMAGE),sizeof(vx_df_image)*2}, - {VX_STRINGERIZE(VX_TYPE_BOOL),sizeof(vx_bool)*2}, - {VX_STRINGERIZE(VX_TYPE_ENUM),sizeof(vx_enum)*2}, + {VX_STRINGERIZE(VX_TYPE_CHAR), sizeof(vx_char) * 2}, + {VX_STRINGERIZE(VX_TYPE_UINT8), sizeof(vx_uint8) * 2}, + {VX_STRINGERIZE(VX_TYPE_UINT16), sizeof(vx_uint16) * 2}, + {VX_STRINGERIZE(VX_TYPE_UINT32), sizeof(vx_uint32) * 2}, + {VX_STRINGERIZE(VX_TYPE_UINT64), sizeof(vx_uint64) * 2}, + {VX_STRINGERIZE(VX_TYPE_INT8), sizeof(vx_int8) * 2}, + {VX_STRINGERIZE(VX_TYPE_INT16), sizeof(vx_int16) * 2}, + {VX_STRINGERIZE(VX_TYPE_INT32), sizeof(vx_int32) * 2}, + {VX_STRINGERIZE(VX_TYPE_INT64), sizeof(vx_int64) * 2}, + {VX_STRINGERIZE(VX_TYPE_FLOAT32), sizeof(vx_float32) * 2}, + {VX_STRINGERIZE(VX_TYPE_FLOAT64), sizeof(vx_float64) * 2}, + {VX_STRINGERIZE(VX_TYPE_SIZE), sizeof(vx_size) * 2}, + {VX_STRINGERIZE(VX_TYPE_DF_IMAGE), sizeof(vx_df_image) * 2}, + {VX_STRINGERIZE(VX_TYPE_BOOL), sizeof(vx_bool) * 2}, + {VX_STRINGERIZE(VX_TYPE_ENUM), sizeof(vx_enum) * 2}, /* struct objects */ - {VX_STRINGERIZE(VX_TYPE_COORDINATES2D),sizeof(vx_coordinates2d_t)*2}, - {VX_STRINGERIZE(VX_TYPE_COORDINATES3D),sizeof(vx_coordinates3d_t)*2}, - {VX_STRINGERIZE(VX_TYPE_RECTANGLE),sizeof(vx_rectangle_t)*2}, - {VX_STRINGERIZE(VX_TYPE_KEYPOINT),sizeof(vx_keypoint_t)*2}, + {VX_STRINGERIZE(VX_TYPE_COORDINATES2D), sizeof(vx_coordinates2d_t) * 2}, + {VX_STRINGERIZE(VX_TYPE_COORDINATES3D), sizeof(vx_coordinates3d_t) * 2}, + {VX_STRINGERIZE(VX_TYPE_RECTANGLE), sizeof(vx_rectangle_t) * 2}, + {VX_STRINGERIZE(VX_TYPE_KEYPOINT), sizeof(vx_keypoint_t) * 2}, /* data objects */ - {VX_STRINGERIZE(VX_TYPE_ARRAY),0}, - {VX_STRINGERIZE(VX_TYPE_DISTRIBUTION),0}, - {VX_STRINGERIZE(VX_TYPE_LUT),0}, - {VX_STRINGERIZE(VX_TYPE_IMAGE),0}, - {VX_STRINGERIZE(VX_TYPE_CONVOLUTION),0}, - {VX_STRINGERIZE(VX_TYPE_THRESHOLD),0}, - {VX_STRINGERIZE(VX_TYPE_MATRIX),0}, - {VX_STRINGERIZE(VX_TYPE_SCALAR),0}, - {VX_STRINGERIZE(VX_TYPE_PYRAMID),0}, - {VX_STRINGERIZE(VX_TYPE_REMAP),0}, + {VX_STRINGERIZE(VX_TYPE_ARRAY), 0}, + {VX_STRINGERIZE(VX_TYPE_DISTRIBUTION), 0}, + {VX_STRINGERIZE(VX_TYPE_LUT), 0}, + {VX_STRINGERIZE(VX_TYPE_IMAGE), 0}, + {VX_STRINGERIZE(VX_TYPE_CONVOLUTION), 0}, + {VX_STRINGERIZE(VX_TYPE_THRESHOLD), 0}, + {VX_STRINGERIZE(VX_TYPE_TENSOR), 0}, + {VX_STRINGERIZE(VX_TYPE_MATRIX), 0}, + {VX_STRINGERIZE(VX_TYPE_OBJECT_ARRAY), 0}, + {VX_STRINGERIZE(VX_TYPE_SCALAR), 0}, + {VX_STRINGERIZE(VX_TYPE_PYRAMID), 0}, + {VX_STRINGERIZE(VX_TYPE_REMAP), 0}, #ifdef OPENVX_KHR_XML - {VX_STRINGERIZE(VX_TYPE_IMPORT),0}, + {VX_STRINGERIZE(VX_TYPE_IMPORT), 0}, #endif }; diff --git a/framework/src/vx_xml_import.cpp b/framework/src/vx_xml_import.cpp index ee6b7777..8fa33c40 100644 --- a/framework/src/vx_xml_import.cpp +++ b/framework/src/vx_xml_import.cpp @@ -26,7 +26,8 @@ typedef unsigned long ulong; #include -typedef enum _vx_xml_tag_e { +typedef enum _vx_xml_tag_e +{ UNKNOWN_TAG = 0, OPENVX_TAG, LIBRARY_TAG, @@ -70,6 +71,7 @@ typedef enum _vx_xml_tag_e { PYRAMID_TAG, REMAP_TAG, SCALAR_TAG, + TENSOR_TAG, THRESHOLD_TAG, OBJECT_ARRAY_TAG, @@ -90,6 +92,7 @@ typedef enum _vx_xml_tag_e { POINT_TAG, BINARY_TAG, RANGE_TAG, + SHAPE_TAG, PIXELS_TAG, BORDERCONST_TAG, @@ -113,27 +116,27 @@ typedef struct _xml_struct_t { static xml_tag_t tags[] = { {"OPENVX", OPENVX_TAG}, - {"LIBRARY",LIBRARY_TAG}, + {"LIBRARY", LIBRARY_TAG}, {"STRUCT", STRUCT_TAG}, - {"GRAPH", GRAPH_TAG}, - {"NODE", NODE_TAG}, + {"GRAPH", GRAPH_TAG}, + {"NODE", NODE_TAG}, {"PARAMETER", PARAMETER_TAG}, {"KERNEL", KERNEL_TAG}, - {"CHAR", CHAR_TAG}, - {"UINT8", UINT8_TAG}, + {"CHAR", CHAR_TAG}, + {"UINT8", UINT8_TAG}, {"UINT16", UINT16_TAG}, {"UINT32", UINT32_TAG}, {"UINT64", UINT64_TAG}, - {"INT8", INT8_TAG}, - {"INT16", INT16_TAG}, - {"INT32", INT32_TAG}, - {"INT64", INT64_TAG}, - {"FLOAT32",FLOAT32_TAG}, - {"FLOAT64",FLOAT64_TAG}, - {"BOOL", BOOL_TAG}, - {"ENUM", ENUM_TAG}, - {"SIZE", SIZE_TAG}, + {"INT8", INT8_TAG}, + {"INT16", INT16_TAG}, + {"INT32", INT32_TAG}, + {"INT64", INT64_TAG}, + {"FLOAT32", FLOAT32_TAG}, + {"FLOAT64", FLOAT64_TAG}, + {"BOOL", BOOL_TAG}, + {"ENUM", ENUM_TAG}, + {"SIZE", SIZE_TAG}, {"DF_IMAGE", DF_IMAGE_TAG}, {"KEYPOINT", KEYPOINT_TAG}, @@ -146,39 +149,41 @@ static xml_tag_t tags[] = { {"SCALAR", SCALAR_TAG}, {"CONVOLUTION", CONVOLUTION_TAG}, - {"DELAY", DELAY_TAG}, - {"LUT", LUT_TAG}, + {"DELAY", DELAY_TAG}, + {"LUT", LUT_TAG}, {"DISTRIBUTION", DISTRIBUTION_TAG}, - {"IMAGE", IMAGE_TAG}, - {"ARRAY", ARRAY_TAG}, + {"IMAGE", IMAGE_TAG}, + {"ARRAY", ARRAY_TAG}, {"MATRIX", MATRIX_TAG}, - {"PYRAMID",PYRAMID_TAG}, - {"REMAP", REMAP_TAG}, + {"PYRAMID", PYRAMID_TAG}, + {"REMAP", REMAP_TAG}, + {"TENSOR", TENSOR_TAG}, {"THRESHOLD", THRESHOLD_TAG}, {"OBJECT_ARRAY", OBJECT_ARRAY_TAG}, {"START_X", START_X_TAG}, {"START_Y", START_Y_TAG}, - {"END_X", END_X_TAG}, - {"END_Y", END_Y_TAG}, - {"X", X_TAG}, - {"Y", Y_TAG}, - {"Z", Z_TAG}, - {"STRENGTH",STRENGTH_TAG}, - {"SCALE", SCALE_TAG}, + {"END_X", END_X_TAG}, + {"END_Y", END_Y_TAG}, + {"X", X_TAG}, + {"Y", Y_TAG}, + {"Z", Z_TAG}, + {"STRENGTH", STRENGTH_TAG}, + {"SCALE", SCALE_TAG}, {"ORIENTATION", ORIENTATION_TAG}, {"TRACKING_STATUS", TRACKING_STATUS_TAG}, - {"ERROR", ERROR_TAG}, - {"FREQUENCY",FREQUENCY_TAG}, - {"POINT", POINT_TAG}, - {"BINARY", BINARY_TAG}, - {"RANGE", RANGE_TAG}, - {"PIXELS", PIXELS_TAG}, - {"BORDERCONST", BORDERCONST_TAG}, - - {"RGB", RGB_TAG}, - {"RGBA", RGBA_TAG}, - {"YUV", YUV_TAG}, + {"ERROR", ERROR_TAG}, + {"FREQUENCY", FREQUENCY_TAG}, + {"POINT", POINT_TAG}, + {"BINARY", BINARY_TAG}, + {"RANGE", RANGE_TAG}, + {"SHAPE", SHAPE_TAG}, + {"PIXELS", PIXELS_TAG}, + {"BORDERCONST", BORDERCONST_TAG}, + + {"RGB", RGB_TAG}, + {"RGBA", RGBA_TAG}, + {"YUV", YUV_TAG}, }; #define XML_FOREACH_CHILD_TAG(child, tag, tags) \ @@ -398,6 +403,22 @@ static void vxSetName(vx_reference ref, xmlNodePtr cur) static xml_struct_t *user_struct_table = nullptr; +static vx_bool areObjectArrayChildrenIdentical(xmlNodePtr node, vx_xml_tag_e tag, vx_size count) +{ + // If count is 1, children are trivially identical + if (count <= 1) return vx_true_e; + + // Count number of unique child elements + vx_size numChildren = 0; + XML_FOREACH_CHILD_TAG(node, tag, tags) + { + numChildren++; + } + + // If there's only one child element and count > 1, children are identical + return (numChildren == 1); +} + static vx_status vxImportFromXMLRoi(vx_image parent, xmlNodePtr cur, vx_reference refs[], vx_size total) { vx_status status = VX_SUCCESS; @@ -1203,6 +1224,872 @@ static vx_status vxImportFromXMLPyramid(vx_reference ref, xmlNodePtr cur, vx_ref return status; } +static vx_status vxImportFromXMLObjectArray(vx_context context, xmlNodePtr cur, vx_xml_tag_e tag, + vx_reference refs[], vx_size total) +{ + vx_status status = VX_SUCCESS; + typedef vx_object_array (*objArrCreateFunction)(vx_context context, vx_reference exemplar, + vx_size count); + objArrCreateFunction createFn = (objArrCreateFunction)(&vxCreateObjectArray); + vx_reference parentReference = nullptr; + vx_reference *internalRefs = nullptr; + vx_uint32 refIdx = xml_prop_ulong(cur, "reference"); + vx_uint32 count = xml_prop_ulong(cur, "count"); + vx_uint32 childNum = 0; + vx_bool identicalObjects = vx_false_e; + vx_int32 parentType = VX_TYPE_OBJECT_ARRAY; + vx_char objType[32]; + xml_prop_string(cur, "objType", objType, sizeof(objType)); + vx_char objectName[16]; + vx_enum type = VX_TYPE_INVALID; + snprintf(objectName, sizeof(objectName), "object_array"); + TypePairs::typeFromString(objType, &type); + identicalObjects = areObjectArrayChildrenIdentical(cur, tag, count); + VX_PRINT(VX_ZONE_LOG, "ref %p contains identical objects: %d\n", parentReference, + identicalObjects); + + if (refIdx < total) + { + XML_FOREACH_CHILD_TAG(cur, tag, tags) + { + switch (tag) + { + case TENSOR_TAG: + { + // Parse shape from XML + vx_size dims[VX_MAX_TENSOR_DIMENSIONS] = {0}; + vx_size numDims = 0; + vx_enum elemType = VX_TYPE_UINT8; + vx_char typeName[32]; + + // Parse elemType attribute + xml_prop_string(cur, "elemType", typeName, sizeof(typeName)); + if (TypePairs::typeFromString(typeName, &elemType) != VX_SUCCESS) + { + status = VX_ERROR_INVALID_TYPE; + VX_PRINT(VX_ZONE_ERROR, "Invalid type %s\n", typeName); + goto exit; + } + + XML_FOREACH_CHILD_TAG(cur, tag, tags) + { + if (tag == SHAPE_TAG) + { + vx_char shapeStr[VX_MAX_REFERENCE_NAME]; + xml_string(cur, shapeStr, sizeof(shapeStr)); + VX_PRINT(VX_ZONE_LOG, "Found shape string: %s\n", shapeStr); + + // Parse comma-separated values + char *token = strtok(shapeStr, ","); + while (token && numDims < VX_MAX_TENSOR_DIMENSIONS) + { + dims[numDims++] = atoi(token); + token = strtok(nullptr, ","); + } + VX_PRINT(VX_ZONE_LOG, + "Parsed %d dimensions: [%d, %d, %d, %d, %d, %d]\n", numDims, + dims[0], dims[1], dims[2], dims[3], dims[4], dims[5]); + } + } + + // Create tensor with parsed dimensions and element type + vx_tensor exemplar = vxCreateTensor(context, numDims, dims, elemType, 0); + status = vxGetStatus((vx_reference)exemplar); + if (status != VX_SUCCESS) + { + goto exit; + } + + if (!identicalObjects) + { + if (childNum == 0) + { + // First tensor - create object array + parentReference = vxCreateObjectArrayWithType(context, VX_TYPE_TENSOR); + status = vxGetStatus(parentReference); + if (status != VX_SUCCESS) + { + vxReleaseTensor(&exemplar); + goto exit; + } + refs[refIdx] = parentReference; + vxSetName(refs[refIdx], cur); + vxInternalizeReference(refs[refIdx]); + } + + // Add tensor to object array + status = vxSetObjectArrayItem((vx_object_array)parentReference, childNum, + (vx_reference)exemplar); + if (status != VX_SUCCESS) + { + vxReleaseTensor(&exemplar); + goto exit; + } + VX_PRINT(VX_ZONE_LOG, "Added tensor %d to object array at reference %d\n", + childNum, refIdx); + } + else + { + // Use exemplar approach for identical objects + parentReference = createFn(context, (vx_reference)exemplar, count); + status = vxGetStatus(parentReference); + if (status != VX_SUCCESS) + { + vxReleaseTensor(&exemplar); + goto exit; + } + refs[refIdx] = parentReference; + vxSetName(refs[refIdx], cur); + vxInternalizeReference(refs[refIdx]); + } + + vxReleaseTensor(&exemplar); + childNum++; + break; + } + case IMAGE_TAG: + { + vx_uint32 width = xml_prop_ulong(cur, "width"); + vx_uint32 height = xml_prop_ulong(cur, "height"); + if (childNum == 0) + { /* Create parent object based on first child */ + vx_image exemplar = nullptr; + vx_df_image format = VX_DF_IMAGE_VIRT; + xml_prop_string(cur, "format", (vx_char *)&format, 4); + status = vxReserveReferences(context, count + 1); + exemplar = vxCreateImage(context, width, height, format); + status |= vxReleaseReferences(context, count + 1); + status |= vxGetStatus((vx_reference)exemplar); + if (status == VX_SUCCESS) + { + parentReference = createFn(context, (vx_reference)exemplar, count); + internalRefs = getRefsFromParent(parentReference, parentType); + status = vxGetStatus(parentReference); + vxReleaseImage(&exemplar); + refs[refIdx] = parentReference; + vxSetName(refs[refIdx], cur); + vxInternalizeReference(refs[refIdx]); + } + } + refIdx = xml_prop_ulong(cur, "reference"); + if (refIdx < total) + { + if (childNum < count) + { + if (((vx_image)internalRefs[childNum])->width == width && + ((vx_image)internalRefs[childNum])->height == height) + { + refs[refIdx] = (vx_reference)internalRefs[childNum]; + vxSetName(refs[refIdx], cur); + refs[refIdx]->incrementReference(VX_INTERNAL); + } + else + { + status = VX_ERROR_INVALID_PARAMETERS; + VX_PRINT(VX_ZONE_ERROR, + "%s image settings doesn't match generated %s image!\n", + objectName, objectName); + goto exit; + } + } + else + { + status = VX_ERROR_INVALID_PARAMETERS; + VX_PRINT(VX_ZONE_ERROR, + "%s has more child nodes than indicated in count!\n", + objectName); + goto exit; + } + } + else + { + REFNUM_ERROR; + goto exit; + } + childNum++; + status |= vxLoadDataForImage((vx_image)refs[refIdx], cur, refs, total); + break; + } + case ARRAY_TAG: + { + vx_size capacity = xml_prop_ulong(cur, "capacity"); + vx_char typeName[32]; + vx_enum type = VX_TYPE_INVALID; + vx_uint32 userNum; + xml_prop_string(cur, "elemType", typeName, sizeof(typeName)); + + if (TypePairs::typeFromString(typeName, &type) != VX_SUCCESS) + { /* Type was not found, check if it is a user type */ + if (sscanf(typeName, "USER_STRUCT_%u", &userNum) == 1) + { + if (vxStructGetEnum(user_struct_table, userNum, &type) != VX_SUCCESS) + { + status = VX_ERROR_INVALID_TYPE; /* INVALID type */ + VX_PRINT(VX_ZONE_ERROR, "Invalid type %s\n", typeName); + goto exit; + } + } + else + { + status = VX_ERROR_INVALID_TYPE; /* INVALID type */ + VX_PRINT(VX_ZONE_ERROR, "Invalid type %s\n", typeName); + goto exit; + } + } + + if (childNum == 0) + { /* Create delay object based on first child */ + vx_array exemplar = nullptr; + status = vxReserveReferences(context, count + 1); + exemplar = vxCreateArray(context, type, capacity); + status |= vxReleaseReferences(context, count + 1); + status |= vxGetStatus((vx_reference)exemplar); + if (status == VX_SUCCESS) + { + parentReference = createFn(context, (vx_reference)exemplar, count); + internalRefs = getRefsFromParent(parentReference, parentType); + status = vxGetStatus(parentReference); + vxReleaseArray(&exemplar); + refs[refIdx] = parentReference; + vxSetName(refs[refIdx], cur); + vxInternalizeReference(refs[refIdx]); + } + } + refIdx = xml_prop_ulong(cur, "reference"); + if (refIdx < total) + { + if (childNum < count) + { + if (((vx_array)internalRefs[childNum])->capacity == capacity && + ((vx_array)internalRefs[childNum])->item_type == type) + { + refs[refIdx] = internalRefs[childNum]; + vxSetName(refs[refIdx], cur); + refs[refIdx]->incrementReference(VX_INTERNAL); + } + else + { + status = VX_ERROR_INVALID_PARAMETERS; + VX_PRINT(VX_ZONE_ERROR, + "%s array settings doesn't match generated %s array!\n", + objectName, objectName); + goto exit; + } + } + else + { + status = VX_ERROR_INVALID_PARAMETERS; + VX_PRINT(VX_ZONE_ERROR, + "%s has more child nodes than indicated in count!\n", + objectName); + goto exit; + } + } + else + { + REFNUM_ERROR; + goto exit; + } + childNum++; + status |= vxLoadDataForArray((vx_array)refs[refIdx], cur); + break; + } + case PYRAMID_TAG: + { + vx_uint32 width = xml_prop_ulong(cur, "width"); + vx_uint32 height = xml_prop_ulong(cur, "height"); + vx_float32 scale = xml_prop_float(cur, "scale"); + vx_size levels = xml_prop_ulong(cur, "levels"); + vx_df_image format = VX_DF_IMAGE_VIRT; + xml_prop_string(cur, "format", (vx_char *)&format, 4); + if (childNum == 0) + { /* Create delay object based on first child */ + vx_pyramid exemplar = nullptr; + status = vxReserveReferences(context, count * (levels + 1) + 1); + exemplar = vxCreatePyramid(context, levels, scale, width, height, format); + status |= vxReleaseReferences(context, count * (levels + 1) + 1); + status |= vxGetStatus((vx_reference)exemplar); + if (status == VX_SUCCESS) + { + parentReference = createFn(context, (vx_reference)exemplar, count); + internalRefs = getRefsFromParent(parentReference, parentType); + status = vxGetStatus(parentReference); + vxReleasePyramid(&exemplar); + refs[refIdx] = parentReference; + vxSetName(refs[refIdx], cur); + vxInternalizeReference(refs[refIdx]); + } + } + refIdx = xml_prop_ulong(cur, "reference"); + if (refIdx < total) + { + if (childNum < count) + { + if (((vx_pyramid)internalRefs[childNum])->width == width && + ((vx_pyramid)internalRefs[childNum])->height == height && + ((vx_pyramid)internalRefs[childNum])->numLevels == levels && + ((vx_pyramid)internalRefs[childNum])->scale == scale) + { + refs[refIdx] = internalRefs[childNum]; + vxSetName(refs[refIdx], cur); + refs[refIdx]->incrementReference(VX_INTERNAL); + } + else + { + status = VX_ERROR_INVALID_PARAMETERS; + VX_PRINT( + VX_ZONE_ERROR, + "%s pyramid settings doesn't match generated %s pyramid!\n", + objectName, objectName); + goto exit; + } + } + else + { + status = VX_ERROR_INVALID_PARAMETERS; + VX_PRINT(VX_ZONE_ERROR, + "%s has more child nodes than indicated in count!\n", + objectName); + goto exit; + } + } + else + { + REFNUM_ERROR; + goto exit; + } + childNum++; + status |= + vxLoadDataForPyramid((vx_pyramid)refs[refIdx], cur, refs, total, levels); + break; + } + case MATRIX_TAG: + { + vx_size rows = xml_prop_ulong(cur, "rows"); + vx_size cols = xml_prop_ulong(cur, "columns"); + vx_char typeName[32]; + xml_prop_string(cur, "elemType", typeName, sizeof(typeName)); + TypePairs::typeFromString(typeName, &type); + if (childNum == 0) + { /* Create delay object based on first child */ + vx_matrix exemplar = nullptr; + status = vxReserveReferences(context, count + 1); + exemplar = vxCreateMatrix(context, type, cols, rows); + status |= vxReleaseReferences(context, count + 1); + status |= vxGetStatus((vx_reference)exemplar); + if (status == VX_SUCCESS) + { + parentReference = createFn(context, (vx_reference)exemplar, count); + internalRefs = getRefsFromParent(parentReference, parentType); + status = vxGetStatus(parentReference); + vxReleaseMatrix(&exemplar); + refs[refIdx] = parentReference; + vxSetName(refs[refIdx], cur); + vxInternalizeReference(refs[refIdx]); + } + } + refIdx = xml_prop_ulong(cur, "reference"); + if (refIdx < total) + { + if (childNum < count) + { + if (((vx_matrix)internalRefs[childNum])->rows == rows && + ((vx_matrix)internalRefs[childNum])->columns == cols) + { + refs[refIdx] = internalRefs[childNum]; + vxSetName(refs[refIdx], cur); + refs[refIdx]->incrementReference(VX_INTERNAL); + } + else + { + status = VX_ERROR_INVALID_PARAMETERS; + VX_PRINT(VX_ZONE_ERROR, + "%s matrix settings doesn't match generated %s matrix!\n", + objectName, objectName); + goto exit; + } + } + else + { + status = VX_ERROR_INVALID_PARAMETERS; + VX_PRINT(VX_ZONE_ERROR, + "%s has more child nodes than indicated in count!\n", + objectName); + goto exit; + } + } + else + { + REFNUM_ERROR; + goto exit; + } + childNum++; + status |= vxLoadDataForMatrix((vx_matrix)refs[refIdx], cur, cols, rows, type); + break; + } + case LUT_TAG: + { + vx_size lut_count = xml_prop_ulong(cur, "count"); + vx_char typeName[32] = "VX_TYPE_UINT8"; + xml_prop_string(cur, "elemType", typeName, sizeof(typeName)); + TypePairs::typeFromString(typeName, &type); + if (lut_count == 0) lut_count = 256; + if (childNum == 0) + { /* Create delay object based on first child */ + vx_lut exemplar = nullptr; + status = vxReserveReferences(context, count + 1); + exemplar = vxCreateLUT(context, type, lut_count); + status |= vxReleaseReferences(context, count + 1); + status |= vxGetStatus((vx_reference)exemplar); + if (status == VX_SUCCESS) + { + parentReference = createFn(context, (vx_reference)exemplar, count); + internalRefs = getRefsFromParent(parentReference, parentType); + status = vxGetStatus(parentReference); + vxReleaseLUT(&exemplar); + refs[refIdx] = parentReference; + vxSetName(refs[refIdx], cur); + vxInternalizeReference(refs[refIdx]); + } + } + refIdx = xml_prop_ulong(cur, "reference"); + if (refIdx < total) + { + if (childNum < count) + { + if (((vx_lut)internalRefs[childNum])->num_items == lut_count && + ((vx_lut)internalRefs[childNum])->item_type == type) + { + refs[refIdx] = internalRefs[childNum]; + vxSetName(refs[refIdx], cur); + refs[refIdx]->incrementReference(VX_INTERNAL); + } + else + { + status = VX_ERROR_INVALID_PARAMETERS; + VX_PRINT(VX_ZONE_ERROR, + "%s lut settings doesn't match generated %s lut!\n", + objectName, objectName); + goto exit; + } + } + else + { + status = VX_ERROR_INVALID_PARAMETERS; + VX_PRINT(VX_ZONE_ERROR, + "%s has more child nodes than indicated in count!\n", + objectName); + goto exit; + } + } + else + { + REFNUM_ERROR; + goto exit; + } + childNum++; + status |= vxLoadDataForLut((vx_lut)refs[refIdx], cur, type, lut_count); + break; + } + case CONVOLUTION_TAG: + { + vx_size rows = xml_prop_ulong(cur, "rows"); + vx_size cols = xml_prop_ulong(cur, "columns"); + vx_uint32 scale = xml_prop_ulong(cur, "scale"); + if (childNum == 0) + { /* Create delay object based on first child */ + vx_convolution exemplar = nullptr; + status = vxReserveReferences(context, count + 1); + exemplar = vxCreateConvolution(context, cols, rows); + status |= vxReleaseReferences(context, count + 1); + status |= vxGetStatus((vx_reference)exemplar); + if (status == VX_SUCCESS) + { + if (!scale) scale = 1; + status |= vxSetConvolutionAttribute(exemplar, VX_CONVOLUTION_SCALE, + &scale, sizeof(scale)); + parentReference = createFn(context, (vx_reference)exemplar, count); + internalRefs = getRefsFromParent(parentReference, parentType); + status = vxGetStatus(parentReference); + vxReleaseConvolution(&exemplar); + refs[refIdx] = parentReference; + vxSetName(refs[refIdx], cur); + vxInternalizeReference(refs[refIdx]); + } + } + refIdx = xml_prop_ulong(cur, "reference"); + if (refIdx < total) + { + if (childNum < count) + { + if (((vx_convolution)internalRefs[childNum])->rows == rows && + ((vx_convolution)internalRefs[childNum])->columns == cols) + { + refs[refIdx] = internalRefs[childNum]; + vxSetName(refs[refIdx], cur); + refs[refIdx]->incrementReference(VX_INTERNAL); + } + else + { + status = VX_ERROR_INVALID_PARAMETERS; + VX_PRINT(VX_ZONE_ERROR, + "%s convolution settings doesn't match generated &s " + "convolution!\n", + objectName, objectName); + goto exit; + } + } + else + { + status = VX_ERROR_INVALID_PARAMETERS; + VX_PRINT(VX_ZONE_ERROR, + "%s has more child nodes than indicated in count!\n", + objectName); + goto exit; + } + } + else + { + REFNUM_ERROR; + goto exit; + } + childNum++; + status |= + vxLoadDataForConvolution((vx_convolution)refs[refIdx], cur, cols, rows); + break; + } + case REMAP_TAG: + { + vx_uint32 src_width = xml_prop_ulong(cur, "src_width"); + vx_uint32 src_height = xml_prop_ulong(cur, "src_height"); + vx_uint32 dst_width = xml_prop_ulong(cur, "dst_width"); + vx_uint32 dst_height = xml_prop_ulong(cur, "dst_height"); + if (childNum == 0) + { /* Create delay object based on first child */ + vx_remap exemplar = nullptr; + status = vxReserveReferences(context, count + 1); + exemplar = + vxCreateRemap(context, src_width, src_height, dst_width, dst_height); + status |= vxReleaseReferences(context, count + 1); + status |= vxGetStatus((vx_reference)exemplar); + if (status == VX_SUCCESS) + { + parentReference = createFn(context, (vx_reference)exemplar, count); + internalRefs = getRefsFromParent(parentReference, parentType); + status = vxGetStatus(parentReference); + vxReleaseRemap(&exemplar); + refs[refIdx] = parentReference; + vxSetName(refs[refIdx], cur); + vxInternalizeReference(refs[refIdx]); + } + } + refIdx = xml_prop_ulong(cur, "reference"); + if (refIdx < total) + { + if (childNum < count) + { + if (((vx_remap)internalRefs[childNum])->src_width == src_width && + ((vx_remap)internalRefs[childNum])->src_height == src_height && + ((vx_remap)internalRefs[childNum])->dst_width == dst_width && + ((vx_remap)internalRefs[childNum])->dst_height == dst_height) + { + refs[refIdx] = internalRefs[childNum]; + vxSetName(refs[refIdx], cur); + refs[refIdx]->incrementReference(VX_INTERNAL); + } + else + { + status = VX_ERROR_INVALID_PARAMETERS; + VX_PRINT(VX_ZONE_ERROR, + "%s remap settings doesn't match generated %s remap!\n", + objectName, objectName); + goto exit; + } + } + else + { + status = VX_ERROR_INVALID_PARAMETERS; + VX_PRINT(VX_ZONE_ERROR, + "%s has more child nodes than indicated in count!\n", + objectName); + goto exit; + } + } + else + { + REFNUM_ERROR; + goto exit; + } + childNum++; + status |= vxLoadDataForRemap((vx_remap)refs[refIdx], cur); + break; + } + case DISTRIBUTION_TAG: + { + vx_size bins = xml_prop_ulong(cur, "bins"); + vx_uint32 range = xml_prop_ulong(cur, "range"); + vx_int32 offset = xml_prop_ulong(cur, "offset"); + if (childNum == 0) + { /* Create delay object based on first child */ + vx_distribution exemplar = nullptr; + status = vxReserveReferences(context, count + 1); + exemplar = vxCreateDistribution(context, bins, offset, range); + status |= vxReleaseReferences(context, count + 1); + status |= vxGetStatus((vx_reference)exemplar); + if (status == VX_SUCCESS) + { + parentReference = createFn(context, (vx_reference)exemplar, count); + internalRefs = getRefsFromParent(parentReference, parentType); + status = vxGetStatus(parentReference); + vxReleaseDistribution(&exemplar); + refs[refIdx] = parentReference; + vxSetName(refs[refIdx], cur); + vxInternalizeReference(refs[refIdx]); + } + } + refIdx = xml_prop_ulong(cur, "reference"); + if (refIdx < total) + { + if (childNum < count) + { + if (((vx_distribution)internalRefs[childNum])->range_x == + (vx_uint32)range && + ((vx_distribution)internalRefs[childNum]) + ->memory.dims[0][VX_DIM_X] == (vx_uint32)bins && + ((vx_distribution)internalRefs[childNum])->offset_x == offset) + { + refs[refIdx] = internalRefs[childNum]; + vxSetName(refs[refIdx], cur); + refs[refIdx]->incrementReference(VX_INTERNAL); + } + else + { + status = VX_ERROR_INVALID_PARAMETERS; + VX_PRINT(VX_ZONE_ERROR, + "%s distribution settings doesn't match generated %s " + "distribution!\n", + objectName, objectName); + goto exit; + } + } + else + { + status = VX_ERROR_INVALID_PARAMETERS; + VX_PRINT(VX_ZONE_ERROR, + "%s has more child nodes than indicated in count!\n", + objectName); + goto exit; + } + } + else + { + REFNUM_ERROR; + goto exit; + } + childNum++; + status |= vxLoadDataForDistribution((vx_distribution)refs[refIdx], cur, bins); + break; + } + case THRESHOLD_TAG: + { + vx_char typeName[32] = "VX_TYPE_UINT8"; // default value + xml_prop_string(cur, "elemType", typeName, sizeof(typeName)); + TypePairs::typeFromString(typeName, &type); + vx_int32 true_value = (vx_int32)xml_prop_ulong(cur, "true_value"); + vx_int32 false_value = (vx_int32)xml_prop_ulong(cur, "false_value"); + if (childNum == 0) + { /* Create delay object based on first child */ + vx_threshold exemplar = nullptr; + status = vxReserveReferences(context, count + 1); + XML_FOREACH_CHILD_TAG(cur, tag, tags) + { + if (tag == BINARY_TAG) + { + vx_int32 value = (vx_int32)xml_ulong(cur); + exemplar = + vxCreateThreshold(context, VX_THRESHOLD_TYPE_BINARY, type); + status |= vxSetThresholdAttribute( + exemplar, VX_THRESHOLD_THRESHOLD_VALUE, &value, sizeof(value)); + } + else if (tag == RANGE_TAG) + { + vx_int32 upper = (vx_int32)xml_prop_ulong(cur, "upper"); + vx_int32 lower = (vx_int32)xml_prop_ulong(cur, "lower"); + exemplar = + vxCreateThreshold(context, VX_THRESHOLD_TYPE_RANGE, type); + status |= vxSetThresholdAttribute( + exemplar, VX_THRESHOLD_THRESHOLD_UPPER, &upper, sizeof(upper)); + status |= vxSetThresholdAttribute( + exemplar, VX_THRESHOLD_THRESHOLD_LOWER, &lower, sizeof(lower)); + } + status |= vxSetThresholdAttribute(exemplar, VX_THRESHOLD_TRUE_VALUE, + &true_value, sizeof(true_value)); + status |= vxSetThresholdAttribute(exemplar, VX_THRESHOLD_FALSE_VALUE, + &false_value, sizeof(false_value)); + } + status |= vxReleaseReferences(context, count + 1); + status |= vxGetStatus((vx_reference)exemplar); + if (status == VX_SUCCESS) + { + parentReference = createFn(context, (vx_reference)exemplar, count); + internalRefs = getRefsFromParent(parentReference, parentType); + status = vxGetStatus(parentReference); + vxReleaseThreshold(&exemplar); + refs[refIdx] = parentReference; + vxSetName(refs[refIdx], cur); + vxInternalizeReference(refs[refIdx]); + } + } + refIdx = xml_prop_ulong(cur, "reference"); + if (refIdx < total) + { + if (childNum < count) + { + vx_enum thresh_type = VX_THRESHOLD_TYPE_BINARY; + XML_FOREACH_CHILD_TAG(cur, tag, tags) + { + if (tag == BINARY_TAG) + { + thresh_type = VX_THRESHOLD_TYPE_BINARY; + } + else if (tag == RANGE_TAG) + { + thresh_type = VX_THRESHOLD_TYPE_RANGE; + } + } + if (((vx_threshold)internalRefs[childNum])->thresh_type == thresh_type) + { + refs[refIdx] = internalRefs[childNum]; + vxSetName(refs[refIdx], cur); + refs[refIdx]->incrementReference(VX_INTERNAL); + } + else + { + status = VX_ERROR_INVALID_PARAMETERS; + VX_PRINT( + VX_ZONE_ERROR, + "%s threshold settings doesn't match generated %s threshold!\n", + objectName, objectName); + goto exit; + } + } + else + { + status = VX_ERROR_INVALID_PARAMETERS; + VX_PRINT(VX_ZONE_ERROR, + "%s has more child nodes than indicated in count!\n", + objectName); + goto exit; + } + } + else + { + REFNUM_ERROR; + goto exit; + } + childNum++; + XML_FOREACH_CHILD_TAG(cur, tag, tags) + { + if (tag == BINARY_TAG) + { + vx_int32 value = (vx_int32)xml_ulong(cur); + status |= vxSetThresholdAttribute((vx_threshold)refs[refIdx], + VX_THRESHOLD_THRESHOLD_VALUE, &value, + sizeof(value)); + } + else if (tag == RANGE_TAG) + { + vx_int32 upper = (vx_int32)xml_prop_ulong(cur, "upper"); + vx_int32 lower = (vx_int32)xml_prop_ulong(cur, "lower"); + status |= vxSetThresholdAttribute((vx_threshold)refs[refIdx], + VX_THRESHOLD_THRESHOLD_UPPER, &upper, + sizeof(upper)); + status |= vxSetThresholdAttribute((vx_threshold)refs[refIdx], + VX_THRESHOLD_THRESHOLD_LOWER, &lower, + sizeof(lower)); + } + status |= vxSetThresholdAttribute((vx_threshold)refs[refIdx], + VX_THRESHOLD_TRUE_VALUE, &true_value, + sizeof(true_value)); + status |= vxSetThresholdAttribute((vx_threshold)refs[refIdx], + VX_THRESHOLD_FALSE_VALUE, &false_value, + sizeof(false_value)); + } + break; + } + case SCALAR_TAG: + { + vx_char typeName[20]; + xml_prop_string(cur, "elemType", typeName, sizeof(typeName)); + TypePairs::typeFromString(typeName, &type); + vx_size nullptrReference = 0; + void *ptr = &nullptrReference; + if (childNum == 0) + { /* Create delay object based on first child */ + vx_scalar exemplar = nullptr; + status = vxReserveReferences(context, count + 1); + exemplar = vxCreateScalar(context, type, ptr); + status |= vxReleaseReferences(context, count + 1); + status |= vxGetStatus((vx_reference)exemplar); + if (status == VX_SUCCESS) + { + parentReference = createFn(context, (vx_reference)exemplar, count); + internalRefs = getRefsFromParent(parentReference, parentType); + status = vxGetStatus(parentReference); + vxReleaseScalar(&exemplar); + refs[refIdx] = parentReference; + vxSetName(refs[refIdx], cur); + vxInternalizeReference(refs[refIdx]); + } + } + refIdx = xml_prop_ulong(cur, "reference"); + if (refIdx < total) + { + if (childNum < count) + { + if (((vx_scalar)internalRefs[childNum])->data_type == type) + { + refs[refIdx] = internalRefs[childNum]; + vxSetName(refs[refIdx], cur); + refs[refIdx]->incrementReference(VX_INTERNAL); + } + else + { + status = VX_ERROR_INVALID_PARAMETERS; + VX_PRINT(VX_ZONE_ERROR, + "%s scalar settings doesn't match generated %s scalar!\n", + objectName, objectName); + goto exit; + } + } + else + { + status = VX_ERROR_INVALID_PARAMETERS; + VX_PRINT(VX_ZONE_ERROR, + "%s has more child nodes than indicated in count!\n", + objectName); + goto exit; + } + } + else + { + REFNUM_ERROR; + goto exit; + } + childNum++; + status |= vxLoadDataForScalar((vx_scalar)refs[refIdx], cur); + break; + } + default: + status = VX_ERROR_INVALID_TYPE; + VX_PRINT(VX_ZONE_ERROR, "Invalid type as child of object array\n"); + } + } + } +exit: + return status; +} + VX_API_ENTRY vx_import VX_API_CALL vxImportFromXML(vx_context context, vx_char xmlfile[]) { @@ -1479,39 +2366,117 @@ VX_API_ENTRY vx_import VX_API_CALL vxImportFromXML(vx_context context, status = VX_ERROR_INVALID_VALUE; goto exit_error; } - } else if (tag == DELAY_TAG || - tag == OBJECT_ARRAY_TAG) { - - typedef vx_reference (*createFunction)(vx_context context, vx_reference exemplar, vx_size count); - typedef vx_object_array (*objArrCreateFunction)(vx_context context, vx_reference exemplar, vx_size count); - createFunction createFn; + } + else if (tag == OBJECT_ARRAY_TAG) + { + status = vxImportFromXMLObjectArray(context, cur, tag, refs, total); + if (status != VX_SUCCESS) + { + goto exit_error; + } + } + else if (tag == DELAY_TAG) + { + typedef vx_reference (*createFunction)(vx_context context, vx_reference exemplar, + vx_size count); + createFunction createFn = (createFunction)(&vxCreateDelay); char objectName[16]; - vx_int32 parentType; - vx_reference parentReference; + vx_int32 parentType = VX_TYPE_DELAY; + vx_reference parentReference = nullptr; vx_reference *internalRefs = nullptr; vx_uint32 refIdx = xml_prop_ulong(cur, "reference"); vx_uint32 count = xml_prop_ulong(cur, "count"); vx_uint32 childNum = 0; - vx_char objType[32] = "VX_TYPE_IMAGE"; // default value + vx_char objType[32]; xml_prop_string(cur, "objType", objType, sizeof(objType)); TypePairs::typeFromString(objType, &type); + snprintf(objectName, sizeof(objectName), "delay"); - if(tag == DELAY_TAG) - { - parentType = VX_TYPE_DELAY; - createFn = (createFunction)(&vxCreateDelay); - snprintf(objectName, sizeof(objectName), "delay"); - } - else + if (refIdx < total) { - parentType = VX_TYPE_OBJECT_ARRAY; - createFn = (createFunction)((objArrCreateFunction)(&vxCreateObjectArray)); - snprintf(objectName, sizeof(objectName), "object_array"); - } + XML_FOREACH_CHILD_TAG(cur, tag, tags) + { + switch (tag) + { + case TENSOR_TAG: + { + VX_PRINT(VX_ZONE_LOG, "Processing tensor child %d\n", childNum); - if (refIdx < total) { - XML_FOREACH_CHILD_TAG (cur, tag, tags) { - switch(tag) { + if (childNum == 0) + { + // Create exemplar + vx_tensor exemplar = nullptr; + status = vxReserveReferences(context, count + 1); + VX_PRINT(VX_ZONE_LOG, "Reserved %d references for object array\n", + count + 1); + + // Parse shape from XML + vx_size dims[VX_MAX_TENSOR_DIMENSIONS] = { + 0}; // Initialize to 0 for up to 6 dimensions + vx_size numDims = 0; + vx_enum elemType = + VX_TYPE_UINT8; // Default to UINT8 if not specified + vx_char typeName[32]; + + // Parse elemType attribute + xml_prop_string(cur, "elemType", typeName, sizeof(typeName)); + if (TypePairs::typeFromString(typeName, &elemType) != VX_SUCCESS) + { + status = VX_ERROR_INVALID_TYPE; + VX_PRINT(VX_ZONE_ERROR, "Invalid type %s\n", typeName); + goto exit_error; + } + + XML_FOREACH_CHILD_TAG(cur, tag, tags) + { + if (tag == SHAPE_TAG) + { + vx_char shapeStr[VX_MAX_REFERENCE_NAME]; + xml_string(cur, shapeStr, sizeof(shapeStr)); + VX_PRINT(VX_ZONE_LOG, "Found shape string: %s\n", shapeStr); + + // Parse comma-separated values + char *token = strtok(shapeStr, ","); + while (token && + numDims < + VX_MAX_TENSOR_DIMENSIONS) // Support up to max + // dimensions + { + dims[numDims++] = atoi(token); + token = strtok(nullptr, ","); + } + VX_PRINT(VX_ZONE_LOG, + "Parsed %d dimensions: [%d, %d, %d, %d, %d, %d]\n", + numDims, dims[0], dims[1], dims[2], dims[3], + dims[4], dims[5]); + } + } + + // Create exemplar tensor with parsed dimensions and element type + exemplar = vxCreateTensor(context, numDims, dims, elemType, 0); + status = vxGetStatus((vx_reference)exemplar); + if (status != VX_SUCCESS) + { + goto exit_error; + } + + // Use exemplar approach for identical objects + parentReference = createFn(context, (vx_reference)exemplar, count); + internalRefs = getRefsFromParent(parentReference, parentType); + status = vxGetStatus(parentReference); + if (status != VX_SUCCESS) + { + vxReleaseTensor(&exemplar); + goto exit_error; + } + refs[refIdx] = parentReference; + vxSetName(refs[refIdx], cur); + vxInternalizeReference(refs[refIdx]); + vxReleaseTensor(&exemplar); + childNum++; + } + break; + } case IMAGE_TAG: { vx_uint32 width = xml_prop_ulong(cur,"width"); @@ -1573,10 +2538,12 @@ VX_API_ENTRY vx_import VX_API_CALL vxImportFromXML(vx_context context, if(sscanf(typeName, "USER_STRUCT_%u", &userNum) == 1) { if(vxStructGetEnum(user_struct_table, userNum, &type) != VX_SUCCESS) { status = VX_ERROR_INVALID_TYPE; /* INVALID type */ + VX_PRINT(VX_ZONE_ERROR, "Invalid type %s\n", typeName); goto exit_error; } } else { status = VX_ERROR_INVALID_TYPE; /* INVALID type */ + VX_PRINT(VX_ZONE_ERROR, "Invalid type %s\n", typeName); goto exit_error; } } @@ -2067,11 +3034,15 @@ VX_API_ENTRY vx_import VX_API_CALL vxImportFromXML(vx_context context, break; } } - } else { + } + else + { REFNUM_ERROR; goto exit_error; } - } else { + } + else + { VX_PRINT(VX_ZONE_ERROR, "Tag %d unhandled!\n", tag); status = VX_ERROR_NOT_IMPLEMENTED; } diff --git a/kernels/ai_server/chatbot.hpp b/kernels/ai_server/chatbot.hpp index 617c7c8b..1b42d037 100644 --- a/kernels/ai_server/chatbot.hpp +++ b/kernels/ai_server/chatbot.hpp @@ -7,14 +7,16 @@ * @copyright Copyright (c) 2025 * */ -#include -#include +#include #include #include + #include +#include +#include -#define DEFAULT_MODEL "gpt-4o-mini" -#define SERVER_URL "http://localhost:8000" +#define DEFAULT_MODEL "llama3:8b" +#define SERVER_URL "http://localhost:11434" #define API_KEY "hardcoded-api-key" class RemoteModelClient @@ -34,7 +36,10 @@ class RemoteModelClient { CURL *curl = curl_easy_init(); if (!curl) + { + std::cerr << "failed to init libcurl" << std::endl; return VX_FAILURE; + } nlohmann::json request_json = { {"model", DEFAULT_MODEL}, @@ -61,7 +66,10 @@ class RemoteModelClient curl_easy_cleanup(curl); if (res != CURLE_OK) + { + std::cerr << "Failed to post message to " << api_url << std::endl; return VX_FAILURE; + } auto json_response = nlohmann::json::parse(response_string); output_text = json_response["choices"][0]["message"]["content"]; @@ -74,7 +82,10 @@ class RemoteModelClient { CURL *curl = curl_easy_init(); if (!curl) + { + std::cerr << "failed to init libcurl for streaming" << std::endl; return VX_FAILURE; + } nlohmann::json request_json = { {"model", DEFAULT_MODEL}, @@ -101,7 +112,10 @@ class RemoteModelClient curl_easy_cleanup(curl); if (res != CURLE_OK) + { + std::cerr << "Failed to post stream to " << api_url << std::endl; return VX_FAILURE; + } // Just return raw streamed response (newline-delimited JSON chunks) output_text = response_chunk; diff --git a/targets/ai_server/BUILD b/targets/ai_server/BUILD index 6ee430a1..c1b1b8be 100644 --- a/targets/ai_server/BUILD +++ b/targets/ai_server/BUILD @@ -1,4 +1,3 @@ - cc_library( name = "ai-server", srcs = glob([ @@ -18,7 +17,7 @@ cc_library( ) cc_shared_library( - name = "openvx-ai-server", + name = "openvx-ai_server", deps = [ ":ai-server", ], @@ -27,6 +26,6 @@ cc_shared_library( cc_import( name = "imported_openvx_ai_server", - shared_library = ":openvx-ai-server", + shared_library = ":openvx-ai_server", visibility = ["//visibility:public"] ) diff --git a/targets/ai_server/vx_chatbot.cpp b/targets/ai_server/vx_chatbot.cpp index 6e3ab048..3a2ceca9 100644 --- a/targets/ai_server/vx_chatbot.cpp +++ b/targets/ai_server/vx_chatbot.cpp @@ -7,7 +7,6 @@ * @copyright Copyright (c) 2025 * */ -#include #include #include @@ -33,9 +32,32 @@ class VxRemoteModelClient { vx_status status = vxTruncateArray(arr, 0); // clear existing contents if (status != VX_SUCCESS) + { + VX_PRINT(VX_ZONE_ERROR, "Failed to clear existing contents out of string\n"); return status; + } - return vxAddArrayItems(arr, in.size(), in.data(), sizeof(char)); + // Get array capacity + vx_size capacity; + status = vxQueryArray(arr, VX_ARRAY_CAPACITY, &capacity, sizeof(capacity)); + if (status != VX_SUCCESS) + { + VX_PRINT(VX_ZONE_ERROR, "Failed to query array capacity\n"); + return status; + } + + // Truncate input string to array capacity + vx_string truncated = in.substr(0, capacity); + + // Add the truncated string to array + status = vxAddArrayItems(arr, truncated.length(), truncated.c_str(), sizeof(char)); + if (status != VX_SUCCESS) + { + VX_PRINT(VX_ZONE_ERROR, "Failed to add string to array\n"); + return status; + } + + return VX_SUCCESS; } static vx_status load_vx_string_from_array(vx_array arr, vx_string &out) @@ -66,11 +88,44 @@ class VxRemoteModelClient static vx_status VX_CALLBACK validate(vx_node node, const vx_reference parameters[], vx_uint32 num, vx_meta_format metas[]) { - (void)node; - (void)parameters; - (void)num; - (void)metas; - return VX_SUCCESS; + vx_status status = VX_SUCCESS; + + if (nullptr == node || nullptr == parameters || num != dimof(kernelParams) || + nullptr == metas) + { + VX_PRINT(VX_ZONE_ERROR, "Error: Invalid parameters during validation!\n"); + status = VX_FAILURE; + } + + if (VX_SUCCESS == status) + { + // Retrieve the kernel instance from the node's local data + if (!kernel) + { + VX_PRINT(VX_ZONE_ERROR, "Error: Kernel instance is null during validation!\n"); + status = VX_FAILURE; + } + } + + if (VX_SUCCESS == status) + { + vx_array outputArr = reinterpret_cast(parameters[1]); + vx_size capacity; + vx_enum itemType; + + status = vxQueryArray(outputArr, VX_ARRAY_CAPACITY, &capacity, sizeof(capacity)); + status |= vxQueryArray(outputArr, VX_ARRAY_ITEMTYPE, &itemType, sizeof(itemType)); + status |= + vxSetMetaFormatAttribute(metas[1], VX_ARRAY_CAPACITY, &capacity, sizeof(capacity)); + status |= + vxSetMetaFormatAttribute(metas[1], VX_ARRAY_ITEMTYPE, &itemType, sizeof(itemType)); + } + + if (VX_SUCCESS != status) + { + VX_PRINT(VX_ZONE_ERROR, "Failed to validate kernel\n"); + } + return status; } static vx_status VX_CALLBACK run(vx_node node, const vx_reference *parameters, vx_uint32 num) @@ -82,10 +137,11 @@ class VxRemoteModelClient vx_string input_text, output_text; status = load_vx_string_from_array((vx_array)parameters[0], input_text); - status |= kernel->AiServerQuery( - input_text, // Input text - output_text, // Output text - api_map["chat"]); // API path + std::cout << "[input]: " << input_text << std::endl; + status |= kernel->AiServerQuery(input_text, // Input text + output_text, // Output text + api_map["chat"]); // API path + std::cout << "[output]: " << output_text << std::endl; status |= store_vx_string_to_array((vx_array)parameters[1], output_text); return status; diff --git a/targets/ai_server/vx_interface.cpp b/targets/ai_server/vx_interface.cpp index c4b3e3a8..75f0f2c7 100644 --- a/targets/ai_server/vx_interface.cpp +++ b/targets/ai_server/vx_interface.cpp @@ -37,6 +37,117 @@ static vx_uint32 num_target_kernels = dimof(target_kernels); /******************************************************************************/ /* EXPORTED FUNCTIONS */ /******************************************************************************/ + +/*! \brief The entry point into this module to add the base kernels to OpenVX. + * \param context The handle to the implementation context. + * \return vx_status Returns errors if some or all kernels were not added + * correctly. + * \ingroup group_implementation + */ +extern "C" VX_API_ENTRY vx_status VX_API_CALL vxPublishKernels(vx_context context) +{ + vx_status status = VX_FAILURE; + vx_uint32 p = 0, k = 0; + for (k = 0; k < num_target_kernels; k++) + { + vx_kernel kernel = 0; + + kernel = vxAddUserKernel(context, target_kernels[k]->name, target_kernels[k]->enumeration, + target_kernels[k]->function, target_kernels[k]->numParams, + target_kernels[k]->validate, target_kernels[k]->initialize, + target_kernels[k]->deinitialize); + if (VX_SUCCESS == vxGetStatus((vx_reference)kernel)) + { + status = VX_SUCCESS; // temporary + for (p = 0; p < target_kernels[k]->numParams; p++) + { + status = + vxAddParameterToKernel(kernel, p, target_kernels[k]->parameters[p].direction, + target_kernels[k]->parameters[p].data_type, + target_kernels[k]->parameters[p].state); + if (status != VX_SUCCESS) + { + vxAddLogEntry((vx_reference)context, status, + "Failed to add parameter %d to kernel %s! (%d)\n", p, + target_kernels[k]->name, status); + break; + } + } + if (status == VX_SUCCESS) + { + status = vxFinalizeKernel(kernel); + if (status != VX_SUCCESS) + { + vxAddLogEntry((vx_reference)context, status, + "Failed to finalize kernel[%u]=%s\n", k, target_kernels[k]->name); + } + } + else + { + status = vxRemoveKernel(kernel); + if (status != VX_SUCCESS) + { + vxAddLogEntry((vx_reference)context, status, "Failed to remove kernel[%u]=%s\n", + k, target_kernels[k]->name); + } + } + } + else + { + vxAddLogEntry((vx_reference)context, status, "Failed to add kernel %s\n", + target_kernels[k]->name); + } + } + return status; +} + +/*! \brief The destructor to remove a user loaded module from OpenVX. + * \param [in] context The handle to the implementation context. + * \return A \ref vx_status_e enumeration. Returns errors if some or all kernels were not added + * correctly. + * \note This follows the function pointer definition of a \ref vx_unpublish_kernels_f + * and uses the predefined name for the entry point, "vxUnpublishKernels". + * \ingroup group_example_kernel + */ +extern "C" VX_API_ENTRY vx_status VX_API_CALL vxUnpublishKernels(vx_context context) +{ + vx_status status = VX_FAILURE; + + vx_uint32 k = 0; + for (k = 0; k < num_target_kernels; k++) + { + vx_kernel kernel = vxGetKernelByName(context, target_kernels[k]->name); + vx_kernel kernelcpy = kernel; + + if (kernel) + { + status = vxReleaseKernel(&kernelcpy); + if (status != VX_SUCCESS) + { + vxAddLogEntry((vx_reference)context, status, "Failed to release kernel[%u]=%s\n", k, + target_kernels[k]->name); + } + else + { + kernelcpy = kernel; + status = vxRemoveKernel(kernelcpy); + if (status != VX_SUCCESS) + { + vxAddLogEntry((vx_reference)context, status, "Failed to remove kernel[%u]=%s\n", + k, target_kernels[k]->name); + } + } + } + else + { + vxAddLogEntry((vx_reference)context, status, "Failed to get added kernel %s\n", + target_kernels[k]->name); + } + } + + return status; +} + extern "C" vx_status vxTargetInit(vx_target target) { if (target) diff --git a/targets/liteRT/vx_interface.cpp b/targets/liteRT/vx_interface.cpp index 52335621..bfac3965 100644 --- a/targets/liteRT/vx_interface.cpp +++ b/targets/liteRT/vx_interface.cpp @@ -37,6 +37,116 @@ static vx_uint32 num_target_kernels = dimof(target_kernels); /******************************************************************************/ /* EXPORTED FUNCTIONS */ /******************************************************************************/ +/*! \brief The entry point into this module to add the base kernels to OpenVX. + * \param context The handle to the implementation context. + * \return vx_status Returns errors if some or all kernels were not added + * correctly. + * \ingroup group_implementation + */ +extern "C" VX_API_ENTRY vx_status VX_API_CALL vxPublishKernels(vx_context context) +{ + vx_status status = VX_FAILURE; + vx_uint32 p = 0, k = 0; + for (k = 0; k < num_target_kernels; k++) + { + vx_kernel kernel = 0; + + kernel = vxAddUserKernel(context, target_kernels[k]->name, target_kernels[k]->enumeration, + target_kernels[k]->function, target_kernels[k]->numParams, + target_kernels[k]->validate, target_kernels[k]->initialize, + target_kernels[k]->deinitialize); + if (VX_SUCCESS == vxGetStatus((vx_reference)kernel)) + { + status = VX_SUCCESS; // temporary + for (p = 0; p < target_kernels[k]->numParams; p++) + { + status = + vxAddParameterToKernel(kernel, p, target_kernels[k]->parameters[p].direction, + target_kernels[k]->parameters[p].data_type, + target_kernels[k]->parameters[p].state); + if (status != VX_SUCCESS) + { + vxAddLogEntry((vx_reference)context, status, + "Failed to add parameter %d to kernel %s! (%d)\n", p, + target_kernels[k]->name, status); + break; + } + } + if (status == VX_SUCCESS) + { + status = vxFinalizeKernel(kernel); + if (status != VX_SUCCESS) + { + vxAddLogEntry((vx_reference)context, status, + "Failed to finalize kernel[%u]=%s\n", k, target_kernels[k]->name); + } + } + else + { + status = vxRemoveKernel(kernel); + if (status != VX_SUCCESS) + { + vxAddLogEntry((vx_reference)context, status, "Failed to remove kernel[%u]=%s\n", + k, target_kernels[k]->name); + } + } + } + else + { + vxAddLogEntry((vx_reference)context, status, "Failed to add kernel %s\n", + target_kernels[k]->name); + } + } + return status; +} + +/*! \brief The destructor to remove a user loaded module from OpenVX. + * \param [in] context The handle to the implementation context. + * \return A \ref vx_status_e enumeration. Returns errors if some or all kernels were not added + * correctly. + * \note This follows the function pointer definition of a \ref vx_unpublish_kernels_f + * and uses the predefined name for the entry point, "vxUnpublishKernels". + * \ingroup group_example_kernel + */ +extern "C" VX_API_ENTRY vx_status VX_API_CALL vxUnpublishKernels(vx_context context) +{ + vx_status status = VX_FAILURE; + + vx_uint32 k = 0; + for (k = 0; k < num_target_kernels; k++) + { + vx_kernel kernel = vxGetKernelByName(context, target_kernels[k]->name); + vx_kernel kernelcpy = kernel; + + if (kernel) + { + status = vxReleaseKernel(&kernelcpy); + if (status != VX_SUCCESS) + { + vxAddLogEntry((vx_reference)context, status, "Failed to release kernel[%u]=%s\n", k, + target_kernels[k]->name); + } + else + { + kernelcpy = kernel; + status = vxRemoveKernel(kernelcpy); + if (status != VX_SUCCESS) + { + vxAddLogEntry((vx_reference)context, status, "Failed to remove kernel[%u]=%s\n", + k, target_kernels[k]->name); + } + } + } + else + { + vxAddLogEntry((vx_reference)context, status, "Failed to get added kernel %s\n", + target_kernels[k]->name); + } + } + + return status; +} + extern "C" vx_status vxTargetInit(vx_target target) { if (target) diff --git a/tests/BUILD b/tests/BUILD index c1f4f13f..0b407797 100644 --- a/tests/BUILD +++ b/tests/BUILD @@ -16,10 +16,11 @@ cc_binary( "//targets/opencl:imported_openvx_opencl", "//tests/xyz:imported_xyz", ], - linkopts = [ - # This tells the linker to search for shared libraries relative to the binary's location - "-Wl,-rpath,@executable_path", - ], + linkopts = select({ + "@platforms//os:linux": ["-Wl,-rpath,$ORIGIN"], + "@platforms//os:macos": ["-Wl,-rpath,@executable_path"], + "//conditions:default": [], + }), data = [ "//tests/raw:raw_data", ], diff --git a/tests/integration_test/BUILD b/tests/integration_test/BUILD index e3e3a099..64dd6cc8 100644 --- a/tests/integration_test/BUILD +++ b/tests/integration_test/BUILD @@ -1,3 +1,25 @@ + +cc_test( + name = "test_aiserver", + srcs = [ + "test_aiserver.cpp" + ], + deps = [ + "//:corevx", + "@googletest//:gtest_main", + "//targets/ai_server:imported_openvx_ai_server", + "//targets/c_model:imported_openvx_c_model", + "//targets/debug:imported_openvx_debug", + "//targets/extras:imported_openvx_extras", + ], + linkopts = select({ + "@platforms//os:linux": ["-Wl,-rpath,$ORIGIN"], + "@platforms//os:macos": ["-Wl,-rpath,@executable_path"], + "//conditions:default": [], + }), + size = "small" +) + # cc_test( # name = "test_ort", # srcs = [ diff --git a/tests/integration_test/test_aiserver.cpp b/tests/integration_test/test_aiserver.cpp new file mode 100644 index 00000000..82ac40bf --- /dev/null +++ b/tests/integration_test/test_aiserver.cpp @@ -0,0 +1,91 @@ +/** + * @file test_aiserver.cpp + * @brief Test with internal model server + * @version 0.1 + * @date 2025-06-07 + * + * @copyright Copyright (c) 2025 + * + */ +#include + +#include +#include +#include + +class AiServerIntegrationTest : public ::testing::Test +{ + protected: + vx_context context; + vx_graph graph; + + void SetUp() override + { + // Initialize OpenVX context + context = vxCreateContext(); + ASSERT_EQ(vxGetStatus((vx_reference)context), VX_SUCCESS); + } + + void TearDown() override + { + vxReleaseGraph(&graph); + vxReleaseContext(&context); + } +}; + +TEST_F(AiServerIntegrationTest, AiServerTest) +{ + std::string query = "what is the capital of the United States ?"; + + // Create input string + vx_array input_string = vxCreateArray(context, VX_TYPE_CHAR, VX_MAX_FILE_NAME); + ASSERT_EQ(vxGetStatus((vx_reference)input_string), VX_SUCCESS); + ASSERT_EQ(VX_SUCCESS, + vxAddArrayItems(input_string, query.length() + 1, query.c_str(), sizeof(char))); + + // Create output string + vx_array output_string = vxCreateArray(context, VX_TYPE_CHAR, VX_MAX_FILE_NAME); + ASSERT_EQ(vxGetStatus((vx_reference)output_string), VX_SUCCESS); + + // Create graph + graph = vxCreateGraph(context); + ASSERT_EQ(vxGetStatus((vx_reference)graph), VX_SUCCESS); + + // Get AI chatbot kernel + vx_kernel kernel = vxGetKernelByEnum(context, VX_KERNEL_AIS_CHATBOT); + ASSERT_EQ(vxGetStatus((vx_reference)kernel), VX_SUCCESS); + + // Create node + vx_node node = vxCreateGenericNode(graph, kernel); + ASSERT_EQ(vxGetStatus((vx_reference)node), VX_SUCCESS); + + // Set node parameters + ASSERT_EQ(VX_SUCCESS, vxSetParameterByIndex(node, 0, (vx_reference)input_string)); + ASSERT_EQ(VX_SUCCESS, vxSetParameterByIndex(node, 1, (vx_reference)output_string)); + + // Verify graph + ASSERT_EQ(vxVerifyGraph(graph), VX_SUCCESS); + + // Process graph + ASSERT_EQ(vxProcessGraph(graph), VX_SUCCESS); + + // Read output + char output_buffer[VX_MAX_FILE_NAME]; + vx_size num_items = 0; + ASSERT_EQ(VX_SUCCESS, + vxQueryArray(output_string, VX_ARRAY_NUMITEMS, &num_items, sizeof(num_items))); + ASSERT_EQ(VX_SUCCESS, vxCopyArrayRange(output_string, 0, num_items, sizeof(char), output_buffer, + VX_READ_ONLY, VX_MEMORY_TYPE_HOST)); + output_buffer[num_items] = '\0'; + + // Validate results + std::string response(output_buffer); + ASSERT_TRUE(response.find("Washington, D.C.") != std::string::npos) + << "Expected response to contain 'Washington, D.C.', but got: " << response; + + // Cleanup + vxReleaseArray(&input_string); + vxReleaseArray(&output_string); + vxReleaseKernel(&kernel); + vxReleaseNode(&node); +} \ No newline at end of file diff --git a/tools/BUILD b/tools/BUILD new file mode 100644 index 00000000..d4d080f9 --- /dev/null +++ b/tools/BUILD @@ -0,0 +1,21 @@ +cc_binary( + name = "run_xml_graph", + srcs = ["run_xml_graph.cpp"], + deps = [ + "//:corevx", + "//targets/ai_server:imported_openvx_ai_server", + "//targets/c_model:imported_openvx_c_model", + "//targets/debug:imported_openvx_debug", + "//targets/extras:imported_openvx_extras", + "//targets/liteRT:imported_openvx_liteRT", + "//targets/opencl:imported_openvx_opencl", + # "//targets/onnxRT:imported_openvx_onnxRT", + "//targets/executorch:imported_openvx_torch", + ], + linkopts = select({ + "@platforms//os:linux": ["-Wl,-rpath,$ORIGIN"], + "@platforms//os:macos": ["-Wl,-rpath,@executable_path"], + "//conditions:default": [], + }), + visibility = ["//visibility:public"], +) \ No newline at end of file diff --git a/tools/run_xml_graph.cpp b/tools/run_xml_graph.cpp new file mode 100644 index 00000000..d96c6a5e --- /dev/null +++ b/tools/run_xml_graph.cpp @@ -0,0 +1,180 @@ +#include +#include +#include +#include + +#include +#include +#include + +class XMLGraphRunner +{ + public: + XMLGraphRunner(const std::string& filename) + { + // Create context + context_ = vxCreateContext(); + if (vxGetStatus((vx_reference)context_) != VX_SUCCESS) + { + throw std::runtime_error("Failed to create OpenVX context"); + } + + // Import XML + import_ = vxImportFromXML(context_, const_cast(filename.c_str())); + if (vxGetStatus((vx_reference)import_) != VX_SUCCESS) + { + vxReleaseContext(&context_); + throw std::runtime_error("Failed to import XML file: " + filename); + } + } + + ~XMLGraphRunner() + { + if (import_) vxReleaseImport(&import_); + if (context_) vxReleaseContext(&context_); + } + + void runAllGraphs() + { + vx_uint32 count = 0; + if (vxQueryImport(import_, VX_IMPORT_ATTRIBUTE_COUNT, &count, sizeof(count)) != + VX_SUCCESS) + { + throw std::runtime_error("Failed to query import count"); + } + + std::cout << "Running all graphs in XML..." << std::endl; + for (vx_uint32 i = 0; i < count; i++) + { + vx_reference ref = vxGetImportReferenceByIndex(import_, i); + if (ref) + { + vx_enum type; + if (vxQueryReference(ref, VX_REF_ATTRIBUTE_TYPE, &type, sizeof(type)) == + VX_SUCCESS) + { + if (type == VX_TYPE_GRAPH) + { + runGraph((vx_graph)ref); + } + } + vxReleaseReference(&ref); + } + } + } + + void runGraphByName(const std::string& name) + { + vx_reference ref = vxGetImportReferenceByName(import_, name.c_str()); + if (!ref) + { + throw std::runtime_error("Graph '" + name + "' not found in XML"); + } + + vx_enum type; + if (vxQueryReference(ref, VX_REF_ATTRIBUTE_TYPE, &type, sizeof(type)) == VX_SUCCESS) + { + if (type == VX_TYPE_GRAPH) + { + runGraph((vx_graph)ref, name); + } + else + { + vxReleaseReference(&ref); + throw std::runtime_error("Reference '" + name + "' is not a graph"); + } + } + vxReleaseReference(&ref); + } + + private: + void runGraph(vx_graph graph, const std::string& name = "") + { + vx_perf_t perf; + vx_status status = vxProcessGraph(graph); + + if (status == VX_SUCCESS) + { + vxQueryGraph(graph, VX_GRAPH_PERFORMANCE, &perf, sizeof(perf)); + if (name.empty()) + { + std::cout << "Graph " << (vx_size)graph << " performance metrics:" << std::endl + << " begin time (ns): " << perf.beg << std::endl + << " end time (ns): " << perf.end << std::endl + << " temp time (ns): " << perf.tmp << std::endl + << " sum time (ns): " << perf.sum << std::endl + << " num runs: " << perf.num << std::endl + << " avg time (ns): " << perf.avg << std::endl + << " min time (ns): " << perf.min << std::endl + << " max time (ns): " << perf.max << std::endl; + } + else + { + std::cout << "Graph '" << name << "' performance metrics:" << std::endl + << " begin time (ns): " << perf.beg << std::endl + << " end time (ns): " << perf.end << std::endl + << " temp time (ns): " << perf.tmp << std::endl + << " sum time (ns): " << perf.sum << std::endl + << " num runs: " << perf.num << std::endl + << " avg time (ns): " << perf.avg << std::endl + << " min time (ns): " << perf.min << std::endl + << " max time (ns): " << perf.max << std::endl; + } + } + else + { + if (name.empty()) + { + std::cerr << "Failed to process graph " << (vx_size)graph << std::endl; + } + else + { + std::cerr << "Failed to process graph '" << name << "'" << std::endl; + } + throw std::runtime_error("Graph processing failed"); + } + } + + vx_context context_ = nullptr; + vx_import import_ = nullptr; +}; + +void printUsage(const char* program) +{ + std::cout << "Usage: " << program << " [graph_name]" << std::endl; + std::cout << " xml_file: Path to the XML file containing graph definition" << std::endl; + std::cout << " graph_name: (Optional) Name of specific graph to run. If not provided, runs " + "all graphs" + << std::endl; +} + +int main(int argc, char* argv[]) +{ + try + { + if (argc < 2 || argc > 3) + { + printUsage(argv[0]); + return -1; + } + + const std::string xmlFile = argv[1]; + XMLGraphRunner runner(xmlFile); + + if (argc == 3) + { + runner.runGraphByName(argv[2]); + } + else + { + runner.runAllGraphs(); + } + + return 0; + } + catch (const std::exception& e) + { + std::cerr << "Error: " << e.what() << std::endl; + return -1; + } +} \ No newline at end of file diff --git a/ui/assets/supported.xml b/ui/assets/supported.xml index 27d715bf..8a65425e 100644 --- a/ui/assets/supported.xml +++ b/ui/assets/supported.xml @@ -822,6 +822,17 @@ + + + + VX_TYPE_ARRAY + VX_TYPE_OBJECT_ARRAY + + + VX_TYPE_OBJECT_ARRAY + + + @@ -910,6 +921,17 @@ + + + + VX_TYPE_ARRAY + VX_TYPE_OBJECT_ARRAY + + + VX_TYPE_OBJECT_ARRAY + + + diff --git a/ui/lib/export.dart b/ui/lib/export.dart index b4baeced..441e2d8b 100644 --- a/ui/lib/export.dart +++ b/ui/lib/export.dart @@ -12,6 +12,17 @@ class XmlExport { final int graphIndex; final int refCount; + // Map of format codes to their standardized form + static const Map _formatMap = { + 'U1': 'U001', + 'U8': 'U008', + 'U16': 'U016', + 'S16': 'S016', + 'U32': 'U032', + 'S32': 'S032', + 'RGB': 'RGB2', + }; + void export(BuildContext context) { if (graphs.isNotEmpty) { final graph = graphs[graphIndex]; @@ -75,82 +86,238 @@ class XmlExport { } } - void _addReferenceElement(xml.XmlBuilder builder, Reference reference) { - if (reference.linkId != -1) { + void _addReferenceElement(xml.XmlBuilder builder, Reference ref) { + if (ref.linkId != -1) { // Reuse the reference by pointing to the linked reference ID return; } - // Handle specific reference types - if (reference is Img) { + if (ref is ObjectArray) { + builder.element('object_array', nest: () { + builder.attribute('reference', ref.id); + builder.attribute('count', ref.numObjects); + builder.attribute('objType', ref.elemType); + + // Only export one child object if applyToAll is true + final numChildren = + (ref.numObjects > 0 && ref.applyToAll) ? 1 : ref.numObjects; + + for (int i = 0; i < numChildren; i++) { + final objectAttrs = ref.applyToAll + ? ref.elementAttributes + : ref.elementAttributes['object_$i'] as Map?; + + if (objectAttrs == null) continue; + + switch (ref.elemType) { + // Handle specific reference types + case 'TENSOR': + builder.element('tensor', nest: () { + builder.attribute('numDims', objectAttrs['numDims'] ?? 0); + builder.attribute('elemType', + "VX_TYPE_${objectAttrs['elemType'] ?? 'UINT8'}"); + if (objectAttrs['shape'] != null) { + builder.element('shape', nest: () { + builder.text((objectAttrs['shape'] as List).join(', ')); + }); + } + }); + break; + case 'IMAGE': + builder.element('image', nest: () { + builder.attribute('width', objectAttrs['width'] ?? 0); + builder.attribute('height', objectAttrs['height'] ?? 0); + final format = objectAttrs['format'] ?? 'U8'; + // Use the format map or keep the original format + final formattedFormat = _formatMap[format] ?? format; + builder.attribute('format', formattedFormat); + }); + break; + case 'ARRAY': + builder.element('array', attributes: { + 'capacity': objectAttrs['capacity']?.toString() ?? '0', + 'elemType': "VX_TYPE_${objectAttrs['elemType'] ?? 'UINT8'}", + }); + break; + case 'MATRIX': + builder.element('matrix', attributes: { + 'rows': objectAttrs['rows']?.toString() ?? '0', + 'columns': objectAttrs['cols']?.toString() ?? '0', + 'elemType': "VX_TYPE_${objectAttrs['elemType'] ?? 'UINT8'}", + }); + break; + case 'SCALAR': + builder.element('scalar', attributes: { + 'elemType': "VX_TYPE_${objectAttrs['elemType'] ?? 'UINT8'}", + }, nest: () { + builder.element( + objectAttrs['elemType']?.toString().toLowerCase() ?? + 'uint8', + nest: objectAttrs['value']?.toString() ?? '0'); + }); + break; + case 'CONVOLUTION': + builder.element('convolution', attributes: { + 'rows': objectAttrs['rows']?.toString() ?? '0', + 'columns': objectAttrs['cols']?.toString() ?? '0', + 'scale': objectAttrs['scale']?.toString() ?? '0', + }); + break; + case 'PYRAMID': + builder.element('pyramid', attributes: { + 'width': objectAttrs['width']?.toString() ?? '0', + 'height': objectAttrs['height']?.toString() ?? '0', + 'format': + objectAttrs['format']?.toString() ?? 'VX_DF_IMAGE_VIRT', + 'levels': objectAttrs['numLevels']?.toString() ?? '0', + }); + break; + case 'REMAP': + builder.element('remap', attributes: { + 'src_width': objectAttrs['srcWidth']?.toString() ?? '0', + 'src_height': objectAttrs['srcHeight']?.toString() ?? '0', + 'dst_width': objectAttrs['dstWidth']?.toString() ?? '0', + 'dst_height': objectAttrs['dstHeight']?.toString() ?? '0', + }); + break; + case 'THRESHOLD': + builder.element('threshold', attributes: { + 'reference': '0', + }, nest: () { + if (objectAttrs['thresType'] == 'TYPE_BINARY') { + builder.element('binary', nest: '0'); + } else if (objectAttrs['thresType'] == 'TYPE_RANGE') { + builder.element('range', attributes: { + 'lower': '0', + 'upper': '0', + }); + } + }); + break; + case 'LUT': + builder.element('lut', attributes: { + 'capacity': objectAttrs['capacity']?.toString() ?? '0', + 'elemType': "VX_TYPE_${objectAttrs['elemType'] ?? 'UINT8'}", + }); + break; + } + } + }); + } else if (ref is Img) { builder.element('image', attributes: { - 'reference': reference.id.toString(), - 'width': reference.width.toString(), - 'height': reference.height.toString(), - 'format': reference.format, + 'reference': ref.id.toString(), + 'width': ref.width.toString(), + 'height': ref.height.toString(), + 'format': ref.format, }); - } else if (reference is Scalar) { + } else if (ref is Scalar) { builder.element('scalar', attributes: { - 'reference': reference.id.toString(), - 'elemType': reference.elemType, + 'reference': ref.id.toString(), + 'elemType': "VX_TYPE_${ref.elemType}", }, nest: () { - builder.element(reference.elemType.toLowerCase(), - nest: reference.value.toString()); - }); - } else if (reference is Array) { - builder.element('array', attributes: { - 'reference': reference.id.toString(), - 'capacity': reference.capacity.toString(), - 'elemType': reference.elemType, + builder.element(ref.elemType.toLowerCase(), nest: ref.value.toString()); }); - } else if (reference is Convolution) { + } else if (ref is Array) { + builder.element('array', + attributes: { + 'reference': ref.id.toString(), + 'capacity': ref.capacity.toString(), + 'elemType': "VX_TYPE_${ref.elemType}", + }, + nest: ref.values.isEmpty + ? null + : () { + final type = ref.elemType.toUpperCase(); + + if (type == 'CHAR') { + // Export as a single ... element + builder.element('char', nest: ref.values.join()); + } else if (type == 'RECTANGLE' && ref.values.length == 4) { + // Export as ...... + builder.element('rectangle', nest: () { + builder.element('start_x', nest: ref.values[0]); + builder.element('start_y', nest: ref.values[1]); + builder.element('end_x', nest: ref.values[2]); + builder.element('end_y', nest: ref.values[3]); + }); + } else if (type == 'COORDINATES2D' && + ref.values.length == 2) { + builder.element('coordinates2d', nest: () { + builder.element('x', nest: ref.values[0]); + builder.element('y', nest: ref.values[1]); + }); + } else if (type == 'COORDINATES3D' && + ref.values.length == 3) { + builder.element('coordinates3d', nest: () { + builder.element('x', nest: ref.values[0]); + builder.element('y', nest: ref.values[1]); + builder.element('z', nest: ref.values[2]); + }); + } else if (type.startsWith('FLOAT') || + type.startsWith('INT') || + type.startsWith('UINT')) { + // Export each value as ... or ... etc. + final tag = type.toLowerCase(); + for (final v in ref.values) { + // Validate/parse as number + final parsed = num.tryParse(v); + builder.element(tag, nest: parsed?.toString() ?? v); + } + } else { + // Default: export each value as ... + for (final v in ref.values) { + builder.element('value', nest: v); + } + } + }); + } else if (ref is Convolution) { builder.element('convolution', attributes: { - 'reference': reference.id.toString(), - 'rows': reference.rows.toString(), - 'columns': reference.cols.toString(), - 'scale': reference.scale.toString(), + 'reference': ref.id.toString(), + 'rows': ref.rows.toString(), + 'columns': ref.cols.toString(), + 'scale': ref.scale.toString(), }); - } else if (reference is Matrix) { + } else if (ref is Matrix) { builder.element('matrix', attributes: { - 'reference': reference.id.toString(), - 'rows': reference.rows.toString(), - 'columns': reference.cols.toString(), - 'elemType': reference.elemType, + 'reference': ref.id.toString(), + 'rows': ref.rows.toString(), + 'columns': ref.cols.toString(), + 'elemType': "VX_TYPE_${ref.elemType}", }); - } else if (reference is Pyramid) { + } else if (ref is Pyramid) { builder.element('pyramid', attributes: { - 'reference': reference.id.toString(), - 'width': reference.width.toString(), - 'height': reference.height.toString(), - 'format': reference.format, - 'levels': reference.numLevels.toString(), + 'reference': ref.id.toString(), + 'width': ref.width.toString(), + 'height': ref.height.toString(), + 'format': ref.format, + 'levels': ref.numLevels.toString(), }); - } else if (reference is Thrshld) { + } else if (ref is Thrshld) { builder.element('threshold', attributes: { - 'reference': reference.id.toString(), + 'reference': ref.id.toString(), }, nest: () { - if (reference.thresType == 'TYPE_BINARY') { - builder.element('binary', nest: reference.binary.toString()); - } else if (reference.thresType == 'TYPE_RANGE') { + if (ref.thresType == 'TYPE_BINARY') { + builder.element('binary', nest: ref.binary.toString()); + } else if (ref.thresType == 'TYPE_RANGE') { builder.element('range', attributes: { - 'lower': reference.lower.toString(), - 'upper': reference.upper.toString(), + 'lower': ref.lower.toString(), + 'upper': ref.upper.toString(), }); } }); - } else if (reference is Remap) { + } else if (ref is Remap) { builder.element('remap', attributes: { - 'reference': reference.id.toString(), - 'src_width': reference.srcWidth.toString(), - 'src_height': reference.srcHeight.toString(), - 'dst_width': reference.dstWidth.toString(), - 'dst_height': reference.dstHeight.toString(), + 'reference': ref.id.toString(), + 'src_width': ref.srcWidth.toString(), + 'src_height': ref.srcHeight.toString(), + 'dst_width': ref.dstWidth.toString(), + 'dst_height': ref.dstHeight.toString(), }); } else { // Default case for unknown reference types builder.element('reference', attributes: { - 'reference': reference.id.toString(), - 'type': reference.type, + 'reference': ref.id.toString(), + 'type': ref.type, }); } } @@ -161,10 +328,9 @@ class XmlExport { builder.processing('xml', 'version="1.0" encoding="utf-8"'); - builder.element('openvx', namespaces: { - '': 'https://www.khronos.org/registry/vx/schema', - 'xsi': 'https://www.w3.org/TR/xmlschema-1' - }, attributes: { + builder.element('openvx', attributes: { + 'xmlns:xsi': 'https://www.w3.org/TR/xmlschema-1', + 'xmlns': 'https://www.khronos.org/registry/vx/schema', 'xsi:schemaLocation': 'https://registry.khronos.org/OpenVX/schema/openvx-1-1.xsd', 'references': refCount.toString() @@ -235,7 +401,7 @@ class XmlExport { final Set targets = {}; for (var node in graph.nodes) { - targets.add('openvx_${node.target}'); + targets.add('openvx-${node.target}'); } return targets; diff --git a/ui/lib/graph_editor.dart b/ui/lib/graph_editor.dart index 104c1766..d9173c3f 100644 --- a/ui/lib/graph_editor.dart +++ b/ui/lib/graph_editor.dart @@ -32,11 +32,17 @@ class GraphEditorState extends State { int? edgeStartOutput; int _refCount = 0; + // Public getter to check if XML is loaded + bool get isXmlLoaded => _supported.isNotEmpty; + + // Future that completes when XML is loaded + late final Future xmlLoaded; + @override void initState() { super.initState(); _focusNode.requestFocus(); - _loadSupportedXml(); + xmlLoaded = _loadSupportedXml(); } // End of initState @override @@ -70,7 +76,7 @@ class GraphEditorState extends State { if (inputsElement != null) { inputs = inputsElement .findElements('Input') - .map((element) => element.innerText.trim()) + .map((element) => element.innerText.trim().replaceAll('VX_', '')) .toList(); } @@ -79,7 +85,7 @@ class GraphEditorState extends State { if (outputsElement != null) { outputs = outputsElement .findElements('Output') - .map((element) => element.innerText.trim()) + .map((element) => element.innerText.trim().replaceAll('VX_', '')) .toList(); } @@ -180,10 +186,18 @@ class GraphEditorState extends State { void _deleteSelected(Graph graph) { setState(() { if (selectedNode != null) { + // First remove all edges connected to this node graph.edges.removeWhere((edge) => edge.source == selectedNode || edge.target == selectedNode); - _refCount -= - selectedNode!.inputs.length + selectedNode!.outputs.length + 1; + + // Decrement reference count for all inputs and outputs + _refCount -= selectedNode!.inputs.length; + _refCount -= selectedNode!.outputs.length; + + // Decrement reference count for the node itself + _refCount--; + + // Remove the node from the graph graph.nodes.remove(selectedNode); selectedNode = null; } else if (selectedEdge != null) { @@ -204,6 +218,11 @@ class GraphEditorState extends State { final kernel = target.kernels.firstWhere((k) => k.name == kernelName); setState(() { + // Decrement reference count for old inputs and outputs + _refCount -= node.inputs.length; + _refCount -= node.outputs.length; + + // Create new inputs and outputs node.inputs = kernel.inputs .map((input) => Reference.createReference(input, _refCount++)) .toList(); @@ -303,166 +322,184 @@ class GraphEditorState extends State { } } }, - child: Row( + child: Stack( children: [ // Center panel for graph visualization and node/edge creation - Expanded( - child: LayoutBuilder( - builder: (context, constraints) { - return Stack( - children: [ - // Draw the grid background. - Positioned.fill( - child: CustomPaint( - painter: GridPainter( - gridSize: 60, - lineColor: Colors.grey.withAlpha(76)), - ), + LayoutBuilder( + builder: (context, constraints) { + return Stack( + children: [ + // Draw the grid background. + Positioned.fill( + child: CustomPaint( + painter: GridPainter( + gridSize: 60, + lineColor: Colors.grey.withAlpha(76)), ), - graphs.isNotEmpty - ? GestureDetector( - onTapDown: (details) { - final graph = - graphs[selectedGraphIndex]; - final tappedNode = graph.findNodeAt( - details.localPosition); - final tappedEdge = graph.findEdgeAt( - details.localPosition); - setState(() { - if (tappedNode != null) { - // Deselect the selected edge - selectedEdge = null; - if (selectedNode == null) { - selectedNode = tappedNode; - } else { - // _addEdge(graph, selectedNode!, tappedNode); - // Deselect the selected node - selectedNode = null; - } - } else if (tappedEdge != null) { - if (selectedEdge == tappedEdge) { - // Deselect the tapped edge if it is already selected - selectedEdge = null; - } else { - // Deselect the selected node - selectedNode = null; - // Select the tapped edge - selectedEdge = tappedEdge; - } + ), + graphs.isNotEmpty + ? GestureDetector( + onTapDown: (details) { + final graph = + graphs[selectedGraphIndex]; + final tappedNode = graph + .findNodeAt(details.localPosition); + final tappedEdge = graph + .findEdgeAt(details.localPosition); + setState(() { + if (tappedNode != null) { + // Deselect the selected edge + selectedEdge = null; + if (selectedNode == null) { + selectedNode = tappedNode; } else { - _addNode( - graph, - details.localPosition, - constraints.biggest); // Deselect the selected node selectedNode = null; - // Deselect the selected edge - selectedEdge = null; - // Deselect the selected graph row - selectedGraphRow = null; - edgeStartNode = null; - edgeStartOutput = null; } - }); - }, - onPanUpdate: (details) { - setState(() { - mousePosition = - details.localPosition; - if (draggingNode != null) { - final newPosition = - draggingNode!.position + - details.delta; - // Assuming the radius of the node is 25 - final nodeRadius = 25.0; - // Ensure the node stays within the bounds of the center panel - if (newPosition.dx - nodeRadius >= - 0 && - newPosition.dx + nodeRadius <= - constraints.maxWidth && - newPosition.dy - nodeRadius >= - 0 && - newPosition.dy + nodeRadius <= - constraints.maxHeight) { - draggingNode!.position = - newPosition; - } + } else if (tappedEdge != null) { + if (selectedEdge == tappedEdge) { + // Deselect the tapped edge if it is already selected + selectedEdge = null; + } else { + // Deselect the selected node + selectedNode = null; + // Select the tapped edge + selectedEdge = tappedEdge; } - }); - }, - onPanStart: (details) { - setState(() { - final graph = - graphs[selectedGraphIndex]; - draggingNode = graph.findNodeAt( - details.localPosition); - dragOffset = details.localPosition; - }); - }, - onPanEnd: (details) { - setState(() { - draggingNode = null; - dragOffset = null; + } else { + _addNode( + graph, + details.localPosition, + constraints.biggest); + // Deselect the selected node + selectedNode = null; + // Deselect the selected edge + selectedEdge = null; + // Deselect the selected graph row + selectedGraphRow = null; edgeStartNode = null; edgeStartOutput = null; - mousePosition = null; - }); - }, - child: CustomPaint( - painter: graphs.isNotEmpty - ? GraphPainter( - graphs[selectedGraphIndex] - .nodes, - graphs[selectedGraphIndex] - .edges, - selectedNode, - selectedEdge, - mousePosition, - ) - : null, - child: Container(), - ), - ) - : Center( - child: Text('No graphs available')), - ..._buildTooltips(), - ], - ); - }, - ), + } + }); + }, + onPanUpdate: (details) { + setState(() { + mousePosition = details.localPosition; + if (draggingNode != null) { + final newPosition = + draggingNode!.position + + details.delta; + // Assuming the radius of the node is 25 + final nodeRadius = 25.0; + // Ensure the node stays within the bounds of the center panel + if (newPosition.dx - nodeRadius >= 0 && + newPosition.dx + nodeRadius <= + constraints.maxWidth - + (selectedNode != null + ? 240 + : 0) && + newPosition.dy - nodeRadius >= + 0 && + newPosition.dy + nodeRadius <= + constraints.maxHeight) { + draggingNode!.position = + newPosition; + } + } + }); + }, + onPanStart: (details) { + setState(() { + final graph = + graphs[selectedGraphIndex]; + draggingNode = graph.findNodeAt( + details.localPosition); + dragOffset = details.localPosition; + }); + }, + onPanEnd: (details) { + setState(() { + draggingNode = null; + dragOffset = null; + edgeStartNode = null; + edgeStartOutput = null; + mousePosition = null; + }); + }, + child: CustomPaint( + painter: graphs.isNotEmpty + ? GraphPainter( + graphs[selectedGraphIndex] + .nodes, + graphs[selectedGraphIndex] + .edges, + selectedNode, + selectedEdge, + mousePosition, + ) + : null, + child: Container(), + ), + ) + : Center(child: Text('No graphs available')), + ..._buildTooltips(), + ], + ); + }, ), // Right panel for node attributes - NodeAttributesPanel( - graph: graphs.isNotEmpty - ? graphs[selectedGraphIndex] - : null, // Pass null if graphs is empty - selectedNode: selectedNode, - supportedTargets: _supported, - nameController: _nameController, - nameFocusNode: _nameFocusNode, - onNameChanged: (value) { - setState(() { - selectedNode!.name = value; - }); - }, - onTargetChanged: (newValue) { - setState(() { - selectedNode!.target = newValue; - final target = _supported - .firstWhere((t) => t.name == newValue); - if (target.kernels.isNotEmpty) { - selectedNode!.kernel = target.kernels.first.name; - _updateNodeIO( - selectedNode!, selectedNode!.kernel); - } - }); - }, - onKernelChanged: (newValue) { - setState(() { - selectedNode!.kernel = newValue; - _updateNodeIO(selectedNode!, newValue); - }); - }, + Positioned( + top: 0, + right: 0, + bottom: 0, + child: AnimatedSlide( + duration: Duration(milliseconds: 300), + offset: Offset(selectedNode != null ? 0 : 1, 0), + child: AnimatedOpacity( + duration: Duration(milliseconds: 300), + opacity: selectedNode != null ? 1.0 : 0.0, + child: Container( + width: 220, + color: Colors.grey[800], + child: selectedNode != null + ? NodeAttributesPanel( + graph: graphs.isNotEmpty + ? graphs[selectedGraphIndex] + : null, + selectedNode: selectedNode, + supportedTargets: _supported, + nameController: _nameController, + nameFocusNode: _nameFocusNode, + onNameChanged: (value) { + setState(() { + selectedNode!.name = value; + }); + }, + onTargetChanged: (newValue) { + setState(() { + selectedNode!.target = newValue; + final target = _supported.firstWhere( + (t) => t.name == newValue); + if (target.kernels.isNotEmpty) { + selectedNode!.kernel = + target.kernels.first.name; + _updateNodeIO(selectedNode!, + selectedNode!.kernel); + } + }); + }, + onKernelChanged: (newValue) { + setState(() { + selectedNode!.kernel = newValue; + _updateNodeIO( + selectedNode!, newValue); + }); + }, + ) + : null, + ), + ), + ), ), ], )), @@ -605,6 +642,24 @@ class GraphEditorState extends State { reference.elemType = value!; }, ), + TextField( + controller: TextEditingController( + text: reference.values.join(', ')), + decoration: InputDecoration(labelText: 'Values'), + keyboardType: TextInputType.text, + onChanged: (value) { + // Remove trailing commas and split + reference.values = value + .split(',') + .map((e) => e.trim()) + .where((e) => e.isNotEmpty) + .toList(); + }, + onEditingComplete: () => + _updateArrayCapacity(context, reference), + onTapOutside: (event) => + _updateArrayCapacity(context, reference), + ), ], if (reference is Convolution) ...[ // Convolution specific attributes @@ -728,14 +783,34 @@ class GraphEditorState extends State { ], if (reference is ObjectArray) ...[ // ObjectArray specific attributes - TextField( - controller: TextEditingController( - text: reference.numObjects.toString()), - decoration: InputDecoration(labelText: 'Number of Objects'), - keyboardType: TextInputType.number, - onChanged: (value) { - reference.numObjects = - int.tryParse(value) ?? reference.numObjects; + Builder( + builder: (context) { + final controller = TextEditingController( + text: reference.numObjects.toString()); + return TextField( + controller: controller, + decoration: + InputDecoration(labelText: 'Number of Objects'), + keyboardType: TextInputType.number, + onEditingComplete: () { + final newValue = int.tryParse(controller.text) ?? 0; + if (newValue >= 0) { + reference.setNumObjects(newValue); + // Force rebuild of dialog to update the UI + Navigator.of(context).pop(); + _showAttributeDialog(context, reference); + } + }, + onTapOutside: (event) { + final newValue = int.tryParse(controller.text) ?? 0; + if (newValue >= 0) { + reference.setNumObjects(newValue); + // Force rebuild of dialog to update the UI + Navigator.of(context).pop(); + _showAttributeDialog(context, reference); + } + }, + ); }, ), DropdownButtonFormField( @@ -748,9 +823,41 @@ class GraphEditorState extends State { ); }).toList(), onChanged: (value) { - reference.elemType = value!; + if (value != null) { + reference.elemType = value; + // Force rebuild of dialog to show new element type attributes + Navigator.of(context).pop(); + _showAttributeDialog(context, reference); + } + }, + ), + CheckboxListTile( + title: Text('Apply To All Objects'), + value: reference.applyToAll, + onChanged: (value) { + if (value != null) { + reference.applyToAll = value; + // Force rebuild of dialog to show/hide individual attributes + Navigator.of(context).pop(); + _showAttributeDialog(context, reference); + } }, ), + if (reference.applyToAll) ...[ + // Show common attributes for all objects + ..._buildElementTypeAttributes(reference), + ] else if (reference.numObjects > 0) ...[ + // Show individual attributes for each object + ...List.generate(reference.numObjects, (index) { + return ExpansionTile( + title: Text('Object ${index + 1}'), + children: _buildElementTypeAttributes( + reference, + objectIndex: index, + ), + ); + }), + ], ], if (reference is Pyramid) ...[ // Pyramid specific attributes @@ -957,7 +1064,392 @@ class GraphEditorState extends State { }, ); } // End of _showAttributeDialog -} // End of GraphEditorState + + void _updateArrayCapacity(BuildContext context, Array reference) { + // For strings, capacity is based on total character count + // For other types, capacity is based on number of elements + final newCapacity = reference.elemType == 'CHAR' + ? reference.values.join(', ').length + : reference.values.length; + + if (newCapacity != reference.capacity) { + reference.capacity = newCapacity; + // Force rebuild of the dialog to update the capacity field + Navigator.of(context).pop(); + _showAttributeDialog(context, reference); + } + } // End of _updateArrayCapacity + + List _buildElementTypeAttributes(ObjectArray reference, + {int? objectIndex}) { + // Get the appropriate attributes map based on whether we're dealing with individual objects + Map attributes = objectIndex != null + ? (reference.elementAttributes['object_$objectIndex'] + as Map? ?? + {}) + : reference.elementAttributes; + + // Helper function to get attribute value + T? getAttribute(String key) { + return attributes[key] as T?; + } + + // Helper function to set attribute value + void setAttribute(String key, dynamic value) { + if (objectIndex != null) { + final objectKey = 'object_$objectIndex'; + if (!reference.elementAttributes.containsKey(objectKey)) { + reference.elementAttributes[objectKey] = {}; + } + + final objectMap = + reference.elementAttributes[objectKey] as Map; + objectMap[key] = value; + } else { + reference.elementAttributes[key] = value; + } + } + + switch (reference.elemType) { + case 'TENSOR': + return [ + TextField( + controller: TextEditingController( + text: getAttribute('numDims')?.toString() ?? '0'), + decoration: InputDecoration(labelText: 'Number of Dimensions'), + keyboardType: TextInputType.number, + onChanged: (value) { + setAttribute('numDims', int.tryParse(value) ?? 0); + }, + ), + TextField( + controller: TextEditingController( + text: getAttribute>('shape')?.toString() ?? '[]'), + decoration: InputDecoration(labelText: 'Shape'), + onChanged: (value) { + setAttribute( + 'shape', + value + .replaceAll(RegExp(r'[\[\]]'), '') + .split(',') + .map((e) => int.tryParse(e.trim()) ?? 0) + .toList()); + }, + ), + DropdownButtonFormField( + value: getAttribute('elemType') ?? numTypes.first, + decoration: InputDecoration(labelText: 'Element Type'), + items: numTypes.map((type) { + return DropdownMenuItem( + value: type, + child: Text(type), + ); + }).toList(), + onChanged: (value) { + if (value != null) { + setAttribute('elemType', value); + } + }, + ), + ]; + case 'IMAGE': + return [ + TextField( + controller: TextEditingController( + text: getAttribute('width')?.toString() ?? '0'), + decoration: InputDecoration(labelText: 'Width'), + keyboardType: TextInputType.number, + onChanged: (value) { + setAttribute('width', int.tryParse(value) ?? 0); + }, + ), + TextField( + controller: TextEditingController( + text: getAttribute('height')?.toString() ?? '0'), + decoration: InputDecoration(labelText: 'Height'), + keyboardType: TextInputType.number, + onChanged: (value) { + setAttribute('height', int.tryParse(value) ?? 0); + }, + ), + DropdownButtonFormField( + value: getAttribute('format') ?? imageTypes.first, + decoration: InputDecoration(labelText: 'Format'), + items: imageTypes.map((type) { + return DropdownMenuItem( + value: type, + child: Text(type), + ); + }).toList(), + onChanged: (value) { + if (value != null) { + setAttribute('format', value); + } + }, + ), + ]; + case 'ARRAY': + return [ + TextField( + controller: TextEditingController( + text: getAttribute('capacity')?.toString() ?? '0'), + decoration: InputDecoration(labelText: 'Capacity'), + keyboardType: TextInputType.number, + onChanged: (value) { + setAttribute('capacity', int.tryParse(value) ?? 0); + }, + ), + DropdownButtonFormField( + value: getAttribute('elemType') ?? arrayTypes.first, + decoration: InputDecoration(labelText: 'Element Type'), + items: arrayTypes.map((type) { + return DropdownMenuItem( + value: type, + child: Text(type), + ); + }).toList(), + onChanged: (value) { + if (value != null) { + setAttribute('elemType', value); + } + }, + ), + ]; + case 'MATRIX': + return [ + TextField( + controller: TextEditingController( + text: getAttribute('rows')?.toString() ?? '0'), + decoration: InputDecoration(labelText: 'Rows'), + keyboardType: TextInputType.number, + onChanged: (value) { + setAttribute('rows', int.tryParse(value) ?? 0); + }, + ), + TextField( + controller: TextEditingController( + text: getAttribute('cols')?.toString() ?? '0'), + decoration: InputDecoration(labelText: 'Columns'), + keyboardType: TextInputType.number, + onChanged: (value) { + setAttribute('cols', int.tryParse(value) ?? 0); + }, + ), + DropdownButtonFormField( + value: getAttribute('elemType') ?? numTypes.first, + decoration: InputDecoration(labelText: 'Element Type'), + items: numTypes.map((type) { + return DropdownMenuItem( + value: type, + child: Text(type), + ); + }).toList(), + onChanged: (value) { + if (value != null) { + setAttribute('elemType', value); + } + }, + ), + ]; + case 'SCALAR': + return [ + DropdownButtonFormField( + value: getAttribute('elemType') ?? scalarTypes.first, + decoration: InputDecoration(labelText: 'Element Type'), + items: scalarTypes.map((type) { + return DropdownMenuItem( + value: type, + child: Text(type), + ); + }).toList(), + onChanged: (value) { + if (value != null) { + setAttribute('elemType', value); + } + }, + ), + TextField( + controller: TextEditingController( + text: getAttribute('value')?.toString() ?? '0'), + decoration: InputDecoration(labelText: 'Value'), + keyboardType: TextInputType.number, + onChanged: (value) { + setAttribute('value', double.tryParse(value) ?? 0.0); + }, + ), + ]; + case 'CONVOLUTION': + return [ + TextField( + controller: TextEditingController( + text: getAttribute('rows')?.toString() ?? '0'), + decoration: InputDecoration(labelText: 'Rows'), + keyboardType: TextInputType.number, + onChanged: (value) { + setAttribute('rows', int.tryParse(value) ?? 0); + }, + ), + TextField( + controller: TextEditingController( + text: getAttribute('cols')?.toString() ?? '0'), + decoration: InputDecoration(labelText: 'Columns'), + keyboardType: TextInputType.number, + onChanged: (value) { + setAttribute('cols', int.tryParse(value) ?? 0); + }, + ), + TextField( + controller: TextEditingController( + text: getAttribute('scale')?.toString() ?? '0'), + decoration: InputDecoration(labelText: 'Scale'), + keyboardType: TextInputType.number, + onChanged: (value) { + setAttribute('scale', int.tryParse(value) ?? 0); + }, + ), + ]; + case 'PYRAMID': + return [ + TextField( + controller: TextEditingController( + text: getAttribute('numLevels')?.toString() ?? '0'), + decoration: InputDecoration(labelText: 'Number of Levels'), + keyboardType: TextInputType.number, + onChanged: (value) { + setAttribute('numLevels', int.tryParse(value) ?? 0); + }, + ), + TextField( + controller: TextEditingController( + text: getAttribute('width')?.toString() ?? '0'), + decoration: InputDecoration(labelText: 'Width'), + keyboardType: TextInputType.number, + onChanged: (value) { + setAttribute('width', int.tryParse(value) ?? 0); + }, + ), + TextField( + controller: TextEditingController( + text: getAttribute('height')?.toString() ?? '0'), + decoration: InputDecoration(labelText: 'Height'), + keyboardType: TextInputType.number, + onChanged: (value) { + setAttribute('height', int.tryParse(value) ?? 0); + }, + ), + DropdownButtonFormField( + value: getAttribute('format') ?? imageTypes.first, + decoration: InputDecoration(labelText: 'Format'), + items: imageTypes.map((type) { + return DropdownMenuItem( + value: type, + child: Text(type), + ); + }).toList(), + onChanged: (value) { + if (value != null) { + setAttribute('format', value); + } + }, + ), + ]; + case 'REMAP': + return [ + TextField( + controller: TextEditingController( + text: getAttribute('srcWidth')?.toString() ?? '0'), + decoration: InputDecoration(labelText: 'Source Width'), + keyboardType: TextInputType.number, + onChanged: (value) { + setAttribute('srcWidth', int.tryParse(value) ?? 0); + }, + ), + TextField( + controller: TextEditingController( + text: getAttribute('srcHeight')?.toString() ?? '0'), + decoration: InputDecoration(labelText: 'Source Height'), + keyboardType: TextInputType.number, + onChanged: (value) { + setAttribute('srcHeight', int.tryParse(value) ?? 0); + }, + ), + TextField( + controller: TextEditingController( + text: getAttribute('dstWidth')?.toString() ?? '0'), + decoration: InputDecoration(labelText: 'Destination Width'), + keyboardType: TextInputType.number, + onChanged: (value) { + setAttribute('dstWidth', int.tryParse(value) ?? 0); + }, + ), + TextField( + controller: TextEditingController( + text: getAttribute('dstHeight')?.toString() ?? '0'), + decoration: InputDecoration(labelText: 'Destination Height'), + keyboardType: TextInputType.number, + onChanged: (value) { + setAttribute('dstHeight', int.tryParse(value) ?? 0); + }, + ), + ]; + case 'THRESHOLD': + return [ + TextField( + controller: TextEditingController( + text: getAttribute('thresType') ?? ''), + decoration: InputDecoration(labelText: 'Threshold Type'), + onChanged: (value) { + setAttribute('thresType', value); + }, + ), + DropdownButtonFormField( + value: getAttribute('dataType') ?? thresholdDataTypes.first, + decoration: InputDecoration(labelText: 'Data Type'), + items: thresholdDataTypes.map((type) { + return DropdownMenuItem( + value: type, + child: Text(type), + ); + }).toList(), + onChanged: (value) { + if (value != null) { + setAttribute('dataType', value); + } + }, + ), + ]; + case 'LUT': + return [ + TextField( + controller: TextEditingController( + text: getAttribute('capacity')?.toString() ?? '0'), + decoration: InputDecoration(labelText: 'Capacity'), + keyboardType: TextInputType.number, + onChanged: (value) { + setAttribute('capacity', int.tryParse(value) ?? 0); + }, + ), + DropdownButtonFormField( + value: getAttribute('elemType') ?? numTypes.first, + decoration: InputDecoration(labelText: 'Element Type'), + items: numTypes.map((type) { + return DropdownMenuItem( + value: type, + child: Text(type), + ); + }).toList(), + onChanged: (value) { + if (value != null) { + setAttribute('elemType', value); + } + }, + ), + ]; + default: + return []; + } + } +} // End of GraphEditorState class class GraphListPanel extends StatelessWidget { const GraphListPanel({ diff --git a/ui/lib/objects.dart b/ui/lib/objects.dart index 44a20ed1..96155dcc 100644 --- a/ui/lib/objects.dart +++ b/ui/lib/objects.dart @@ -91,6 +91,7 @@ class Reference { String name; String type; int linkId; + Reference({ required this.id, this.name = '', @@ -100,7 +101,7 @@ class Reference { static Reference createReference(String name, int refCount) { // Logic to determine the type of Reference to create - if (name == ('VX_TYPE_ARRAY')) { + if (name == ('TYPE_ARRAY')) { return Array( id: refCount, name: name, capacity: 0, elemType: arrayTypes.first); } else if (name.contains('CONVOLUTION')) { @@ -228,12 +229,14 @@ class Graph extends Reference { class Array extends Reference { int capacity; String elemType; + List values; Array({ required super.id, required super.name, super.type = 'Array', required this.capacity, required this.elemType, + this.values = const [], }); } @@ -245,7 +248,7 @@ class Convolution extends Matrix { required super.rows, required super.cols, this.scale = 1, - super.elemType = 'VX_TYPE_INT16', + super.elemType = 'TYPE_INT16', super.type = 'Convolution', }); } @@ -269,7 +272,7 @@ class Lut extends Array { required super.id, required super.name, required super.capacity, - super.elemType = 'VX_TYPE_UINT8', + super.elemType = 'TYPE_UINT8', super.type = 'Lut', }); } @@ -291,13 +294,41 @@ class Matrix extends Reference { class ObjectArray extends Reference { int numObjects; String elemType; + Map elementAttributes; + bool applyToAll; + ObjectArray({ required super.id, required super.name, super.type = 'ObjectArray', required this.numObjects, required this.elemType, - }); + Map? elementAttributes, + this.applyToAll = true, + }) : elementAttributes = elementAttributes ?? {}; + + // Helper method to get element attribute with type safety + T? getElementAttribute(String key) { + return elementAttributes[key] as T?; + } + + // Helper method to set element attribute + void setElementAttribute(String key, dynamic value) { + elementAttributes[key] = value; + } + + void setNumObjects(int value) { + if (value < 0) { + throw ArgumentError('Number of objects cannot be negative'); + } + // Clean up individual object attributes for removed objects + if (value < numObjects) { + for (int i = value; i < numObjects; i++) { + elementAttributes.remove('object_$i'); + } + } + numObjects = value; + } } class Pyramid extends Reference { diff --git a/ui/test/widget_test.dart b/ui/test/widget_test.dart index a312b5d2..bbb6253e 100644 --- a/ui/test/widget_test.dart +++ b/ui/test/widget_test.dart @@ -6,25 +6,89 @@ // tree, read text, and verify that the values of widget properties are correct. import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; - import 'package:ui/main.dart'; +import 'package:ui/graph_editor.dart'; void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. + late ByteData mockXmlData; + + setUpAll(() { + // Create mock XML data once + final mockXml = ''' + + + + + + input1 + + + output1 + + + + +'''; + final mockXmlBytes = Uint8List.fromList(mockXml.codeUnits); + mockXmlData = ByteData.view(mockXmlBytes.buffer); + + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMessageHandler('flutter/assets', (ByteData? message) async { + if (message == null) return null; + final String assetPath = + String.fromCharCodes(message.buffer.asUint8List()); + if (assetPath == 'assets/supported.xml') { + return mockXmlData; + } + return null; + }); + }); + + testWidgets('App functionality test', (WidgetTester tester) async { + // Launch app and wait for initial build await tester.pumpWidget(const GraphEditorApp()); + await tester.pump(); + + // Verify initial UI + expect(find.text('Edge Studio'), findsOneWidget); + expect(find.byIcon(Icons.code_rounded), findsOneWidget); + expect(find.byType(GraphEditor), findsOneWidget); + expect(find.byType(GraphListPanel), findsOneWidget); + + // Wait for XML to load + final state = tester.state(find.byType(GraphEditor)); + await state.xmlLoaded; + await tester.pump(); + + // Test graph operations + await tester.tap(find.byType(IconButton).first); + await tester.pump(); + + await tester.tap(find.byType(Chip).first); + await tester.pump(); + + await tester.sendKeyEvent(LogicalKeyboardKey.delete); + await tester.pump(); + + expect(find.byType(GraphEditor), findsOneWidget); + + // Test node addition + await tester.tap(find.byType(IconButton).first); + await tester.pump(); + + final graphArea = find.byType(CustomPaint).first; + await tester.tapAt(tester.getCenter(graphArea)); + await tester.pump(); - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); + expect(find.byType(CustomPaint), findsWidgets); - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); + // Test export menu + await tester.tap(find.byIcon(Icons.code_rounded)); await tester.pump(); - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); + expect(find.text('Export DOT'), findsOneWidget); + expect(find.text('Export XML'), findsOneWidget); }); }