ラボの分析装置を hack して便利にした話――研究以外でもプログラマがどう研究に貢献できるか

以前所属していたラボで書いたプログラムの話をする。学問的な見所はほとんどないけれど、プログラマとしては愉快なタスクだったし、ラボの仕事の効率化に少なからず貢献できたと思っている。これらのプログラムは、今でもほぼ毎日使われているはずだ。エピソードは全部で3つ。最後に「まとめ」もつけた。

注意: 諸事情により細部を伏せたり変更している。実際のデータやスクリーンショットを出したほうが分かりやすいだろうが、これも出せない。申し訳ない。あと、自慢話うざいという人は読まない方がお互い幸せだと思う。

測定データを記録するバイナリファイルを解析した話

高速液体クロマトグラフィーという装置がある。装置は分離部と検出部からなる。前者はサンプルを分子量や電荷などによって分離する。後者は分離後一定の順序でチューブの中を流れてくるサンプルを、一秒間に数回ずつ検出する。結果は、横軸が時間、縦軸が測定値の折れ線グラフとして表示される(Wikipedia にある参照)。研究者としては、シグナルの強度変化が一峰性なのか複数のピークがあるのか(サンプルが純粋なのか混合物なのかを表す)、一番大きな山が他の山と比べてどのくらい大きいか(成分の存在比)、幅が狭くて鋭いピークなのか幅広いピークなのか(蛋白質の立体構造の均一性を反映する)といったことに興味がある。

つまり、この装置のデータは、(時刻, チャンネル1, チャンネル2, ..., チャンネルn) が測定回数分繰り返される時系列データだ。チャンネル1, 2, ... は、紫外線吸収や蛍光といった各検出方法に対応する。装置付属の制御ソフトには取得したデータを CSV形式で出力する機能があって、それを Excel で開いて例示したような折れ線グラフにするのがルーチンだった。問題は、このソフトにデータをまとめて出力する機能がなかったことだ。つまり、データを開く→メニューから[ファイル]-[エクスポート]-[CSV]を選ぶ→ファイル名を入力して[保存]ボタンを押す、という操作を、サンプルごとに延々と繰り返さないといけない。一度の実験では 96 サンプルをまとめて測定するというのに! 測定セットを登録する際にサンプル名を設定しているにも関わらず、エクスポート時のダイアログでは "無題.csv" とかしょうもないファイル名がデフォルトで入力されていて、さらなる苛立ちを煽る。その上、エクスポートされた CSV ファイルを1つ1つ Excel で開いてグラフにするのは相当面倒である。

この仕様に苛ついた私は、ソフトウェアが蓄えこんでいる測定データを直接読みだして、CSV 形式に自動で変換するプログラムを書くことにした。もちろんファイル名についても、分析ソフトに登録したサンプル名を読みだして使うことにする。

まず、データファイルの所在を特定するところから始めた。このソフトは、日付や実験バッチごとに分類された測定結果を、専用のダイアログボックスで選択して開くようになっており、データファイルという実体はユーザからは遮蔽されている。それらしいフォルダを探索したところ、測定バッチごとに 001.ch0, 001.ch1, ..., 096.ch0, 096.ch2 のようなファイルが存在することが分かった。 どうやらチャンネルごとに別々のファイルになっているようだ。

早速、001.ch0 をバイナリエディタで開いた。一目見て、ファイルの前半がヘッダであり、測定条件が記録されているのが分かった。SampleName, InjectionTime, DetectionFrequency など項目名を表す文字列と、データと思われる文字列やバイナリが、 NULL 区切りで並んでいた。そして 00 で埋めつくされたブロックのあと、ファイルの先頭からちょうど 4096 バイト目からバイナリデータが並んでいた。よく見ると同じようなバイトが繰り返されている。測定データは時系列で連続的に変化するから、値の上のほうの桁はめったに変化しない。これは予測される振る舞いである。

――この調子で詳細を書いても仕方がないから、結論だけ書くが、測定データはリトル・エンディアンの符号付き24ビット整数値だった。確かに測定値の範囲を考えると 32 ビットの精度はいらないのであるが、わざわざ 1 バイトケチる必要があったとも思えない。不思議なフォーマットであった。

