Skip to content
Guides9 min read

Migrando do Label Studio para o Potato

Migre do Label Studio para o Potato, convertendo configurações de projeto, esquemas de anotação e dados exportados com este guia passo a passo que cobre os tipos de anotação mais comuns.

Potato Team

Este guia mostra como mover um projeto existente do Label Studio para o Potato. Já adiantamos: não existe uma ferramenta oficial de migração. Você converte a configuração à mão e escreve um pouco de Python para reformatar os dados, então vai precisar conhecer bem as duas plataformas.

Para uma comparação de recursos lado a lado e as próprias ferramentas de migração do Potato, veja a documentação de origem e a documentação da CLI de migração.

Por que migrar?

O Potato se encaixa melhor em alguns projetos. Ele foi feito para estudos acadêmicos de anotação, já vem com integração ao Prolific e ao MTurk e se configura por YAML, sem precisar subir um banco de dados. É fácil de estender em Python e, como o armazenamento é só de arquivos, é fácil de implantar.

Visão geral da migração

O processo é manual e segue mais ou menos esta sequência:

  1. Converter manualmente o template XML do Label Studio para a configuração YAML do Potato
  2. Escrever scripts em Python para transformar o formato dos dados (JSON para JSONL)
  3. Escrever scripts para migrar anotações existentes (se houver)
  4. Testar a fundo e validar os dados convertidos

Conversão de template

Classificação de texto

XML do Label Studio:

xml
<View>
  <Text name="text" value="$text"/>
  <Choices name="sentiment" toName="text" choice="single">
    <Choice value="Positive"/>
    <Choice value="Negative"/>
    <Choice value="Neutral"/>
  </Choices>
</View>

YAML do Potato:

yaml
annotation_task_name: "Sentiment Classification"
 
data_files:
  - "data/items.jsonl"
 
item_properties:
  id_key: id
  text_key: text
 
annotation_schemes:
  - annotation_type: radio
    name: sentiment
    description: "What is the sentiment?"
    labels:
      - name: positive
        tooltip: "Positive sentiment"
      - name: negative
        tooltip: "Negative sentiment"
      - name: neutral
        tooltip: "Neutral sentiment"

Classificação multirrótulo

XML do Label Studio:

xml
<View>
  <Text name="text" value="$text"/>
  <Choices name="topics" toName="text" choice="multiple">
    <Choice value="Politics"/>
    <Choice value="Sports"/>
    <Choice value="Technology"/>
    <Choice value="Entertainment"/>
  </Choices>
</View>

YAML do Potato:

yaml
annotation_schemes:
  - annotation_type: multiselect
    name: topics
    description: "Select all relevant topics"
    labels:
      - name: politics
        tooltip: "Politics content"
      - name: sports
        tooltip: "Sports content"
      - name: technology
        tooltip: "Technology content"
      - name: entertainment
        tooltip: "Entertainment content"

Reconhecimento de entidades nomeadas

XML do Label Studio:

xml
<View>
  <Labels name="entities" toName="text">
    <Label value="PERSON" background="#FFC0CB"/>
    <Label value="ORG" background="#90EE90"/>
    <Label value="LOCATION" background="#ADD8E6"/>
  </Labels>
  <Text name="text" value="$text"/>
</View>

YAML do Potato:

yaml
annotation_schemes:
  - annotation_type: span
    name: entities
    description: "Select entity spans in the text"
    labels:
      - name: PERSON
        tooltip: "Person names"
      - name: ORG
        tooltip: "Organization names"
      - name: LOCATION
        tooltip: "Location names"

Observação: a anotação de spans do Potato pode usar um destaque diferente do Label Studio. Teste sua configuração convertida para verificar se a exibição atende às suas necessidades.

Classificação de imagens

XML do Label Studio:

xml
<View>
  <Image name="image" value="$image_url"/>
  <Choices name="category" toName="image">
    <Choice value="Cat"/>
    <Choice value="Dog"/>
    <Choice value="Other"/>
  </Choices>
</View>

YAML do Potato:

yaml
data_files:
  - "data/images.jsonl"
 
item_properties:
  id_key: id
  text_key: image_url
 
annotation_schemes:
  - annotation_type: radio
    name: category
    description: "What animal is in the image?"
    labels:
      - name: cat
        tooltip: "Cat"
      - name: dog
        tooltip: "Dog"
      - name: other
        tooltip: "Other animal"

Anotação por caixa delimitadora

XML do Label Studio:

xml
<View>
  <Image name="image" value="$image_url"/>
  <RectangleLabels name="objects" toName="image">
    <Label value="Car"/>
    <Label value="Person"/>
    <Label value="Bicycle"/>
  </RectangleLabels>
