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)