VEX V5 PYTHON | 08.2 AI VISION DATA

Guide objective

Configure AI Vision, select a detection mode and inspect bounding boxes, classification IDs and confidence scores before controlling movement.

01 | SEPARATE SETUP, OBSERVATION AND ACTION

In 08.1 you used a coloured target to steer. Here, keep the robot stationary and examine exactly what the AI Vision Sensor reports.

Build three layers: configure what to detect, read and validate observations, then decide what a robot should do. A diagnostic monitor tests the first two without hiding errors behind movement.

The complete program below has no motor commands. Use it as a standalone sensor project, not a second loop pasted into an autonomous movement program.

02 | CONNECT AND NAME THE DEVICE

  1. Use desktop VEXcode V5 and a Python project.
  2. Connect AI Vision to an available Brain smart port; this guide uses port 15.
  3. Add AI Vision in Devices and select that port.
  4. Name it ai_vision_1, or adapt every example to your generated name.
  5. Open the sensor utility using the connection workflow shown by VEXcode; use the official setup guide below if prompted for a direct sensor connection.
  6. Follow any required firmware-update prompts, then save the device configuration.

Mount the sensor rigidly and upright. Aim it at the region your task needs, with the lens unobstructed. Keep the mounting consistent while comparing trials.

Changing the project name is not the same as changing a generated device variable. Check the declarations that VEXcode actually creates.

03 | CHOOSE A DETECTION MODE

Enable the feature you intend to inspect
ModeConfigure firstSnapshot selector
Colour signatureSample and save TARGET in the utilityai_vision_1__TARGET
AI classificationEnable AI detection and select an available modelAiVision.ALL_AIOBJS
AprilTagsEnable tag detectionAiVision.ALL_TAGS

Colour matching asks whether the scene contains your sampled appearance. Classification asks whether it resembles a class supported by the selected model. A tag provides a marker ID.

Do not assume enabling AI means every classroom object becomes recognizable. Check the classes provided by the installed configuration. Tag-specific selection and missing detections are covered in 08.3.

04 | SAVE A COLOUR SIGNATURE

For the first run, use MODE = COLOUR. Present a distinctive, solid-coloured target under representative lighting, select a clean region and save its signature as TARGET.

Adjust the permitted colour variation while checking target and background detections. Test shadows and highlights rather than tuning against one ideal image.

VEXcode generates names such as ai_vision_1__TARGET. Retain the utility-generated camera and signature configuration. Write your own functions below # Begin project code, outside the generated region.

A Color Code combines configured colour signatures into a pattern. It is useful when one colour alone is ambiguous, but still needs suitable target layout and configuration.

05 | READ A SNAPSHOT SAFELY

objects = ai_vision_1.take_snapshot(ai_vision_1__TARGET)
if not objects:
    print("No matching detections")
else:
    for index, obj in enumerate(objects):
        if obj.exists:
            print(index, obj.centerX, obj.centerY, obj.width, obj.height)

Never index objects[0] until you have checked that the tuple is non-empty. An empty result is a normal observation, while a disconnected device or API exception is a separate failure.

The API’s returned order is by width. The first entry is not necessarily the largest by area, the desired class or the same physical object as the previous first entry.

The inspector requests at most eight detections. That is a result cap, not proof that only eight or fewer objects exist in the scene.

06 | UNDERSTAND PIXELS AND THE BOX

Read properties in the context of the selected mode
FieldInterpretation
originX / originYTop-left of bounding box
centerX / centerYBox centre
width / heightBox dimensions
existsWhether the returned entry represents a detection
idClassification or tag identifier, interpreted by mode
scoreAI classification confidence score; not a colour-quality score
Read a bounding box in image coordinatesAn example box starts at X 180 Y 60, has width 80 and height 60, and is centred at X 220 Y 90. Its centre is 60 pixels right of the image midpoint.Origin of image: (0, 0)Box origin: (180, 60)Height: 60Width: 80Centre: (220, 90)Image centre X = 160Illustrative image drawn wider; coordinates remain in pixels.
Box origin is its top-left corner. Image Y increases downward, unlike field +Y in our navigation convention.

