{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":91249,"databundleVersionId":11294684,"sourceType":"competition"}],"dockerImageVersionId":31040,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"try:\n    from tqdm.notebook import tqdm  # Jupyter用\nexcept ImportError:\n    from tqdm import tqdm  # Fallback（通常環境）","metadata":{}},{"cell_type":"code","source":"## YOROデータ向け前処理\n\nimport os\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nimport shutil\nimport time\nimport yaml\nfrom pathlib import Path\nfrom tqdm.notebook import tqdm  # Jupyter/Kaggle環境用の進捗バー\n\n# 再現性のために乱数シードを固定\nnp.random.seed(42)\n\n# Kaggleデータセットのパスを定義\ndata_path = \"/kaggle/input/byu-locating-bacterial-flagellar-motors-2025/\"\ntrain_dir = os.path.join(data_path, \"train\")\n\n# YOLO形式のデータ構造を定義\nyolo_dataset_dir = \"/kaggle/working/yolo_dataset\"\nyolo_images_train = os.path.join(yolo_dataset_dir, \"images\", \"train\")\nyolo_images_val = os.path.join(yolo_dataset_dir, \"images\", \"val\")\nyolo_labels_train = os.path.join(yolo_dataset_dir, \"labels\", \"train\")\nyolo_labels_val = os.path.join(yolo_dataset_dir, \"labels\", \"val\")\n\n# 必要なディレクトリを作成\nfor dir_path in [yolo_images_train, yolo_images_val, yolo_labels_train, yolo_labels_val]:\n    os.makedirs(dir_path, exist_ok=True)\n\n# 定数を定義\nTRUST = 4  # 中心スライスの前後に含めるスライス数（合計2*TRUST+1枚）\nBOX_SIZE = 24  # アノテーション用バウンディングボックスのサイズ（ピクセル単位）\nTRAIN_SPLIT = 0.8  # 学習データと検証データの分割比（80%:20%）\n\n# スライス画像の正規化関数\ndef normalize_slice(slice_data):\n    \"\"\"\n    スライス画像を2パーセンタイルと98パーセンタイルでクリップして正規化\n    \"\"\"\n    p2 = np.percentile(slice_data, 2)\n    p98 = np.percentile(slice_data, 98)\n    clipped_data = np.clip(slice_data, p2, p98)\n    normalized = 255 * (clipped_data - p2) / (p98 - p2)\n    return np.uint8(normalized)\n\n# YOLO用データセットを準備する関数\ndef prepare_yolo_dataset(trust=TRUST, train_split=TRAIN_SPLIT):\n    \"\"\"\n    鞭毛モーターを含むスライスを抽出して、YOLO用画像とラベルを保存\n    \"\"\"\n    # アノテーションCSVを読み込み\n    labels_df = pd.read_csv(os.path.join(data_path, \"train_labels.csv\"))\n    \n    # モーターの総数を表示\n    total_motors = labels_df['Number of motors'].sum()\n    print(f\"データセット内のモーター総数: {total_motors}\")\n    \n    # モーターを含むユニークなトモグラムを取得\n    tomo_df = labels_df[labels_df['Number of motors'] > 0].copy()\n    unique_tomos = tomo_df['tomo_id'].unique()\n    print(f\"モーターを含むユニークなトモグラム数: {len(unique_tomos)}\")\n    \n    # トモグラム単位で学習/検証用に分割（同じトモグラムは片方にしか属さない）\n    np.random.shuffle(unique_tomos)\n    split_idx = int(len(unique_tomos) * train_split)\n    train_tomos = unique_tomos[:split_idx]\n    val_tomos = unique_tomos[split_idx:]\n    print(f\"分割結果: 学習用 {len(train_tomos)}個, 検証用 {len(val_tomos)}個\")\n\n    # トモグラムを処理する関数\n    def process_tomogram_set(tomogram_ids, images_dir, labels_dir, set_name):\n        motor_counts = []\n        for tomo_id in tomogram_ids:\n            tomo_motors = labels_df[labels_df['tomo_id'] == tomo_id]\n            for _, motor in tomo_motors.iterrows():\n                if pd.isna(motor['Motor axis 0']):\n                    continue\n                motor_counts.append(\n                    (tomo_id, \n                     int(motor['Motor axis 0']), \n                     int(motor['Motor axis 1']), \n                     int(motor['Motor axis 2']),\n                     int(motor['Array shape (axis 0)']))\n                )\n        \n        print(f\"{set_name}セットで処理するスライスの概算数: {len(motor_counts) * (2 * trust + 1)}\")\n        \n        processed_slices = 0\n        \n        # モーターごとに処理\n        for tomo_id, z_center, y_center, x_center, z_max in tqdm(motor_counts, desc=f\"{set_name}モーター処理中\"):\n            z_min = max(0, z_center - trust)\n            z_max = min(z_max - 1, z_center + trust)\n            \n            for z in range(z_min, z_max + 1):\n                slice_filename = f\"slice_{z:04d}.jpg\"\n                src_path = os.path.join(train_dir, tomo_id, slice_filename)\n                \n                if not os.path.exists(src_path):\n                    print(f\"警告: {src_path} が存在しません。スキップします。\")\n                    continue\n                \n                img = Image.open(src_path)\n                img_array = np.array(img)\n                normalized_img = normalize_slice(img_array)\n                \n                # 出力用ファイル名をユニークに作成\n                dest_filename = f\"{tomo_id}_z{z:04d}_y{y_center:04d}_x{x_center:04d}.jpg\"\n                dest_path = os.path.join(images_dir, dest_filename)\n                \n                # 画像を保存\n                Image.fromarray(normalized_img).save(dest_path)\n                \n                # 画像サイズを取得\n                img_width, img_height = img.size\n                \n                # YOLO形式のラベルを作成（正規化された中心座標とサイズ）\n                x_center_norm = x_center / img_width\n                y_center_norm = y_center / img_height\n                box_width_norm = BOX_SIZE / img_width\n                box_height_norm = BOX_SIZE / img_height\n                \n                label_path = os.path.join(labels_dir, dest_filename.replace('.jpg', '.txt'))\n                with open(label_path, 'w') as f:\n                    f.write(f\"0 {x_center_norm} {y_center_norm} {box_width_norm} {box_height_norm}\\n\")\n                \n                processed_slices += 1\n        \n        return processed_slices, len(motor_counts)\n    \n    # 学習データ処理\n    train_slices, train_motors = process_tomogram_set(train_tomos, yolo_images_train, yolo_labels_train, \"学習\")\n    \n    # 検証データ処理\n    val_slices, val_motors = process_tomogram_set(val_tomos, yolo_images_val, yolo_labels_val, \"検証\")\n    \n    # YOLOの学習設定用のYAMLファイルを作成\n    yaml_content = {\n        'path': yolo_dataset_dir,\n        'train': 'images/train',\n        'val': 'images/val',\n        'names': {0: 'motor'}\n    }\n    \n    with open(os.path.join(yolo_dataset_dir, 'dataset.yaml'), 'w') as f:\n        yaml.dump(yaml_content, f, default_flow_style=False)\n    \n    # 処理のサマリを表示\n    print(f\"\\n処理結果:\")\n    print(f\"- 学習データ: {len(train_tomos)} トモグラム, {train_motors} モーター, {train_slices} スライス\")\n    print(f\"- 検証データ: {len(val_tomos)} トモグラム, {val_motors} モーター, {val_slices} スライス\")\n    print(f\"- 合計: {len(train_tomos) + len(val_tomos)} トモグラム, {train_motors + val_motors} モーター, {train_slices + val_slices} スライス\")\n    \n    return {\n        \"dataset_dir\": yolo_dataset_dir,\n        \"yaml_path\": os.path.join(yolo_dataset_dir, 'dataset.yaml'),\n        \"train_tomograms\": len(train_tomos),\n        \"val_tomograms\": len(val_tomos),\n        \"train_motors\": train_motors,\n        \"val_motors\": val_motors,\n        \"train_slices\": train_slices,\n        \"val_slices\": val_slices\n    }\n\n# 前処理を実行\nsummary = prepare_yolo_dataset(TRUST)\nprint(f\"\\n前処理完了:\")\nprint(f\"- 学習データ: {summary['train_tomograms']} トモグラム, {summary['train_motors']} モーター, {summary['train_slices']} スライス\")\nprint(f\"- 検証データ: {summary['val_tomograms']} トモグラム, {summary['val_motors']} モーター, {summary['val_slices']} スライス\")\nprint(f\"- データセット保存先: {summary['dataset_dir']}\")\nprint(f\"- YOLO設定ファイル: {summary['yaml_path']}\")\nprint(\"\\nYOLO学習の準備が整いました！\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-23T22:49:54.264702Z","iopub.execute_input":"2025-05-23T22:49:54.265151Z","iopub.status.idle":"2025-05-23T22:54:05.994949Z","shell.execute_reply.started":"2025-05-23T22:49:54.265117Z","shell.execute_reply":"2025-05-23T22:54:05.993830Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# YOLOv8のインストール\n!pip install -q ultralytics\n\n# ライブラリのインポート\nimport os\nimport torch\nimport numpy as np\nimport random\nfrom ultralytics import YOLO\n\n# 再現性のためのシード設定\ntorch.manual_seed(42)\nrandom.seed(42)\nnp.random.seed(42)\n\n# パスの定義\ndataset_path = \"/kaggle/working/yolo_dataset\"\nyaml_path = os.path.join(dataset_path, \"dataset.yaml\")\noutput_dir = \"/kaggle/working/motor_yolo_train\"\n\n# モデルの定義\nmodel = YOLO(\"yolov8n.pt\")\n\n# 学習の実行（明示的にディレクトリを指定）\nmodel.train(\n    data=yaml_path,\n    epochs=5,\n    imgsz=256,\n    batch=8,\n    project=\"/kaggle/working\",  # 上位ディレクトリ\n    name=\"motor_yolo_train\",    # サブディレクトリ\n    exist_ok=True               # 既存フォルダがあってもOK\n)\n\n# モデル保存パスの確認\ntrained_model_path = os.path.join(output_dir, \"weights\", \"best.pt\")\n\nif os.path.exists(trained_model_path):\n    print(f\"✅ モデル保存成功: {trained_model_path}\")\nelse:\n    # 実際に存在するパスを列挙してヒントを出す\n    print(\"❌ モデルが保存されていません。保存先を探索します...\")\n    for root, dirs, files in os.walk(\"/kaggle/working\"):\n        for file in files:\n            if file == \"best.pt\":\n                print(f\"🔎 発見: {os.path.join(root, file)}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T00:58:19.008037Z","iopub.execute_input":"2025-05-24T00:58:19.009298Z","iopub.status.idle":"2025-05-24T01:32:40.843561Z","shell.execute_reply.started":"2025-05-24T00:58:19.009251Z","shell.execute_reply":"2025-05-24T01:32:40.842394Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nweights_dir = \"/kaggle/working/motor_yolo_train/weights\"\nif os.path.exists(weights_dir):\n    print(\"📂 weightsフォルダの内容：\", os.listdir(weights_dir))\nelse:\n    print(\"❌ weightsフォルダが存在しません。\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-24T01:38:34.762496Z","iopub.execute_input":"2025-05-24T01:38:34.763668Z","iopub.status.idle":"2025-05-24T01:38:34.770204Z","shell.execute_reply.started":"2025-05-24T01:38:34.763631Z","shell.execute_reply":"2025-05-24T01:38:34.769019Z"}},"outputs":[],"execution_count":null}]}