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.
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:
- Converter manualmente o template XML do Label Studio para a configuração YAML do Potato
- Escrever scripts em Python para transformar o formato dos dados (JSON para JSONL)
- Escrever scripts para migrar anotações existentes (se houver)
- Testar a fundo e validar os dados convertidos
Conversão de template
Classificação de texto
XML do Label Studio:
<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:
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:
<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:
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:
<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:
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:
<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:
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:
<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:
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:
<View>
<Text name="text" value="$text"/>
<Rating name="quality" toName="text" maxRating="5"/>
</View>YAML do Potato:
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:
[
{
"id": 1,
"data": {
"text": "This is great!",
"meta_info": "source1"
}
},
{
"id": 2,
"data": {
"text": "This is terrible.",
"meta_info": "source2"
}
}
]Formato JSONL do Potato:
{"id": "1", "text": "This is great!", "metadata": {"source": "source1"}}
{"id": "2", "text": "This is terrible.", "metadata": {"source": "source2"}}Script de conversão
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
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:
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_spansMapeamento de recursos
| Label Studio | Potato |
|---|---|
| Choices (single) | radio |
| Choices (multiple) | multiselect |
| Labels | span |
| Rating | likert |
| TextArea | text |
| RectangleLabels | bounding_box |
| PolygonLabels | polygon |
| Taxonomy | (use nested multiselect) |
| Pairwise | comparison |
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:
# 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:
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
passAnotaçã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
# 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
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
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.