Skip to content
Guides9 min read

Migrare da Label Studio a Potato

Come migrare da Label Studio a Potato convertendo la configurazione del progetto, gli schemi di annotazione e i dati esportati, con una guida passo passo sui tipi di annotazione più comuni.

Potato Team

Questa guida ripercorre lo spostamento di un progetto Label Studio esistente su Potato. Mettiamo subito le mani avanti: non esiste uno strumento di migrazione ufficiale. La configurazione si converte a mano e serve un po' di Python per rimodellare i dati, quindi devi muoverti con una certa disinvoltura su entrambe le piattaforme.

Per un confronto affiancato delle funzionalità e per gli strumenti di migrazione di Potato, vedi la documentazione sorgente e la documentazione della CLI di migrazione.

Perché migrare?

Per certi progetti Potato è più adatto. È pensato per gli studi di annotazione accademici, arriva con l'integrazione di Prolific e MTurk e si configura in YAML senza dover mettere in piedi un database. È facile da estendere in Python e, dato che i dati stanno in file, è facile da mettere in produzione.

Quadro generale della migrazione

Il processo è manuale e procede grosso modo così:

  1. Convertire a mano il template XML di Label Studio nella configurazione YAML di Potato
  2. Scrivere script Python per trasformare il formato dei dati (da JSON a JSONL)
  3. Scrivere script per migrare le annotazioni esistenti (se ce ne sono)
  4. Testare a fondo e validare i dati convertiti

Conversione dei template

Classificazione di testo

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

Classificazione multi-etichetta

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

Riconoscimento di entità nominate

XML di 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 di 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: l'annotazione a span di Potato può evidenziare in modo diverso da Label Studio. Prova la configurazione convertita per verificare che la resa a schermo vada bene per te.

Classificazione di immagini

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

Annotazione con bounding box

XML di 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 di 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: il supporto per le bounding box in Potato può differire da quello di Label Studio. Controlla nella documentazione le funzionalità attuali.

Scale di valutazione

XML di Label Studio:

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

YAML di 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"

Conversione del formato dei dati

Dal JSON di Label Studio al JSONL di Potato

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

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

Script di conversione

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

Migrazione delle annotazioni

Convertire le annotazioni esistenti

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

Conversione delle annotazioni a span

Label Studio usa gli offset in caratteri; anche Potato usa gli offset in caratteri, quindi la conversione è diretta:

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

Corrispondenza delle funzionalità

Label StudioPotato
Choices (single)radio
Choices (multiple)multiselect
Labelsspan
Ratinglikert
TextAreatext
RectangleLabelsbounding_box
PolygonLabelspolygon
Taxonomy(usa un multiselect annidato)
Pairwisecomparison

Controllo qualità

È il punto della migrazione in cui guadagni qualcosa invece di rinunciare a qualcosa. L'edizione community di Label Studio non offre alcuna metrica di accordo, e la marcatura della ground truth, l'assegnazione dei revisori e le dashboard di qualità stanno nei piani a pagamento. In Potato sono chiavi di configurazione.

I controlli di attenzione sono una funzionalità di prima classe, non qualcosa da infilare di nascosto nel file di dati. Potato li inserisce al posto tuo e tiene traccia di chi li sbaglia:

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

I gold standard funzionano allo stesso modo, dando un punteggio a ciascun annotatore su elementi di cui conosci già la risposta:

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

L'accordo tra annotatori viene calcolato per te. Fai lavorare gli annotatori in sovrapposizione su un sottoinsieme condiviso e attivalo. L'alpha di Krippendorff compare nella dashboard di amministrazione, quindi non c'è nessun passaggio offline con scikit-learn da scrivere:

yaml
num_annotators_per_item: 3
 
agreement_metrics:
  enabled: true

L'incertezza sulle etichette, se vuoi andare oltre, è la cosa per cui Label Studio non ha un equivalente a nessun livello. Potato adatta alle tue annotazioni un modello di item response theory e riporta ogni etichetta con una distribuzione a posteriori e un intervallo di confidenza, pesando gli annotatori in base a quanto si sono dimostrati affidabili invece di contare i voti:

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

Vedi controllo qualità, accordo tra annotatori e il motore psicometrico per l'elenco completo delle opzioni.

Migrazione degli utenti

Esportare gli utenti da 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()

Creare la configurazione degli utenti in 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: "..."

Testare la migrazione

Script di validazione

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 di controllo per la migrazione

  • Esportare i dati da Label Studio (formato JSON)
  • Convertire a mano il template XML in YAML per Potato
  • Scrivere ed eseguire gli script Python per trasformare il formato dei dati (da JSON a JSONL)
  • Scrivere ed eseguire gli script per convertire le annotazioni esistenti (se ce ne sono)
  • Predisporre la struttura del progetto Potato
  • Provare con dati di esempio
  • Verificare che i dati convertiti corrispondano all'originale
  • Formare gli annotatori sulla nuova interfaccia
  • Fare un lotto pilota di annotazione

Problemi frequenti

Ci sono alcune cose su cui si inciampa spesso. Entrambi gli strumenti usano UTF-8, ma vale comunque la pena controllare i dati per eventuali stranezze di codifica. I percorsi locali delle immagini di solito vanno trasformati in URL, o quantomeno adeguati al formato che Potato si aspetta. I componenti personalizzati costruiti in Label Studio vanno rifatti come template personalizzati di Potato. E se avevi degli script che parlavano con l'API di Label Studio, quegli script vanno puntati sull'API di Potato.


Ti serve una mano con la migrazione? Consulta la documentazione completa o scrivi su GitHub.