</View>

YAML do Potato:

yaml
annotation_schemes:
  - annotation_type: bounding_box
    name: objects
    description: "Draw boxes around objects"
    labels:
      - name: car
        tooltip: "Car"
      - name: person
        tooltip: "Person"
      - name: bicycle
        tooltip: "Bicycle"

Observação: o suporte a caixas delimitadoras no Potato pode diferir do Label Studio. Consulte a documentação para conhecer os recursos atuais.

Escalas de avaliação

XML do Label Studio:

xml
<View>
  <Text name="text" value="$text"/>
  <Rating name="quality" toName="text" maxRating="5"/>
</View>

YAML do Potato:

yaml
annotation_schemes:
  - annotation_type: likert
    name: quality
    description: "Rate the quality"
    size: 5
    labels:
      - name: "1"
        tooltip: "Poor"
      - name: "2"
        tooltip: "Below average"
      - name: "3"
        tooltip: "Average"
      - name: "4"
        tooltip: "Good"
      - name: "5"
        tooltip: "Excellent"

Conversão do formato de dados

JSON do Label Studio para JSONL do Potato

Formato do Label Studio:

json
[
  {
    "id": 1,
    "data": {
      "text": "This is great!",
      "meta_info": "source1"
    }
  },
  {
    "id": 2,
    "data": {
      "text": "This is terrible.",
      "meta_info": "source2"
    }
  }
]

Formato JSONL do Potato:

json
{"id": "1", "text": "This is great!", "metadata": {"source": "source1"}}
{"id": "2", "text": "This is terrible.", "metadata": {"source": "source2"}}

Script de conversão

python
import json
 
def convert_label_studio_to_potato(ls_file, potato_file):
    """Convert Label Studio JSON to Potato JSONL"""
 
    with open(ls_file, 'r') as f:
        ls_data = json.load(f)
 
    with open(potato_file, 'w') as f:
        for item in ls_data:
            potato_item = {
                "id": str(item["id"]),
                "text": item["data"].get("text", ""),
            }
 
            # Convert nested data fields
            if "data" in item:
                for key, value in item["data"].items():
                    if key != "text":
                        if "metadata" not in potato_item:
                            potato_item["metadata"] = {}
                        potato_item["metadata"][key] = value
 
            # Handle image URLs
            if "image" in item.get("data", {}):
                potato_item["image_url"] = item["data"]["image"]
 
            f.write(json.dumps(potato_item) + "\n")
 
    print(f"Converted {len(ls_data)} items")
 
# Usage
convert_label_studio_to_potato("label_studio_export.json", "data/items.jsonl")

Migração de anotações

Convertendo anotações existentes

python
def convert_annotations(ls_export, potato_output):
    """Convert Label Studio annotations to Potato format"""
 
    with open(ls_export, 'r') as f:
        ls_data = json.load(f)
 
    with open(potato_output, 'w') as f:
        for item in ls_data:
            if "annotations" not in item or not item["annotations"]:
                continue
 
            for annotation in item["annotations"]:
                potato_ann = {
                    "id": str(item["id"]),
                    "text": item["data"].get("text", ""),
                    "annotations": {},
                    "annotator": annotation.get("completed_by", {}).get("email", "unknown"),
                    "timestamp": annotation.get("created_at", "")
                }
 
                # Convert results
                for result in annotation.get("result", []):
                    scheme_name = result.get("from_name", "unknown")
 
                    if result["type"] == "choices":
                        # Classification
                        potato_ann["annotations"][scheme_name] = result["value"]["choices"][0]
 
                    elif result["type"] == "labels":
                        # NER spans
                        if scheme_name not in potato_ann["annotations"]:
                            potato_ann["annotations"][scheme_name] = []
 
                        potato_ann["annotations"][scheme_name].append({
                            "start": result["value"]["start"],
                            "end": result["value"]["end"],
                            "label": result["value"]["labels"][0],
                            "text": result["value"]["text"]
                        })
 
                    elif result["type"] == "rating":
                        potato_ann["annotations"][scheme_name] = result["value"]["rating"]
 
                f.write(json.dumps(potato_ann) + "\n")
 
# Usage
convert_annotations("ls_annotated_export.json", "annotations/migrated.jsonl")

Conversão de anotação de spans

O Label Studio usa offsets de caracteres; o Potato também usa offsets de caracteres, então a conversão é direta:

