Skip to content
أدلةمدة القراءة 8 دقيقة

الترحيل من Label Studio إلى Potato

انقل مشروعك من Label Studio إلى Potato وحوّل تكوينات المشاريع ومخططات التوسيم والبيانات المصدّرة، مع دليل خطوة بخطوة يغطي أنواع التوسيم الشائعة.

Potato Team

يشرح هذا الدليل خطوات نقل مشروع قائم في Label Studio إلى Potato. لا توجد أداة رسمية تحوّل مشروع Label Studio، لذا ستحوّل التكوين يدوياً وتكتب قليلاً من شيفرة Python لإعادة تشكيل بياناتك، وهذا يتطلب بعض المعرفة بكلتا المنصتين.

للاطلاع على مقارنة الميزات جنباً إلى جنب، راجع الوثائق المصدرية. أما أداة الترحيل عبر سطر الأوامر في Potato فتحدّث ملفات التكوين المكتوبة لإصدارات أقدم من Potato ولا تقرأ مشاريع Label Studio، كما أن potato import لا يدعم تنسيق Label Studio أيضاً.

لماذا الترحيل؟

يناسب Potato بعض المشاريع أكثر. فهو مصمم لدراسات التوسيم الأكاديمية، ويأتي مع تكامل جاهز مع Prolific وSONA، ويُكوَّن عبر YAML دون الحاجة إلى إعداد قاعدة بيانات. ومن السهل توسيعه باستخدام Python، ولأن التخزين مجرد ملفات فإن نشره سهل أيضاً.

نظرة عامة على الترحيل

العملية يدوية وتسير تقريباً على النحو التالي:

  1. تحويل قالب XML الخاص بـ Label Studio يدوياً إلى تكوين YAML الخاص بـ Potato
  2. كتابة سكريبتات Python لتحويل صيغة البيانات (من JSON إلى JSONL)
  3. كتابة سكريبتات لترحيل التوسيمات الموجودة (إن وجدت)
  4. الاختبار الشامل والتحقق من البيانات المحوّلة

تحويل القوالب

تصنيف النصوص

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"

التصنيف متعدد التسميات

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"

التعرف على الكيانات المسماة

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"

ملاحظة: قد يستخدم توسيم النطاقات في Potato تمييزاً مختلفاً عن Label Studio. اختبر تكوينك المحوّل للتحقق من أن طريقة العرض تلبي احتياجاتك.

تصنيف الصور

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"

توسيم مربعات الإحاطة

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"

ملاحظة: قد يختلف دعم مربعات الإحاطة في Potato عنه في Label Studio. راجع الوثائق لمعرفة القدرات الحالية.

مقاييس التقييم

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
    min_label: "Poor"
    max_label: "Excellent"
    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"

تحويل صيغة البيانات

من JSON الخاص بـ Label Studio إلى JSONL الخاص بـ Potato

صيغة Label Studio:

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

صيغة Potato JSONL:

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

سكريبت التحويل

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

ترحيل التوسيمات

تحويل التوسيمات الموجودة

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

تحويل توسيمات النطاقات

يستخدم Label Studio إزاحات الأحرف، ويستخدمها Potato أيضاً، لذا فالتحويل مباشر:

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

مطابقة الميزات

Label StudioPotato
Choices (single)radio
Choices (multiple)multiselect
Labelsspan
Ratinglikert
TextAreatext
RectangleLabelsimage_annotation مع tools: [bbox]
PolygonLabelsimage_annotation مع tools: [polygon]
Taxonomy(استخدم multiselect متداخلاً)
Pairwisepairwise

ضبط الجودة

في ضبط الجودة يكسب الترحيل شيئاً فعلياً. فالنسخة المجتمعية من Label Studio لا تتضمن أي مقاييس اتفاق على الإطلاق، أما تحديد الإجابات المرجعية وإسناد المراجعين ولوحات الجودة فتقع في الخطط المدفوعة. وفي Potato كلها مفاتيح في ملف التكوين.

فحوص الانتباه مدمجة في الأداة بدل أن تُدسّ في ملف البيانات. يدرجها Potato نيابة عنك ويتتبع من يخفق فيها:

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

المعايير الذهبية تعمل بالطريقة نفسها، فتقيّم كل مُوسِّم على عناصر تعرف إجابتها سلفاً:

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

الاتفاق بين المُوسِّمين يُحسب نيابة عنك. اجعل مُوسِّميك يتداخلون على مجموعة فرعية مشتركة وفعّل الخيار. يظهر معامل ألفا لكريبندورف في لوحة الإدارة، فلا حاجة إلى كتابة خطوة منفصلة بـ scikit-learn:

yaml
num_annotators_per_item: 3
 
agreement_metrics:
  enabled: true

عدم اليقين في التسميات لا مقابل له في Label Studio في أي خطة. يلائم Potato نموذجاً من نظرية الاستجابة للمفردة على توسيماتك، ويعرض كل تسمية مع توزيع لاحق وفاصل ثقة، مع ترجيح المُوسِّمين بحسب الموثوقية التي أثبتوها بدل عدّ الأصوات:

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

راجع ضبط الجودة والاتفاق بين المُوسِّمين والمحرك السيكومتري للاطلاع على مجموعة الخيارات كاملة.

ترحيل المستخدمين

تصدير المستخدمين من 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()

إنشاء تكوين مستخدمي Potato

yaml
user_config:
  allow_all_users: false
  users:
    - username: user1@example.com
      password: "..."   # set a new password; Potato hashes it on first load
 
    - username: user2@example.com
      password: "..."

اختبار الترحيل

سكريبت التحقق

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

قائمة مراجعة الترحيل

  • تصدير البيانات من Label Studio (بصيغة JSON)
  • تحويل قالب XML يدوياً إلى YAML الخاص بـ Potato
  • كتابة سكريبتات Python وتشغيلها لتحويل صيغة البيانات (من JSON إلى JSONL)
  • كتابة سكريبتات وتشغيلها لتحويل التوسيمات الموجودة (إن وجدت)
  • إعداد هيكل مشروع Potato
  • الاختبار ببيانات نموذجية
  • التحقق من تطابق البيانات المحوّلة مع الأصلية
  • تدريب المُوسِّمين على الواجهة الجديدة
  • تشغيل دفعة توسيم تجريبية

مشكلات شائعة

هناك أمور قليلة يتعثر فيها الناس عادة. تستخدم الأداتان ترميز UTF-8، ومع ذلك يستحسن فحص بياناتك بحثاً عن أخطاء الترميز. وغالباً ما يلزم تحويل مسارات الصور المحلية إلى عناوين URL، أو على الأقل مطابقتها مع الصيغة التي يتوقعها Potato. وأي مكونات مخصصة بنيتها في Label Studio يجب إعادة بنائها كقوالب مخصصة في Potato. وإذا كتبت سكريبتات تعتمد على واجهة API الخاصة بـ Label Studio، فيجب توجيهها إلى واجهة API الخاصة بـ Potato بدلاً منها.


هل تحتاج مساعدة في الترحيل؟ راجع الوثائق الكاملة أو تواصل معنا على GitHub.