ここまで解析できれば、変換プログラムを Ruby で実装するのは簡単である。CSV 形式に変換するだけでなく、gnuplot を呼んでグラフも自動で描くようにした。問題はユーザ・インタフェースだが、ラボの OS は Mac OS X ばかりだったので、ドロップされたファイルの名前を引数に Ruby スクリプトを実行するような AppleScript を書いて、Droplet として提供した。

このスクリプトは、ラボのほとんどの人が使うようになった。

sprintf の書式に迷惑した話

2つ目も液体クロマトグラフィー装置の話だが、メーカが違う。当然、付属の制御ソフトも上とは異なる。

こちらの装置には拡張ポートがついていて使用者が自分で測定チャンネルを追加できるようになっており、そこにある分析装置が接続されていた。機器はうまく連動し、増設した分析装置からのデータも制御ソフト上できちんとグラフ表示されていた。

ところが制御ソフトから測定データを CSV形式でエクスポートしたところ、内蔵チャンネルのデータは "3.52" のように正しく出力されるのに、増設した分析装置からのデータがほとんどすべて "0.00" になってしまうという問題が発覚した。画面上ではきちんとグラフ表示されるので、ソフト内部にデータが正常に蓄積されていることは間違いない。では、何がいけないのだろう?

この装置を使っていた研究者は、もちろん販売元に相談した。ところが「サードパーティの分析装置の動作までは保証できない。初めからその分析手法に対応した新モデルを購入されたし」と言われるばかりであったという。新モデルは500万円くらいするのだ。そんな金はポンとは出ない。そこで私に話が回ってきたのである。いや、正確に言うと、上のような問題に困っているという話を耳にした私が「なんとかなるはずです」と首を突っ込んだのである。

純正ソフトでのグラフ表示を見なおしたところ、チャンネルごとに縦軸のスケールが大きく違うことが分かった。内蔵チャネルの値は 0 から 200 くらいの範囲なのだが、増設した装置からのデータはずっと小さく、ピークの部分でもせいぜい 0.015 程度である。グラフ表示はピークの高さに応じて自動でスケール調節されるため見落としていたわけだが、生データはとても小さいのである!

これで謎が解けた。おそらくこのプログラムは、内部的にデータを浮動小数点数として処理・保存している。これを CSV形式で出力する際、sprintf (かそれに類する文字列フォーマット関数)を使っているはずだ。その変換フォーマットが、"%.2f" のように桁数で精度指定されているに違いない。そのため、増設チャネルからの絶対値が小さい数値データが、ほとんど 0.00 になってしまったのである。本当は 0.0047 とかなのに。

ここまで分かれば、治すのは簡単である。実行ファイルをバイナリエディタで開いて、"%.2f" 等をキーワードに検索して問題の書式文字列を特定し、"%.4f" に変えればよい。長さが変わらないので、アドレスの辻褄をあわせる必要もない。とはいっても、商用ソフトウェアのバイナリを勝手に書き換えるのはライセンス的に大問題である。仕方がないので、最初のエピソードと同じようにデータファイルの形式を解析して、自分で CSV エクスポータを書いた。上のスクリプトのパーサ部分を差し替えるだけなので、あっという間に片付いた。問題を耳にしてから、半日もかからなかったと思う。

リアルタイムで画面表示を読み取る話

これは、液体の濁り具合を測定する装置(分光光度計)の話である。

ラボでは、細胞(大腸菌とか酵母とか)をたくさん飼っている。しばらく培養していると細胞が増えすぎて栄養分が枯渇してくるので、新鮮な培養液に植え継がなければならない(継代という)。このとき、新しい培養液 1ml あたり50万個の細胞になるように入れるという具合に細胞密度が決められている。

