Skip to content
Guides8 min read

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.

Potato Team

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:

  1. Das XML-Template von Label Studio von Hand in eine Potato-YAML-Konfiguration übersetzen
  2. Python-Skripte schreiben, die das Datenformat umwandeln (JSON zu JSONL)
  3. Skripte schreiben, die bestehende Annotationen migrieren (falls vorhanden)
  4. Gründlich testen und die umgewandelten Daten prüfen

Templates umwandeln

Textklassifikation

Label-Studio-XML:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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

Potato-YAML:

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:

json
[
  {
    "id": 1,
    "data": {
      "text": "This is great!",
      "meta_info": "source1"
    }
  },
  {
    "id": 2,
    "data": {
      "text": "This is terrible.",
      "meta_info": "source2"
    }
  }
]

JSONL-Format von Potato:

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

Skript zur Umwandlung

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

Annotationen migrieren

Bestehende Annotationen umwandeln

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

Span-Annotationen umwandeln

Label Studio arbeitet mit Zeichen-Offsets, Potato ebenfalls, die Umwandlung ist also unkompliziert:

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

Zuordnung der Funktionen

Label StudioPotato
Choices (single)radio
Choices (multiple)multiselect
Labelsspan
Ratinglikert
TextAreatext
RectangleLabelsbounding_box
PolygonLabelspolygon
Taxonomy(verschachteltes multiselect verwenden)
Pairwisecomparison

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:

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

Goldstandards funktionieren genauso und bewerten jede annotierende Person anhand von Elementen, deren Antwort du bereits kennst:

yaml
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:

yaml
num_annotators_per_item: 3
 
agreement_metrics:
  enabled: true

Label-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:

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

Alle Einstellungen findest du unter Qualitätskontrolle, Inter-Annotator-Übereinstimmung und Psychometrie-Engine.

Nutzende migrieren

Nutzende aus Label Studio exportieren

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

Nutzerkonfiguration für Potato anlegen

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: "..."

Migration testen

Skript zur Prüfung

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

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.