Skip to content

Open In Colab

How to attach files to a label

A file job is answered with a file rather than a drawing or a piece of text: a render, a scene file, a report. It is how a step whose output is produced outside Kili -- a 3D tool, a conversion script -- hands that output back.

In this recipe we will:

  • declare two file jobs in a video project,
  • submit one label naming a file for each,
  • read them back and download them.

A file job holds one file, the way a transcription holds one text. Several deliverables means several jobs, all in the same label.

Setup

%pip install kili
from pathlib import Path

from kili.client import Kili

kili = Kili(
    # api_endpoint='https://cloud.kili-technology.com/api/label/v2/graphql',
    # the line above can be uncommented and changed if you are working with an on-premise version of Kili
)

Creating a project with file jobs

FILE declares no categories and no input: its answer is the file itself.

json_interface = {
    "jobs": {
        "RENDER": {
            "mlTask": "FILE",
            "instruction": "Rendered video",
            "required": 0,
            "isChild": False,
            "content": {},
        },
        "SCENE": {
            "mlTask": "FILE",
            "instruction": "Scene file",
            "required": 0,
            "isChild": False,
            "content": {},
        },
    }
}

project = kili.create_project(
    input_type="VIDEO",
    json_interface=json_interface,
    title="[Kili SDK Notebook]: File annotations",
)
project_id = project["id"]
project_id
kili.append_many_to_dataset(
    project_id=project_id,
    content_array=[
        "https://storage.googleapis.com/label-public-staging/asset-test-sample/video/short_video.mp4"
    ],
    external_id_array=["short_video"],
)

asset_id = kili.assets(project_id=project_id, fields=["id"])[0]["id"]
asset_id

Submitting a label that names the files

A file job is answered with the path of the file to attach. append_labels uploads it and stores the reference, so there is no separate upload step -- and no way to put bytes in Kili that are not the answer to a job.

A file has no frame, so on a video project a file job sits at asset level, beside the frames rather than inside one.

Path("render.mp4").write_bytes(b"not really a video, but enough for the recipe")
Path("scene.blend").write_bytes(b"not really a scene either")

kili.append_labels(
    project_id=project_id,
    asset_id_array=[asset_id],
    json_response_array=[{"assetLevel": {"RENDER": "render.mp4", "SCENE": "scene.blend"}}],
)

Reading the files back

label = kili.labels(project_id=project_id, asset_id=asset_id, fields=["jsonResponse"])[-1]
label["jsonResponse"]["assetLevel"]

Each path has been replaced by what was stored: the file's id, the name it was uploaded under, and its mime type. render.mp4 resolves to video/mp4, while .blend is not a type Python knows, so it comes back empty and Kili fills it in from the file name.

Getting the files out

kili.export_labels(
    project_id=project_id,
    filename="export.zip",
    fmt="kili",
)

An export brings the files along: each asset gets a folder of its own, labels/<externalId>/, beside its labels/<externalId>.json, holding every file its jobs answered with. Each answer gains a filePath pointing at it.

Warning

output_format='parsed_label' does not reach a file job on a video project yet: the parser does not read the assetLevel block. That predates file jobs -- asset level transcription and classification are affected the same way -- so read jsonResponse directly, as above.

Cleanup

kili.delete_project(project_id=project_id)

Path("render.mp4").unlink(missing_ok=True)
Path("scene.blend").unlink(missing_ok=True)
Path("export.zip").unlink(missing_ok=True)