Visualization Problem when uploading Video annotations from SDK

Hi there, I’m experiencing unexpected behavior on the Labelbox platform. When I upload video segmentation masks using the SDK, the annotations are not displayed correctly on the frame timeline. Specifically, not all frames with uploaded segmentation masks appear in the timeline. However, if I manually select a frame where a segmentation mask is expected, it does display correctly in the visualizer, even though it’s missing from the timeline.

There is a workaround, but I can’t use it as it’s too time-consuming. When I manually add another segmentation mask on any frame, a drop-down menu appears for that segmentation class. Strangely, this makes all the previously invisible segmentation become visible on the frame timeline.

Maybe I am doing something wrong in the code, but I do not get any errors on the import task.

2 Likes

Hi Christian,

A few questions to help as I investigate:

  1. Are you able to provide the project ID that this is occurring in?
  2. Are you also able to provide any relevant code you are using for this task?
  3. Could you provide some context about your workflow here? (How data is being stored/imported/used?)

Best,
Christian

2 Likes

Sorry for the delayed response. I’ll provide a detailed explanation.

Project ID

I could share the project IDs for both annotation projects, but they are not linked to an account associated with the email I am writing from.

Benchmark annotation project_id: cm8h82a0l0arr07vlgnnf8r1v
Upload annotation project_id: cm8h9cbaq0nqg07zmflpxashg

Workflow

I have annotations stored locally on my computer, and I want to upload them as pre-labeling/MAL into an existing project that already contains unlabeled data rows.

Objective

I’m trying to achieve the following:

  1. For each frame of a video, I collect all the corresponding grayscale masks and merge them into a single RGB mask.
  2. I then write this composite mask to a byte buffer and use it as the mask associated with that frame.

Code for Loading Video Annotation Masks

The _write function processes all annotations for a given video. It generates a Label from a list of VideoMaskAnnotation objects by creating a composite mask for each frame using the combine_masks function.

THIS IS PSEUDO CODE JUST TO GIVE AN IDEA OF WHAT I AM DOING

import labelbox.types as lb_types
import io 
import numpy as np
from PIL import Image

instance_colors = {
    "solid" : (255,0,0)
}

def is_empty_mask(mask:np.ndarray):
    return np.max(mask) == 0

def is_gray_scale(mask:np.ndarray):
    return len(mask.shape) == 2

def validate_mask(mask:np.ndarray):
    if is_empty_mask(mask) and is_gray_scale(mask):
        raise ValueError("Mask not valid")

def combine_binary_masks(mask_data_rows, instance_value_mapping):
    """
    mask_paths: list of file paths for the instance masks (for one frame).
    instance_value_mapping: dict mapping each mask filename (or an identifier) to a unique int value.
    """
    # Assume all masks have the same shape; load the first mask to get shape
    mask_paths = mask_data_rows["file_uri"].to_list()
    keys = mask_data_rows["file_name"].to_list()
    
    sample_mask = np.array(Image.open(mask_paths[0]))
    height, width = sample_mask.shape
    # Initialize an empty RGB composite mask
    composite = np.zeros((height, width, 3), dtype=np.uint8)

    
    for path, key in zip(mask_paths, keys):
        # Use a key that is consistent (for example, the file basename) to get the unique value
        color = instance_value_mapping.get(key, 0)

        arr_img = np.array(Image.open(path))
        validate_mask(arr_img)

        # Set composite pixels to the unique value where the mask is present
        indices = arr_img > 0
        composite[indices] = color
    
    return Image.fromarray(composite)

def _write(self, data_rows:pd.DataFrame):

    instances_mask = list()


    # Create unique Mask instance value one for each annotation class
    global_key = "SomeGlobalKey"
    for file_name in data_rows["file_name"].unique().tolist():
        annotation_name = file_name
        color_rgb = instance_colors[file_name]
        instances_mask.append(
            lb_types.MaskInstance(
                color_rgb=color_rgb, 
                name=annotation_name
            )
        )

    # For each frame Compose multiple binary masks in a single RGB one
    frames_mask = list()
    for _, f_group in data_rows:
        frame_no = f_group["frame"].to_list()[0]
        
        pil_image = combine_binary_masks(
            mask_data_rows=f_group,
            instance_value_mapping=instance_colors
        )


        # Save the PIL image to this buffer
        buffer = io.BytesIO()
        pil_image.save(buffer, format="PNG")
        # Get the byte data
        img_bytes_io = buffer.getvalue()

        frames_mask.append(
            lb_types.MaskFrame(
                index=int(frame_no) + 1,
                im_bytes=img_bytes_io # Instead of bytes you could also pass an instance URI : instance_uri=url
            )
        )

    annotations = list()
    annotations.append(lb_types.VideoMaskAnnotation(
        frames= frames_mask,
        instances= instances_mask
    ))

    return lb_types.Label(
        data= {"global_key": global_key},
        annotations = annotations,
    )

I get those warning errors and I do not know why since actually the type of the buffer is “bytes”

