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.
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 :
- Convertir manuellement le modèle XML Label Studio en configuration YAML Potato
- Écrire des scripts Python pour transformer le format des données (JSON vers JSONL)
- Écrire des scripts pour migrer les annotations existantes (le cas échéant)
- Tester minutieusement et valider vos données converties
Conversion de modèles
Classification de texte
XML 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 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"Classification multi-étiquettes
XML 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 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"Reconnaissance d'entités nommées
XML 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 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"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 :
<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 :
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 :
<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 :
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 :
<View>
<Text name="text" value="$text"/>
<Rating name="quality" toName="text" maxRating="5"/>
</View>YAML 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"Conversion du format de données
JSON Label Studio vers JSONL Potato
Format Label Studio :
[
{
"id": 1,
"data": {
"text": "This is great!",
"meta_info": "source1"
}
},
{
"id": 2,
"data": {
"text": "This is terrible.",
"meta_info": "source2"
}
}
]Format JSONL Potato :
{"id": "1", "text": "This is great!", "metadata": {"source": "source1"}}
{"id": "2", "text": "This is terrible.", "metadata": {"source": "source2"}}Script de conversion
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
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 :
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_spansCorrespondance des fonctionnalités
| Label Studio | Potato |
|---|---|
| Choices (single) | radio |
| Choices (multiple) | multiselect |
| Labels | span |
| Rating | likert |
| TextArea | text |
| RectangleLabels | bounding_box |
| PolygonLabels | polygon |
| Taxonomy | (utiliser multiselect imbriqué) |
| Pairwise | comparison |
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 :
# 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 :
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
passAnnotation 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
# 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
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
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.