pyMEA(MEA_modules) という、MEA計測データを解析するためのPythonライブラリを開発しています。

pyMEAが提供するのは、解析のコア機能をAPIとして取り揃えることです。完成された解析アプリケーションを1つ用意するのではなく、データの読み込み・ピーク検出・数値計算・可視化といった部品を揃え、研究者がそれらを組み合わせて、自分の研究テーマに沿った解析コードを組み立てられるようにしています。

MEA計測を使う研究は、対象が心筋か神経かによっても、何を明らかにしたいかによっても、必要な解析が変わります。あらゆるテーマを1つの完成品でカバーするのは現実的ではありません。それならば、どのテーマでも共通して必要になる処理を確実な部品として提供し、組み合わせ方は研究者に委ねるほうが素直だと考えました。実際、後で示すコード例のように、数行のコードを繋ぐだけで「読み込み → ピーク検出 → 指標の計算」という解析の流れが書けます。

この記事では、そもそもMEAとは何かという話から、ライブラリのアーキテクチャ、そして「プログラミングが初めての研究者でも使えること」を目指したドキュメントの取り組みまでを紹介します。

MEAシステムとは

MEA(Multi Electrode Array / 多電極アレイ)は、培養した細胞の電気活動を多数の電極で同時に記録する装置です。

心筋細胞や神経細胞は、活動するときに細胞外へ微弱な電位変化を生みます。MEAはシャーレの底に電極を格子状に並べ、その電位を多点で同時に拾います。pyMEAが対象としているシステムでは64個の電極を使うため、64チャネル分の時系列波形が一度に得られます。

このデータから、たとえば次のような指標を読み取ります。

  • ISI(Inter Spike Interval) — 拍動と拍動の間隔。心筋なら拍動リズムの指標になります
  • FPD(Field Potential Duration) — 細胞外電位の継続時間。心電図でいうQT間隔に対応する指標として、薬剤の影響評価などに使われます
  • 伝導速度 — 電極間の到達時間差と距離から、興奮が細胞シート上を伝わる速さを求めます

つまり「電極ごとの生波形」から「生物学的に意味のある数値」へ変換するのが解析の仕事で、その部分を担うのがpyMEAです。

pyMEAで何ができるか

計測装置が吐き出すのは .hed / .bio という形式の生データです。これを読み込み、ピークを検出し、数値計算やグラフ描画を行うまでを短いコードで書けるようにしています。

from pyMEA import *

hed_path = "/User/you/your_record_data.hed"
start, end = 0, 30
electrode_distance = 450

# 0〜30秒、電極間距離450µmとして読み込む
mea = read_MEA(hed_path, start, end, electrode_distance)

# 負のピーク(スパイク)を検出
peak_index = detect_peak_neg(mea.data)

# 拍動間隔(ISI)を計算
isi = mea.calculator.isi(peak_index, ch=2)
isi_mean = isi.mean

# 2電極間の伝導速度を計算
conduction_velocity = mea.calculator.conduction_velocity(peak_index, ch1=9, ch2=54)

PyMEA のインスタンスが、役割ごとのオブジェクトを束ねる形になっています。

オブジェクト 役割
data (MEA) 計測データの保持
fig (FigMEA) グラフ描画
calculator (Calculator) 数値計算
electrode (Electrode) 電極の位置情報

描画は64電極の一覧表示、単一電極の波形、ラスタープロット、2D/3Dカラーマップなどに対応しています。

アーキテクチャ

pyMEAはレイヤードアーキテクチャで構成しています。

presentation
プロット・可視化

application
ユースケース

domain
モデル・値・サービス

infrastructure
計測ファイルの読み込み

presentation
プロット・可視化

application
ユースケース

domain
モデル・値・サービス

infrastructure
計測ファイルの読み込み

  • domain — 解析の中核となるモデル・値オブジェクト・ドメインサービス
  • application — ユースケースの実装
  • infrastructure — .hed / .bio といった計測ファイルの読み込み
  • presentation — グラフ描画・可視化

この分け方には理由があります。MEAの計測装置やファイル形式は、環境によって変わり得ます。一方で「ピークを検出する」「拍動間隔を求める」といった解析ロジックの本質は、ファイル形式が変わっても変わりません。変わりやすいもの(入出力)と変わりにくいもの(解析ロジック)を分けておくことで、片方の変更がもう片方に波及しないようにしています。

設計思想:作業の性質に道具を合わせる

解析そのものはpyMEAで書きます。研究室のメンバーは全員このライブラリを使う前提です。それでも1つだけ、Goで書いたCLIツール mea2npz を別に用意しています。

理由は、大量の計測データをまとめて圧縮するという作業が、解析とは性質の違う仕事だからです。