具体的な作業はこうだ。古い方の培養液を希釈して、キュベット という透明な筒に入れ、機械で濁り(吸光度)を測定する。希釈するのは、濃すぎると正確に測定できないからである。細胞が増えてくると透明だった培養液が濁ってくるから、濁り具合を測定することで細胞の密度が分かるという仕組みだ。古い培養液の細胞数が分かれば、新しい培養液に古い培養液を何ml混ぜれば所定の細胞密度になるかが計算できる。小学生でもできる濃度の計算である。「100mg/ml の食塩水を薄めて 10mg/ml の食塩水 1L を作るにはどうすればいいですか?」と同じだ。

問題は、培養液が何十本もあることである。一本一本サンプリングして希釈して濃度を測定するだけでもかなり厄介なのに、測定結果をいちいちメモして、電卓を叩いて、加えるべき体積を計算するのはなおさら面倒である。気が短い私はすぐに嫌になった。そこで、画面に表示されている「濁り具合」から「入れるべき体積」を自動的に計算するプログラムを作ることにした。

この装置では測定ボタンを押す必要がなく、リアルタイムでその瞬間の測定値が画面に表示されている。つまり、キュベットを挿入する前は 0 だった表示が、挿入後はサンプルの吸光度に変わり、キュベットを外せば再び 0 に戻るという具合だ。この表示を拡張して、「入れるべき体積」もリアルタイム表示されると美しい。希釈倍率・目標密度・目標体積の3つを設定項目とし、「今、古い培養液を100倍希釈で測定している。50万細胞/mlの培養液を500ml作りたい」という希望を入力できるようにする。これを元に、吸光度の表示にリアルタイムで連動して、「256ul 加えればよい」と表示したいのだ。

技術的な困難は、純正ソフトのウィンドウ内に表示されている測定値を、ファイルに保存したりユーザに再入力させることなく、自作プログラムで利用したいということである。自分のプログラムが表示しているテキストボックスやラベルの値を取得するのは簡単である。言語にもよるが、コントロールvalue とか text プロパティを参照するだけだ。一方、他のプログラムが表示している文字列を取得する方法は自明ではない。普通のプログラムで必要となる機能でもないから、一般的なプログラミング本にもあまり出ていないと思う。

幸い私は大昔、中学から高校にかけて、Windows API でいろいろ遊んだことがあり、土地勘があった*1。Win32API は10年近く触っていないが、ラベルに適切なメッセージを投げれば値が取得できたような記憶がある。他のプログラムのラベルであっても、ウィンドウ・ハンドルさえ取得できれば同じはずだ。

久しぶりに MSDN を参照しながら、

  1. enumWindows でウィンドウを列挙する
  2. getWindowText でタイトルを見て制御ソフトか判定
  3. 目的のラベルはその子ウィンドウなので、enumChildWindows で取得
  4. sendMessage で WM_GETTEXT を投げて中身を取得する

というロジックを書いた。測定データさえ得れば、加えるべき体積の計算は自明である。

結果は測定ソフトの中に表示することもできたが、これまたライセンス的にどうかと思ったので、最前面属性を付与した別ウィンドウに表示するようにした。

実装には Ruby を使った。全部で100行弱である。Windows 版の Ruby では Win32API ライブラリから API を叩ける。GUI は VisualuRuby (タイプミスではない) ライブラリで書いた。API を呼ぶなら C 言語を使うのが普通ではないかという指摘もあろうが、動作確認・微調整を実際の測定装置付属 PC で行う必要がある以上、コンパイラ型の言語は使いたくなかったのである。

まとめ

ここまで述べてきたケースは、ただの雑用だと言われがちである。研究に少なからず貢献できたとは思うけれど、研究ではない。論文に書きようがないから、何の業績にもならない。新しい解析方法を考えたわけではないから、author に名前を連ねるには値しないだろう。せいぜい "The data were analyzed and plotted with in-house scripts." の一文と共に acknowledgement に名前を載せてもらえるかどうかというところだ。それは研究の世界では「何もやっていない」のと同じ扱いを受ける。

けれども、こういうタスクこそ、私が最もやりがいを感じることの1つである。謎のファイル形式を解析するのはパズル的で楽しかったし、自分のコードが役立って喜んでもらえたのがすごく嬉しかった。目の前に困っている人がいて、問題を解決し、感謝されるというのは、ささやかだけれど社会的成功体験の原型ではないだろうか。