In the worked example, box area is 80 × 60 = 4,800 pixel². Horizontal centre error is 220 − 160 = +60 pixels. Neither gives a physical distance without additional calibration.

A box can include background and can change when part of the target leaves view. Do not treat rectangle area as the exact object silhouette.

07 | CLASS IDs DEPEND ON THE MODEL

A classification ID is a label within the chosen model. It is not a permanent identity assigned to one physical object.

Inspect the enabled model and its class list before setting TARGET_CLASS_ID. Different models can assign the same numerical ID to different classes.

The inspector prints IDs in AI mode and can filter to one configured class. TARGET_CLASS_ID = None means inspect all returned classes.

A confidence score helps rank evidence, but a score of 80 should not be treated as a guaranteed 80% real-world correctness rate. Compare scores with actual test outcomes.

08 | SELECT A CANDIDATE EXPLICITLY

The inspector separates all validated records from candidates that pass the area and optional classification filters. Among candidates, it selects the largest box area.

Selection rule changes the result
Example recordAreaPossible outcome
A: 80 × 201,600 pixel²Wider but smaller area
B: 50 × 502,500 pixel²Selected by area if both pass filters

In AI mode, the starter also requires score ≥ 70 and, if configured, a matching class ID. These are teaching settings to test, not universal thresholds.

CANDIDATE means a record passed this policy. It does not mean it is safe to drive toward, unique or consistently tracked across snapshots.

09 | COMPLETE STATIONARY DATA INSPECTOR

Configure ai_vision_1 on port 15. For the default COLOUR mode, save TARGET first. Keep generated VEX imports, Brain and device configuration above this code.

The program declares a Controller for B cancellation; use your existing generated controller instead if one is already configured. It does not need drive motors, odometry or inertial calibration.

# =============================================================================
# TEAM [MY TEAM NAME / NUMBER] | STATIONARY AI VISION INSPECTOR
# =============================================================================
# Configure AI Vision in Devices on port 15, named ai_vision_1.
# For COLOUR mode, configure a colour signature named TARGET.
# Keep generated VEX imports, brain and camera setup above '# Begin project code'.
# This standalone program has NO motor commands or competition movement callbacks.
import math
controller = Controller(PRIMARY)  # Do not duplicate a generated Controller.

# =============================================================================
# INSPECTION SETTINGS
# =============================================================================
MODE = "COLOUR"       # COLOUR, AI or TAGS; enable the corresponding mode in Devices
RUN_MS = 20000
POLL_MS = 250
MAX_SNAPSHOT_MS = 500
MAX_OBJECTS = 8       # Requested result cap, not total objects present in the scene
MIN_AREA_PX2 = 150
MIN_AI_SCORE = 70     # Teaching threshold; test rather than treating as probability
TARGET_CLASS_ID = None  # Optional AI class filter; use the configured model's IDs
IMAGE_WIDTH = 320.0
IMAGE_HEIGHT = 240.0

def selected_signature():
    if MODE == "COLOUR":
        return ai_vision_1__TARGET
    if MODE == "AI":
        return AiVision.ALL_AIOBJS
    if MODE == "TAGS":
        return AiVision.ALL_TAGS
    raise ValueError("Choose COLOUR, AI or TAGS")

