Error when rendering two images for classification

Hi!

I'm slowly getting familiar with Prodigy: the first task I want to do is to classify pairs of images and to make a binary choice (Yes/No) about them.

My input data looks like this:

{"image_1": "https://s3_bucket_url/0.png", "image_2": "https://s3_bucket_url/1.png"}

And here's my task definition, which builds on previous posts on this forum:

import prodigy
from prodigy.components.loaders import JSONL

@prodigy.recipe('classify-two-images')
def classify_images(dataset, path_to_jsonl):

    stream = JSONL(path_to_jsonl)

    blocks = [
        {'view_id': 'html'},
        {'view_id': 'choice', 'text': None, 'choice_style': 'single', 'choice_auto_accept': True}
    ]

    html_template = (
        '<div style="display: grid; grid-template-columns: 1fr 1fr; grid-gap: 2rem">'
        '<div style="font-family: monospace; white-space: pre-wrap; border-right: 1px solid #ccc"> {{image_1}} </div>'
        '<div> {{image_2}} </div>'
        '</div>'
    )

    return {'dataset': dataset,
            'stream': stream,
            'view_id': 'blocks',
            'config': {'blocks': blocks,
                       'html_template': html_template}
            }

I can launch the annotation task, but the data is not displayed due to the following error message:

Any ideas what I am doing wrong here?

Hi @tuomo_h,

It looks like the choice UI used as a component of your blocks is missing the list of options to choose from.
Each task sent to choice should contain the options field with the following format:

{
  "text": "Pick the odd one out.",
  "options": [
    {"id": 1, "text": "🍌 banana"},
    {"id": 2, "text": "🥦 broccoli"},
    {"id": 3, "text": "🍅 tomato"}
  ]
}

(btw. whenever you are doubt you can find the documentation of the JSONL format requirements for all UIs in our Annotation Interfaces doc section)

The most convenient way to add options programmatically to your stream of tasks would be by applying a wrapper function to the stream. This way you can preserve the benefits of stream being a generator-like data structure (StreamType). However, to be able to use apply, we need to load the stream via Prodigy get_stream function (JSONL loader is completely valid, but a newer and recommended way is to load the data source is via get_stream :

from prodigy.components.stream import get_stream
from prodigy.types import StreamType

def add_options(stream: StreamType) -> StreamType:
    for eg in stream:
        eg["options"] = [
            {"id": 1, "text": "🍌 banana"},
            {"id": 2, "text": "🥦 broccoli"},
            {"id": 3, "text": "🍅 tomato"}
        ]
        yield eg
stream = get_stream(path_to_jsonl)
stream.apply(add_options, stream=stream)

This should eliminate the input task validation error you see in the UI.

1 Like

Thanks for your help @magdaaniol – I managed to solve the problem!

I switched to get_stream and the classification interface, as it seems to be better suited for binary classification.

Also, I noticed a few errors in my HTML template, which were missing the <img> elements. I've added them below with a fixed with of 300 pixels for each image:

html_template = (
    '<div style="display: grid; grid-template-columns: 1fr 1fr; grid-gap: 2rem">'
    '<div style="font-family: monospace; white-space: pre-wrap; border-right: 1px solid #ccc"> <img src="{{image_1}}" width=300/></div>'
    '<div> <img src="{{image_2}}"  width=300/> </div>'
    '</div>'
    )

A follow-up question: is there a way to automatically resize the div element so that it would take up e.g. 80% of the screen width available in the "main window" where data is displayed?

Hi @tuomo_h,

Of course classification UI is a better option for binary annotation - sorry I totally missed that :see_no_evil: Hopefully the info on options will be useful for the future :slight_smile:

As for the width, I'm not entirely sure which part would you like to make it 80% of its parent:


Taking the above image as a reference, I understand that you want your custom div to occupy 80% of the Prodigy card so that it smaller than it currently is?
Or is it that you'd like the Prodigy card to take up 80% of the Prodigy content container?

Sorry, I'm still lacking the vocabulary for Prodigy UI components! To clarify:

  1. I would like to have the width of Prodigy card to be 80% of the content window.
  2. The images, in turn, would be dynamically sized so that they fit side-by-side in the custom div.

No worries @tuomo_h! Now it's all clear!

To set the Prodigy card width, you can use cardMaxWidth property of the custom_theme setting in your prodigy json file:

{"custom_theme": {
    "cardMaxWidth": "80%"
    }
}

This should make sure that the card will take up to 80% of the parent container whenever possible.
Other custom_theme options are documented here in case you're interested what other css "shortcuts" are available.

As for the image container, the styling of the parent container looks appropriate to me. I changed the fixed width of the immediate image container to max-width: 100%; height: auto to make sure it resizes automatically.
I also implemented the vertical divider as a separate div to make sure it always appears in the middle of the gap.
This result should be something like this (it should look the same on mobile devices as well):
images
And here's the template I used:

html_template = (
    '<div style="position: relative; display: grid; grid-template-columns: 1fr 1fr; gap: 2rem">'
    '<div>'
    '<img src="{{image_1}}" style="max-width: 100%; height: auto"/>'
    '</div>'
    '<div style="position: absolute;left: 50%;top: 0;bottom: 0;width: 2px;background-color: #ccc;transform: translateX(-50%); z-index: 1;">'
    '</div>'
    '<div>'
    '<img src="{{image_2}}" style="max-width: 100%; height: auto"/>'
    '</div>'
    '</div>')

We could also make the images occupy the entire available space but that might lead to poor resolution in the case of smaller images.

1 Like

@magdaaniol Thank you for the informative reply – I'm learning a lot here!

1 Like