Skip to content
This repository has been archived by the owner on Aug 21, 2024. It is now read-only.

Commit

Permalink
Merge pull request #268 from HumanSignal/fb-LEAP-608/support-Repeater…
Browse files Browse the repository at this point in the history
…-in-lsc

feat: LEAP-608: Support Repeater in JSON-MIN exports
  • Loading branch information
jombooth authored Jan 30, 2024
2 parents 9a2eaca + b4ecfe3 commit a926c6f
Show file tree
Hide file tree
Showing 12 changed files with 476 additions and 154 deletions.
32 changes: 30 additions & 2 deletions label_studio_converter/converter.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from typing import Optional

import os
import io
import math
import re
import logging
import ujson as json
import ijson
Expand Down Expand Up @@ -374,6 +377,31 @@ def iter_from_json_file(self, json_file):
if item is not None:
yield item

def _maybe_matching_tag_from_schema(self, from_name: str) -> Optional[str]:
"""If the from name exactly matches an output tag from the schema, return that tag.
Otherwise, certain tags (like those from Repeater) contain
placeholders like {{idx}}. Such placeholders are mapped to a regex in self._schema.
For example, if "my_output_tag_{{idx}}" is a tag in the schema,
then the from_name "my_output_tag_0" should match it, and we should return "my_output_tag_{{idx}}".
"""

for tag_name, tag_info in self._schema.items():
if tag_name == from_name:
return tag_name

if not tag_info.get('regex'):
continue

tag_name_pattern = tag_name
for variable, regex in tag_info['regex'].items():
tag_name_pattern = tag_name_pattern.replace(variable, regex)

if re.compile(tag_name_pattern).match(from_name):
return tag_name

return None

def annotation_result_from_task(self, task):
has_annotations = 'completions' in task or 'annotations' in task
if not has_annotations:
Expand Down Expand Up @@ -412,9 +440,9 @@ def annotation_result_from_task(self, task):

