Skip to content
Guides9 min read

Migrer de Label Studio vers Potato

Migrez de Label Studio vers Potato : convertissez les configurations de projet, les schémas d'annotation et les données exportées grâce à ce guide pas à pas couvrant les types d'annotation courants.

Potato Team

Ce guide explique comment faire passer un projet Label Studio existant vers Potato. Soyons clairs d'emblée : il n'existe pas d'outil de migration officiel. Vous convertissez la configuration à la main et écrivez un peu de Python pour remodeler vos données, il faut donc bien connaître les deux plateformes.

Pour une comparaison fonctionnelle côte à côte et l'outillage de migration propre à Potato, consultez la documentation source et la documentation du CLI de migration.

Pourquoi migrer ?

Potato convient mieux à certains projets. Il est conçu pour les études d'annotation académiques, intègre nativement Prolific et MTurk, et se configure via YAML sans base de données à mettre en place. Il est facile à étendre en Python et, comme le stockage repose simplement sur des fichiers, il est facile à déployer.

Aperçu de la migration

Le processus est manuel et se déroule à peu près ainsi :

  1. Convertir manuellement le modèle XML Label Studio en configuration YAML Potato
  2. Écrire des scripts Python pour transformer le format des données (JSON vers JSONL)
  3. Écrire des scripts pour migrer les annotations existantes (le cas échéant)
  4. Tester minutieusement et valider vos données converties

Conversion de modèles

Classification de texte

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

Classification multi-étiquettes

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

Reconnaissance d'entités nommées

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

Note : L'annotation par span de Potato peut utiliser un surlignage différent de Label Studio. Testez votre configuration convertie pour vérifier que l'affichage correspond à vos besoins.

Classification d'images

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

Annotation par boîte englobante

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

yaml
annotation_schemes:
  - annotation_type: bounding_box
    name: objects
    description: "Draw boxes around objects"
    labels:
      - name: car
        tooltip: "Car"
      - name: person
        tooltip: "Person"
      - name: bicycle
        tooltip: "Bicycle"

Note : La prise en charge des boîtes englobantes dans Potato peut différer de Label Studio. Consultez la documentation pour connaître les capacités actuelles.

Échelles de notation

XML Label Studio :

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

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

Conversion du format de données

JSON Label Studio vers JSONL Potato

Format Label Studio :

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

Format JSONL Potato :

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

Script de conversion

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

Migration des annotations

Convertir les annotations existantes

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

Conversion des annotations par span

Label Studio utilise des décalages de caractères ; Potato utilise aussi des décalages de caractères, la conversion est donc directe :

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

Correspondance des fonctionnalités

Label StudioPotato
Choices (single)radio
Choices (multiple)multiselect
Labelsspan
Ratinglikert
TextAreatext
RectangleLabelsbounding_box
PolygonLabelspolygon
Taxonomy(utiliser multiselect imbriqué)
Pairwisecomparison

Contrôle qualité

C'est l'endroit de la migration où vous gagnez quelque chose au lieu d'en perdre. L'édition communautaire de Label Studio ne fournit aucune métrique d'accord, et le marquage de la vérité terrain, l'affectation des relecteurs et les tableaux de bord qualité se trouvent dans les offres payantes. Dans Potato, ce sont des clés de configuration.

Les tests d'attention sont une fonctionnalité à part entière, pas quelque chose que vous glissez dans le fichier de données. Potato les insère pour vous et note qui échoue :

yaml
attention_checks:
  enabled: true
  items_file: "attention_checks.json"
  frequency: 10
  min_response_time: 3.0

Les étalons or fonctionnent de la même façon et notent chaque annotateur sur des éléments dont vous connaissez déjà la réponse :

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

L'accord inter-annotateurs est calculé pour vous. Faites se recouvrir vos annotateurs sur un sous-ensemble commun et activez-le. L'alpha de Krippendorff apparaît dans le tableau de bord d'administration : aucune étape scikit-learn à écrire :

yaml
num_annotators_per_item: 3
 
agreement_metrics:
  enabled: true

L'incertitude des annotations, si vous voulez aller plus loin, est ce à quoi Label Studio n'a d'équivalent dans aucune offre. Potato ajuste un modèle de réponse à l'item sur vos annotations et renvoie chaque annotation avec une postérieure et un intervalle de confiance, en pondérant les annotateurs selon leur fiabilité démontrée plutôt qu'en comptant les voix :

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

Voir contrôle qualité, accord inter-annotateurs et le moteur psychométrique pour toutes les options.

Migration des utilisateurs

Exporter les utilisateurs depuis 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()

Créer la configuration des utilisateurs 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: "..."

Tester la migration

Script de validation

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

Liste de vérification de migration

  • Exporter les données de Label Studio (format JSON)
  • Convertir manuellement le modèle XML en YAML Potato
  • Écrire et exécuter des scripts Python pour transformer le format des données (JSON vers JSONL)
  • Écrire et exécuter des scripts pour convertir les annotations existantes (le cas échéant)
  • Configurer la structure du projet Potato
  • Tester avec des données d'exemple
  • Valider que les données converties correspondent à l'original
  • Former les annotateurs à la nouvelle interface
  • Exécuter un lot d'annotation pilote

Problèmes courants

Quelques détails ont tendance à coincer. Les deux outils utilisent l'UTF-8, mais il vaut tout de même la peine de vérifier vos données pour repérer d'éventuels problèmes d'encodage. Les chemins d'images locaux doivent généralement devenir des URL, ou au moins correspondre au format attendu par Potato. Tout composant personnalisé créé dans Label Studio doit être reconstruit sous forme de modèles personnalisés Potato. Et si vous avez écrit des scripts contre l'API de Label Studio, ces scripts doivent désormais pointer vers l'API de Potato.


Besoin d'aide pour migrer ? Consultez la documentation complète ou contactez-nous sur GitHub.