.hed / .bio の生データはサイズが大きく、計測を重ねるほど保管や持ち運びが厳しくなります。これを軽量な .npz へ変換したいわけですが、対象は1ファイルではありません。溜まった計測データを一気に処理したいのです。

こうした定型の一括処理に対して、解析用のPython環境を立ち上げてノートブックを開き、変換のためだけにコードを書く——というのは、目的に対して手数が多すぎます。ここで欲しいのは、置いてすぐ叩ける道具です。

そこで mea2npz は単一バイナリのCLIツールとして切り出しました。

  • 実行ファイルを1つ置くだけで動く — セットアップの手順がない
  • Windows / Mac / Linux で動く — Goのクロスコンパイルで各OS向けのバイナリを配布
  • 出力はpyMEAと数値的に完全一致 — 変換後の .npz は read_MEA_npz でそのまま読める
# 対話モードでも、引数指定でも動く
mea2npz data.hed -start 30 -end 60 -dtype int16 -distance 450

引数で完結するので、シェルのループに載せてまとめて変換することもできます。「解析はライブラリで、定型作業はCLIで」と役割を分けたことで、どちらも本来やりたいことに素直な形になりました。

このGo製ツールも cmd/ + internal/{domain, usecase, infrastructure, interface/cli} という構成で、Python側と同じレイヤー分割にしています。言語が違っても設計の考え方を揃えておくと、行き来するときの認知コストが下がります。

なお int16 で保存するとサイズは約半分になりますが、ドキュメントには「元の計測生データは必ずバックアップとして保持し、削除しないこと」と明記しています。研究データは失われたら取り返しがつきません。便利さを提供するツールほど、こうした注意書きをはっきり書いておくべきだと考えています。

環境構築:devcontainerでワンクリック

解析を始める前に立ちはだかるのが環境構築です。ここも工夫しているところで、VSCode + Dev Container を採用しています。

きっかけは「動かない」の再発

以前は各自がWindowsに直接Pythonを入れて環境を作っていました。しかしこの運用だと、人によってバージョンが噛み合わず動かないということが起きます。Python本体、ライブラリ、それぞれの組み合わせで少しずつ環境が違い、「自分のところでは動くのに」が発生する。原因調査に時間を取られるのは、研究にとって完全に無駄な時間です。

そこで環境そのものをリポジトリに含めてしまうことにしました。.devcontainer/devcontainer.json に定義を置き、VSCodeでリポジトリを開いて「コンテナーで再度開く」を選ぶだけで環境が立ち上がります。

{
  "image": "mcr.microsoft.com/devcontainers/python:1-3.12-bullseye",
  "postCreateCommand": "pip install -r requirements.txt && pip install -e .",
  "customizations": {
    "vscode": {
      "extensions": [
        "ms-python.python",
        "charliermarsh.ruff",
        "ms-toolsai.jupyter",
        "marp-team.marp-vscode"
      ]
    }
  }
}

ポイントは、Pythonのバージョンと依存パッケージだけでなく、VSCodeの拡張機能まで含めて揃うことです。Jupyter、Ruff、Black Formatter、Marpなど、この解析作業で使う拡張が最初から入った状態で開きます。「拡張を入れ忘れていてノートブックが動かない」といったつまずきも同時に消せます。

postCreateCommand で依存パッケージのインストールとpyMEA自体のエディタブルインストールまで済ませているので、コンテナが立ち上がった時点ですぐ解析を書き始められます。

課題:動作が重い

いいことばかりではありません。Dev Containerで動かすと動作が重くなるという明確なデメリットがあります。ここは今後改善したい点です。

原因はおそらくファイルアクセスにあります。Windowsのファイルシステム上にリポジトリを置いたままコンテナから触ると、ファイルI/Oがボトルネックになります。リポジトリをWSL側に置けば、この点はかなり改善するはずです。

ただ、ここには別の難しさがあります。「WSL側にリポジトリを置いてください」と言われて、その意味と手順がすぐ分かる人ばかりではありません。Windowsのエクスプローラで見えている場所とWSLのファイルシステムが別物である、という前提知識が必要になるからです。

技術的な解決策は分かっているのに、それをどう伝えるかが難しい——このプロジェクトで繰り返しぶつかるのは、いつもこの種類の問題です。手順書に一文足すだけで済むのか、セットアップスライドに図を入れるべきか、あるいはそもそも手順を踏まなくていい仕組みにできないか。ここは今も模索しているところです。

ドキュメントを厚く書く

このプロジェクトで最も力を入れているのが、実はドキュメントです。

想定ユーザーはMEA計測をしている研究者であって、プログラマではありません。「ライブラリのAPIリファレンスさえ置いておけば使えるだろう」という前提は、この人たちには通用しません。そこで、目的別に何層かのドキュメントを用意しています。

