User Tools

Site Tools


guides:ai

Running AI Models in Developer Kernel (Experimental)

Documentation | Running AI Models in Developer Kernel (Experimental)

This tutorial explains how to run Python-driven AI models on TatukGIS Viewer snapshots and raster layers.

Status (read this first):

  • This feature is experimental and work in progress.
  • Supported in DK.Delphi (RAD Studio / Delphi) and DK.NET (.NET / C#).
  • DK.Delphi requires the Python4Delphi package.
  • DK.NET requires the Python.NET (pythonnet) package. It is NOT bundled with the TatukGIS DK .NET NuGet package and must be installed separately (the pythonnet NuGet package).
  • Provided samples - AIModelRunner:
    • DK.Delphi - an FMX sample.
    • DK.NET - a WinForms (C#) sample, dual-targeting .NET 6 (net6.0-windows) and .NET Framework 4.6.2.
  • DK.NET must run as a 64-bit (x64) process - Python.NET loads a native pythonXX.dll whose bitness must match the host (a 32-bit process cannot load 64-bit Python).
  • Report bugs and suggestions to support@tatukgis.com or directly to stanislaw.kosinski@tatukgis.com.

1. Introduction

TatukGIS AI runs inference through Python scripts.

What TatukGIS AI Model Runner Is:

  • A runner/bridge to execute Python scripts directly from TatukGIS.
  • An automated pipeline that exports a Viewer snapshot or layer to an image file.
  • A parser that converts a JSON returned by the Python script into TatukGIS results (e.g., layers, polygons).

What TatukGIS AI Model Runner Is Not:

  • A proprietary AI engine or custom AI model.
  • A tool that executes AI directly on the Viewer without a Python intermediary.

Supported inputs

A Viewer snapshot can contain content from multiple layers, but it is less precise than a single layer input. Exporting an image with pixel-perfect precision is difficult when combining multiple layers with different dimensions.

An exported TGIS_LayerPixel maintains single-pixel precision. An imported layer with AI model postprocessing will align perfectly within the project.

Supported outputs (as implemented by GisAIModelOutput)

  • Layer: file-backed layer reference (pixel/vector automatically assigned)
  • Detection: bbox/polygon detections converted to vector polygon layers
  • Logs: strings containing info
  • Raw: fallback / unknown output, optionally capturing stdout/stderr

2. Prerequisites

2.1 Installing Python

Install a Python 3.x runtime and make sure it is usable on the machine where the application runs. The sample and all predefined models were tested on Python 3.10 and that is the version I would recommend for running the sample.

You do not need to install required Python modules manually via pip. The TatukGIS AI feature allows you to specify the required modules for a given script. The system will automatically install these dependencies before executing the Python environment.

2.2 Delphi: Installing Python4Delphi

Install Python4Delphi in RAD Studio via GetIt or manual installation.

You must end up with:

  • Python4Delphi units available for compilation.
  • A working runtime initialization (Python DLL path correctly resolved).

Verification step: Upon installing Python4Delphi, you will find several included demos. Run one of these demos to verify that Python4Delphi correctly locates and uses the Python distribution on your machine. If Python initialization fails in the demo, the TatukGIS AI feature will not run. Resolve environmental issues before proceeding.

2.3 Delphi: Compiling sources with the Python4Delphi define

TatukGIS AI integration requires conditional compilation. After installing Python4Delphi extension, enable the Python4Delphi define in your project settings:

  • Project Options → Delphi Compiler → Conditional defines

Use the following define:

  • GIS_PYTHON4DELPHI

Without this define, Python-backed AI classes will not work.

2.3.1 DK.Delphi Binary Version

If you are using DK.Delphi Binary Version (installed through installer, not SourceCodeManager), having sources for GisAIPython.pas class is essential to run the SDK with the GIS_PYTHON4DELPHI define.

In latest version (DK 112) we have added the necessary source file to installation - it is located in $(DK_SRC)\Source. This path has been added to Rad Studio configuration (Library Paths) as well.

As a result, you may enable the define both in project settings, but also globally through adding:

{$DEFINE GIS_PYTHON4DELPHI}

to $(DK_SRC)\Source\GisAIPython.pas file.

2.3.2 DK.Delphi SourceCodeManager version

If you are using DK.Delphi SourceCodeManager version, DK sources are recompiled when building the project. As so, adding the define to project options is enough.

But of course, you may also enable the define globally through adding:

{$DEFINE GIS_PYTHON4DELPHI}

to $(DK_SRC)\Source\GisAIPython.pas file.

2.4 .NET: Installing Python.NET (pythonnet)

The .NET integration uses Python.NET (the pythonnet package, namespace Python.Runtime) instead of Python4Delphi.

Important: Python.NET is not bundled with the TatukGIS DK .NET NuGet package. You must add it to your own project separately:

  • Add the pythonnet NuGet package (the AIModelRunner sample uses version 3.1.0) alongside the TatukGIS DK .NET NuGet package (e.g. TatukGIS.DK.NET.Windows).

The TatukGIS DK .NET assemblies are already compiled with Python support enabled (the GIS_PYTHONNET define), so - unlike Delphi - you do not need to set any conditional define yourself. You only need to make the Python.Runtime assembly available by adding the pythonnet package. If it is missing at runtime, Python initialization fails with a “Python.Runtime” load error.

As with Delphi, you do not need to pip install the model dependencies manually - the AI feature installs the required modules for a given script automatically.

2.4.1 64-bit (x64) requirement

Python.NET loads the native pythonXX.dll via LoadLibrary, and its bitness must match the host process. Because most Python installations are 64-bit, you should build and run the application as x64:

  • Project Properties → Build → Platform target → x64, or build as AnyCPU with “Prefer 32-bit” unchecked.

A 32-bit process cannot load a 64-bit Python and initialization will fail (e.g. BadImageFormatException loading python3xx.dll, or “This property must be set before runtime is initialized”). The sample shows a warning at startup if it detects it is running as a 32-bit process.


3. AIModelRunner Sample

Start with the AIModelRunner sample to validate the full pipeline. Two samples are provided:

  • DK.Delphi - FMX sample: Products/DK.Delphi/Samples/FMX/Delphi/AIModelRunner
  • DK.NET - WinForms (C#) sample: Products/DK.NET/Samples/WinForms/CS/AIModelRunner (open AIModelRunner.sln for .NET 6, or AIModelRunner_NETFramework.sln for .NET Framework 4.6.2)

Both samples use the following command to download SampleData (models, config files, example images) onto your PC:

TGIS_Utils.GisSamplesDataDirDownload('AI.1')

It's around 200mb of data and it may take a moment to download the data.

I would recommend Python 3.10 to use with the sample. You may define which Python DLL should be used:

  • Delphi: PythonWorker.Python.SetPythonDllPath()
  • .NET: set it inside the worker's OnConfigure handler, e.g. pm.SetPythonDllPath(@“C:\Python310\python310.dll”). If you leave the path empty, Python.NET auto-detects Python from the PYTHONNET_PYDLL environment variable or the system PATH.

3.1 Running Upscaling sample using Real-ESRGAN model

The sample includes a pre-configured setup for Real-ESRGAN, an AI model used for image resolution upscaling. This requires no additional configuration.

Validation steps:

  • Load a small raster extent in the Viewer.
  • Zoom in on the object you want to upscale. The sample only upscales the VisibleExtent.
  • Run the Real-ESRGAN AI action on the Viewer/Layer input.
  • Confirm that:
    • Python starts successfully.
    • Required modules install automatically.
    • The output raster is produced and georeferencing is preserved (the final image is located correctly on the map).

If this step fails, investigate your Python/Python4Delphi environment setup. Do not attempt custom models yet.

3.2 Running object detection sample using MMRotate model

The sample may also be used with an MMRotate model, an AI model used for oriented object detection. Unlike Real-ESRGAN, this model requires additional config files. They are included in SampleData downloaded through TGIS_Utils.GisSamplesDataDirDownload('AI.1').

MMRotate wrapper will create a vector layer for each object category it detected and will add it to Viewer.

Validation steps:

  • Load a small raster extent in the Viewer.
  • Zoom to the area you want to analyze. The sample runs detection only on the exported VisibleExtent.
  • Run the MMRotate model on the Viewer/Layer input.
  • Confirm that:
    • Python starts successfully.
    • Required Python modules are available.
    • The model loads without errors.
    • Detection results are returned.
    • Output vector layer or layers are created correctly.
    • Detection geometry is placed correctly on the map.

If this step fails, first investigate your Python/Python4Delphi environment setup. Make sure that all required model files, config files, and Python package dependencies are present and compatible before troubleshooting the TatukGIS integration itself.

3.3 Running Custom Models via the Sample

Once predefined models work, you can use the AIModelRunner sample interface to load a custom model. You will need to provide the model file, the Python script, and specify the necessary dependencies. The steps for structuring custom models are detailed below.


4. Running Custom Models

Custom models are driven by TGIS_AIModelCustom (unit GisAIModelCustom). You must provide:

  • A model file path.
  • A list of required Python modules (for automatic installation).
  • A Python script template that performs inference and prints a JSON manifest consumed by TGIS_AIModelOutput.

4.1 Model File Formats

While the system defaults to looking for .onnx or .pt (PyTorch) extensions, the model file can be in any format. TatukGIS does not strictly enforce the extension; the provided Python script is solely responsible for loading and executing the model correctly.

4.2 Tutorial: Adapting External Models (e.g., Ultralytics YOLO)

This tutorial demonstrates the structural requirements for adapting an external model to the TatukGIS JSON contract. Ultralytics YOLO OBB (Oriented Bounding Box) is used as the conceptual example.

Third-party licensing notice (Ultralytics example): Ultralytics YOLO, the ultralytics Python package, model weights, and related documentation are third-party materials and are not part of any TatukGIS product, license, source code, or distribution. This section is provided solely to demonstrate how a third-party model may be adapted to interoperate with TatukGIS software through a Python script. TatukGIS does not grant, transfer, sublicense, or imply any rights to use Ultralytics software, models, trademarks, or documentation.

Use of Ultralytics software and models is governed exclusively by Ultralytics' own license terms, which may change over time. Personal, research, or open-source use may be available under Ultralytics' published terms. Proprietary or closed-source commercial use may require obtaining an appropriate license directly from Ultralytics. Before any internal, customer-facing, or commercial deployment, review the current terms on the official Ultralytics pages:
* Ultralytics licensing page
* Ultralytics plans / commercial licensing page

This documentation is technical guidance only and is not legal advice. You are responsible for verifying third-party licensing obligations before use or distribution.

Step 1: Define Required Modules

Configure TatukGIS to install the necessary packages required by the vendor's model. For a YOLO model, you would specify: ['ultralytics', 'numpy']

Step 2: Provide the Model File

Acquire the model file. For Ultralytics, download a pre-trained OBB model (e.g., yolo11n-obb.pt) from the Ultralytics GitHub repository or train your own custom weights. Pass this absolute path to the TatukGIS wrapper.

Step 3: Build the Python Script Wrapper

Due to licensing compliance, you must write the model execution code yourself using the vendor's documentation. For the specific inference code required to load the model and extract bounding boxes, refer to the Ultralytics OBB documentation.

The script below provides the required wrapper. It shows how to capture the input paths from TatukGIS and how to structure the final JSON output.

TatukGIS replaces the placeholders («MODEL_PATH» and «IMAGE_PATH») with single-quoted absolute paths at runtime.

import json
# Import chosen third-party AI libraries here (e.g., ultralytics, numpy)
 
# TatukGIS runtime parameters injected automatically
MODEL_PATH = r<<MODEL_PATH>>
IMAGE_PATH = r<<IMAGE_PATH>>
 
def run_inference():
    # -----------------------------------------------------------------
    # 1. LOAD MODEL AND RUN INFERENCE
    # -----------------------------------------------------------------
    # Example conceptual flow:
    # model = load_vendor_model(MODEL_PATH)
    # results = model(IMAGE_PATH)
 
    # -----------------------------------------------------------------
    # 2. POSTPROCESS INTO TATUKGIS JSON CONTRACT
    # -----------------------------------------------------------------
    detections = []
 
    # Example mapping logic for Oriented Bounding Box
    # for result in results:
    #     for obb in result.extracted_bounding_boxes:
    #         detections.append({
    #             "LabelText": str(obb.class_name),
    #             "ClassId": int(obb.class_id),
    #             "Score": float(obb.confidence),
    #             "GeomKind": "Polygon",
    #             "Polygon": obb.points.tolist() # Format: [[x,y], [x,y], ...]
    #         })
 
    # Example fallback dimensions
    img_h, img_w = 0, 0
 
    manifest = {
        "Version": 1,
        "Items": [
            {
                "SourceType": "Detection",
                "CoordSpace": "Image",
                "ImageWidth": int(img_w),
                "ImageHeight": int(img_h),
                "Detections": detections
            }
        ]
    }
 
    # Output must be printed to stdout for TatukGIS to parse
    print(json.dumps(manifest, ensure_ascii=False))
 
if __name__ == "__main__":
    run_inference()

4.3 Script Output Contract (JSON)

The TGIS_AIModelOutput.ParseJson() method expects a root JSON object containing an array of items.

Root Structure

Property Type Requirement Description
Version Integer Optional Contract version (defaults to 1).
Items Array Required Array of output item objects.

Item Definitions by SourceType

1. SourceType: “Layer” (Loads a file-backed vector or raster layer)

Property Type Requirement Description
Path String Required Absolute path to the file on disk.
Title String Optional Layer name in the Viewer.

2. SourceType: “Detection” (Generates vector polygon layers from bounding boxes/polygons)

Property Type Requirement Description
Detections Array Required Array of Detection Objects (see below).
CoordSpace String Optional “Image” (pixels, top-left origin) or “Map” (map units).
ImageWidth Integer Optional Width of the source image.
ImageHeight Integer Optional Height of the source image.

Detection Object Structure:

Property Type Requirement Description
GeomKind String Required “BBox” or “Polygon”.
LabelText String Optional Classification label.
ClassId Integer Optional Classification ID.
Score Float Optional Confidence score.
BBoxXMin, BBoxYMin, BBoxXMax, BBoxYMax Float Conditional Required if GeomKind is “BBox”.
Polygon Array Conditional Required if GeomKind is “Polygon”. Format: [[x,y], [x,y], ...].

3. SourceType: “Logs” (Outputs processing logs)

Property Type Requirement Description
Messages Array Required Array of string messages.

4. SourceType: “Raw” (Fallback container for unparsed standard output)

Property Type Requirement Description
StdOut String Optional Captured standard output.
StdErr String Optional Captured standard error.

Example JSON Manifest

{
  "Version": 1,
  "Items": [
    {
      "SourceType": "Layer",
      "Path": "C:\\temp\\output_raster.tif",
      "Title": "Processed Raster"
    },
    {
      "SourceType": "Detection",
      "CoordSpace": "Image",
      "ImageWidth": 1024,
      "ImageHeight": 1024,
      "Detections": [
        {
          "GeomKind": "BBox",
          "LabelText": "Vehicle",
          "ClassId": 2,
          "Score": 0.95,
          "BBoxXMin": 150.0,
          "BBoxYMin": 200.0,
          "BBoxXMax": 350.0,
          "BBoxYMax": 400.0
        },
        {
          "GeomKind": "Polygon",
          "LabelText": "Structure",
          "ClassId": 5,
          "Score": 0.88,
          "Polygon": [[10, 10], [50, 10], [50, 50], [10, 50]]
        }
      ]
    }
  ]
}

4.4 Running Custom Models Programmatically (Without UI)

To execute custom models in a background process or non-GUI application, bypass the UI components and interact directly with the core AI classes.

Execution Steps

Step Action Relevant Method / Class
1 Initialize Python runtime TGIS_AIPythonManager.Create and .Initialize
2 Instantiate the model TGIS_AIModelCustom.Create()
3 Export map state to image TGIS_ImageExporter.ExportToImage()
4 Verify dependencies Model.InstallModules()
5 Run inference Model.Run()
6 Retrieve results GetDetections(), GetDetectionLayers(), and GetLayers()

Code Example (Delphi)

procedure RunModelHeadless(const LayerPix: TGIS_LayerPixel; const Viewer: TGIS_ViewerWnd);
var
  PythonManager: TGIS_AIPythonManager;
  Model: TGIS_AIModelCustom;
  RequiredModules: TArray<string>;
  ScriptText: string;
  ImagePath: string;
  AlignedExtent: TGIS_Extent;
  Dx, Dy: Double;
  RawDetections: TArray<TGIS_AIDetection>;
  Detection: TGIS_AIDetection;
  DetectionLayers: TObjectList<TGIS_LayerVector>;
  DetectionLayer: TGIS_LayerVector;
  LayerItem: TGIS_LayerAbstract;
begin
  // 1. Initialize Python runtime
  PythonManager := TGIS_AIPythonManager.Create;
  try
    if not PythonManager.Initialize then
      raise Exception.Create('Python initialization failed.');
 
    // 2. Instantiate the model
    SetLength(RequiredModules, 1);
    RequiredModules[0] := 'numpy';
    ScriptText := TFile.ReadAllText('C:\Path\To\script.py');
 
    Model := TGIS_AIModelCustom.Create(
      'HeadlessModel',
      PythonManager,
      'C:\Path\To\model.pt',
      ScriptText,
      RequiredModules
    );
 
    try
      // 3. Export map state to image
      ImagePath := TGIS_ImageExporter.ExportToImage(LayerPix, Viewer.VisibleExtent, AlignedExtent);
 
      try
        // 4. Verify dependencies
        Model.InstallModules;
 
        // 5. Run inference
        if Model.Run(ImagePath) then
        begin
          // GetPixelSize determines how a single pixel from the output image translates
          // to distance in the map extent. This is required to properly map outputs.
          TGIS_ImageExporter.GetPixelSize(LayerPix, Dx, Dy);
 
          // 6a. Retrieve raw detections for manual processing
          RawDetections := Model.LastResult.GetDetections;
          for Detection in RawDetections do
          begin
            // Map the detection to a given layer or process geometry manually
            // e.g., read Detection.GeomKind, Detection.Polygon, or Detection.LabelText
          end;
 
          // 6b. Retrieve pre-built detection layers
          DetectionLayers := Model.LastResult.GetDetectionLayers(AlignedExtent, Dx, Dy);
          if Assigned(DetectionLayers) then
          begin
            for DetectionLayer in DetectionLayers do
              Viewer.Add(DetectionLayer);
          end;
 
          // 6c. Retrieve and render standard layers
          for LayerItem in Model.LastResult.GetLayers() do
          begin
            if LayerItem is TGIS_LayerVector then
              (LayerItem as TGIS_LayerVector).Extent := AlignedExtent
            else if LayerItem is TGIS_LayerPixel then
              (LayerItem as TGIS_LayerPixel).Extent := AlignedExtent;
 
            Viewer.Add(LayerItem);
          end;
        end;
      finally
        if FileExists(ImagePath) then
          TFile.Delete(ImagePath);
      end;
    finally
      Model.Free;
    end;
  finally
    PythonManager.Free;
  end;
end;

Code Example (.NET / C#)

using System;
using System.IO;
using TatukGIS.NDK;
using TatukGIS.NDK.WinForms; // TGIS_ViewerWnd
using GisAIPythonManager; // TGIS_AIPythonManager
using GisAIModelCustom;   // TGIS_AIModelCustom
using GisAIModelOutput;   // TGIS_AIDetection
using GisImageExporter;   // TGIS_ImageExporter
 
// NOTE: requires the "pythonnet" NuGet package and a 64-bit (x64) build.
void RunModelHeadless(TGIS_LayerPixel layerPix, TGIS_ViewerWnd viewer)
{
    // 1. Initialize Python runtime
    TGIS_AIPythonManager pythonManager = new TGIS_AIPythonManager();
    try
    {
        // Optional: pin a specific interpreter. Leave unset to let Python.NET
        // auto-detect (PYTHONNET_PYDLL env var or system PATH).
        // pythonManager.SetPythonDllPath(@"C:\Python310\python310.dll");
 
        if (!pythonManager.Initialize())
            throw new Exception("Python initialization failed: " + pythonManager.LastError);
 
        // 2. Instantiate the model
        string[] requiredModules = new string[] { "numpy" };
        string scriptText = File.ReadAllText(@"C:\Path\To\script.py");
 
        TGIS_AIModelCustom model = new TGIS_AIModelCustom(
            "HeadlessModel",
            pythonManager,
            @"C:\Path\To\model.pt",
            scriptText,
            requiredModules);
        try
        {
            // 3. Export map state to image
            TGIS_Extent alignedExtent;
            string imagePath = TGIS_ImageExporter.ExportToImage(layerPix, viewer.VisibleExtent, out alignedExtent);
            try
            {
                // 4. Verify dependencies
                model.InstallModules();
 
                // 5. Run inference
                if (model.Run(imagePath))
                {
                    // GetPixelSize maps one output pixel to a distance in map units,
                    // which is required to place outputs correctly.
                    double dx, dy;
                    TGIS_ImageExporter.GetPixelSize(layerPix, out dx, out dy);
 
                    // 6a. Raw detections for manual processing
                    foreach (TGIS_AIDetection detection in model.LastResult.GetDetections())
                    {
                        // Map the detection to a given layer or process geometry manually
                        // e.g., read Detection.GeomKind, Detection.Polygon, or Detection.LabelText
                    }
 
                    // 6b. Pre-built detection layers
                    TGIS_LayerVector[] detectionLayers =
                        model.LastResult.GetDetectionLayers(alignedExtent, dx, dy);
                    if (detectionLayers != null)
                        foreach (TGIS_LayerVector dl in detectionLayers)
                            viewer.Add(dl);
 
                    // 6c. Standard layers
                    foreach (object layerItem in model.LastResult.GetLayers())
                    {
                        TGIS_LayerVector lv = layerItem as TGIS_LayerVector;
                        if (lv != null) { lv.Extent = alignedExtent; viewer.Add(lv); continue; }
                        TGIS_LayerPixel lp = layerItem as TGIS_LayerPixel;
                        if (lp != null) { lp.Extent = alignedExtent; viewer.Add(lp); }
                    }
                }
            }
            finally
            {
                if (File.Exists(imagePath)) File.Delete(imagePath);
            }
        }
        finally
        {
            model.Dispose();   // .NET uses Dispose() rather than Delphi's Free
        }
    }
    finally
    {
        pythonManager.Dispose();
    }
}
2026/06/17 14:53

Page Tools