Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Labelstudio instances support #2706

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 50 additions & 10 deletions fiftyone/utils/labelstudio.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,10 +244,13 @@ def _prepare_tasks(self, samples, label_schema, media_field):

predictions, id_map = {}, {}
for label_field, label_info in label_schema.items():
label_type = label_info["type"]
if label_info["existing_field"]:
predictions[label_field] = {
smp.id: export_label_to_label_studio(
smp[label_field],
smp,
label_field,
label_type,
full_result={
"from_name": "label",
"to_name": "image",
Expand All @@ -262,7 +265,6 @@ def _prepare_tasks(self, samples, label_schema, media_field):
smp.id: _get_label_ids(smp[label_field])
for smp in samples.select_fields(label_field)
}

return tasks, predictions, id_map

def _upload_tasks(self, project, tasks, predictions=None):
Expand Down Expand Up @@ -354,11 +356,12 @@ def _import_annotations(self, tasks, task_map, label_type):
)
if label_type == "keypoints":
labels = import_label_studio_annotation(
latest_annotation["result"]
latest_annotation["result"],
label_type
)
else:
labels = [
import_label_studio_annotation(r)
import_label_studio_annotation(r, label_type)
for r in latest_annotation.get("result", [])
]

Expand Down Expand Up @@ -575,11 +578,12 @@ def generate_labeling_config(media, label_type, labels=None):
return config_str


def import_label_studio_annotation(result):
def import_label_studio_annotation(result, label_type):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make label_type=None the default, since this extra information is only needed when deciding between semantic and instance segmentations?

"""Imports an annotation from Label Studio.

Args:
result: the annotation result from Label Studio
label_type: the FiftyOne Label type of the annotation

Returns:
a :class:`fiftyone.core.labels.Label`
Expand All @@ -602,7 +606,7 @@ def import_label_studio_annotation(result):
elif ls_type == "keypointlabels":
label = _from_keypointlabels(result)
elif ls_type == "brushlabels":
label = _from_brushlabels(result)
label = _from_brushlabels(result, label_type)
elif ls_type == "number":
label = _from_number(result)
else:
Expand All @@ -624,7 +628,7 @@ def _update_dict(src_dict, update_dict):
return new


def export_label_to_label_studio(label, full_result=None):
def export_label_to_label_studio(sample, label_field, label_type, full_result=None):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

full_result already includes original_width and original_height, so can you just use that and avoid changing the signature of this method?

"""Exports a label to the Label Studio format.

Args:
Expand All @@ -636,12 +640,16 @@ def export_label_to_label_studio(label, full_result=None):
a dictionary or a list in Label Studio format
"""
# TODO model version and model score
label = sample[label_field]
if label is None:
result_value = {}
ls_type = None
ids = []
elif _check_type(label, fol.Classification, fol.Classifications):
result_value, ls_type, ids = _to_classification(label)
elif _check_type(label, fol.Detection, fol.Detections) and label_type == 'instances':
size = (sample.metadata['width'], sample.metadata['height'])
result_value, ls_type, ids = _to_instance(label, size)
elif _check_type(label, fol.Detection, fol.Detections):
result_value, ls_type, ids = _to_detection(label)
elif _check_type(label, fol.Polyline, fol.Polylines):
Expand Down Expand Up @@ -699,7 +707,6 @@ def _to_classification(label):

def _to_detection(label):
ls_type = "rectanglelabels"

if isinstance(label, list):
return (
[_to_detection(l)[0] for l in label],
Expand All @@ -725,6 +732,30 @@ def _to_detection(label):
}
return result, ls_type, label.id

def _to_instance(label, size):
ls_type = "brushlabels"
if isinstance(label, list):
return (
[_to_instance(l, size)[0] for l in label],
ls_type,
[l.id for l in label],
)

if isinstance(label, fol.Detections):
return (
[_to_instance(l, size)[0] for l in label.detections],
ls_type,
[l.id for l in label.detections],
)

brush = fou.lazy_import(
"label_studio_converter.brush",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: lazy_import() can be placed at the very top of the module with other imports, since the module won't actually be imported unless it is used.

I notice that local lazy_import() is used elsewhere in this module, perhaps you could clean that up too? 🤗

callback=lambda: fou.ensure_import("label_studio_converter.brush"),
)
rle = brush.mask2rle(label.to_segmentation(frame_size=size).get_mask())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please install the dev dependencies? Basically just run:

bash install.bash -d

This will install pre-commit hooks that will automatically run some code formatting tools when you commit your edits.

result = {"format": "rle", "rle": rle, "brushlabels": [label.label]}
return result, ls_type, label.id


def _to_polyline(label):
ls_type = "polygonlabels"
Expand Down Expand Up @@ -832,7 +863,7 @@ def _from_keypointlabels(result):
return keypoints


def _from_brushlabels(result):
def _from_brushlabels(result, label_type):
brush = fou.lazy_import(
"label_studio_converter.brush",
callback=lambda: fou.ensure_import("label_studio_converter.brush"),
Expand All @@ -843,7 +874,11 @@ def _from_brushlabels(result):
mask = img.reshape(
(result["original_height"], result["original_width"], 4)
)[:, :, 3]
return fol.Segmentation(label=label_values[0], mask=mask)
if label_type == "segmentation":
return fol.Segmentation(label=label_values[0], mask=mask)
elif label_type == "detections":
mask_targets = {255: label_values[0]}
return fol.Segmentation(label=label_values[0], mask=mask).to_detections(mask_targets).detections[0]


def _from_number(result):
Expand Down Expand Up @@ -944,6 +979,11 @@ def _get_label_ids(label):
child_tag="Label",
label=fol.Detection,
),
"instances": dict(
parent_tag="BrushLabels",
child_tag="Label",
label=fol.Detection,
),
"polylines": dict(
parent_tag="PolygonLabels",
child_tag="Label",
Expand Down