python
def convert_spans(ls_spans):
    """Convert Label Studio span format to Potato format"""
    potato_spans = []
 
    for span in ls_spans:
        potato_spans.append({
            "start": span["value"]["start"],
            "end": span["value"]["end"],
            "label": span["value"]["labels"][0],
            "text": span["value"]["text"]
        })
 
    return potato_spans

Mapeamento de recursos

Label StudioPotato
Choices (single)radio
Choices (multiple)multiselect
Labelsspan
Ratinglikert
TextAreatext
RectangleLabelsbounding_box
PolygonLabelspolygon
Taxonomy(use nested multiselect)
Pairwisecomparison

Considerações sobre controle de qualidade

O controle de qualidade é uma das áreas em que você terá de fazer parte do trabalho à mão. O Potato tem um QC básico, mas não um sistema único e abrangente, então conte com a tarefa de preencher as lacunas você mesmo.

Abordagens para controle de qualidade

Verificações de atenção: você pode adicionar manualmente itens de verificação de atenção ao seu arquivo de dados. São itens comuns com respostas corretas conhecidas que você inclui para verificar se o anotador está prestando atenção:

python
# Add attention check items to your data
attention_items = [
    {"id": "attn_1", "text": "ATTENTION CHECK: Please select 'Positive'", "is_attention": True},
    {"id": "attn_2", "text": "ATTENTION CHECK: Please select 'Negative'", "is_attention": True},
]
 
# Intersperse with regular items
import random
all_items = regular_items + attention_items
random.shuffle(all_items)

Cálculo de concordância: calcule a concordância entre anotadores de forma offline usando as anotações coletadas:

python
from sklearn.metrics import cohen_kappa_score
import numpy as np
 
def compute_agreement(annotations_file):
    """Compute agreement from collected annotations"""
    # Load annotations and compute metrics externally
    # Potato does not have built-in agreement calculation
    pass

Anotação redundante: configure vários anotadores por item, atribuindo itens a vários usuários no seu processo de gerenciamento de dados.

Migração de usuários

Exportar usuários do Label Studio

python
# Label Studio API call to get users
import requests
 
def export_ls_users(ls_url, api_key):
    response = requests.get(
        f"{ls_url}/api/users",
        headers={"Authorization": f"Token {api_key}"}
    )
    return response.json()

Criar a configuração de usuários do Potato

yaml
user_config:
  # Simple auth for migrated users
  auth_type: password
 
  user_config:
    - username: user1@example.com
      password_hash: "..."  # Generate new passwords
 
    - username: user2@example.com
      password_hash: "..."

Testando a migração

Script de validação

python
def validate_migration(original_ls, converted_potato):
    """Validate converted data matches original"""
 
    with open(original_ls) as f:
        ls_data = json.load(f)
 
    with open(converted_potato) as f:
        potato_data = [json.loads(line) for line in f]
 
    # Check item count
    assert len(ls_data) == len(potato_data), "Item count mismatch"
 
    # Check IDs preserved
    ls_ids = {str(item["id"]) for item in ls_data}
    potato_ids = {item["id"] for item in potato_data}
    assert ls_ids == potato_ids, "ID mismatch"
 
    # Check text content
    for ls_item, potato_item in zip(
        sorted(ls_data, key=lambda x: x["id"]),
        sorted(potato_data, key=lambda x: x["id"])
    ):
        assert ls_item["data"]["text"] == potato_item["text"], \
            f"Text mismatch for item {ls_item['id']}"
 
    print("Validation passed!")
 
validate_migration("label_studio_export.json", "data/items.jsonl")

Checklist de migração

  • Exportar os dados do Label Studio (formato JSON)
  • Converter manualmente o template XML para YAML do Potato
  • Escrever e rodar scripts em Python para transformar o formato dos dados (JSON para JSONL)
  • Escrever e rodar scripts para converter anotações existentes (se houver)
  • Montar a estrutura do projeto Potato
  • Testar com dados de amostra
  • Validar que os dados convertidos correspondem ao original
  • Treinar os anotadores na nova interface
  • Rodar um lote piloto de anotação

Problemas comuns

Algumas coisas costumam tropeçar as pessoas. As duas ferramentas usam UTF-8, mas ainda vale a pena checar seus dados em busca de gremlins de codificação. Caminhos locais de imagem normalmente precisam virar URLs, ou pelo menos seguir o formato que o Potato espera. Quaisquer componentes personalizados que você criou no Label Studio têm de ser refeitos como templates personalizados do Potato. E se você escreveu scripts contra a API do Label Studio, esses scripts precisam apontar para a API do Potato.


Precisa de ajuda para migrar? Veja a documentação completa ou entre em contato no GitHub.