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

Points relatifs au contrôle qualité

Le contrôle qualité est un domaine où vous devrez faire un peu de travail à la main. Potato dispose d'un contrôle qualité de base, mais pas d'un système unique et complet, prévoyez donc de combler les manques vous-même.

Approches pour le contrôle qualité

Contrôles d'attention : Vous pouvez ajouter manuellement des éléments de contrôle d'attention à votre fichier de données. Ce sont des éléments ordinaires aux réponses correctes connues, que vous incluez pour vérifier l'attention des annotateurs :

python
# Add attention check items to your data
attention_items = [
    {"id": "attn_1", "text": "ATTENTION CHECK: Please select 'Positive'", "is_attention": True},
    {"id": "attn_2", "text": "ATTENTION CHECK: Please select 'Negative'", "is_attention": True},
]
 
# Intersperse with regular items
import random
all_items = regular_items + attention_items
random.shuffle(all_items)

Calcul de l'accord : Calculez l'accord inter-annotateurs hors ligne à partir des annotations collectées :

python
from sklearn.metrics import cohen_kappa_score
import numpy as np
 
def compute_agreement(annotations_file):
    """Compute agreement from collected annotations"""
    # Load annotations and compute metrics externally
    # Potato does not have built-in agreement calculation
    pass

Annotation redondante : Configurez plusieurs annotateurs par élément en assignant les éléments à plusieurs utilisateurs dans votre processus de gestion des données.

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.