~/miniconda3/envs/general/lib/python3.10/site-packages/pydantic/main.py:426: UserWarning: Pydantic serializer warnings:
  Expected `bytes` but got `str` with value `'iVBORw0KGgoAAAANSUhEUgAA...LnWtPAPAAAAAElFTkSuQmCC'` - serialized value may not be as expected
  Expected `bytes` but got `str` with value `'iVBORw0KGgoAAAANSUhEUgAA...npr8gAAAABJRU5ErkJggg=='` - serialized value may not be as expected
  Expected `bytes` but got `str` with value `'iVBORw0KGgoAAAANSUhEUgAA...G4gLoMAAAAASUVORK5CYII='` - serialized value may not be as expected
  Expected `bytes` but got `str` with value `'iVBORw0KGgoAAAANSUhEUgAA...Pw6QQAAAABJRU5ErkJggg=='` - serialized value may not be as expected
  Expected `bytes` but got `str` with value `'iVBORw0KGgoAAAANSUhEUgAA...WQK6wAAAABJRU5ErkJggg=='` - serialized value may not be as expected
  Expected `bytes` but got `str` with value `'iVBORw0KGgoAAAANSUhEUgAA...8ifwgkAAAAASUVORK5CYII='` - serialized value may not be as expected
  Expected `bytes` but got `str` with value `'iVBORw0KGgoAAAANSUhEUgAA...7T5uDcAAAAASUVORK5CYII='` - serialized value may not be as expected
  Expected `bytes` but got `str` with value `'iVBORw0KGgoAAAANSUhEUgAA...jEYQsUAAAAASUVORK5CYII='` - serialized value may not be as expected
  Expected `bytes` but got `str` with value `'iVBORw0KGgoAAAANSUhEUgAA...cAM0WoSAAAAAElFTkSuQmCC'` - serialized value may not be as expected
  Expected `bytes` but got `str` with value `'iVBORw0KGgoAAAANSUhEUgAA...oT9QAAAAABJRU5ErkJggg=='` - serialized value may not be as expected
  Expected `bytes` but got `str` with value `'iVBORw0KGgoAAAANSUhEUgAA...fQYPaAsAAAAAElFTkSuQmCC'` - serialized value may not be as expected
  Expected `bytes` but got `str` with value `'iVBORw0KGgoAAAANSUhEUgAA...AW+5GeGAAAAAElFTkSuQmCC'` - serialized value may not be as expected
  Expected `bytes` but got `str` with value `'iVBORw0KGgoAAAANSUhEUgAA...5DkEo4AAAAASUVORK5CYII='` - serialized value may not be as expected
  Expected `bytes` but got `str` with value `'iVBORw0KGgoAAAANSUhEUgAA...F7o3wAAAABJRU5ErkJggg=='` - serialized value may not be as expected
  Expected `bytes` but got `str` with value `'iVBORw0KGgoAAAANSUhEUgAA...Zc5JeMAAAAASUVORK5CYII='` - serialized value may not be as expected
  Expected `bytes` but got `str` with value `'iVBORw0KGgoAAAANSUhEUgAA...kXztgAAAABJRU5ErkJggg=='` - serialized value may not be as expected
  Expected `bytes` but got `str` with value `'iVBORw0KGgoAAAANSUhEUgAA...+W2e+8AAAAASUVORK5CYII='` - serialized value may not be as expected
  Expected `bytes` but got `str` with value `'iVBORw0KGgoAAAANSUhEUgAA...rj97X+sAAAAAElFTkSuQmCC'` - serialized value may not be as expected

How to Reproduce the Error

Step 1: Creating the Benchmark

  1. I uploaded a sample video.
  2. I added it to a video annotation project.
  3. I manually created segmentation masks for various frames.

Here is a screenshot of the annotated video with segmentation masks, which serves as the benchmark for what I expect when uploading video masks:

Step 2: Downloading the Masks

I exported the segmentation masks from the annotated video.

Step 3: Uploading the Masks to a New Video

  1. I uploaded an identical video but assigned it a different global_key so that Labelbox treats it as a separate data row.
  2. I added this video to a different annotation project.
  3. I uploaded the previously downloaded masks as MAL.

Unexpected Behavior

After uploading the masks, I noticed that:

  • The frame timeline does not display all the annotations, only some of them.
  • However, the masks are correctly applied to the corresponding frames, which suggests that the upload was successful.

Here are some screenshots illustrating the issue:

The frame timeline does not show all the annotations but only some of them. I do not know what is the criteria. As you can see from the image above even if it is not shown on the frame timeline the masks are highlighted correctly in correspondence with the annotated frame indicating that the upload went well.

Strange Behavior

As soon as I manually add another mask of the same type to any frame, a dropdown menu appears to group similar annotation masks.

The “Magic” Fix

When the dropdown menu is open, I still can’t see all the annotations.

However, when I close the dropdown menu, all the previously uploaded annotations appear correctly as expected!

Immediately showing all the previously uploaded frames correctly.

I hope this helps clarify the issue.

Best,

Christian

Hi @christian.bardella,

I have created a Labelbox Support ticket on your behalf. Please check your email. This ticket will serve as a private channel.

Best,
Christian

1 Like

Hi there,

I have a few updates regarding this issue. I do not open a new issue in the community since is strictly related to the previous one.

I’ve realized that not only are some SDK-loaded labels missing in the interface, but even the visible ones are not included in the exported JSON file.

In my case, I am uploading annotations in a single action for a single video, with one mask per frame. However, after the upload, I am unable to export the newly added masks.

To clarify further:

  • I upload a single mask per frame for a single video as ground truth.
  • Not all masks are visible in the frame-timeline UI, but they can still be found if you know where to look or by using the workaround I mentioned earlier.
  • Since all uploaded masks exist within Labelbox and are visible when navigating to the corresponding frames, I would expect them to appear in the export JSON. However, this is not the case.
  • Additionally, the export JSON contains a mix of masks—some that are visible in the timeline and others that are not. However, only a subset of all masks is included, and the logic behind this selection remains unclear to me.

Could you help me understand what might be causing this issue and how to resolve it?

Thanks in advance for your support!

Best,
Christian