def object_record(obj, mode, index):
    if not obj.exists:
        return None
    values = (obj.originX, obj.originY, obj.centerX, obj.centerY,
              obj.width, obj.height)
    if not all(math.isfinite(v) for v in values):
        raise ValueError("Non-finite box data")
    ox, oy, cx, cy, width, height = values
    if not (0 <= ox <= IMAGE_WIDTH and 0 <= oy <= IMAGE_HEIGHT
            and 0 <= cx <= IMAGE_WIDTH and 0 <= cy <= IMAGE_HEIGHT
            and 0 < width <= IMAGE_WIDTH and 0 < height <= IMAGE_HEIGHT):
        raise ValueError("Box data outside image bounds")
    record = {"index": index, "origin_x": ox, "origin_y": oy,
              "x": cx, "y": cy, "width": width, "height": height,
              "area": width*height, "error_x": cx-IMAGE_WIDTH/2,
              "class_or_tag_id": None, "score": None}
    if mode in ("AI", "TAGS"):
        identity = obj.id
        if not math.isfinite(identity) or identity < 0 or int(identity) != identity:
            raise ValueError("Invalid classification/tag ID")
        record["class_or_tag_id"] = int(identity)
    if mode == "AI":
        score = obj.score
        if not math.isfinite(score) or not 1 <= score <= 100:
            raise ValueError("Invalid AI score")
        record["score"] = score
    return record

def analyse_objects(objects, mode):
    records = []
    for index, obj in enumerate(objects):
        record = object_record(obj, mode, index)
        if record is not None:
            records.append(record)
    candidates = []
    for r in records:
        if r["area"] < MIN_AREA_PX2:
            continue
        if mode == "AI":
            if r["score"] < MIN_AI_SCORE:
                continue
            if TARGET_CLASS_ID is not None and r["class_or_tag_id"] != TARGET_CLASS_ID:
                continue
        candidates.append(r)
    if not records:
        return "NO_DETECTIONS", records, None
    if not candidates:
        return "FILTERED_OUT", records, None
    # Deliberate area selection; snapshot order is not persistent object identity.
    chosen = max(candidates, key=lambda r: r["area"])
    return "CANDIDATE", records, chosen

def print_frame(status, records, chosen, snapshot_ms):
    print("MODE", MODE, "STATUS", status, "VALID", len(records),
          "SNAPSHOT_MS", snapshot_ms)
    for r in records:
        print("INDEX", r["index"], "ID", r["class_or_tag_id"], "SCORE", r["score"],
              "ORIGIN", r["origin_x"], r["origin_y"], "CENTRE", r["x"], r["y"],
              "SIZE", r["width"], r["height"], "AREA", r["area"])
    brain.screen.clear_screen()
    brain.screen.set_cursor(1, 1)
    brain.screen.print(MODE, " ", status)
    brain.screen.set_cursor(2, 1)
    brain.screen.print("Valid: ", len(records))
    if chosen is not None:
        brain.screen.set_cursor(3, 1)
        brain.screen.print("X/Y: ", chosen["x"], "/", chosen["y"])
        brain.screen.set_cursor(4, 1)
        brain.screen.print("X error: ", chosen["error_x"], " px")
        brain.screen.set_cursor(5, 1)
        brain.screen.print("Area: ", chosen["area"])
        print("SELECTED_INDEX", chosen["index"], "ERROR_X", chosen["error_x"])

def inspect_ai_vision():
    try:
        if (MODE not in ("COLOUR", "AI", "TAGS")
                or not all(math.isfinite(v) for v in
                    (RUN_MS, POLL_MS, MAX_SNAPSHOT_MS, MAX_OBJECTS,
                     MIN_AREA_PX2, MIN_AI_SCORE))
                or RUN_MS <= 0 or POLL_MS <= 0 or MAX_SNAPSHOT_MS <= 0
                or not 1 <= MAX_OBJECTS <= 24 or int(MAX_OBJECTS) != MAX_OBJECTS
                or MIN_AREA_PX2 < 0 or not 1 <= MIN_AI_SCORE <= 100
                or (TARGET_CLASS_ID is not None and
                    (not math.isfinite(TARGET_CLASS_ID) or TARGET_CLASS_ID < 0
                     or int(TARGET_CLASS_ID) != TARGET_CLASS_ID))):
            return "INVALID_SETTINGS"
        signature = selected_signature()
        started = brain.timer.time(MSEC)
        while brain.timer.time(MSEC)-started < RUN_MS:
            if controller.buttonB.pressing():
                return "CANCELLED"
            if not ai_vision_1.installed():
                return "SENSOR_NOT_INSTALLED"
            before = brain.timer.time(MSEC)
            objects = ai_vision_1.take_snapshot(signature, int(MAX_OBJECTS))
            after = brain.timer.time(MSEC)
            if controller.buttonB.pressing():
                return "CANCELLED"
            if after-started >= RUN_MS:
                return "FINISHED"
            if after-before > MAX_SNAPSHOT_MS:
                return "SLOW_SNAPSHOT"
            status, records, chosen = analyse_objects(objects, MODE)
            print_frame(status, records, chosen, after-before)
            wait(POLL_MS, MSEC)
        return "FINISHED"
    except Exception:
        return "CONFIGURATION_OR_DATA_FAULT"