おそらく、ここで挙げたような「ちょっとした不便」は現場にたくさん転がっていて、プログラミング・スキルのある人が手を貸せば、ぐんと研究効率があがるはずである。昔の大学にガラス細工の職人がいて、特注で実験器具を作ったり修理したりしてくれたように、共通利用機器センタみたいな部署にいろいろプログラムを書いてくれる人がいて、各ラボのちょっとした困りごとを解決して研究の効率化を図ってくれるとしたら、有意義なんじゃないかと思う。Web サイトの作成やメールサーバの運営を IT業者に外注しているラボは結構あるが、もっと研究寄りの IT部分を、実際にサイエンスが分かっているプログラマが手助けできたら。領域固有の知識(ドメイン知識)があるからこそ、「こんなものがあったら便利なのではないか」と提案できる。あるいは、依頼側の言葉をそのまま理解できる。

もちろん現実問題としては、そういう職を設置するのは難しいだろう。制度や予算の問題があるし、そもそも依頼が来るとは思えない。私の経験では、プログラミングのことを全く知らない研究者は、日常の研究活動で不便を感じていても「そういうものだ」と諦めていて、解決の可能性に思いが至らない。だから、相談すらしてくれない。上のケースのうち2つは私自身が実験をしていて気がついたもので、もう1つは予算がなくて困ったという雑談を深堀りしてはじめて問題が発覚したのである。そして、私のほうから「それ、簡単に解決できますよ」と声をかけてはじめて、「えっ、本当!?」と驚かれると同時に、「じゃあ、お願いするよ」となったのだ*2。ラボの内部にいるからこそ、問題に気づき、改善策を提示できるのである。ラボの外にいても、うまく機能しないだろう。

だから一番いいのは、一人ひとりの研究者が、この程度のプログラムはさらさらと書き下せるようになることである。それが無理でも、誰かに相談してみようという気になること。そういう方面でも、私は手助けを惜しまないつもりだ。これを読んで「ちょっとスキルを持てば、こういう風に問題解決できるのか」「自分にはプログラミングを勉強している余裕はない/費用対効果的にそのつもりはない/etc けれど、身近な人に相談してみよう」と考えるきっかけになれば、「成功体験を書いてドヤ顔をする」という私的な動機を超えて、この記事が役に立てたことになる。

それから、測定機器の制御ソフトなどを自由なライセンスで配布にしてほしいものだ。オープンソースならば、バイナリデータを解析するまでもなく、ソースコードを読んでファイル・フォーマットを理解できる。制御ソフトの不便さを、上のような支援ソフトを作る形ではなく、直接改善できる。数百万・数千万円する研究用機器の購入においては、価格と性能が最優先され、ソフトウェアの使い勝手はあまり考慮されない。そのせいか使い勝手が非常に悪いソフトが多く、研究効率を低下させているように感じる。

最後に、ラボにこういう困り事があったら、ぜひ声を掛けてほしい。作業のネタを提供してほしい。金を払えとか論文の共著者にしろなんて言わない。金銭や名誉が目的なのではなく、純粋に、面白くてやりがいがあるからするのだ。「そのやりがいって、要は研究に挫折してボロボロになった承認欲求を満たすということでしょ」と言われたら、そうかもしれないけれども、私もあなたも happy になれるのだから、それで何か悪いですか?

2013/5/9 執筆開始, 2014/1/4 公開

*1:API を直接叩くことで、「ふつう」じゃないことがいろいろできるのが楽しかった。とりわけサブクラス化とかメッセージフックとかは面白かった。実用的なものは何一つ作らなかったし、シェル拡張とかを試作して OS をクラッシュさせてばかりいたけれど、他のウィンドウの情報を取得したり書き換えたりするのは愉快だった。タイトルバーをドラックして移動しようとすると、逆方向に逃げてしまう謎ウィンドウとかも作った。

*2:8行12列に並んだサンプルを分析する装置があって、その出力した CSV ファイルを Excelコピーアンドペーストして縦一列に並びかえるという操作を、毎日黙々と行う研究者もいた。VBAマクロを使えば一瞬である。