Skip to content
Guías9 min de lectura

Migración de Label Studio a Potato

Migra de Label Studio a Potato y convierte configuraciones de proyecto, esquemas de anotación y datos exportados con esta guía paso a paso que cubre los tipos de anotación más comunes.

Potato Team

Esta guía explica cómo trasladar un proyecto existente de Label Studio a Potato. Desde Potato 2.12.0, potato import -f label_studio convierte directamente la exportación de un proyecto: la interfaz de etiquetado se convierte en esquemas de anotación, las respuestas de cada anotador se convierten en el trabajo guardado de esa persona, y las predicciones del modelo se convierten en preanotaciones. Importar proyectos enumera qué corresponde a qué. La vía manual que sigue es para versiones anteriores de Potato y para las etiquetas que el importador no traduce, que son <Pairwise>, <Ranker>, <Taxonomy>, <BrushLabels> y las relaciones.

Para una comparación de funciones lado a lado, consulta la documentación fuente. La CLI de migración de Potato actualiza configuraciones escritas para versiones anteriores de Potato y no lee proyectos de Label Studio. La herramienta que los lee es potato import.

¿Por qué migrar?

Potato encaja mejor en algunos proyectos. Está pensado para estudios de anotación académicos, incluye integración con Prolific y SONA, y se configura con YAML sin necesidad de montar una base de datos. Es fácil de ampliar con Python y, como el almacenamiento son simples archivos, es fácil de desplegar.

Resumen de la migración

Sin el importador, el proceso es manual y sigue aproximadamente estos pasos:

  1. Convertir manualmente la plantilla XML de Label Studio a una configuración YAML de Potato
  2. Escribir scripts de Python para transformar el formato de datos (JSON a JSONL)
  3. Escribir scripts para migrar las anotaciones existentes (si las hay)
  4. Probar a fondo y validar los datos convertidos

Conversión de plantillas

Clasificación de texto

XML de 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 de 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"

Clasificación multietiqueta

XML de 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 de 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"

Reconocimiento de entidades nombradas

XML de 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 de 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"

Nota: la anotación de spans de Potato puede usar un resaltado distinto al de Label Studio. Prueba la configuración convertida para comprobar que la visualización cumple tus necesidades.

Clasificación de imágenes

XML de 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 de 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"

Anotación con cuadros delimitadores

XML de 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 de Potato:

yaml
annotation_schemes:
  - annotation_type: image_annotation
    tools: [bbox]
    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 conocer las capacidades actuales.

Escalas de valoración

XML de Label Studio:

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

YAML de Potato:

yaml
annotation_schemes:
  - annotation_type: likert
    name: quality
    min_label: "Poor"
    max_label: "Excellent"
    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 del formato de datos

De JSON de Label Studio a JSONL de Potato

Formato de 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 de Potato:

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

Script de conversión

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")

Migración de anotaciones

Conversión de anotaciones 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")

Conversión de anotaciones de spans

Label Studio usa desplazamientos de caracteres y Potato también, por lo que la conversión es directa:

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

Correspondencia de funciones

Label StudioPotato
Choices (single)radio
Choices (multiple)multiselect
Labelsspan
Ratinglikert
TextAreatext
RectangleLabelsimage_annotation con tools: [bbox]
PolygonLabelsimage_annotation con tools: [polygon]
Taxonomy(usar multiselect anidado)
Pairwisepairwise

Control de calidad

Aquí la migración te aporta algo. La edición comunitaria de Label Studio no incluye ninguna métrica de acuerdo, y la marcación de verdad de referencia, la asignación de revisores y los paneles de calidad están en los planes de pago. En Potato son claves de configuración.

Los controles de atención vienen integrados en lugar de colarse en el archivo de datos. Potato los inserta por ti y registra quién falla:

yaml
attention_checks:
  enabled: true
  items_file: "attention_checks.json"
  frequency: 10              # one check every ten items
  min_response_time: 3.0     # also flag suspiciously fast answers

Los estándares de oro funcionan igual, puntuando a cada anotador con elementos cuya respuesta ya conoces:

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

El acuerdo entre anotadores se calcula por ti. Haz que tus anotadores se solapen en un subconjunto común y actívalo. La alfa de Krippendorff aparece en el panel de administración, así que no hay que escribir ningún paso de scikit-learn aparte:

yaml
num_annotators_per_item: 3
 
agreement_metrics:
  enabled: true

La incertidumbre de las etiquetas no tiene equivalente en Label Studio en ningún plan. Potato ajusta un modelo de teoría de respuesta al ítem a tus anotaciones y devuelve cada etiqueta con una posterior y un intervalo de confianza, ponderando a los anotadores según lo fiables que hayan demostrado ser en lugar de contar votos:

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

Consulta control de calidad, acuerdo entre anotadores y el motor psicométrico para ver todas las opciones.

Migración de usuarios

Exportar usuarios de 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()

Crear la configuración de usuarios de Potato

yaml
user_config:
  allow_all_users: false
  users:
    - username: user1@example.com
      password: "..."   # set a new password; Potato hashes it on first load
 
    - username: user2@example.com
      password: "..."

Pruebas de la migración

Script de validación

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")

Lista de verificación de la migración

  • Exportar los 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 las anotaciones existentes (si las hay)
  • Configurar la estructura del proyecto de Potato
  • Probar con datos de ejemplo
  • Validar que los datos convertidos coinciden con los originales
  • Formar a los anotadores en la nueva interfaz
  • Ejecutar un lote piloto de anotación

Problemas comunes

Hay algunas cosas que suelen dar problemas. Ambas herramientas usan UTF-8, pero aun así conviene revisar los datos en busca de errores de codificación. Las rutas locales de imágenes normalmente tienen que convertirse en URL o, al menos, ajustarse al formato que espera Potato. Los componentes personalizados que hayas creado en Label Studio deben reconstruirse como plantillas personalizadas de Potato. Y si automatizaste tareas con la API de Label Studio, esos scripts tienen que apuntar a la API de Potato.


¿Necesitas ayuda con la migración? Consulta la documentación completa o escríbenos en GitHub.