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
19.34k stars 2.4k forks source link

Hide `Choices` on condition #6516

Open mspronesti opened 1 month ago

mspronesti commented 1 month ago

Say I have a dataset of items of variable length and the list of their pre-annotations (0,1 or 2), e.g.

item preannotation
[a1, a2]. [0, 2]
[b1, b2, b3, b4] [1, 2, 2, 0]
[c1] [0]

As it seems like that the label configuration has to be of fixed length, I am computing the maximum length of the item entry and adding some empty "padding entries". However, hiding them using a condition such as name == '' would be nice.

Here's a minimal reproducible example:

from label_studio_sdk import Client

def generate_label_config(max_items):
    items = ""
    for i in range(max_items):
        items += f"""
              <View>
                <Text name="item_{i}_name" value="$item_{i}_name" />
                <Choices name="item_{i}_label" toName="item_{i}_name" choice="single" required="true" showInline="true">
                  <Choice value="0"/>
                  <Choice value="1"/>
                  <Choice value="2"/>
                </Choices>
              </View>
        """
    return f"""
    <View>
      <Header value="Title: $title" />
      <Header value="Items and Labels:" />
        {items}
    </View>
    """

if __name__ == '__main__':
    fake_data = [
        {
            "data": {
                'title': 'title0',
                'item_0_name': 'a0',
                'item_0_label': 'label0',
                'item_1_name': 'a1',
                'item_1_label': 'label1',
                'item_2_name': '',  # padding
                'item_2_label': '',  # padding
                'item_3_name': '',  # padding
                'item_3_label': '',  # padding
            },
            "predictions": [
                {
                    "result": [
                        {
                            "from_name": "item_0_label",
                            "to_name": "item_0_name",
                            "type": "choices",
                            "value": {"choices": ["0"]}
                        },
                        {
                            "from_name": "item_1_label",
                            "to_name": "item_1_name",
                            "type": "choices",
                            "value": {"choices": ["2"]}
                        }
                    ]
                }
            ]
        },
        {
            "data": {
                'title': 'title1',
                'item_0_name': 'b0',
                'item_0_label': 'label0',
                'item_1_name': 'b1',
                'item_1_label': 'label1',
                'item_2_name': 'b2',
                'item_2_label': 'label2',
                'item_3_name': 'b3',
                'item_3_label': 'label3',
            },
            "predictions": [
                {
                    "result": [
                        {
                            "from_name": "item_0_label",
                            "to_name": "item_0_name",
                            "type": "choices",
                            "value": {"choices": ["1"]}
                        },
                        {
                            "from_name": "item_1_label",
                            "to_name": "item_1_name",
                            "type": "choices",
                            "value": {"choices": ["2"]}
                        },
                        {
                            "from_name": "item_2_label",
                            "to_name": "item_2_name",
                            "type": "choices",
                            "value": {"choices": ["2"]}
                        },
                        {
                            "from_name": "item_3_label",
                            "to_name": "item_3_name",
                            "type": "choices",
                            "value": {"choices": ["0"]}
                        }
                    ]
                }
            ]
        },
        {
            "data": {
                'title': 'title2',
                'item_0_name': 'c0',
                'item_0_label': 'label0',
                'item_1_name': '', # padding
                'item_1_label': '', # padding
                'item_2_name': '',  # padding
                'item_2_label': '',  # padding
                'item_3_name': '',  # padding
                'item_3_label': '',  # padding
            },
            "predictions": [
                {
                    "result": [
                        {
                            "from_name": "item_0_label",
                            "to_name": "item_0_name",
                            "type": "choices",
                            "value": {"choices": ["0"]}
                        }
                    ]
                }
            ]
        },
    ]

    max_items = 4
    ls = Client(url='http://localhost:8080', api_key='API-KEY')
    label_config = generate_label_config(max_items)

    project = ls.start_project(
        title="Fo project",
        label_config=label_config
    )
    project.import_tasks(fake_data)
heidi-humansignal commented 1 month ago

Thank you for reaching out with your question. You can achieve the desired functionality by using the visibleWhen and when attributes in your labeling configuration to conditionally display components based on the presence of data. This way, you can hide the padding entries where item_{i}_name is empty. Here's how you can modify your generate_label_config function:

def generate_label_config(max_items): items = "" for i in range(max_items): items += f""" <View visibleWhen="not equal" when="$item_{i}_name" value=""> <Text name="item_{i}_name" value="$item_{i}_name" /> <Choices name="item_{i}_label" toName="item_{i}_name" choice="single" required="true" showInline="true"> <Choice value="0"/> <Choice value="1"/> <Choice value="2"/> </Choices> </View> """ return f""" <View> <Header value="Title: $title" /> <Header value="Items and Labels:" /> {items} </View> """

In this updated configuration:

Since item_2_name and item_3_name are empty strings, the corresponding Views will be hidden, and only item_0 and item_1 will be displayed. Additional Notes:

Thanks,

Tyler Conlee Head of Support HumanSignal

Comment by Tyler Conlee Workflow Run

mspronesti commented 1 month ago

Hi @heidi-humansignal, unfortunately, your suggestion doesn't work for me. Are you sure that "not equal" is a valid value for visibleWhen and that when is a valid parameter? They don't seem to be mentioned anywhere in the docs.

heidi-humansignal commented 3 weeks ago

Hello,

Thank you for bringing this to my attention, and I apologize for any confusion earlier. You are correct—the visibleWhen attribute does not support operators like not_equal or not equal. Instead, visibleWhen can only have the following values:

Given this limitation, to conditionally display components based on whether a variable is empty, we can use dynamic rendering with the <List> and <Template> tags. This approach allows you to display only the items that are present in your data without padding or the need for conditional visibility.

Solution Using <List> and <Template> Tags

Labeling Configuration:

<View> <Header value="Title: $title" /> <Header value="Items and Labels:" /> <List name="item_list" value="$items"> <Template> <View> <Text name="item_name_$index" value="$item.name" /> <Choices name="item_label_$index" toName="item_name_$index" choice="single" required="true" showInline="true"> <Choice value="0" /> <Choice value="1" /> <Choice value="2" /> </Choices> </View> </Template> </List></View>

Task Data Format: Modify your task data to include an array of items:

{ "data": { "title": "title0", "items": [{"name": "a0"}, {"name": "a1"}] }, "predictions": [{ "result": [ { "from_name": "item_label_0", "to_name": "item_name_0", "type": "choices", "value": {"choices": ["0"]} }, { "from_name": "item_label_1", "to_name": "item_name_1", "type": "choices", "value": {"choices": ["2"]} } ] } ]}

Explanation

Predictions

Ensure that your prediction JSON references the correct dynamically generated name attributes using the $index variable.

Updated Script Example

Here's how you might modify your script:

def generate_label_config(): return """ <View> <Header value="Title: $title" /> <Header value="Items and Labels:" /> <List name="item_list" value="$items"> <Template> <View> <Text name="item_name_$index" value="$item.name" /> <Choices name="item_label_$index" toName="item_name_$index" choice="single" required="true" showInline="true"> <Choice value="0" /> <Choice value="1" /> <Choice value="2" /> </Choices> </View> </Template> </List> </View> """if __name__ == '__main__': fake_data = [{ "data": { "title": "title0", "items": [ {"name": "a0"}, {"name": "a1"}] }, "predictions": [{ "result": [ { "from_name": "item_label_0", "to_name": "item_name_0", "type": "choices", "value": {"choices": ["0"]} }, { "from_name": "item_label_1", "to_name": "item_name_1", "type": "choices", "value": {"choices": ["2"]} } ] } ] }, { "data": { "title": "title1", "items": [{"name": "b0"}, {"name": "b1"}, {"name": "b2"}, {"name": "b3"}] }, "predictions": [{ "result": [ { "from_name": "item_label_0", "to_name": "item_name_0", "type": "choices", "value": {"choices": ["1"]} }, { "from_name": "item_label_1", "to_name": "item_name_1", "type": "choices": ["2"]} }, { "from_name": "item_label_2", "to_name": "item_name_2", "type": "choices", "value": {"choices": ["2"]} }, { "from_name": "item_label_3", "to_name": "item_name_3", "type": "choices", "value": {"choices": ["0"]} } ] } ] }, { "data": { "title": "title2", "items": [{"name": "c0"}] }, "predictions": [{ "result": [ { "from_name": "item_label_0", "to_name": "item_name_0", "type": "choices", "value": {"choices": ["0"]} } ] } ] } ] ls = Client(url='http://localhost:8080', api_key='API-KEY') label_config = generate_label_config() project = ls.start_project( title="Dynamic Items Project", label_config=label_config ) project.import_tasks(fake_data)

Notes

Thank you, Abu

Comment by Abubakar Saad Workflow Run