# get results only as output
for r in result:
if 'from_name' in r and r['from_name'] in self._output_tags:
if 'from_name' in r and (tag_name := self._maybe_matching_tag_from_schema(r['from_name'])):
v = deepcopy(r['value'])
v['type'] = self._schema[r['from_name']]['type']
v['type'] = self._schema[tag_name]['type']
if 'original_width' in r:
v['original_width'] = r['original_width']
if 'original_height' in r:
Expand Down
1 change: 1 addition & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""All tests for label-studio-converter should be written using pytest"""
Binary file added tests/data/test_brush/test.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
79 changes: 79 additions & 0 deletions tests/data/test_export_json_min/data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
[
{
"id": 87,
"annotations": [
{
"id": 22,
"completed_by": 1,
"result": [
{
"id": "qd1jIVAq1h",
"type": "polygonlabels",
"value": {
"closed": true,
"points": [
[
44.44444444444444,
48.25396825396825
],
[
42.53968253968254,
60.317460317460316
],
[
56.19047619047619,
50.79365079365079
]
],
"polygonlabels": [
"Airplane"
]
},
"origin": "manual",
"to_name": "image",
"from_name": "label",
"image_rotation": 0,
"original_width": 128,
"original_height": 128
}
],
"was_cancelled": false,
"ground_truth": false,
"created_at": "2024-01-30T02:40:43.893565Z",
"updated_at": "2024-01-30T02:40:43.893578Z",
"draft_created_at": "2024-01-30T02:40:42.917892Z",
"lead_time": 8.65,
"prediction": {},
"result_count": 0,
"unique_id": "3bc9da85-967f-47a1-bfa8-410206e54cf9",
"import_id": null,
"last_action": null,
"task": 87,
"project": 29,
"updated_by": 1,
"parent_prediction": null,
"parent_annotation": null,
"last_created_by": null
}
],
"file_upload": "example.png",
"drafts": [],
"predictions": [],
"data": {
"image": "/data/upload/29/example.png"
},
"meta": {},
"created_at": "2024-01-30T02:40:33.867975Z",
"updated_at": "2024-01-30T02:40:43.923985Z",
"inner_id": 1,
"total_annotations": 1,
"cancelled_annotations": 0,
"total_predictions": 0,
"comment_count": 0,
"unresolved_comment_count": 0,
"last_comment_updated_at": null,
"project": 29,
"updated_by": 1,
"comment_authors": []
}
]
101 changes: 101 additions & 0 deletions tests/data/test_export_json_min/data_repeater.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
[
{
"id": 86,
"annotations": [
{
"id": 21,
"completed_by": 1,
"result": [
{
"id": "FmoGRgYXtw",
"type": "rectanglelabels",
"value": {
"x": 19.466292134831463,
"y": 53.70786516853933,
"width": 20.35112359550562,
"height": 20.224719101123597,
"rotation": 0,
"rectanglelabels": [
"Document Title"
]
},
"origin": "manual",
"to_name": "page_0",
"from_name": "labels_0",
"image_rotation": 0,
"original_width": 1280,
"original_height": 720
},
{
"id": "FmoGRgYXtw",
"type": "taxonomy",
"value": {
"x": 19.466292134831463,
"y": 53.70786516853933,
"width": 20.35112359550562,
"height": 20.224719101123597,
"rotation": 0,
"taxonomy": [
[
"Archaea"
]
]
},
"origin": "manual",
"to_name": "page_0",
"from_name": "categories_0",
"image_rotation": 0,
"original_width": 1280,
"original_height": 720
}
],
"was_cancelled": false,
"ground_truth": false,
"created_at": "2024-01-25T23:52:15.182319Z",
"updated_at": "2024-01-25T23:52:15.182333Z",
"draft_created_at": "2024-01-25T23:52:13.732002Z",
"lead_time": 13.042,
"prediction": {},
"result_count": 0,
"unique_id": "5061fcba-4030-45b3-8079-71b7a134c254",
"import_id": null,
"last_action": null,
"task": 86,
"project": 28,
"updated_by": 1,
"parent_prediction": null,
"parent_annotation": null,
"last_created_by": null
}
],
"file_upload": "a5ef7e93-task_copy.json",
"drafts": [],
"predictions": [],
"data": {
"images": [
{
"url": "https://example.org/example1.jpeg"
},
{
"url": "https://example.org/example2.jpeg"
},
{
"url": "https://example.org/example3.jpeg"
}
]
},
"meta": {},
"created_at": "2024-01-25T23:52:00.852852Z",
"updated_at": "2024-01-25T23:52:15.207596Z",
"inner_id": 1,
"total_annotations": 1,
"cancelled_annotations": 0,
"total_predictions": 0,
"comment_count": 0,
"unresolved_comment_count": 0,
"last_comment_updated_at": null,
"project": 28,
"updated_by": 1,
"comment_authors": []
}
]
13 changes: 13 additions & 0 deletions tests/data/test_export_json_min/label_config.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<View>

<Header value="Select label and click the image to start"/>
<Image name="image" value="$image" zoom="true"/>

<PolygonLabels name="label" toName="image"
strokeWidth="3" pointSize="small"
opacity="0.9">
<Label value="Airplane" background="red"/>
<Label value="Car" background="blue"/>
</PolygonLabels>

</View>
72 changes: 72 additions & 0 deletions tests/data/test_export_json_min/label_config_repeater.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{
"labels_{{idx}}": {
"type": "RectangleLabels",
"regex": {
"{{idx}}": ".*"
},
"inputs": [
{
"type": "Image",
"value": "images[{{idx}}].url"
}
],
"labels": [
"Document Title",
"Document Date"
],
"to_name": [
"page_{{idx}}"
],
"labels_attrs": {
"Document Date": {
"value": "Document Date"
},
"Document Title": {
"value": "Document Title"
}
}
},
"categories_{{idx}}": {
"type": "Taxonomy",
"regex": {
"{{idx}}": ".*"
},
"inputs": [
{
"type": "Image",
"value": "images[{{idx}}].url"
}
],
"labels": [
"Archaea",
"Bacteria",
"Eukarya",
"Human",
"Oppossum",
"Extraterrestrial"
],
"to_name": [
"page_{{idx}}"
],
"labels_attrs": {
"Human": {
"value": "Human"
},
"Archaea": {
"value": "Archaea"
},
"Eukarya": {
"value": "Eukarya"
},
"Bacteria": {
"value": "Bacteria"
},
"Oppossum": {
"value": "Oppossum"
},
"Extraterrestrial": {
"value": "Extraterrestrial"
}
}
}
}
6 changes: 3 additions & 3 deletions tests/test_brush.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"""
Test for the brush.py module
"""
import unittest
import urllib3
import json
import os

from label_studio_converter.brush import encode_rle, image2annotation

Expand All @@ -13,7 +13,7 @@ def test_image2annotation():
Import from png to LS annotation with RLE values
"""
annotation = image2annotation(
'tests/test.png',
os.path.abspath(os.path.dirname(__file__)) + '/data/test_brush/test.png',
label_name='Airplane',
from_name='tag',
to_name='image',
Expand All @@ -28,7 +28,7 @@ def test_image2annotation():
}

""" You can import this `task.json` to the Label Studio project with this labeling config:
<View>
<Image name="image" value="$image" zoom="true"/>
<BrushLabels name="tag" toName="image">
Expand Down
Loading

0 comments on commit a926c6f

Please sign in to comment.