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.
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ì:
- Convertire a mano il template XML di Label Studio nella configurazione YAML di Potato
- Scrivere script Python per trasformare il formato dei dati (da JSON a JSONL)
- Scrivere script per migrare le annotazioni esistenti (se ce ne sono)
- Testare a fondo e validare i dati convertiti
Conversione dei template
Classificazione di testo
XML di 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 di 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"Classificazione multi-etichetta
XML di 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 di 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"Riconoscimento di entità nominate
XML di 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 di 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: 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:
<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:
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:
<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:
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:
<View>
<Text name="text" value="$text"/>
<Rating name="quality" toName="text" maxRating="5"/>
</View>YAML di 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"Conversione del formato dei dati
Dal JSON di Label Studio al JSONL di Potato
Formato di Label Studio:
[
{
"id": 1,
"data": {
"text": "This is great!",
"meta_info": "source1"
}
},
{
"id": 2,
"data": {
"text": "This is terrible.",
"meta_info": "source2"
}
}
]Formato JSONL di Potato:
{"id": "1", "text": "This is great!", "metadata": {"source": "source1"}}
{"id": "2", "text": "This is terrible.", "metadata": {"source": "source2"}}Script di conversione
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
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:
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_spansCorrispondenza delle funzionalità
| Label Studio | Potato |
|---|---|
| Choices (single) | radio |
| Choices (multiple) | multiselect |
| Labels | span |
| Rating | likert |
| TextArea | text |
| RectangleLabels | bounding_box |
| PolygonLabels | polygon |
| Taxonomy | (usa un multiselect annidato) |
| Pairwise | comparison |
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:
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 answersI gold standard funzionano allo stesso modo, dando un punteggio a ciascun annotatore su elementi di cui conosci già la risposta:
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:
num_annotators_per_item: 3
agreement_metrics:
enabled: trueL'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:
psychometrics:
enabled: true
schema: sentiment
confidence_threshold: 0.95Vedi controllo qualità, accordo tra annotatori e il motore psicometrico per l'elenco completo delle opzioni.
Migrazione degli utenti
Esportare gli utenti da 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()Creare la configurazione degli utenti in 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: "..."Testare la migrazione
Script di validazione
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.