result = inspect_ai_vision()
print("INSPECTOR_END", result)
brain.screen.clear_screen()  # Remove the last candidate when monitoring ends.
brain.screen.set_cursor(1, 1)
brain.screen.print("Inspector: ", result)

Open the VEXcode console for every validated record. The Brain shows the current candidate summary. The screen is cleared each observation, and cleared again when the inspector ends, so an old candidate is not left displayed as current.

RUN_MS bounds the observation session. The measured snapshot duration is host-call duration, not a sensor-supplied exposure timestamp. A slow-call check runs after the API returns; it cannot interrupt a blocked API call.

10 | DISTINGUISH OBSERVATION FROM FAILURE

No detection and an unavailable sensor require different explanations
StatusMeaning
NO_DETECTIONSNo existing entries in the current result
FILTERED_OUTRecords exist, but none pass the chosen filters
CANDIDATEOne record selected by this policy
SENSOR_NOT_INSTALLEDDevice connection check failed
SLOW_SNAPSHOTThe snapshot call exceeded the timing policy
CONFIGURATION_OR_DATA_FAULTGenerated name, API access or returned data failed
CANCELLED / FINISHEDUser stopped the inspector or the session ended

If changing MODE to AI yields no detections, check the model and enabled feature before changing the colour signature. They are different detection mechanisms.

If records appear but FILTERED_OUT persists, inspect size, score and class filtering independently. Do not loosen every setting at once.

11 | BUILD A SMALL TEST MATRIX

Keep the camera fixed and record results while changing one factor at a time.

Compare the data with the actual scene
TrialRecord
Target left, centre, rightCentre coordinates and horizontal error
Near and farWidth, height and area
Bright and dim lightDetection count and stability
Target partially hiddenBox changes and missed detections
Two similar targetsReturned order and selected index
Wrong class or distractorID, score and filtering result
Target removedNO_DETECTIONS without stale selection

Use repeated trials. One correct detection does not establish reliability, and the selected index can change when boxes change width.

12 | COMMON MISTAKES

  • Reading before a snapshot: properties describe a captured observation.
  • Using an old generated name: match the actual device and signature declarations.
  • Hard-coding a universal class ID: check the selected model.
  • Treating tuple index as identity: detection ordering can change.
  • Using score for a colour signature: score belongs to AI classification.
  • Confusing image Y and field Y: image Y increases downward.
  • Turning a diagnostic candidate into movement immediately: add selection, freshness and loss policies first.

CHALLENGES | CONFIGURE, INSPECT AND EXPLAIN

  1. Configure: save a signature and identify its generated Python name.
  2. Read: report all boxes from one snapshot without indexing an empty tuple.
  3. Calculate: find area and horizontal error for a 40 × 30 box centred at X = 110.
  4. Compare: show a scene where width order and area selection choose different entries.
  5. Filter: inspect a class filter using the selected model’s actual IDs.
  6. Mock: distinguish no detections, filtered detections and malformed data.
  7. Measure: complete a test matrix under three lighting conditions.
  8. Evaluate: explain why a high score does not guarantee object identity or a collision-free approach.
Calculation check

Area = 40 × 30 = 1,200 pixel². Horizontal error = 110 − 160 = −50 pixels, so the centre is left of the image midpoint.

OFFICIAL REFERENCES

The inspector and filtering rules are independently written teaching examples. Verify the generated names and behaviour with your own sensor configuration.