Migración de Label Studio a Potato
Guía paso a paso para convertir manualmente proyectos, plantillas y anotaciones de Label Studio al formato de Potato.
Migración de Label Studio a Potato
Esta guía te ayuda a migrar manualmente proyectos existentes de Label Studio a Potato. La migración implica convertir configuraciones a mano y escribir scripts de Python para transformar los formatos de datos.
Ten en cuenta que no existe una herramienta oficial de migración - este es un proceso manual que requiere comprender ambas plataformas.
¿Por Qué Migrar?
Potato ofrece ventajas para ciertos casos de uso:
- Enfoque en investigación: Construido para estudios de anotación académica
- Crowdsourcing: Integración nativa con Prolific y MTurk
- Simplicidad: Configuración basada en YAML, sin base de datos requerida
- Personalización: Fácil de extender con Python
- Ligereza: Almacenamiento basado en archivos, fácil de desplegar
Resumen de la Migración
La migración es un proceso manual que involucra estos pasos:
- Convertir manualmente la plantilla XML de Label Studio a configuración YAML de Potato
- Escribir scripts de Python para transformar el formato de datos (JSON a JSONL)
- Escribir scripts para migrar anotaciones existentes (si las hay)
- Probar exhaustivamente y validar los datos convertidos
Conversión de Plantillas
Clasificación de Texto
XML de 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 de 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"Clasificación Multi-Etiqueta
XML de 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 de 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"Reconocimiento de Entidades Nombradas
XML de 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 de 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"Nota: La anotación de span de Potato puede usar un resaltado diferente al de Label Studio. Prueba tu configuración convertida para verificar que la visualización cumple tus necesidades.
Clasificación de Imágenes
XML de 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 de 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"Anotación de Cuadros Delimitadores
XML de 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 de 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"Nota: El soporte de cuadros delimitadores en Potato puede diferir del de Label Studio. Consulta la documentación para las capacidades actuales.
Escalas de Calificación
XML de Label Studio:
<View>
<Text name="text" value="$text"/>
<Rating name="quality" toName="text" maxRating="5"/>
</View>YAML de 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"Conversión de Formato de Datos
JSON de Label Studio a JSONL de Potato
Formato de Label Studio:
[
{
"id": 1,
"data": {
"text": "This is great!",
"meta_info": "source1"
}
},
{
"id": 2,
"data": {
"text": "This is terrible.",
"meta_info": "source2"
}
}
]Formato JSONL de Potato:
{"id": "1", "text": "This is great!", "metadata": {"source": "source1"}}
{"id": "2", "text": "This is terrible.", "metadata": {"source": "source2"}}Script de Conversión
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")Migración de Anotaciones
Conversión de Anotaciones 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")Conversión de Anotación de Span
Label Studio usa desplazamientos de caracteres; Potato también usa desplazamientos de caracteres, por lo que la conversión es directa:
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_spansMapeo de Funciones
| Label Studio | Potato |
|---|---|
| Choices (single) | radio |
| Choices (multiple) | multiselect |
| Labels | span |
| Rating | likert |
| TextArea | text |
| RectangleLabels | bounding_box |
| PolygonLabels | polygon |
| Taxonomy | (usar multiselect anidado) |
| Pairwise | comparison |
Consideraciones de Control de Calidad
Al migrar desde Label Studio, necesitarás implementar medidas de control de calidad manualmente. Potato proporciona algunas capacidades básicas de QC, pero no hay un sistema integral de QC integrado.
Enfoques para el Control de Calidad
Verificaciones de atención: Puedes agregar manualmente elementos de verificación de atención a tu archivo de datos. Estos son elementos regulares con respuestas correctas conocidas que incluyes para verificar la atención del anotador:
# 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 acuerdo: Calcula el acuerdo entre anotadores fuera de línea usando tus anotaciones recopiladas:
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
passAnotación redundante: Configura múltiples anotadores por elemento asignando elementos a múltiples usuarios en tu proceso de gestión de datos.
Migración de Usuarios
Exportar Usuarios de 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()Crear Configuración de Usuarios en Potato
user_config:
# Simple auth for migrated users
auth_type: password
users:
- username: user1@example.com
password_hash: "..." # Generate new passwords
- username: user2@example.com
password_hash: "..."Pruebas de Migración
Script de Validación
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")Lista de Verificación de Migración
- Exportar datos de Label Studio (formato JSON)
- Convertir manualmente la plantilla XML a YAML de Potato
- Escribir y ejecutar scripts de Python para transformar el formato de datos (JSON a JSONL)
- Escribir y ejecutar scripts para convertir anotaciones existentes (si las hay)
- Configurar la estructura del proyecto de Potato
- Probar con datos de ejemplo
- Validar que los datos convertidos coincidan con los originales
- Entrenar a los anotadores en la nueva interfaz
- Ejecutar un lote piloto de anotación
Problemas Comunes
Codificación de Caracteres
Label Studio y Potato ambos usan UTF-8, pero verifica problemas de codificación en tus datos.
Rutas de Imágenes
Convierte rutas locales a URLs o actualiza las rutas para coincidir con el formato esperado de Potato.
Componentes Personalizados
Los componentes personalizados de Label Studio necesitan ser recreados como plantillas personalizadas de Potato.
Diferencias de API
Si automatizaste Label Studio, actualiza los scripts para usar la API de Potato.
¿Necesitas ayuda con la migración? Consulta la documentación completa o contacta en GitHub.