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

Controle de qualidade

É aqui que a migração lhe dá algo em vez de tirar. A edição comunitária do Label Studio não traz nenhuma métrica de concordância, e a marcação de ground truth, a atribuição de revisores e os painéis de qualidade ficam nos planos pagos. No Potato são chaves de configuração.

Os testes de atenção são um recurso de primeira classe, não algo que você enfia no arquivo de dados. O Potato os injeta por você e registra quem erra:

yaml
attention_checks:
  enabled: true
  items_file: "attention_checks.json"
  frequency: 10
  min_response_time: 3.0

Os padrões-ouro funcionam do mesmo jeito, pontuando cada anotador contra itens cuja resposta você já conhece:

yaml
gold_standards:
  enabled: true
  items_file: "gold_standards.json"

A concordância entre anotadores é calculada para você. Faça seus anotadores se sobreporem num subconjunto comum e ative isto. O alfa de Krippendorff aparece no painel de administração, então não há nenhuma etapa de scikit-learn para escrever:

yaml
num_annotators_per_item: 3
 
agreement_metrics:
  enabled: true

A incerteza dos rótulos, se quiser ir além, é aquilo para o qual o Label Studio não tem equivalente em plano nenhum. O Potato ajusta um modelo de teoria de resposta ao item sobre suas anotações e devolve cada rótulo com uma posterior e um intervalo de confiança, pesando os anotadores pela confiabilidade que demonstraram em vez de contar votos:

yaml
psychometrics:
  enabled: true
  schema: sentiment
  confidence_threshold: 0.95

Veja controle de qualidade, concordância entre anotadores e o motor psicométrico para todas as opções.

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.