HumanSignal / label-studio

Label Studio is a multi-type data labeling and annotation tool with standardized output format
https://labelstud.io
Apache License 2.0
18.19k stars 2.28k forks source link

Allow variables in label config to create dynamic image panels #6106

Closed bw4sz closed 3 weeks ago

bw4sz commented 1 month ago

I would like the annotator to be able to see a series of images at once. We are labeling unique objects among several views of the same scene.

The number of images of each scene varies. Right now my options are to

Desired Psuedo code

for $image in images:
<View>
  <Image name="image" value="$image"/>
  <Choices name="choice" toName="image" showInLine="true">
    <Choice value="Boeing" background="blue"/>
    <Choice value="Airbus" background="green" />
  </Choices>

Is there any other workaround for upload a .json of images and creating a dynamic view?

jombooth commented 1 month ago

Does valueList work for your usecase, as described in this example? https://labelstud.io/tags/image#Example-1

bw4sz commented 1 month ago

thanks for your idea, I will update.

bw4sz commented 3 weeks ago

Related to https://github.com/HumanSignal/label-studio/issues/225 for others coming here.

bw4sz commented 3 weeks ago

I am posting the solution I came up with and closing. Its not perfect, but sufficient for our needs.

Since the label config xml for each project is fixed, it was simpler to generate a new 'project' for each image panel. So using the python API, if we have a 6 image panel we need to create, loop through and name each img [img1, img2, img3], then make the corresponding labels point to each image.

https://github.com/weecology/DoubleCounting/blob/main/scripts/labelstudio.py

def create_label_config(predictions):
    """
    Creates a Label Studio XML configuration based on the provided predictions.

    Args:
        predictions (dict): A dictionary containing the preannotations for each image.

    Returns:
        str: The Label Studio XML configuration.
    """

    xml = '''<View>
        <Header value="Select unique birds in each image to create a full colony count" />'''

    for i, image_name in enumerate(predictions.keys()):
        xml += f'''
            <View style="display: flex;">
                <View style="width: {100/len(predictions)}%; margin-right: 1.99%">
                <Image name="img{i+1}" value="$img{i+1}"/>
                <RectangleLabels name="label{i+1}" toName="img{i+1}">
                    <Label value="Great Egret" background="#FFA39E"/>
                    <Label value="Great Blue Heron" background="#D4380D"/>
                    <Label value="Wood Stork" background="#FFC069"/>
                    <Label value="Snowy Egret" background="#AD8B00"/>
                    <Label value="Anhinga" background="#D3F261"/>
                    <Label value="Unidentified White" background="#389E0D"/>
                    <Label value="White Ibis" background="#5CDBD3"/>
                    <Label value="Nest" background="#FFA39E"/>
                    <Label value="Help me!" background="#D4380D"/>
                    <Label value="Eggs" background="#FFA39E"/>
                    <Label value="Roseate Spoonbill" background="#FFA39E"/>
                </RectangleLabels>
                </View>
            </View>'''

    xml += '''
    </View>'''

    return xml

create the project

https://github.com/weecology/DoubleCounting/blob/e2563c525d1a60ea8336180153fd126b03b34c13/scripts/labelstudio.py#L23

and then format our preannotations to match the naming structure for upload

def label_studio_bbox_format(local_image_dir, preannotations, from_name="label"):
    """
    Create a JSON string for a single image in the Label Studio API format.

    Args:
        local_image_dir (str): The local directory where the images are stored.
        preannotations (DataFrame): A DataFrame containing the preannotations for the image.
        to_name (str, optional): The name of the image. Defaults to "image".
        from_name (str, optional): The name of the label. Defaults to "label".

    Returns:
        dict: The JSON string in the Label Studio API format.
    """
    predictions = []
    image_path = preannotations.image_path.unique()[0]
    original_width, original_height = Image.open(os.path.join(local_image_dir, os.path.basename(image_path))).size

    #Unique images and their index
    unique_images = preannotations.image_path.unique()
    image_index_dict = {}
    for index, image in enumerate(unique_images):
        image_index_dict[image] = "img{}".format(index)

    for index, row in preannotations.iterrows():
        result = {
            "value": {
                "x": row['xmin'] / original_width * 100,
                "y": row['ymin'] / original_height * 100,
                "width": (row['xmax'] - row['xmin']) / original_width * 100,
                "height": (row['ymax'] - row['ymin']) / original_height * 100,
                "rotation": 0,
                "rectanglelabels": [row["label"]]
            },
            "score": row["score"],
            "to_name": image_index_dict[row["image_path"]],
            "type": "rectanglelabels",
            "from_name": image_index_dict[row["image_path"]].replace("img", "label"),
            "original_width": original_width,
            "original_height": original_height
        }
        predictions.append(result)

    return {"result": predictions}

Closing. Thanks for your response. I appreciate it.