Skip to content
ガイド10分で読めます

Label StudioからPotatoへの移行

Label StudioからPotatoへ移行し、プロジェクト設定、アノテーションスキーマ、エクスポート済みデータを変換します。よく使われるアノテーションタイプを扱うステップバイステップガイドです。

Potato Team

このガイドでは、既存のLabel StudioプロジェクトをPotatoに移す手順を説明します。Label Studioのプロジェクトを変換する公式ツールはないため、設定は手作業で変換し、データの形を整えるために少しPythonを書くことになります。そのため、両方のプラットフォームをある程度理解している必要があります。

機能の比較表についてはソースドキュメントを参照してください。Potatoの移行CLIは古いバージョンのPotato向けに書かれた設定をアップグレードするもので、Label Studioのプロジェクトは読み込みません。potato importにもLabel Studio形式はありません。

なぜ移行するのか?

プロジェクトによってはPotatoのほうが適しています。Potatoは学術的なアノテーション研究のために作られており、ProlificとSONAとの統合を備え、YAMLで設定するためデータベースを用意する必要がありません。Pythonで拡張しやすく、ストレージは単なるファイルなのでデプロイも簡単です。

移行の概要

作業は手動で、おおよそ次の流れで進みます。

  1. Label StudioのXMLテンプレートをPotatoのYAML設定に手動で変換する
  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"

データ形式変換

Label Studio JSONからPotato JSONLへ

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"

アノテーター間一致度は自動で計算されます。共通のサブセットでアノテーターの担当を重ね、これを有効にします。Krippendorffのαは管理ダッシュボードに表示されるので、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をPotato YAMLに手動で変換する
  • データ形式を変換するPythonスクリプトを書いて実行する(JSONからJSONLへ)
  • 既存アノテーションを変換するスクリプトを書いて実行する(ある場合)
  • Potatoのプロジェクト構成を用意する
  • サンプルデータでテストする
  • 変換したデータが元のデータと一致することを検証する
  • 新しいインターフェースについてアノテーターをトレーニングする
  • パイロットのアノテーションバッチを実行する

よくある問題

つまずきやすい点がいくつかあります。どちらのツールもUTF-8を使いますが、それでもデータに文字化けなどのエンコーディングの問題がないか確認する価値はあります。ローカルの画像パスは通常URLに変換するか、少なくともPotatoが想定する形式に合わせる必要があります。Label Studioで作ったカスタムコンポーネントは、Potatoのカスタムテンプレートとして作り直す必要があります。また、Label StudioのAPIに対してスクリプトを書いていた場合は、それらをPotatoのAPIに向け直す必要があります。


移行でお困りですか?完全なドキュメントを確認するか、GitHubでお問い合わせください。