docs/
├── api/                          # API仕様
│   ├── calculator.md / calculator_ja.md
│   ├── figure.md / figure_ja.md
│   ├── denoising.md
│   └── npz_io_ja.md
├── setup/                        # 環境構築ガイド
├── prd/                          # 要件定義
├── python_learning_roadmap.md    # Python学習ロードマップ
├── mea2npz_manual.md             # 変換ツールのマニュアル
└── *_slides.md                   # スライド版

プログラミング初心者向けの学習ロードマップ

一番の力作が python_learning_roadmap.md です。想定読者は「MEA解析をpyMEAで行いたいが、プログラミングが初めて」という研究者。STEP 0(環境構築)からSTEP 10(自走と応用)までの段階的な構成にしています。

このロードマップで大事にしたのは、Pythonを網羅的に教えないことです。冒頭にこう書いています。

膨大なPython文法をすべて覚える必要はない。必要なのは「用意された関数を正しく呼び出す」ための最小限の文法です。

クラスの定義方法もデコレータも継承も、pyMEAを使うだけなら要りません。だから教えません。代わりに最重要ステップとして置いたのが、STEP 3の「関数呼び出し」です。

pyMEAのコードの8割は「関数・メソッドを引数を指定して呼び出す」だけで成り立っている

実際その通りで、位置引数とキーワード引数の区別さえ理解できれば、あとは各機能のドキュメントを見ながら書けます。ライブラリの実際の使われ方から逆算して、学習内容を最小化する——これが学習ロードマップの設計方針です。

各ステップには「なぜpyMEAで必要か」を必ず添えています。たとえばリストを学ぶ場面では「描画したい電極番号をリストで渡す場面が頻繁にある」と、実際の用途とセットで説明します。文法のための文法にならないようにするためです。

つまずきどころも具体的に書いています。「番号は0から始まる」という初心者が必ず引っかかる概念は、mea.data[1] が電極1に対応する、という実例で説明しています。

スライド版はMarpで書いてGitHub Pagesに公開する

*_slides.md という形で、いくつかのドキュメントにはスライド版も用意しています。

これは、読む場面が違うと最適な形式も違うからです。手を動かしながら参照するならマニュアル形式がいいですが、研究室で説明するときや初めて全体像を掴むときは、スライドのほうが圧倒的に伝わります。同じ内容でも入口を複数用意しておくと、届く相手が増えます。

スライドの作成には Marp を使っています。Markdownをそのままスライドに変換できるツールで、frontmatterに設定を書くだけで済みます。

---
marp: true
theme: default
paginate: true
size: 16:9
footer: pyMEA Python 学習ロードマップ
---

PowerPointを使わずMarkdownで書ける利点は大きく、スライドもGitで差分管理できるようになります。ドキュメント本体と同じリポジトリ・同じ書き方で管理できるので、内容を更新するときの心理的なハードルがぐっと下がります。

さらに、書いたスライドはGitHub Actionsで自動ビルドしてGitHub Pagesに公開しています。docs/**/*_slides.md の変更をトリガーに、Marp CLIでHTMLへ変換してデプロイする流れです。

# .github/workflows/deploy-slides.yml(抜粋)
- run: npx -y @marp-team/marp-cli@latest "$f" -o "site/${base}.html"
- uses: actions/upload-pages-artifact@v3
- uses: actions/deploy-pages@v4

ビルド時にスライド一覧のインデックスページも自動生成しているので、公開ページを開けば読みたいスライドをカードから選べます。

→ pyMEA スライド一覧

ここが地味に効いていて、「URLを渡すだけで読んでもらえる」状態になりました。相手にリポジトリをcloneしてもらう必要も、Marpのビューアを入れてもらう必要も、PDFを添付して送る必要もありません。ブラウザさえあれば矢印キーでスライドを送れます。ドキュメントは書くだけでなく、届ける経路まで作って初めて読まれるのだと実感しています。

おわりに

ライブラリの価値は機能の数では決まらない、と作りながら実感しています。どれだけ高機能でも、対象ユーザーが使い始められなければ意味がありません。

  • 解析ロジックと入出力を分けるレイヤードアーキテクチャ
  • 解析はライブラリで、定型の一括処理はCLIでという道具の分担
  • 環境ごとリポジトリに含めて、バージョン差による「動かない」をなくすDev Container
  • 網羅ではなく「使うために必要な最小限」から教える学習ロードマップ

これらはすべて、研究者が本来やりたいこと(研究)に集中できる状態を作るという一点に向かっています。Dev Containerの動作の重さのように、まだ解ききれていない課題も残っていますが、そこも含めて改善を続けていくつもりです。

ソースコードは GitHub で公開しています。

最後まで読んでいただきありがとうございました!!