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 |
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:
attention_checks:
enabled: true
items_file: "attention_checks.json"
frequency: 10
min_response_time: 3.0Os padrões-ouro funcionam do mesmo jeito, pontuando cada anotador contra itens cuja resposta você já conhece:
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:
num_annotators_per_item: 3
agreement_metrics:
enabled: trueA 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:
psychometrics:
enabled: true
schema: sentiment
confidence_threshold: 0.95Veja 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
# 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.