Von Label Studio zu Potato wechseln
Schritt für Schritt von Label Studio zu Potato migrieren: Projektkonfigurationen, Annotationsschemata und exportierte Daten umwandeln, mit den gängigen Annotationstypen.
Dieser Leitfaden zeigt, wie du ein bestehendes Label-Studio-Projekt nach Potato überführst. Gleich vorweg: Ein offizielles Migrationswerkzeug gibt es nicht. Du wandelst die Konfiguration von Hand um und schreibst etwas Python, um deine Daten umzuformen, du solltest dich also in beiden Plattformen auskennen.
Einen direkten Funktionsvergleich und Potatos eigenes Migrationswerkzeug findest du in der Quelldokumentation und in der Dokumentation zur Migrations-CLI.
Warum migrieren?
Für manche Projekte passt Potato besser. Es ist für akademische Annotationsstudien gebaut, bringt Anbindungen an Prolific und MTurk mit und wird über YAML konfiguriert, ohne dass du eine Datenbank aufsetzen musst. Es lässt sich in Python leicht erweitern, und weil die Speicherung nur aus Dateien besteht, ist der Betrieb unkompliziert.
Ablauf der Migration
Der Vorgang läuft manuell und grob so ab:
- Das XML-Template von Label Studio von Hand in eine Potato-YAML-Konfiguration übersetzen
- Python-Skripte schreiben, die das Datenformat umwandeln (JSON zu JSONL)
- Skripte schreiben, die bestehende Annotationen migrieren (falls vorhanden)
- Gründlich testen und die umgewandelten Daten prüfen
Templates umwandeln
Textklassifikation
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>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"Klassifikation mit mehreren Labels
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>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"Eigennamenerkennung (NER)
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>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"Hinweis: Potatos Span-Annotation hebt womöglich anders hervor als Label Studio. Teste deine umgewandelte Konfiguration, um zu prüfen, ob die Darstellung deinen Anforderungen genügt.
Bildklassifikation
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>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"Bounding-Box-Annotation
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>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"Hinweis: Die Unterstützung für Bounding Boxes in Potato kann sich von der in Label Studio unterscheiden. Sieh in der Dokumentation nach, was aktuell möglich ist.
Bewertungsskalen
Label-Studio-XML:
<View>
<Text name="text" value="$text"/>
<Rating name="quality" toName="text" maxRating="5"/>
</View>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"Datenformat umwandeln
Label-Studio-JSON zu Potato-JSONL
Format von Label Studio:
[
{
"id": 1,
"data": {
"text": "This is great!",
"meta_info": "source1"
}
},
{
"id": 2,
"data": {
"text": "This is terrible.",
"meta_info": "source2"
}
}
]JSONL-Format von Potato:
{"id": "1", "text": "This is great!", "metadata": {"source": "source1"}}
{"id": "2", "text": "This is terrible.", "metadata": {"source": "source2"}}Skript zur Umwandlung
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")Annotationen migrieren
Bestehende Annotationen umwandeln
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")Span-Annotationen umwandeln
Label Studio arbeitet mit Zeichen-Offsets, Potato ebenfalls, die Umwandlung ist also unkompliziert:
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_spansZuordnung der Funktionen
| Label Studio | Potato |
|---|---|
| Choices (single) | radio |
| Choices (multiple) | multiselect |
| Labels | span |
| Rating | likert |
| TextArea | text |
| RectangleLabels | bounding_box |
| PolygonLabels | polygon |
| Taxonomy | (verschachteltes multiselect verwenden) |
| Pairwise | comparison |
Qualitätskontrolle
An dieser Stelle der Migration gewinnst du etwas dazu, statt etwas aufzugeben. Die Community-Edition von Label Studio bringt überhaupt keine Übereinstimmungsmaße mit, und Ground-Truth-Markierung, Zuweisung von Prüfenden sowie die Qualitäts-Dashboards stecken in den kostenpflichtigen Stufen. In Potato sind das Konfigurationsschlüssel.
Aufmerksamkeitstests sind eine vollwertige Funktion und nichts, was du in die Datendatei einschmuggelst. Potato spielt sie für dich ein und protokolliert, wer durchfällt:
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 answersGoldstandards funktionieren genauso und bewerten jede annotierende Person anhand von Elementen, deren Antwort du bereits kennst:
gold_standards:
enabled: true
items_file: "gold_standards.json"Die Inter-Annotator-Übereinstimmung wird für dich berechnet. Lass deine Annotierenden sich auf einer gemeinsamen Teilmenge überlappen und schalte sie ein. Krippendorffs Alpha erscheint im Admin-Dashboard, du musst also keinen Offline-Schritt mit scikit-learn schreiben:
num_annotators_per_item: 3
agreement_metrics:
enabled: trueLabel-Unsicherheit ist, falls du weiter gehen willst, das, wofür Label Studio in keiner Stufe eine Entsprechung hat. Potato passt ein Modell aus der Item-Response-Theorie an deine Annotationen an und berichtet jedes Label mit einer A-posteriori-Verteilung und einem Konfidenzintervall, wobei Annotierende danach gewichtet werden, wie verlässlich sie sich erwiesen haben, statt Stimmen zu zählen:
psychometrics:
enabled: true
schema: sentiment
confidence_threshold: 0.95Alle Einstellungen findest du unter Qualitätskontrolle, Inter-Annotator-Übereinstimmung und Psychometrie-Engine.
Nutzende migrieren
Nutzende aus Label Studio exportieren
# 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()Nutzerkonfiguration für Potato anlegen
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: "..."Migration testen
Skript zur Prüfung
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")Checkliste für die Migration
- Daten aus Label Studio exportieren (JSON-Format)
- Template-XML von Hand in Potato-YAML übersetzen
- Python-Skripte zur Umwandlung des Datenformats schreiben und ausführen (JSON zu JSONL)
- Skripte zur Umwandlung bestehender Annotationen schreiben und ausführen (falls vorhanden)
- Projektstruktur für Potato aufsetzen
- Mit Beispieldaten testen
- Prüfen, ob die umgewandelten Daten dem Original entsprechen
- Annotierende an der neuen Oberfläche einarbeiten
- Einen Pilotdurchlauf annotieren
Häufige Stolpersteine
Ein paar Dinge bereiten regelmäßig Ärger. Beide Werkzeuge nutzen UTF-8, trotzdem lohnt ein Blick auf die Daten nach Kodierungsmacken. Lokale Bildpfade müssen meist zu URLs werden oder wenigstens dem Format entsprechen, das Potato erwartet. Eigene Komponenten, die du in Label Studio gebaut hast, musst du als Potato-Custom-Templates neu bauen. Und wenn du Skripte gegen die API von Label Studio geschrieben hast, müssen die künftig auf Potatos API zeigen.
Brauchst du Hilfe bei der Migration? Sieh in die vollständige Dokumentation oder melde dich auf GitHub.