Ubuntu 18.04 LTSにDockerを使ってGitLabとMattermostを導入してみた

はじめに

Gitのバージョン管理システムとしてGitHub継続的インテグレーションツールとしてCircleCIやTravisCI、開発者向けのコミュニケーションツールとしてSlackといったクラウドサービス(SaaS)が注目を集めています(というより、昨今では当たり前に使われているツールかも)。

しかし、会社によってはクラウドはNGでオンプレミスである必要があったり、予算的な問題があったりとなかなか導入できない事情があるかと思います。

オープンソースGitHubやCircleCI、Slack風の機能を実装したツールはないものか…。 そこで、GitLabの出番です!

about.gitlab.com

GitLabって何?

GitLabは、GitHub風なGitのバージョン管理ツールに加えて、システム開発のフロー全体をカバーする様々なツールを提供してくれます。コア機能についてはオープンソースで開発が行われており、フリーで自前のサーバに導入可能です(オープンソース版であるGitLab Community Edition (CE)はMIT License下で配布されています)。

GitLabの具体的な機能としては、Gitによるバージョン管理以外に、継続的インテグレーション・継続的デリバリー(GitLab CI/CD)、チケット管理ツール、各種コミュニケーションツール(Mattermost含む)、監視ツール(Prometheus)、Dockerイメージレジストリ(GitLab Container Registry)、Web IDEなどなど、たくさんの有用な機能を使うことができます(もちろん、フリーの範囲内で利用可能)。

上記のサービスに対応するSaaSGitHub、CircleCI、JIRAなど)を使う場合には、どうしても費用がかかってしまいますし、別々のサービスになるので管理も大変になります。GitLabならオールインワンで、それらのサービスがフリーで手に入ります(ただし、オンプレミスに限る。もちろん、サーバの管理も必要になる)。

機能の詳細については、公式のドキュメントを参照してみてください(Core / Freeとなっているのが、オープンソースで開発されているFree版で使える機能になります)。

about.gitlab.com

また、これらのサービスを活用することで、チケット駆動開発TiDD)、テスト駆動開発(TDD)、継続的インテグレーション・継続的デリバリー(CI/CD)などのモダンな開発手法を開発プロセスに組み込んでいくことができます。

最近、Ubuntu 18.04 LTS(デスクトップ版)をインストールしたPCを用意したので、今回はこの環境にGitLabを導入してみたいと思います。また、GitLabのDockerイメージからGitLabサーバを立ててみたいと思います(SlackクローンであるMattermostも同梱されているのでこれも導入してみます)。

なぜDockerを使うのか?

以下は、個人的な意見です。Dockerは使い始めたばかりなので、間違っている箇所もあるかもしれませんが、DockerコンテナとしてGitLabを導入するメリットを挙げてみました。

GitLabのすべての構成が1つのDockerイメージに集約されている (Monolithic Image)

GitLabのDockerイメージは、すべてのツールを単一のコンテナに収めたモノリシックなDockerイメージです。そのため、NginxやPostgreSQLなどのGitLabを構成するミドルウェアのDockerコンテナを別途立ち上げる必要はありません。やることとしては、GitLabのDockerイメージをpullした後、イメージを起動させるだけです(使用者から見ると構成がシンプル)。

インフラ構成がDockerfileとしてコード化されている (Infrastructure as Code)

DockerならOS固有のインストール手順に従う必要がありません(仮想環境で実行するため、OSの違いを吸収できる)。DockerfileとしてDockerイメージの構成がコード化されており、利用者はそのファイルを実行することでDockerイメージを自動で生成することができます。そのため、インストール手順書を見ながらGitLabを導入するなんてことも必要ありません(誰でも簡単にGitLabを立ち上げることができる)。

別環境への移行が簡単 (Portability)

Dockerはポータビリティ性に優れています。新しいサーバにGitLabのシステムを移行したい場合には、既存のGitLabの環境を簡単に新しい環境に移行することが可能です。異なるOSだから、インストール方法が変わってしまう、データの引き継ぎが面倒ということはありません。

以上の観点から、中長期的にGitLabを運用していくにあたり、ホストOSに直接GitLabをインストールするのではなく、DockerコンテナとしてGitLabを運用したほうがメリットが大きいと考えられます。

検証環境

  • Ubuntu 18.04 LTS (デスクトップ版)(サーバ側)
  • Windows10 Pro(クライアント側)

Dockerのインストール

以下のUbuntuへのDockerのインストール方法は、Dockerの公式ドキュメントを参考にしました。詳しくは、そちらを参照のこと。

Get Docker CE for Ubuntu
https://docs.docker.com/install/linux/docker-ce/ubuntu/#install-using-the-repository

まず、aptパッケージのインデックスをアップデートします。

$ sudo apt-get update

HTTPS経由でリポジトリを使うようにするため、追加のパッケージをインストールします。

$ sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    software-properties-common

Docker公式のGPGキーを追加します。

$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

GPGキー(9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C 0EBF CD88)を取得できたか確認します。

$ sudo apt-key fingerprint 0EBFCD88
pub   rsa4096 2017-02-22 [SCEA]
      9DC8 5822 9FC7 DD38 854A  E2D8 8D81 803C 0EBF CD88
uid           [  不明  ] Docker Release (CE deb) <docker@docker.com>
sub   rsa4096 2017-02-22 [S]

Stable版のDockerのリポジトリを設定します。 現状、Ubuntu17.10以前のバージョンでは以下の方法でOKです。

$ sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"

一方、Ubuntu18.04の場合、まだDockerがバージョンに対応できていないので、$(lsb_release -cs)の箇所(bionic)をartfulに書き換えて以下のように設定します。

$ sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   artful \
   stable"
参考

https://linuxconfig.org/how-to-install-docker-on-ubuntu-18-04-bionic-beaver

もう一度、aptパッケージのインデックスをアップデートします。

$ sudo apt-get update

最新のStable版Dockerをインストールします。

$ sudo apt-get install docker-ce

特定のバージョンのDockerをインストールしたい場合、以下のコマンドで、インストール可能なDockerのバージョンを確認できます。

$ apt-cache madison docker-ce
 docker-ce | 18.03.0~ce-0~ubuntu | https://download.docker.com/linux/ubuntu artful/stable amd64 Packages
 docker-ce | 17.12.1~ce-0~ubuntu | https://download.docker.com/linux/ubuntu artful/stable amd64 Packages
 docker-ce | 17.12.0~ce-0~ubuntu | https://download.docker.com/linux/ubuntu artful/stable amd64 Packages

GitLabのDockerイメージの準備・起動

それでは、いよいよGitLabのDockerイメージの準備をしていきます。

詳しくは公式ドキュメントを参照してください。
https://docs.gitlab.com/omnibus/docker/

GitLab公式のDockerイメージはこちら。
https://hub.docker.com/r/gitlab/gitlab-ce/

まず、GitLabのDockerイメージをpullしてきます。

$ sudo docker pull gitlab/gitlab-ce

次に、GitLabのデータの永続化のために、GitLab用のファイルを保存するディレクトリをあらかじめつくっておきます(ディレクトリが存在しない場合、Docker起動後に自動的に生成されるようなので必要ないかも…)。

/home/imamachi/desktop/は適当なディレクトリを指定してください。以下は、設定例になります。

$ mkdir -p /home/imamachi/desktop/gitlab/config
$ mkdir -p /home/imamachi/desktop/gitlab/logs
$ mkdir -p /home/imamachi/desktop/gitlab/data

今回は、ローカルネットワーク経由でアクセスできるようにしたいので、Ubuntuのマシンに割り当てられているIPアドレスを調べておきます。

$ ifconfig

上記のコマンドをインストールしていない場合、以下のコマンドでインストールしておきましょう。

$ sudo apt install net-tools

GitLabのDockerイメージを起動するために、以下のコマンドを実行します。 以下のオプションは、自身の環境に合わせて設定します。
--hostname: Ubuntu(サーバ側)に割り当てられているIPアドレス
--volume: /home/imamachi/desktop/の部分は、先ほど作成したディレクトリのパスに合わせてください。
--name: 起動するコンテナの名称。任意の名前でOKですが、わかりやすくgitlabにしておきました。

sudo docker run --detach \
    --hostname 192.168.100.114 \
    --publish 443:443 --publish 80:80 --publish 22:22 --publish 8001:8001 \
    --name gitlab \
    --restart always \
    --volume /home/imamachi/desktop/gitlab/config:/etc/gitlab \
    --volume /home/imamachi/desktop/gitlab/logs:/var/log/gitlab \
    --volume /home/imamachi/desktop/gitlab/data:/var/opt/gitlab \
    gitlab/gitlab-ce:latest

--volumeの内容をまとめると、以下の通り。

ローカルディレクト コンテナ内ディレクト 内容
/home/imamachi/desktop/gitlab/config /etc/gitlab GitLabの設定ファイルを格納
/home/imamachi/desktop/gitlab/logs /var/log/gitlab GitLabのログファイルを格納
/home/imamachi/desktop/gitlab/data /var/opt/gitlab GitLabの各種データを格納

--publishでは、サービスごとのポート番号(ホストOS側 : Dockerコンテナ側)を指定しています。

サービス等 ポート番号
HTTPS 443
HTTP 80
SSH 22
Mattermost 8001

--restartalwaysを設定しておくと、ホストOSをリブート(再起動)したときに、自動的にGitLabのDockerイメージを起動させることができます。特別な理由がない限り、この設定でOKだと思います。

毎回、上記のコマンドをコピペして実行していたら面倒なので、Dockerのコマンド類をmakefileにまとめて置くと便利です。

以下は、GitLab用のmakefileの例です。

start:
  sudo docker run --detach \
      --hostname 192.168.100.114 \
      --publish 443:443 --publish 80:80 --publish 22:22 --publish 8001:8001  --publish 9090:9090 \
      --name gitlab \
      --restart always \
      --volume /home/imamachi/Desktop/gitlab/config:/etc/gitlab \
      --volume /home/imamachi/Desktop/gitlab/logs:/var/log/gitlab \
      --volume /home/imamachi/Desktop/gitlab/data:/var/opt/gitlab \
      gitlab/gitlab-ce:latest

restart:
  sudo docker restart gitlab

stop:
  sudo docker stop gitlab

rm:
  sudo docker rm gitlab

例えば、GitLabのDockerイメージの起動は以下のコマンドで実行可能になります。

$ make start

Ubuntuにmakeコマンドがインストールされていない場合、以下のコマンドでインストールしてください。

$ sudo apt install make

GitLabの起動が完了したかどうか確認するために、Dockerイメージの起動時に設定したGitLabのログファイルの出力先(上の例では、/home/imamachi/Desktop/gitlab/logs)を参照します。

logsディレクトリ直下のreconfigureディレクトリ内に*.logファイルが出力されるはずです。コンテナ起動中は、随時ログ情報が書き込まれていきます。

起動が正常に完了すると、最後に以下のログが出力されるはずです。

[2018-04-28T22:33:27+00:00] INFO: Running report handlers
[2018-04-28T22:33:27+00:00] INFO: Report handlers complete

GitLabにアクセスしてみる

http://<IPアドレス>から、GitLabにアクセスすることができます(上記の例では、http://192.168.100.114)。

初期画面では、rootユーザのパスワードの設定画面が表示されます。任意のパスワードを設定して、Change your passwordをクリックします。

Screenshot from 2018-04-19 07-31-27.png (46.9 kB)

次に、ログイン画面が表示されるので、rootユーザもしくは新規にユーザを作成(Register)してGitLabにログイン(Sign In)します。

Screenshot from 2018-04-19 07-31-51.png (43.7 kB)

初回ログインでは、以下のスタートメニューが表示されます。

image.png (123.7 kB)

SSHキーの設定

公開鍵認証によりGitLabとやり取りをするために、SSHキーの設定を行います。

Windows10の場合

WindowsではおなじみのPuTTY Keyだとうまく行かなかったので、OpenSSHにしました。

こちらのGitのサイトから、Git for Windowsをインストールしておきます。
https://git-scm.com/download/win

また、Git用のクライアントアプリとして、SourceTreeをインストールしておきます。
https://ja.atlassian.com/software/sourcetree

Git for Windowsのインストールが完了したら、Git Bashを起動します。

image.png (28.0 kB)

Git Bash上で、以下のコマンドから、SSHキーを生成します。

$ ssh-keygen -t rsa

すると、C:\Users\<ユーザ名>\.ssh以下にid_rsa.pubという名前の公開鍵が生成されるので、テキストエディタなどでファイルを開き、ファイルの中身をコピーします。

image.png (92.6 kB)

User SettingsのSSH Keysの画面から、先ほどコピーしたSSHキーをKey欄に貼り付けます(Titleも自動的につきます)。内容に問題がなければ、Add KeyをクリックしSSHキーを登録します。

image.png (81.6 kB)

SSH Keysを再度クリックして見ると、以下のようにSSHキーが登録されていることが確認できると思います。

image.png (54.6 kB)

今度は、SourceTreeを起動し、先ほど用意した秘密鍵の方を設定しておきます。 SourceTreeの[ツール] -> [オプション]をクリックします。

image.png (25.4 kB)

SSHクライアントの設定で、SSHキーに先ほど用意した秘密鍵をセットします。 SSHクライアントには、OpenSSHを設定します。

image.png (63.6 kB)

続いて、GitLab上のリモートレポジトリを取得します。 まず、GitLab上でリポジトリを作成し、赤枠で囲んだリポジトリのパスをクリップボードにコピーします。

image.png (183.9 kB)

SourceTreeのCloneタブから、先ほどコピーしたリポジトリのパスをペーストします。 「クローン」ボタンをクリックすると、ローカルにリモートリポジトリのクローンを作成できます。

image.png (98.3 kB)

Ubuntuの場合(MacOSの場合もほぼ同様)

以下のコマンドから、SSHキーを生成します。

$ ssh-keygen -t rsa

すると、~/.ssh以下にid_rsa.pubという名前の公開鍵が生成されるので、ファイルの中身をコピーします。

例えば、catコマンドでファイルの中身をコンソールに出力し、内容をコピーしていきます。

$ cat ~/.ssh/id_rsa.pub
(余談)クリップボードへのコピー

上記のように、手作業でコピーしているとミスを誘発する可能性があるので、できればやりたくないです。そこで、コマンド操作でクリップボードへコピーする方法を取ります。

xselコマンドは標準でUbuntuに入っていないので、以下のコマンドでインストールしておきます(何かと使うので入れておくと便利です)。

$ sudo apt install xsel

以下のように、クリップボードへファイルの中身をコピーすることが可能です。でも、xselのコマンドオプションが2つもあって面倒くさいです…。

$ cat ~/.ssh/id_rsa.pub | xsel --clipboard --input

なので、MacOSpbcopyと同じ名前のエイリアスをつけておきます(名前はわかりやすければ何でもいいです)。

$ echo "alias pbcopy='xsel --clipboard --input'" >> ~/.bashrc
$ source ~/.bashrc

すると、以下のコマンドでクリップボードにファイルの中身をコピーできるようになります。

$ cat ~/.ssh/id_rsa.pub | pbcopy

User SettingsのSSH Keysの画面から、先ほどコピーしたSSHキーをKey欄に貼り付けます(Titleも自動的につきます)。内容に問題がなければ、Add KeyをクリックしSSHキーを登録します。

image.png (81.6 kB)

SSH Keysを再度クリックして見ると、以下のようにSSHキーが登録されていることが確認できると思います。

image.png (54.6 kB)

以降の処理は、Windowsと同じなので割愛します。

参考

公開鍵暗号を用いてのSSH接続(きほん)
https://qiita.com/mukoya/items/f20def019e25dc162ca8
コマンドラインからクリップボードへのコピー
https://qiita.com/Kzno/items/6f2fa98256bdffb0fd43

MattermostへGitLabのアカウントでシングルサインオン(SSO)する

GitLabに同梱されているMattermostは、別途Mattermost用のアカウントを作成しなくても、GitLabのアカウントからログインすることができます(シングルサインオン(SSO))。

以下では、GitLabアカウントでのシングルサインオンを行うための設定について紹介します。

ブラウザからGitLabにアクセスし、rootユーザでログイン後、Settingsから、

Screenshot from 2018-04-19 07-33-41.png (18.4 kB)

Applicationsタブに移動し、Mattermostという名前のアプリケーションをGitLabに登録します。 Redirect URIには、以下のURIを指定します(IPアドレスにはホストOSに割り当てられている値を設定してください。ここでは、192.168.100.114にしています)。

http://192.168.100.114:8001/signup/gitlab/complete
http://192.168.100.114:8001/login/gitlab/complete

同じサーバで運用している場合、以下のようにMattermostのポート番号も指定する必要があります。そうしないと、リダイレクトに失敗してしまうので注意です。

Screenshot from 2018-04-19 07-34-47.png (101.4 kB)

Save applicationボタンをクリックし、登録を完了してください。登録に成功すると、以下の画面が表示されます。ここで、Application idSecretの値を次に使うので、控えておいてください。

image.png (85.8 kB)

詳しくは以下を参考のこと。
https://docs.gitlab.com/omnibus/gitlab-mattermost/

続いて、GitLabの設定ファイルを変更します。 まず、一時的にgitlab.rbを編集可能にしておきます(もしくはVimで編集)。

$ cd /home/imamachi/desktop/gitlab/config/
$ sudo chmod 755 gitlab.rb

1125行目辺りにある、以下を編集します。 http://<IPアドレス>:8001と設定します。

mattermost_external_url 'http://192.168.100.114:8001'

Screenshot from 2018-04-19 07-37-17.png (35.5 kB)

1219-1225行辺りにある、以下をコメントアウトをはずして内容を編集します。
gitlab_enable: trueに変更。
gitlab_id: 先ほどのApplication idを指定。
gitlab_secret: 先ほどのSecretを指定。
gitlab_auth_endpoint: http://<IPアドレス>/oauth/authorizeを指定。
gitlab_token_endpoint: http://<IPアドレス>/oauth/tokenを指定。
gitlab_user_api_endpoint: http://<IPアドレス>/api/v4/userを指定。

mattermost['gitlab_enable'] = true
mattermost['gitlab_id'] = "abfb91bd4ebd564d515be4f3d34cb3feb23eeb30a1d47b21548e3f9a63d2b502"
mattermost['gitlab_secret'] = "1e60f2d1148c712a69a3771ccf095db8dd812f482e98360c3b7e0330f168eae7"
mattermost['gitlab_scope'] = ""
mattermost['gitlab_auth_endpoint'] = "http://192.168.100.114/oauth/authorize"
mattermost['gitlab_token_endpoint'] = "http://192.168.100.114/oauth/token"
mattermost['gitlab_user_api_endpoint'] = "http://192.168.100.114/api/v4/user"

Screenshot from 2018-04-20 07-54-05.png (38.8 kB)

GitLabのDockerコンテナを一旦停止させて、破棄します。

$ sudo docker stop gitlab
$ sudo docker rm gitlab

makefileを用意していれば、以下のコマンドで実行できます。

$ make stop
$ make rm

続いて、Dockerコンテナを起動します。

$ sudo docker run --detach \
    --hostname 192.168.100.114 \
    --publish 443:443 --publish 80:80 --publish 22:22 --publish 8001:8001 \
    --name gitlab \
    --restart always \
    --volume /home/imamachi/desktop/gitlab/config:/etc/gitlab \
    --volume /home/imamachi/desktop/gitlab/logs:/var/log/gitlab \
    --volume /home/imamachi/desktop/gitlab/data:/var/opt/gitlab \
    gitlab/gitlab-ce:latest

makefileを用意していれば、以下のコマンドで実行できます。

$ make start

参考

https://docs.gitlab.com/omnibus/docker/
https://docs.gitlab.com/omnibus/gitlab-mattermost/

GitLabのDockerコンテナを起動した後、http://<IPアドレス>:8001/にアクセスします(ここまでの例では、http://192.168.100.114:8001/)。すると、GitLab Mattermostのログイン画面が表示されます。

Screenshot from 2018-04-19 07-48-49.png (27.7 kB)

リダイレクトが正しく設定されていると、GitLab Single Sign-Onをクリックした後、以下の画面が表示されます。

image.png (85.4 kB)

Authorizeボタンをクリックすると、Mattermostにログインできます。Create a new teamをクリックし、

Screenshot from 2018-04-19 07-53-09.png (29.7 kB)

チーム名を入力して登録すると、

Screenshot from 2018-04-19 07-53-25.png (28.0 kB)

Screenshot from 2018-04-19 07-53-32.png (37.9 kB)

Mattermostのメイン画面が表示されるはずです。

Screenshot from 2018-04-19 07-53-44.png (60.4 kB)

基本的な準備は、以上で完了です。

まとめ

  • GitLabのDockerイメージの準備・起動
  • GitLabへのSSHキーの設定
  • Mattermostの起動(GitLabアカウントによるSSO)

今回、上記の3つについて、やり方をまとめてみました。他にも、重要なこととしてバックアップのとり方や、サーバ監視などがありますが、今回は割愛しました。以下に、公式のドキュメントのリンクを示すのにとどめておきます。

Backing up and restoring GitLab
https://docs.gitlab.com/ee/raketasks/backup_restore.html#restore-for-omnibus-installations
GitLab Prometheus
https://docs.gitlab.com/ee/administration/monitoring/prometheus/

Windows10もしくはMacOSからUbuntu 18.04 LTSにリモート接続(リモートデスクトップ)してみた

Ubuntu 18.04 LTS(デスクトップ版)にWindowsもしくはMacOSからアクセス方法(いわゆる、リモートデスクトップのやり方)の紹介です。ネットで調べてみるといろいろな情報が散見されており、個人的にわかりにくかったのでやり方をまとめてみました。

以下では、VNCによるWindowsもしくはMacOSからUbuntu 18.04 LTS(デスクトップ版)へのリモート接続方法を示しました。ちなみに、サーバ版のUbuntuではやり方が異なるので注意してください。

使用したOS

注意事項

Ubuntu17.10で初めて採用されたディスプレイサーバの「WayLand」では、現在、以下の方法でリモート接続できません(Ubuntu18.04 LTSではデフォルトで「Xorg」が採用されているので大丈夫です)。

1. Ubuntu18.04 LTS側での設定

以下では、Ubnutu18.04における設定方法を示しました。 まず、設定から「共有」をオンにし、画面共有をアクティブにします。

image.png (63.5 kB)

以下の詳細設定では、「このスクリーンの操作する接続を許可する」にチェック、アクセスオプションの「パスワードを要求する」にチェックをつけます。

このとき、「新規接続の場合アクセス要求を必要とする」にチェックすると、Ubuntu側で接続要求を許可するかどうか毎回確認が入るので、パスワードの要求のほうが便利です。

image.png (72.7 kB)

さらに、Windowsからリモート接続する場合、通信の暗号化処理をオフにしておく必要があります(通信内容が暗号化されなくなるので、セキュリティ的に問題があります。リモート接続はローカルネットワークなどの限定された範囲で行ったほうが良さそうです)。

$ sudo gsettings set org.gnome.Vino require-encryption false

また、すでにファイアウォールの設定がされているかどうか確認します。

$ sudo ufw status
状態:非アクティブ

となっていれば、ファイアウォールが有効になっていないのでリモート接続できる状態にあります。

ファイアウォールが有効になっている場合、以下のように5900番ポートを許可する設定を行います。

$ sudo ufw allow 5900
$ sudo ufw reload
$ sudo ufw status

qiita.com

2. Windows10の設定

好きなVNC Viewerを使って、Ubuntuにリモート接続します。Windowsにデフォルトで入っているリモートデスクトップアプリは通信プロトコルが異なるため利用できません。

ここでは、TightVNCを使いました。オープンソースで開発されており、フリーで利用可能です。いくつかのViewerを使ってみたところ、TightVNCが最も軽快に動作する印象でした。

以下のサイトからダウンロードします。
www.tightvnc.com

image.png (314.3 kB)

インストールするといろいろなアプリが入ってきますが、Viewerである「TightVNC Viewer」をクリックして起動します。

202018-04-28%2019.52.38.png (40.4 kB)

「Remote Host」にローカルネットワーク上のIPアドレス(Ubnutu側)を入力し、ポート番号を「5900」に設定します。

image.png (105.4 kB)

Ubuntu側のPCに割り当てられているIPアドレスがわからない場合は、以下のコマンドで調べることができます。

$ ifconfig

接続に問題がなければ、Ubnutu側で設定したパスワードを入力する画面が出てきます。

image.png (21.4 kB)

パスワードを入力した後、OKボタンをクリックするとUbuntu側のPCにリモート接続できるはずです。

image.png (704.9 kB)

3. MacOSの設定

次に、MacOSからUbuntu側へのリモート接続を試したいと思います。

まず、Finderの「移動」から「サーバへ接続」をクリックします。

image.png (1.1 MB)

サーバアドレスにローカルネットワークのIPアドレスUbuntu側のもの)とポート番号を入力します。

image.png (78.9 kB)

この時、「+」ボタンをクリックし、設定を保存しておくと次回以降の接続が簡単になります。

image.png (88.6 kB)

接続に問題がなければ、Ubuntu側で設定したパスワードを入力する画面が出てきます。

image.png (74.2 kB)

パスワードを入力した後、OKボタンをクリックするとUbuntu側のPCにリモート接続できるはずです。

image.png (4.2 MB)

4. おまけ

4月26日にリリースされたUbuntu 18.04 LTSをインストールしたらとりあえずやっておきたいことをまとめました。

とりあえずアップデート

インターネットに接続していれば、自動でアップデートがかかるので更新しておく。 続いて、以下のコマンドでアップデートをかける。

$ sudo apt update
$ sudo apt upgrade

ifconfigとmakeコマンドを使えるようにする

$ sudo apt install net-tools
$ sudo apt install make

日本語名のディレクトリを英語表記に変更する

フォルダ名が日本語なのは使いにくいので、以下で一括で英語表記に設定。 ウインドウが表示されるので、「Update Names」をクリックしディレクトリ名を変更する。

$ LANG=C xdg-user-dirs-gtk-update

image.png (75.6 kB)

変更されると、以下のようになる。

image.png (88.8 kB)

もしくは、すでに自力でディレクトリ名を変更してしまった場合は、以下の方法で新しいディレクトリ名とデスクトップやドキュメントなどのリンクを対応付けする。

cd ~/.config
sudo chmod 755 user-dirs.dirs
code user-dirs.dirs

VSCodeで編集。

# This file is written by xdg-user-dirs-update
# If you want to change or add directories, just edit the line you're
# interested in. All local changes will be retained on the next run
# Format is XDG_xxx_DIR="$HOME/yyy", where yyy is a shell-escaped
# homedir-relative path, or XDG_xxx_DIR="/yyy", where /yyy is an
# absolute path. No other format is supported.
# 
XDG_DESKTOP_DIR="$HOME/desktop"
XDG_DOWNLOAD_DIR="$HOME/download"
XDG_TEMPLATES_DIR="$HOME/templates"
XDG_PUBLICSHARE_DIR="$HOME/share"
XDG_DOCUMENTS_DIR="$HOME/document"
XDG_MUSIC_DIR="$HOME/music"
XDG_PICTURES_DIR="$HOME/picture"
XDG_VIDEOS_DIR="$HOME/video"
chmod 600  user-dirs.dirs

日付表示

$ gsettings set org.gnome.desktop.interface clock-show-date true

image.png (5.2 kB)

参考

Ubuntu 17.10のインストール直後にやっておきたいことまとめ
https://linuxfan.info/ubuntu-17-10-basic-settings https://qiita.com/scapegoat_11_/items/3ff95addfabef8f9d10d

Dockerコンテナ上でNGSのソフトウェアを実行してみる(Windows10・MacOSXローカル環境編)

きっかけ

近年では、BiocondaのようなBiology関連ソフトウェアに対するパッケージマネージャが登場し、NGS関連のソフトウェアのインストールがグッと簡単になりました(Biocondaが登場する前は、いちいちソースコードからコンパイルする必要があったり、各ソフトウェア固有のインストール方法を理解する必要があったり、とにかく手間がかかっていました…。NGS関連のソフトは依存関係が複雑で本当にやっかい)。

ただ、いろんな人のNGS関連のブログを見ていると、環境によってはBiocondaでうまくインストールできないという事例もあるようです(Biocondaレシピで想定されていなかったパッケージへの依存によるもの?)。確かに、Biocondaは非常に便利なんですが、他の環境でも同じ解析環境を構築しようとすると、再現よく環境を構築できないケースがあることがわかってきました。

そこで、ソフトウェアごとに仮想環境を構築し、各ソフトウェアの動作が保証された環境を色々な解析に使いまわすことで、上記の問題を解決しようと考えました。

そこで、Dockerの出番です。Dockerはコンテナ型仮想化技術の1つであり、ホストOS上で単一のプロセスとして起動します。ホストOSと同じカーネルを共有するため、VMWare, VirtualBoxなどのホスト型仮想化技術と比較して、軽量であるとされています。

「Docker」を全く知らない人のために「Docker」の魅力を伝えるための「Docker」入門
https://qiita.com/bremen/items/4604f530fe25786240db

前置きが長くなりましたが、今回は、Dockerを土台としたシステム基盤をつくることを考えるために、まずはローカル環境でDockerコンテナを動かしてみた (FastQCを実行して見る)という内容になります。

Dockerをベースにした解析パイプラインの活用事例

パッと目についた事例をあげてみました。他にもたくさんあると思います。

High-Throughput Genomics on AWS - LFS309 - re:Invent 2017

AWSのカンファレンス re:Invent 2017で紹介されたNGS解析のパイプライン
https://www.slideshare.net/AmazonWebServices/highthroughput-genomics-on-aws-lfs309-reinvent-2017

Broad Institute: Genomes-in-the-cloud

Broad Instituteで利用されているゲノム解析用のパイプライン
https://hub.docker.com/r/broadinstitute/genomes-in-the-cloud/

BioContainers

バイオインフォマティクス関連のDockerイメージファイルをまとめたもの
https://biocontainers.pro/

ベースとなるDockerイメージの選択

NGSのデータ解析をする上では、必要最小限のLinuxディストリビューションのDockerイメージを使用した方がいいと思います。1つはイメージサイズが小さいほど、Dockerイメージの起動や破棄、ダウンロードなどの取り回しの面で有利だから。もう1つは、軽量コンテナの方が不要なパッケージが少なく、セキュリティ上、潜在的な攻撃を受けにくいからです。

また、ベースイメージを選定する上で、OSにインストールマネージャーがついているかどうかや、ITベンダーなどにより定期的にメンテナンスされているかどうかもポイントになります。

以下では、主要なLinuxディストリビューションについて簡単に比較をして見たいと思います。

Dockerイメージのサイズ

以下が主要なLinuxディストリビューションのDockerイメージのサイズになります。

Linux OS Compressed size
debian8.10-slim 30 MB
debian 8.10 53 MB
ubuntu 18.04 35 MB
centos 7.4.1708 73 MB

上記のデータは、2018-03-13時点のデータです。少し前までは、Debianが軽量なDockerイメージだったので、Debian一択なのかと思っていましたが、Ubuntuも直近ではかなりスリム化を果たしていますね(ちなみに、Anacondaやその他NGS関連のDockerイメージはDebianをベースに作られたものが多い気がします)。

脆弱性レポート

各Dockerイメージのレポジトリのtagタブから、各バージョンの脆弱性レポートを確認することができます。

image.png (328.0 kB)

スキャンされた各イメージをクリックすると、詳細を見ることができます。以下では、各LinuxディストリビューションのDockerイメージの脆弱性レポートを見ていくことにします。

debian8.10-slim

image.png (281.9 kB)

debian 8.10

image.png (283.9 kB)

ubuntu

image.png (281.8 kB)

centos 7.4.1708

image.png (308.9 kB)

脆弱性レポートまとめ

上記で示した脆弱性レポートの結果をまとめて見ました。いくつのコンポーネント脆弱性を抱えているかカウントした表が以下の通りになります。

Linux OS Vulnerability (Components)
debian8.10-slim 6
debian 8.10 6
ubuntu 18.04 0
centos 7.4.1708 16

上記のデータは、2018-03-13時点のデータです。Ubuntu脆弱性が0なのが際立っている気がします(本当かなって結果になりました)。

現状では、Debian-slimかUbuntuを選択するのが良いのかなと思いました。

Dockerのインストール

自身のパソコンにDockerをインストールします。 以下のURLから、Docker (Community Edition)をダウンロードできます。
https://www.docker.com/community-edition#/download

MacOSXにDockerをインストールして使う場合は以上でインストール完了です。
Windows10環境の場合、既存のコマンドプロンプトPowershellがどうも使いにくい(個人的な感想ですが)ので、Windows Subsystem for Linux (WSL)を導入して、BashシェルからDockerを扱えるようにしたいと思います。

Windows Subsystem for Linux (WSL)経由でホストOS (Windows10)のDockerを動かす

1. WSLをインストールする

注意: Dockerを使用するにはHyper-Vを利用する必要があるため、OSがWindows10 Proであることが必須です。

Miscrosoft storeからWSLをダウンロード・インストールできるようになったので、ストアからインストールしましょう。今回はUbuntuを導入しました。

image.png (22.4 kB)

WSLを使用するためには、「プログラムと機能」から「Windowsの機能の有効化または無効化」を開き、Windows Subsystem for Linuxにチェックをつけます。

image.png (73.0 kB)

2. DockerをWSLにインストールする

以下のUbuntuへのDockerのインストール方法は、Dockerの公式ドキュメントを参考にしました。詳しくは、そちらを参照のこと。

Get Docker CE for Ubuntu
https://docs.docker.com/install/linux/docker-ce/ubuntu/#install-using-the-repository

WSLのBash shellを起動し、まずaptパッケージをアップグレードしておきます。

$ sudo apt-get update

次のDockerを動かすために必要な各種パッケージをインストールします。

$ sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    software-properties-common

Dockerの公式のGPGキーを加え、キー情報の確認を行っています。

$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$ sudo apt-key fingerprint 0EBFCD88

pub   4096R/0EBFCD88 2017-02-22
      Key fingerprint = 9DC8 5822 9FC7 DD38 854A  E2D8 8D81 803C 0EBF CD88
uid                  Docker Release (CE deb) <docker@docker.com>
sub   4096R/F273FCD8 2017-02-22

Dockerをインストールします。

$ sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"
$ sudo apt-get update
$ sudo apt-get install docker-ce

3. WSL側とホストOS (Windows10)側のDockerを連携させる

Docker for Windowsのデーモンに接続するための設定を行います。今の状態では、あくまでホストOS側(Windows10)とWSL側で別々にDockerをインストールしただけなので、WSL側から、ホストOS側のDockerのデーモンプロセスを操作できるのように連携させる必要があります。

以下のように、WSL側のDockerクライアントからホストOS側のデーモンを見れるように、環境変数DOCKER_HOSTを設定します。

$ echo "export DOCKER_HOST='tcp://0.0.0.0:2375'" >> ~/.bashrc
$ source ~/.bashrc

最後に、Windows10側のDockerの設定画面を開き、Expose daemon on tcp://localhost:2375 without TLSにチェックを付けます。これで、WSL側からホストOS側のDockerのデーモンを操作できるようになりました。

image.png (142.5 kB)

参照

Docker for Windowsで快適な環境を得るまでの そこそこ長い闘い
https://qiita.com/YukiMiyatake/items/73c7d6c4f2c9739ebe60
WSL(Bash on Windows)でDockerを使用する
https://qiita.com/yoichiwo7/items/0b2aaa3a8c26ce8e87fe
Docker for WindowsをWSLから使う時のVolumeの扱い方
https://qiita.com/gentaro/items/7dec88e663f59b472de6

VSCodeの総合ターミナルでWSLを使う

VSCodeでは、エディタ内でターミナルを開くことができるので、こちらの標準のターミナルをWSLに変更しておくと便利です。

まず。設定画面から、terminal.integrated.shell.windowsで検索します。

対象の設定に対して "terminal.integrated.shell.windows": "C:\\Windows\\System32\\bash.exe"のようにWSLのBash.exeのパスを指定します。

image.png (111.0 kB)

総合ターミナルを開いて確認すると、めでたくBashシェルが使えるようになっているはずです!

image.png (30.8 kB)

Dockerファイルの作成

まず、Linux OSのDockerイメージをベースイメージにして、その上にNGS解析のソフトウェアをインストールしたDockerイメージを作成します。

1. Debian9-slimにBiocondaをインストールしたDockerイメージを作成する

Linux OSのDebina9-slimをベースイメージにして、BiocondaをインストールしたDockerイメージを作成します。

以下のDockerfileは、condaの公式Dockerfileを参考しました。
https://hub.docker.com/r/conda/miniconda2/~/dockerfile/

&&で処理を連続して行っているのは、レイヤーを減らすためです。また、中間ファイルを削除することでDockerのイメージファイルのサイズを小さくしています。

Dockerfile

# Base image
FROM debian:9-slim

# Install Miniconda2 & Bioconda
RUN apt-get -qq update && apt-get -qq -y install curl bzip2 \
  && curl -sSL https://repo.continuum.io/miniconda/Miniconda2-latest-Linux-x86_64.sh -o /tmp/miniconda.sh \
  && bash /tmp/miniconda.sh -bfp /usr/local \
  && rm -rf /tmp/miniconda.sh \
  && conda install -y python=2 \
  && conda update conda \
  && conda config --add channels r \
  && conda config --add channels defaults \
  && conda config --add channels conda-forge \
  && conda config --add channels bioconda \
  && apt-get -qq -y remove curl bzip2 \
  && apt-get -qq -y autoremove \
  && apt-get autoclean \
  && rm -rf /var/lib/apt/lists/* /var/log/dpkg.log \
  && conda clean --all --yes

ENV PATH /opt/conda/bin:$PATH

次に、DockerfileからDockerイメージを作成します。

$ docker build -t debian9-slim-bioconda2:latest -f Dockerfile .

2. FastQCをインストールしたDockerイメージを作成する

先程作ったDockerイメージをベースにして、FastQCのインストールしたイメージファイルを作成します。

Dockerfile

# Base image
FROM debian9-slim-bioconda2:latest

# Application entry point
# Dejavu fonts are not included with default::openjdk(fastqc recipe).
# They are already included with conda-forge::openjdk.
# https://github.com/conda-forge/staged-recipes/issues/3164 
RUN conda update conda \
  && conda install fastqc=0.11.6 \
  && conda install -c conda-forge openjdk \
  && conda clean --all --yes
COPY ./fastqc/run_fastqc_local.py /run_fastqc_local.py

ENTRYPOINT ["python", "/run_fastqc_local.py"]

Pythonのコードをエントリポイントに設定します。以下が、そのソースコードになります。

from __future__ import print_function

import os
import shlex
import subprocess
from argparse import ArgumentParser


def run_fastqc(fastq_path, cmd_args, working_dir):
    """
    Runs fastqc  
   :param fastq_path: Local path to fastq file  
   :param cmd_args: Additional command-line arguments to pass in  
   :param working_dir: Working directory  
   :return: Path to the result folder
    """
    # Prepare fastqc result path
    fastqc_result_dir = os.path.join(working_dir, '')

    # Prepare fastqc log path
    fastqc_log_path = os.path.join(fastqc_result_dir, "log_fastqc.txt")

    # Prepare fastqc command
    cmd = "fastqc -o {0} {1} {2}".format(
        fastqc_result_dir, cmd_args, fastq_path)
    print(cmd)

    # Execute fastqc
    with open(fastqc_log_path, 'w') as log_file:
        subprocess.check_call(shlex.split(cmd), stdout=log_file)

    return fastqc_result_dir


def main():
    argparser = ArgumentParser()

    argparser.add_argument('--fastq_path', type=str,
                           help="fastq sequence file", required=True)
    argparser.add_argument('--cmd_args', type=str,
                           help="arguments/options for fastqc", default="")
    argparser.add_argument('--working_dir', type=str, default="data/")

    args = argparser.parse_args()

    print("Creating working directory...")
    if not os.path.exists(args.working_dir):
        os.mkdir(args.working_dir)

    print("Running fastqc...")
    local_result_dir = run_fastqc(
        args.fastq_path, args.cmd_args, args.working_dir)

    print('Save the result in {0}.'.format(local_result_dir))

    print("successfully Completed !!")


if __name__ == '__main__':
    main()

次に、DockerfileからDockerイメージを作成します。Pythonのコードも同じディレクトリに置いておきます。

$ docker build -t debian9-slim-bioconda2-fastqc:latest -f Dockerfile .

以下のコマンドで作成したDockerイメージをチェックできます。

$ docker images

image.png (53.4 kB)

Dockerイメージの実行

1. FASTQファイルの準備

適当なFASTQファイルを持っていない場合、以下の記事を参考にしてテストに使うFASTQファイルを用意してください。

imamachi-n.hatenablog.com

2. Dockerコンテナを起動する

FASTQ_FILEにFASTQファイル、FASTQファイルが置いてあるLOCAL_WORKING_DIRに作業ディレクトリを指定します。また、DOCKER_IMAGEに先ほど作成したDockerイメージを、DOCKER_WORKING_DIRにDockerコンテナ内の適当な作業ディレクトリを指定します。

#!/bin/bash
#LOCAL_WORKING_DIR="/Users/imamachinaoto/Desktop/NGS/data" # MacOS
LOCAL_WORKING_DIR="/c/Users/imama/Desktop/NGS/fastqc/data"    # Windows10(WSL)
DOCKER_WORKING_DIR="/data"
DOCKER_IMAGE="debian9-slim-bioconda2-fastqc:latest"
FASTQ_FILE="RefSeq_hg38_2015-08-19_NM_ultraSlim.fq"

docker run --rm -it \
-v ${LOCAL_WORKING_DIR}:${DOCKER_WORKING_DIR} \
${DOCKER_IMAGE} \
--fastq_path ${DOCKER_WORKING_DIR}/${FASTQ_FILE} \
--working_dir ${DOCKER_WORKING_DIR}

ちなみに、DockerでWSL環境からホストOS(Windows10)のフォルダを見るとき、Cドライブは/mnt/cではなく、/cです。Bashシェルからアクセスするときは、/mnt/c(WSLにマウントされているCドライブを見る形)なのでパスの違いに注意が必要です。Windowsだと、これで詰まりました…。

コンテナでデータを管理する
http://docs.docker.jp/engine/userguide/dockervolumes.html

上記シェルスクリプト(もしくは以下で説明するmakeコマンド)を実行することで、FastQCが実行されます。

image.png (191.3 kB)

ホストOSのディレクトリを、Docker上のディレクトリに紐づけておくこと(ボリュームのマウント)で、以下のようにDockerコンテナが破棄されたあとでも、データを永続化させることができます。

image.png (36.4 kB)

Dockerコマンドをまとめたmakefileの作成

Dockerコマンドはイメージファイルの作成やコンテナ起動時など、繰り返し使用します。なので、今回はmakefileにコマンド類をまとめて、make buildmake startなどを用意することで、コマンド操作が簡単になります。

Windows10やWSLには、makeコマンドがインストールされていないため、あらかじめインストールしておきます。

Windows10でmakeファイルを実行する

WSLを使わない場合のmakeコマンドのインストール方法を示します。Windows10にはコマンドプロンプトからmakeコマンドを使えないので、まずmakeコマンドをインストールします。

1. makeコマンドをコマンドプロンプトに導入する(参考)

以下のURLから、makeをダウンロードします。
http://gnuwin32.sourceforge.net/packages/make.htm

image.png (225.1 kB)

C:\Program Files (x86)\GnuWin32\binにmake.exeが保存されているので、パスを通します。

image.png (56.5 kB)

2. WSL環境にmakeコマンドを導入する

WSLのコンソールから、以下のコマンドを実行するだけです。

$ sudo apt-get install make

Windowsでmakeコマンドを使う
https://qiita.com/tokikaze0604/items/e13c04192762f8d4ec85

makefileの例

NAME=debian9-slim-bioconda2-fastqc
TAG=0.11.6.local
UNDERSCORE_TAG=0_11_6_local

LOCAL_WORKING_DIR=/c/dev/NGS/fastqc/data
DOCKER_WORKING_DIR=/data
DOCKER_IMAGE="fastqc-aws-debian9-slim-bioconda2:0.11.6.local"
FASTQ_FILE=RefSeq_hg38_2015-08-19_NM_ultraSlim.fq

all: build push

build:
      docker build -t $(NAME):$(TAG) -t $(NAME):latest -f Dockerfile .
build-no-cache:
      docker build --no-cache -t $(NAME):$(TAG) -t $(NAME):latest -f Dockerfile .

push:
      docker push $(NAME):$(TAG)
      docker push $(NAME):latest

start:
      docker run --rm -it -v ${LOCAL_WORKING_DIR}:${DOCKER_WORKING_DIR} ${DOCKER_IMAGE} --fastq_path ${DOCKER_WORKING_DIR}/${FASTQ_FILE} --working_dir ${DOCKER_WORKING_DIR}

rm:
      docker rm `docker ps -aq`

rmi:
      docker rmi $(NAME):$(TAG) $(NAME):latest

つまづいたポイント

makefileの作成

インデントはスペースではなく、タブにしないとエラーになる。 Visual Studio Codeの場合、makefileと認識されたファイルでは、インデントをタブにするように設定されている。しかし、ファイル名がMakefileだと下記の設定が適応されない。makefileと小文字のmにする必要がある。

image.png (76.5 kB)

Docker関連の参考

docker コマンド チートシート
https://qiita.com/voluntas/items/68c1fd04dd3d507d4083
dockerでなrepositoryを消す
https://qiita.com/lirispp/items/06fc74c5bbc64fddf9ab

ART: シミュレーション用のRNA-seqデータを作成する

テスト用のFASTQファイルの作成

ART (a next-generation sequencing read simulator)を用いて、テスト用のFASTQファイルを作成します。

ARTのインストール

※ Biocondaをインストールしていることを前提にしています。
ARTをインストールします。

$ conda intall art

ARTが正しくインストールされたか確認します。

$ art_illumina --help

RefseqのGTFファイルをダウンロード

ARTを使ってテスト用のFASTQファイルを作るのですが、今回はRNA-seqのデータを作ろうと思います。

そこで、元となる配列を用意したいと思います。 TranscriptomeのデータをGTFファイルと取得し、それをFASTQのファイルに変換したいと思います。

GencodeのGTFファイルでもいいですが、ここでは必要なTranscriptomeデータが含まれておりコンパクトなRefseqのデータを使います。

illuminaのiGenomeサイトから、hg38のデータを丸ごとダウンロードします。この中に、RefSeqのGTFファイルも含まれています。

image.png (182.7 kB)

ダウンロードしたら、解凍します。

$ tar zxvf Home_sapiens_UCSC_hg38.tar.gz

解凍が終わったら、Homo_sapiensというディレクトリができるはずです。 Homo_sapiens/UCSC/hg38/Annotation/Archives/archive-2015-08-14-08-18-15/Genes/にあるgenes.gtfというファイルがRefSeqのGTFファイルになります(ちょっと古いデータですが、実際の解析に用いるわけではないので、良しとしましょう)。

これだけ適当なディレクトリに移動もしくはコピーしておき、それ以外のファイルは削除します。

このままだと、何のファイルかわからないのでリネームしておきます。

$ mv genes.gtf RefSeq_hg38_2015-08-19.gtf

GTFファイルからBEDファイルを作成

以下のスクリプトを使って、GTFファイルをBEDファイルに変換する。 gtf2bed.pl (3.8 kB)

$ perl gtf2bed.pl RefSeq_hg38_2015-08-19.gtf > RefSeq_hg38_2015-08-19.bed

このままだと、データが大きすぎるのでmRNAに限定し、かつRepresentative transcriptsをできるだけ抽出するようにします。
ExtractMrna.py (360 B)

$ python ExtractMrna.py RefSeq_hg38_2015-08-19.bed RefSeq_hg38_2015-08-19_NM_slim.bed

ヒトゲノム配列をダウンロード

image.png (1.5 MB)

image.png (1.5 MB)

image.png (300.8 kB)

image.png (71.1 kB)

image.png (273.5 kB)

$ wget http://hgdownload.soe.ucsc.edu/goldenPath/hg38/bigZips/hg38.fa.gz
$ gunzip hg38.fa.gz

BEDファイルからFASTAファイルを作成

BEDファイルからFASTA ファイルへ変換するために、Bedtoolsを使います。 まず、Bioconda経由でBedtoolsをインストールします。

$ conda install bedtools

BEDファイルをFASTAファイルに変換します。 ここで、先ほどダウンロードしたhg38.faファイルを使います。

$ bedtools getfasta -name -s -split -fi hg38.fa -bed RefSeq_hg38_2015-08-19_NM_slim.bed > RefSeq_hg38_2015-08-19_NM_slim.fa

-name: BEDファイルのname列の値を各配列の名称とする。
-s: ゲノム上での向き (Strandness)を考慮する(Transcriptomeなのでもちろん考慮します)。
-split: 12BED (12列のデータがあるBEDふぁいる)がInputである場合に指定 。
-fi: Referenceになる配列(ゲノム配列)のFASTAファイルを指定。
-bed: TranscriptomeのBEDファイルを指定。

このコマンドの実行には、FASTAファイルのインデックスファイルが必要になりますが、ファイルがない場合、自動的に生成してくれるので問題ないです。

> index file hg38.fa.fai not found, generating...

生成されたTranscriptomeのFATSAファイルは、標準出力で出力されます。

TranscriptomeのFASTAファイルからFASTQファイルを生成

今回は、illuminaのFASTQシーケンスデータ (RNA-seq, single-end)を生成したいと思います。ここでは、HiSeq2000のデータを作ります。

$ art_illumina -ss HS20 -i RefSeq_hg38_2015-08-19_NM_slim.fa -o RefSeq_hg38_2015-08-19_NM_slim -l 36 -f 2 --noALN
$ art_illumina -ss HS20 -i RefSeq_hg38_2015-08-19_NM_slim.fa -o RefSeq_hg38_2015-08-19_NM_slim_paired -l 36 -f 1 -p -m 300 -s 10 --noALN

-i --in: fasta形式の入力ファイルの指定。
-o --out: fastq形式の出力ファイルの指定 (拡張子はつけなくて良い)。
-ss --seqSys: シーケンサーの種類を選択。
指定する引数(System ID) - シーケンサー名 (リード長)

  • GA1 - GenomeAnalyzer I (36bp,44bp),
  • GA2 - GenomeAnalyzer II (50bp, 75bp)
  • HS10 - HiSeq 1000 (100bp)
  • HS20 - HiSeq 2000 (100bp)
  • HS25 - HiSeq 2500 (125bp, 150bp)
  • HS10 - HiSeq 1000 (100bp)
  • HS20 - HiSeq 2000 (100bp)
  • HS25 - HiSeq 2500 (125bp, 150bp)
  • HSXn - HiSeqX PCR free (150bp)
  • HSXt - HiSeqX TruSeq (150bp)
  • MinS - MiniSeq TruSeq (50bp)
  • MSv1 - MiSeq v1 (250bp)
  • MSv3 - MiSeq v3 (250bp)
  • NS50 - NextSeq500 v2 (75bp)

-l --len: リード長の指定。
-f --fcov: リードカバレージの指定。
-p --paired: Paired-endのデータとして出力(指定しない場合、Single-endのデータとして出力)。
m --mflen: フラグメントの平均サイズの指定(Paired-endのときのみ)。
-s --sdev: フラグメントのサイズの標準偏差の指定(Paired-endのときのみ)。
-sam --samout: SAMファイルを出力。
-na --noALN: アライメントファイル (ALN)を出力しない。
別にリード長は、指定された長さよりも短くても大丈夫です。

もっとファイルサイズを小さくしたい場合は、以下のように特定の行を抽出したファイルを作成します。

$ sed -n 1,200000p RefSeq_hg38_2015-08-19_NM_slim.fq > RefSeq_hg38_2015-08-19_NM_ultraSlim.fq

Seleniumコトハジメ - PubMed検索とUCSC Genome browserへのCustom Trackのアップロードを自動化してみた

はじめに

Seleniumとは、Webアプリケーションの画面操作を自動化するためのツールです。主に、Webアプリケーション開発のUIテスト時に利用するものです。また、キー入力などの操作を自動化できることから、Webスクレイピングのツールとして活用される例もあるようです。

Seleniumは、Apatch 2.0ライセンスで公開されているオープンソースのツールです。
https://www.seleniumhq.org/about/license.jsp

Seleniumの内部で使われているWebDriverは、Webブラウザを外部から遠隔操作するためのインターフェースであり、W3Cによって標準化されています(厳密には、標準化された内容と乖離する部分あり?)。
https://www.w3.org/TR/webdriver/

Selenium2では、Selenium RCと呼ばれる古い実装が残っていましたが、2016年に公開されたSelenium3では、完全にWebDriverの実装に移行しました。古い記事では、Selenium RCでの操作方法が記載されており、これは現在では動作しない方法になるので注意が必要です。

本来はUIテスト用のツールですが、今回は研究室時代にめんどくさいと思っていた作業を自動化してみました。1つはPubMedによるAdvanced Search、もう1つはUCSC Genome BrowserにCustom trackをアップロードする作業です。繰り返し同じことを繰り返す作業に対して、こういった自動化の方法は役に立ちますね。

Selenium実行サンプル

PubMed検索の自動化

selenium-test.gif (753.1 kB)

UCSC Genome BrowserへのCustom Trackのアップロード

selenium-test2.gif (834.3 kB)

実行環境・ツール

Mac OSX Sierra (v10.12.6)

  • Selenium Client for Java (v3.10.0)
  • ChromeDriver (v2.35)
  • GeckoDriver (v0.19.0)
  • SafariDriver + Safari (v11.0.3)

Windows10 (Build 16299)

Seleniumの導入

Seleniumを動かすのに必要なものは以下の通りです。

  • Selenium Client & WebDriver Language Bindings
  • 各種ブラウザのWebDriver

今回はJavaでの例を示したいと思いますが、javaであれば、MavenやGradleを使って導入する方法もあります。ただ、今回はJARファイルをダウンロードして1からプロジェクトを作っていきます。

Selenium Client & WebDriver Language Bindings

Seleniumは、JavaC#RubyPythonJavascript (Node)を公式でサポートしており、それぞれの言語についてSeleniumAPIが使えるライブラリ(Selenium Client)を用意しています。

まず、以下のサイトからJavaSelenium Clientのファイルをダウンロードします。 selenium-java-3.xx.xx.zipというファイルをダウンロードできるはずです。
https://www.seleniumhq.org/download/

image.png (195.3 kB)

各種ブラウザのWebDriver

続いて、各種ブラウザのWebDriverをダウンロードします。私の使っているPCはMac OSXなので、とりあえずFireFoxGoogle ChromeSafariのWebDriverをそれぞれダウンロードすることにします(自身のパソコンにそれぞれのブラウザがすでにインストール済みでであることを前提とします)。

image.png (310.2 kB)

余談ですが、WebDriverはSeleniumを開発しているグループが作成しているわけではなく、Webブラウザの開発元であるGoogleMozillaAppleMicrosoftなどがそれぞれ開発をしています。そのため、基本的に最新バージョンのWebブラウザに対応するWebDriverを、これらのITベンダーが提供してくれる状態にあります。こういった点も、Seleniumを使うメリットですね。

Mozilla GeckoDriver (FireFox) (Selenium3.5以降でのみ動作)

以下からダウンロードします。
https://github.com/mozilla/geckodriver/releases

自身のパソコンのOSに合ったWebDriverをダウンロードしてきます。

image.png (251.7 kB)

以下のコマンドでファイルを解凍します。

$ tar zxvf geckodriver-v0.19.1-macos.tar.gz

geckodriverという名前のWebDriverの実行ファイルが解凍されます。

Google Chrome Driver (Google Chrome)

以下からダウンロードします。
https://sites.google.com/a/chromium.org/chromedriver/

image.png (410.3 kB)

WebDriverをインストールする際は、Google Chromeのバージョンに気をつけてください。最新のものだと、対応しているバージョンはv62-64に限定されます(もちろん、以前のバージョンのWebDriverについてもダウンロード可能です)。自分のパソコンにインストールされているGoogle Chromeのバージョンを確認し、古いバージョンであれば、そのバージョンをサポートしているWebDriverをダウンロードしてください。

image.png (334.8 kB)

自身のパソコンのOSに合ったWebDriverをダウンロードしてきます。 image.png (175.0 kB)

以下のコマンドでファイルを解凍します。

$ unzip chromedriver_mac64.zip

chromedriverという名前のWebDriverの実行ファイルが解凍されます。

SafariDriver (Safari)

Mac OSX内にすでにインストール済みです。
/usr/bin/safaridriverの場所に存在します。

Safariの設定を変更する必要があります。 まず、Safariの環境設定を開きます。 image.png (67.6 kB)

次に、「メニューバーに'開発'メニューを表示」にチェックをつけます。 image.png (362.8 kB)

すると、メニューバーに「開発」が追加されるので、そこから「リモートオートメーションを許可」をクリックします。これで、ブラウザ側の設定は完了です。 image.png (1.1 MB)

リモートオートメーションを許可していない場合、Javaのテストコードを実行すると、以下のようなエラーが出力されます。このエラーが出た場合は、上記の設定ができてないので、設定を変更してください。

org.openqa.selenium.SessionNotCreatedException: Could not create a session: You must enable the 'Allow Remote Automation' option in Safari's Develop menu to control Safari via WebDriver. (WARNING: The server did not provide any stacktrace information)

Microsoft Edge

以下からダウンロードします。
https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/

image.png (389.2 kB)

自身のパソコンのOSのビルドに合ったWebDriverをダウンロードしてきます。 image.png (120.6 kB)

Internet Explorer 11

設定が複雑なので、必要でない限り使わないほうが良いと思います。

こちらに詳しい使用方法が記載されているので参照してください。 保護モードの設定が、「インターネット」「イントラネット」「信頼済みサイト」「制限付きサイト」の4つで統一されていることがかなり重要です(この設定が揃っていないとWebDriverが動作しません)。

seleniumでinternet-explorer11を動かす方法

http://bitwave.showcase-tv.com/seleniumでinternet-explorer11を動かす方法/

トラブルシューティング(一部)

画面は拡大せずに、100%でないと動作しません。以下のエラーが出た場合は、拡大率を100%にしましょう。

org.openqa.selenium.SessionNotCreatedException: Unexpected error launching Internet Explorer. Browser zoom level was set to 110%. It should be set to 100%

image.png (26.9 kB)

Seleniumを動かしてみる

以下では、Eclipseがインストール済みであることを前提として説明を行います。 素のEclipseをインストールするのではなく、以下のAll-in-Oneのパッケージ(Pleiades All in One)をインストールすることをお勧めします。
http://mergedoc.osdn.jp/

1. プロジェクトファイルの作成

Seleniumを動かすためのプロジェクトファイルを作成します。 [新規] -> [Javaプロジェクト]をクリックします。 image.png (70.3 kB)

適当なプロジェクト名をつけて、「次へ」をクリックします。 image.png (51.9 kB)

先ほどダウンロードしたSelenium Clientを解凍し、libsフォルダのjarとclient-combined-3.10.0.jarclient-combined-3.10.0-sources.jarをそれぞれプロジェクトファイルに取り込みます。

image.png (86.4 kB)

まず、「ライブラリ」タブを選択し、「外部JARの追加」をクリックします。 image.png (41.3 kB)

上述のSelenium ClientのJARファイル一式を追加します。 最後に、「完了」ボタンをクリックし、プロジェクトを生成します。 image.png (75.1 kB)

すると、参照ライブラリに先ほど取り込んだSelenium ClientのJARファイルが参照されて取り込まれていることが確認できると思います。

image.png (51.5 kB)

次に、WebDriverを保存するためのフォルダを用意します。 [新規] -> [フォルダー]をクリックします。

image.png (117.2 kB)

フォルダー名を「exe」として、「完了」ボタンをクリックします。 image.png (25.0 kB)

作成した「exe」フォルダ内に、先ほどダウンロードしたWebDriverをドラック&ドロップでインポートします。(ここでは、chromedriver.exeをインポートした例を示した。)

image.png (13.3 kB)

Mac OSXの場合、実行権限を付与しておきます。 image.png (58.4 kB)

$ chmod 755 geckodriver

続いて、Seleniumを実行するためのテストクラスを作成していきます。 プロジェクトの「src」フォルダを右クリックし、[新規] -> [Junitテスト・ケース]をクリックします。

image.png (80.9 kB)

適当なパッケージ名とクラス名を記入し、setUp()tearDown()にチェックを付けます。

image.png (47.3 kB)

JUnit4がビルドパスに入っていない場合、追加するかどうか聞かれるので、「OK」をクリックしてビルドパスに加えます。

image.png (20.0 kB)

2. PubMedUCSC Genome Browserを操作するコードを書いてみる

ページオブジェクトパターン

下図にページオブジェクトパターンと呼ばれる、一般的なクラスの設計方法を示しました。この方法を取り入れることで、メニューやボタン、検索入力欄など、Webページへの操作を共通クラスにまとめることでメンテナンス性の高いコードを書くことができます。

image.png (538.4 kB)

ロケータの調べ方

selenium用のコードを書くために、まずロケータ(ボタンやメニューなどを操作するための目印)を調べる必要があります。例えば、FireFoxを使うと、要素(下の例だと"Advanced")を右クリックして「要素を調査」をクリックすることでロケータを調べることができます。

image.png (353.4 kB)

どのタグが使われているかなどの情報を調べることができます(以下の図だと、INPUTタグのID要素が"Submit"であることがわかります。これを目印にしてSeleniumでWebブラウザを操作していきます)。

image.png (682.9 kB)

コーディング

以下のような構成で、プログラムを用意しました。 pageパッケージにページオブジェクトクラスを、testパッケージにJUnitのテストケースクラスを配置するようにしました。

image.png (36.0 kB)

WebDriverの呼び出し

System.setProperty()の2番目の引数は、ダウンロードしたWebDriverを指定してください。OSによって、拡張子が異なるので注意する必要があります。以下でいくつか例を示しました。

Google Chrome (Mac OSX)
System.setProperty("webdriver.chrome.driver", "exe/chromedriver");
WebDriver driver = new ChromeDriver();
Firefox (Mac OSX)
System.setProperty("webdriver.gecko.driver", "exe/geckodriver");
WebDriver driver = new FirefoxDriver();
Safari (Mac OSX)
WebDriver driver = new SafariDriver();
Microsoft Edge (Windows10)
System.setProperty("webdriver.edge.driver", "exe/MicrosoftWebDriver.exe");
WebDriver driver = new EdgeDriver();
Internet Explorer (Windows10)
System.setProperty("webdriver.ie.driver", "exe/IEDriverServer.exe");
WebDriver driver = new InternetExplorerDriver();

ソースコード

ソースコードの内容について言及しません。 主に、「Selenium実践入門」と言う本と下記のWebサイトを参考にしました。

PubMedSearchTest.java
package test;

import java.util.concurrent.TimeUnit;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;

import page.PubMedAdvancedSearchPage;

public class PubMedSearchTest {

    private WebDriver driver;

    @Before
    public void setUp() throws Exception {
        System.setProperty("webdriver.chrome.driver", "exe/chromedriver.exe");
        driver = new ChromeDriver();
        //System.setProperty("webdriver.edge.driver", "exe/MicrosoftWebDriver.exe");
        //driver = new EdgeDriver();
        //System.setProperty("webdriver.ie.driver", "exe/IEDriverServer.exe");
        //driver = new InternetExplorerDriver();

        driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);
    }

    @After
    public void tearDown() throws Exception {
        //driver.quit();
    }

    @Test
    public void pubmedAdvancedSearchTest() {
        // PubMed Top
        PubMedAdvancedSearchPage pmPage = new PubMedAdvancedSearchPage(driver);

        // Go to Advanced Search page
        pmPage.goToAdvancedSearch();

        // Set keywords
        pmPage.setJournal("Nature communications");
        pmPage.setJournal("Nature biotechnology");
        pmPage.setSearchSelect(1, "OR");
        pmPage.setJournal("Nature methods");
        pmPage.setSearchSelect(2, "OR");
        pmPage.setJournal("Nature genetics");
        pmPage.setSearchSelect(3, "OR");
        pmPage.setJournal("Molecular cell");
        pmPage.setSearchSelect(4, "OR");
        pmPage.setJournal("eLife");
        pmPage.setSearchSelect(5, "OR");
        pmPage.setJournal( "PLoS biology");
        pmPage.setSearchSelect(6, "OR");
        pmPage.setJournal("Genome research");
        pmPage.setSearchSelect(7, "OR");
        pmPage.setJournal("Genes & development");
        pmPage.setSearchSelect(8, "OR");
        pmPage.setJournal("Nature cell biology");
        pmPage.setSearchSelect(9, "OR");

        // Click Search button
        pmPage.clickSearchButton();
    }

}
UCSCGenomeBrowserTest.java
package test;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;

import page.UCSCGenomeBrowserUploadPage;

public class UCSCGenomeBrowserTest {

    private WebDriver driver;

    @Before
    public void setUp() throws Exception {
        System.setProperty("webdriver.chrome.driver", "exe/chromedriver.exe");
        driver = new ChromeDriver();
        //System.setProperty("webdriver.edge.driver", "exe/MicrosoftWebDriver.exe");
        //driver = new EdgeDriver();
        //System.setProperty("webdriver.ie.driver", "exe/IEDriverServer.exe");
        //driver = new InternetExplorerDriver();
    }

    @After
    public void tearDown() throws Exception {
    }

    @Test
    public void test() {
        UCSCGenomeBrowserUploadPage gmPage = new UCSCGenomeBrowserUploadPage(driver);
        gmPage.goToCustomTrack();

        gmPage.setGenomeRefSelect("hg19");

        // Upload file
        gmPage.UploadFile("C://Users/imama/Desktop/test1.bed");
        gmPage.submit();

        // Back to custom track page
        gmPage.goBackToCustomTrack();

        // Upload file
        gmPage.UploadFile("C://Users/imama/Desktop/test2.bed");
        gmPage.submit();

        // Back to custom track page
        gmPage.goBackToCustomTrack();

        // Upload file
        gmPage.UploadFile("C://Users/imama/Desktop/test3.bed");
        gmPage.submit();

    }

}
PubMedAdvancedSearchPage.java
package page;

import java.util.concurrent.TimeUnit;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.Select;
import org.openqa.selenium.support.ui.WebDriverWait;

public class PubMedAdvancedSearchPage {

    /* WebDriverクラスのインスタンス */
    private WebDriver driver;

    // 明示的な待機
    WebDriverWait wait;

    // キーワードカウンタ
    private Integer keywordCounter;

    // コンストラクタ
    public PubMedAdvancedSearchPage(WebDriver driver){
        this.driver = driver;
        this.keywordCounter = 0;
        this.wait = new WebDriverWait(driver, 10);
    }

    // Advanced saerch画面へ遷移
    public void goToAdvancedSearch() {
        driver.get("https://www.ncbi.nlm.nih.gov/pubmed/");
        driver.findElement(By.linkText("Advanced")).click();

        // Goto PubMed Advanced search
        String previousURL = driver.getCurrentUrl();
        System.out.println(previousURL);
        driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
        ExpectedCondition<Boolean> e = new ExpectedCondition<Boolean>() {
              public Boolean apply(WebDriver d) {
                return (d.getCurrentUrl() != previousURL);
              }
        };
        wait.until(e);
        String currentURL = driver.getCurrentUrl();
        System.out.println(currentURL);

        // ページタイトルが変更されるまで待ち
        wait.until(ExpectedConditions.titleContains("Advanced search"));
    }

    // Set journal
    public void setJournal(String journal) {
        // FieldにJournalを選択
        Select select = new Select(driver.findElement(By.id("ff_" + keywordCounter)));
        select.selectByValue("Journal");

        // Journal名を入力
        WebElement searchBox = driver.findElement(By.id("fv_" + keywordCounter));
        searchBox.sendKeys(journal);

        // カウントアップ
        keywordCounter += 1;
    }

    // Set search select
    public void setSearchSelect(Integer id, String value) {
        Select select = new Select(driver.findElement(By.id("fop_" + id)));
        select.selectByValue(value);
    }

    // Click Search button
    public void clickSearchButton() {
        driver.findElement(By.id("search")).click();
    }
}
UCSCGenomeUploadPage.java
package page;

import java.util.concurrent.TimeUnit;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.Select;
import org.openqa.selenium.support.ui.WebDriverWait;

public class UCSCGenomeBrowserUploadPage {

    /* WebDriverクラスのインスタンス */
    private WebDriver driver;

    // 明示的な待機
    WebDriverWait wait;

    // コンストラクタ
    public UCSCGenomeBrowserUploadPage(WebDriver driver){
        this.driver = driver;
        this.wait = new WebDriverWait(driver, 10);

        driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);
    }

    // Go to Custom track page
    public void goToCustomTrack() {
        driver.get("https://genome.ucsc.edu/");
        driver.findElement(By.id("myData")).click();

        wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("customTracksMenuLink")));
        driver.findElement(By.id("customTracksMenuLink")).click();
    }

    // Upload file
    public void UploadFile(String filePath) {
        WebElement upload = driver.findElement(By.name("hgt.customFile"));
        upload.sendKeys(filePath);
    }

    // Submit
    public void submit() {
        driver.findElement(By.id("Submit")).click();
    }

    // Return Custom track page
    public void goBackToCustomTrack() {
        WebDriverWait wait2 = new WebDriverWait(driver, 10000);
        wait2.until(ExpectedConditions.visibilityOfElementLocated(By.id("addTracksButton")));
        driver.findElement(By.id("addTracksButton")).click();
    }

    public void setGenomeRefSelect(String genomeRef) {
        Select select = new Select(driver.findElement(By.id("db")));
        select.selectByValue(genomeRef);
    }
}

スクリーンショットの取り方について(トラブルシューティング

余談ですが、Java8の場合、FileUtils(Java7以前)ではなく、FileHandler(Java8以降)なので間違いがないように。 https://github.com/SeleniumHQ/selenium/issues/4919

File tmpFile = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE);
try {
    FileHandler.copy(tmpFile, new File("/Users/imamachinaoto/Desktop/test.png"));
} catch (IOException e) {
    // TODO 自動生成された catch ブロック
    e.printStackTrace();
}

参考

[初心者向け] JavaSeleniumを動かす
https://qiita.com/tsukakei/items/41bc7f3827407f8f37e8

selenium get current url after loading a page
https://stackoverflow.com/questions/16242340/selenium-get-current-url-after-loading-a-page

Selenium WebDriverのwaitを活用しよう
http://softwaretest.jp/labo/tech/labo-294/

Best Practices & Tips: Selenium File Upload
https://saucelabs.com/resources/articles/best-practices-tips-selenium-file-upload

bioRxivでバズってる論文を抽出してSNSにメッセージを流してみた

きっかけ

近年、NatureやCell, Scienceに論文が投稿される前に、preprintサーバであるBioRxivに投稿する流れが増えてきています(2015年以降、急激に増加。ちなみに、Natureなどの多くの主要ジャーナルが、preprintサーバへ事前に投稿することを許可しているので、BioRxivへの投稿はいわゆる二重投稿に引っかかりません)。

また、BioRxivへの投稿が増えている背景には、オープンイノベーションを推進するという目的だけでなく、論文のPriorityを確保することや研究の宣伝を行う目的なども相まってのことだと考えられます。

とにかく、これからのサイエンスでは最新の研究動向を理解する上で、BioRxivの論文をフォローしていくことはますます重要になってくるでしょう。しかしながら、その論文の本数が多すぎます…。

直近で、preprintサーバに1ヶ月あたり1673本の論文が登録されています(1日あたり約50〜60本)。今後、さらに数が伸びることが予想されます。これを人力で全て確認するのは不可能です…。 image.png (218.9 kB) 元データはこちら。
http://www.prepubmed.org/monthly_stats/

BioRxivに登録されている論文はとにかく玉石混交なので、注目されている論文だけを抽出することはできないか考えました。そんな中で見つけたのが、こちらの記事です。

A Twitter bot to find the most interesting bioRxiv preprints
https://gigabaseorgigabyte.wordpress.com/2017/08/08/a-twitter-bot-to-find-the-most-interesting-biorxiv-preprints/

こちらの記事では、 Altmetric scoreという指標(論文の注目度を数値化したもの)を用いて、BioRxivに投稿された論文の中で、特に注目度の高い記事を抽出し、Tweetするという仕組みを作っていました。

ちなみに、Altmetric scoreとは、TwitterFacebookなどのSNSやNews、Mendeleyへの登録数などの様々な指標を元に算出した論文の注目度を示すスコアです(詳しく解析したわけではないですが、個人的な印象として、BioRxivに登録されている論文のAltmetric scoreは、Tweet数に引っ張られている印象を受けましたが…)。

上記の記事で紹介されているスクリプトをそのまま使うのも手でしたが、後々PubMedに登録されている論文に対しても応用したかったのと、データのストアをSQLデータベースではなく、Pickleで管理しているなど、実装部分で個人的に作り変えたい部分があったので、1から自分で作ってみることにしました。

そんなわけで、上記の記事を参考に、Raspberry Pi上で動作するBioRxivのキュレーションシステムを作って見たというのが今回の内容になります。

サンプル

SNSへのメッセージ出力として、SlackとTwitterに対応させました(現状、個人的に興味のある「Genomics」と「Bioinformatics」の分野に関する論文しかキュレーションの対象にしていません)。

サンプルのTwitter botはこちらになります。
https://twitter.com/BioRxivCurator

image.png (546.9 kB)

ソースコード

BioRxivCurator
https://github.com/Imamachi-n/BioRxivCurator

内容

全体像

システム全体の流れは以下の通りです(内部的なデータ処理の流れとはちょっと違いますが、概略図ということで解釈してもらえば良いです)。 image.png (218.7 kB)

RSS feedで取得した論文データについて、Altmetric scoreを取得し、データをSQLiteデータベースに格納しておきます。Altmetric scoreが一定以上の論文(注目度の高い論文)について、定期的にTwitterやSlackにメッセージを配信します。

また、一定のスコアに達しない論文については、情報をデータベースに保持し続けておき、定期的にAltmetric scoreを確認します。データの保持期限は1ヶ月とします(多くの場合、1ヶ月以降はあまりAltmeric scoreが変動しない傾向にあるため)。それを過ぎた論文データは破棄します(厳密には、フラグ管理により論理削除する形をとります)。

Pythonで実装し、プログラム自体はRaspberry Pi3 ModelB上で動かします。手作業でプログラムを動かすのは面倒なので、ジョブスケジューラであるcronを使って毎日論文を自動でチェックする形にしました。 image.png (171.0 kB) https://commons.wikimedia.org/w/index.php?curid=47497384

Pythonによる実装

まず、Pythonによる実装例を見ていきます。かいつまんで重要な箇所を説明しているだけなので、詳しくは上述のソースコードをご覧ください。

RSS feedの取得

BioRxivのRSS feedはこちらから取得できます。直近の30 articlesを取得できます。
https://www.biorxiv.org/alertsrss

論文の絞り込みとしては、
http://connect.biorxiv.org/biorxiv_xml.php?subject=all
で全体から取得するか、
http://connect.biorxiv.org/biorxiv_xml.php?subject=キーワード
で特定の分野の論文のみを取得する方法があります。

後者の場合、+でつなげて
http://connect.biorxiv.org/biorxiv_xml.php?subject=genomics+bioinformatics
のように複数の分野からRSSを取得することも可能です。

PythonRSS feedから得たデータをパースするために、今回はfeedparserライブラリを使いました。

import feedparser

feed = feedparser.parse(
        "http://connect.biorxiv.org/biorxiv_xml.php?subject={0}".format("+".join(subjects)))

 for pub in feed["items"]:
    <処理を書く>
     # pub["dc_identifier"] # DOI
     # pub["title"]          # 論文のタイトル
     # pub["link"].split('?')[0]  # 論文のURL
     # pub["updated"]        # 更新日

以上のように、feedparser.parse()に先ほど取得したRSS feedのURLを渡すことで、ディクショナリ型にパースされたRSSデータを取得できます。

各論文のデータは、itemsごとに格納されているので、Forループで各論文の情報を取得します。例えば、dc_identifierにはDOI、titleには論文のタイトル、linkには論文のリンク、updatedには更新日が格納されています。DOIはAltmetric scoreの取得の際に必要となるので、必ず取得しておきます。

実際にどんなデータが入っているか確認したい場合は、RSS feed URLに直接アクセスして、中身のXMLファイルを覗いてみるといいです。 ブラウザによって見え方が異なります。以下の例では、Google Chromeを使って表示しています。 image.png (389.8 kB)

Altmetric scoreの取得

RSS feedから取得したDOIを使って、Altmetric scoreを取得します。 Altmetric scoreの取得には、AltmetricのAPIを使用します。
https://api.altmetric.com/

ソースコード中では、Altmetric APIの薄いラッパークラスを作っていますが、要はAltmetric scoreの取得先URLに対してGETしてるだけです。

import requests
import json

# GET request
doi = "10.1038/480426a"
request = requests.get("https://api.altmetric.com/v1/doi/{0}".format(doi))
response = json.loads(request.text)
# response["context"]['journal']['pct']    # Altmetric scoreを「0〜100」に正規化した値。
# response["score"]            # Altmetric score

Altmetric APIは、
リクエストURL: https://api.altmetric.com/v1/doi/DOI
に対してGETすると、JSON形式でデータを返してくれます。

そこで、JSON形式のデータをjson.load()を使ってディクショナリ型に変換します。 あとは、キーを指定して欲しいデータを取り出すだけです。

こちらも、実際にどんなデータが入っているか確認したい場合は、リクエストURLに直接アクセスして、中身のJSONファイルを覗いてみるといいです。
ブラウザによって見え方が異なります。以下の例では、Firefoxを使って表示しています。Firefoxではこのように、 JSON形式のデータを見やすく表示してくれます。 image.png (298.8 kB) 今回は、赤枠で囲った値を取得しています。

SQLiteデータベースへの格納

順番は前後しますが、ここでまとめてSQLiteデータベースの処理を説明します。

1. テーブルの作成(CREATE TABLE

まず、テーブルを作成します。 ここでは、CREATE TABLE IF NOT EXISTSとすることで、システムの初回起動時にのみテーブルが作成される仕組みになっています。 また、論文のDOIをprimary keyに指定します(各論文に対してユニークな文字列が割り振られるため)。

import sqlite3

sqlite3_file = "./storeAltmetrics.sqlite3"
with sqlite3.connect(sqlite3_file) as conn:
        c = conn.cursor()

        # Create biorxiv_altmetrics_log table
        sql = """CREATE TABLE IF NOT EXISTS biorxiv_altmetrics_log
                (doi TEXT,
                 title TEXT,
                 link TEXT,
                 update_date TEXT,
                 altmetric_score INTEGER,
                 altmetric_pct INTEGER,
                 altmetric_flg INTEGER,
                 PRIMARY KEY(doi)
                )"""
        c.execute(sql)
        conn.commit()

基本的に、Javaなどの言語と書き方は似てます。 sqlite3.connect()でデータベースに接続します。ここでは、sqlite3_fileが格納先のデータベースのファイルだとします。 続いて、cursorオブジェクトを作成し、execute()メソッドでSQL文を実行します。最後に、変更点を保存するために、Connectionオブジェクトのcommit()メソッドを実行します(コミット)。

テーブルのフィールドについてです。 RSS feedから取得したデータをdoi, title, link, update_dateにそれぞれ格納します。 また、Altmetric APIから取得したスコアを、altmetric_score, altmetric_pctにそれぞれ格納します。

フラグ 意味
0 PCTが90未満
1 PCTが90以上
-1 PCTが90未満で1ヶ月が経過(解析対象から除外)
2. RSS feedから得た論文データの格納(INSERT INTO

RSS feedから取得した論文のデータ(論文のタイトル、論文のURL、DOI、更新日)をデータベースのテーブルに格納します。 ここでは、INSERT OR IGNORE INTOとすることで、テーブルに格納されていない論文についてのみ登録する形になっています。

with sqlite3.connect(sqlite3_file) as conn:
            c = conn.cursor()

            # Insert article info into biorxiv_altmetrics_log if not already exists
            sql = """INSERT OR IGNORE INTO biorxiv_altmetrics_log
                     VALUES(?,?,?,?,?,?,?)"""
            doi_info = [tuple([p.doi, p.title, p.url, p.date, 0, 0, 0])
                        for p in RSS_data_list]
            c.executemany(sql, doi_info)
            conn.commit()

ここでは、RSS_dataクラスのインスタンスRSS_data_listに論文のデータが格納されていることを想定しています。 データを渡す際には、タプル型である必要があります。

("value1,")
("value1", "value2", "value3")

また、タプル型のデータのリストを作成し、cursorオブジェクトのexecutemany()メソッドを使うことで、一気に複数のレコードをテーブルに格納することができます。最後は、connectionオブジェクトのcommit()メソッドを使って、変更点をコミットします。

ちなみに、突然出てきたRSS_data_listですが、下記の通りに定義しています。

class RSS_data(object):
    def __init__(self, doi, title, url, date):
        self.doi = doi
        self.title = title
        self.url = url
        self.date = date

RSS_data(doi=pub["dc_identifier"],
         title=pub["title"],
         url=pub["link"].split('?')[0],
         date=pub["updated"]))

またここでは、pubは上述したfeedparser.parse()メソッドで取得したRSS feedから得たデータをディクショナリ型で格納したオブジェクトだとします。

3. Altmeric scoreを取得する論文データの抽出(SELECT

続いて、過去に取得した論文データを含めて、一定のAltmetric scoreを超えていない論文のリストを取得します。
先ほど説明した通り、altmetric_flgが「0」である論文がAltmetric scoreを検索する対象となります。

with sqlite3.connect(sqlite3_file) as conn:
            c = conn.cursor()

            # Select target doi from biorxiv_altmetrics_log
            sql = """SELECT doi, title, link, update_date from biorxiv_altmetrics_log
                     WHERE altmetric_flg = 0"""
            c.execute(sql)

            # Store doi data as target_doi_data object
            target_doi_list = []
            for doi_info in c.fetchall():
                target_doi_list.append(target_doi_data(
                    doi=doi_info[0], title=doi_info[1], url=doi_info[2], date=doi_info[3]))

cursorオブジェクトのexecute()メソッドでSQL文を実行した後、検索した結果はcursorオブジェクトに格納されます。fetchall()メソッドを使うことで、条件に一致した全てのレコードをリストとして取得できます。

class target_doi_data(object):
    def __init__(self, doi, title, url, date):
        self.doi = doi
        self.title = title
        self.url = url
        self.date = date

取得したレコードの情報は、上記target_doi_dataオブジェクトのリストとして格納しておきます。

4. Altmetric scoreの値の格納(UPDATE

最後に、Altmetric scoreの更新を行います。APIから取得したスコアをテーブルに書き込みます。最後に忘れずに、変更をコミットします。

with sqlite3.connect(sqlite3_file) as conn:
            c = conn.cursor()

            # insert altmetric score into biorxiv_altmetrics_log
            sql = """UPDATE biorxiv_altmetrics_log
                     SET altmetric_score = ?,
                          altmetric_pct = ?,
                          altmetric_flg = ?
                     WHERE doi = ?"""
            c.execute(sql, tuple([altmetrics_data.altmetric_score,
                                  altmetrics_data.pct, altmetrics_data.flg,
                                  doi]))
            conn.commit()

Slackのアクセストークンを取得する

SlackのAPIを介してデータのやり取りをします。そのため、Slackのアクセストークンを取得する必要があります。以下ではその取得方法について説明します。

基本的に以下の記事を参考にしました。
https://qiita.com/ykhirao/items/3b19ee6a1458cfb4ba21

1. アプリの登録

まず、以下のURLにアクセスします。Slackにログインしていない場合、ログインしてください。
https://api.slack.com/apps

Create an Appをクリックします。 image.png (250.5 kB)

Create a Slack Appという画面が出てくるので、
App Name: 好きな名前を入力する。
Development Slack Workspace: Slackのメッセージを流したいワークスペースを選択。
を入力して、Create Appをクリックします。 image.png (194.2 kB)

2. スコープの設定

登録したアプリの権限の範囲を設定します。 Install your app to your workspaceを開くと、権限のスコープを設定してくださいとメッセージが出ているので、permission scopeをクリックします。 image.png (280.0 kB)

今回は、Slackへメッセージを送るだけなので、ChatのSend messages as xxxxxxを選択します。 image.png (233.3 kB)

すると、選択したPermission Scopeが表示されます。
状態を保存するために、Save Changesを必ずクリックしてください。 image.png (181.5 kB)

3. アプリをSlackにインストールする

最後に、アプリをSlackにインストールします。 image.png (168.1 kB)

先ほど設定したメッセージを送る権限を付与していいか聞かれるので、Authrizeをクリックします。 image.png (81.0 kB)

インストールが完了すると、OAuth Access Tokenを取得できます。 この文字列を使うので、コピーして保存しておきます。 image.png (119.7 kB)

Slackへのメッセージ配信

Slackへのメッセージ送信は、公式のPythonライブラリであるSlackClientを使います。
https://slackapi.github.io/python-slackclient/basic_usage.html#sending-a-message

使い方は簡単で、先ほど取得したSlackのアクセストークslack_tokenを指定し、spi_call()メソッドに、送り先のチャンネルchannelと送信するテキストtextを指定するだけです。エラーハンドリングについては、上記のURLの記事を参照してください。

slack_token = "xxxxxxxx"
channel = "@imamachi"
text = "Hello World !!"

sc = SlackClient(slack_token)
    response = sc.api_call(
        "chat.postMessage",
        channel=channel,
        text=message
    )

Twitterのアクセストークンを取得する

Slack同様、TwitterAPIを介してデータのやり取りをします。そのため、Twitterについてもアクセストークンを取得する必要がありmす。以下ではその取得方法について説明します。

1. Twitterアプリの作成

まず、Twitter Application Managementにアクセスします。アカウントにログインしていない場合は、ツイート対象となるアカウントにログインしてください。
https://apps.twitter.com/

Create New Appをクリックします。 image.png (84.9 kB)

Name: アプリ名を記載する。
Description: アプリの説明を記載する。
Website: ソースコードやアプリが入手できるURLを入力。
Create your Twitter applicationをクリックし、アプリを作ります。 image.png (343.5 kB)

image.png (55.1 kB)

すると、次のようなページに遷移します。 image.png (223.4 kB)

2. アクセストークンの取得

Keys and Access Tokensタブをクリックし、 Consumer Key (API Key)Consumer Secret (API Secret)を確認します。 これらはAPIでのやり取りに必要となるので、コピーして保存しておきます。 image.png (318.7 kB)

続いて、Your Access TokenでCreate my access tokenをクリックします。 image.png (65.5 kB)

Access TokenAccess Token Secretを取得します。 これらも同様に、APIでのやり取りに必要となるので、コピーして保存しておきます。 image.png (85.4 kB)

Twitterへのメッセージ配信

Twitterへのツイートは、tweepyというライブラリを使います。 こちらも使い方は非常に簡単で、先ほど取得した各種の値を設定し、メッセージをupdate_status()メソッドに指定するだけです。

こちらも参照。
https://review-of-my-life.blogspot.jp/2017/07/python-cloud9-tweepy.html

consumer_key = "xxxxxxxx"
consumer_secret = "xxxxxxxx"
access_token = "xxxxxxxx"
access_token_secret = "xxxxxxxx"
message = "Hello World !!"

auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_token, access_secret)
api = tweepy.API(auth)
api.update_status(message)

追記: 余談ですが、TweepyはメインのContributorがいなくなり、他のmaintainerに引き継がれています。アップデートが最近されていないので、TwitterAPIの仕様変更で動かなくなるリスクがあります。
https://qiita.com/utgwkk/items/4beed333e8262c675028

Raspberry Pi3の用意

以下のサイトを参考にRaspberry Pi3のOSをインストールしたMicro SDカードを用意します。
https://getpocket.com/redirect?url=http%3A%2F%2Fhirazakura.hatenablog.com%2Fentry%2Fraspberrypi%2Fsetup%2Ffirst&formCheck=3f0b1d005779f9a1ac22bfac92159bea

続いて、アップデートをかけて、リブートします(再起動)。

$ sudo apt-get update
$ sudo apt-get upgrade
$ sudo rpi-update
$ sudo reboot

テキストエディタが入っていないので、VSCodeをインストールします。
https://code.headmelted.com/ 管理者権限でないとインストールできないので注意してください。

$ wget -O - https://code.headmelted.com/installers/apt.sh 
$ chmod 755 apt.sh
$ sudo apt.sh

以下でVisual Studio Codeを実行できます。

$ code-oss

SQLiteのクライアントアプリをインストール

SQLiteデータベースをGUIで見たい場合、以下のクライアントアプリがオススメです。
http://sqlitebrowser.org/

Raspberry Pi上では、以下のコマンドでインストール可能です。

$ sudo apt-get install sqlitebrowser

cronの設定

Raspberry Piで作成したPythonスクリプトを定期的に自動で実行するために、cronを使います。 cronはUnix OS系に標準で搭載されているジョブスケジューリングのプログラムです。

cronについての記事はこちらを参照のこと。
https://qiita.com/hikouki/items/e744b3a4d356d2af12cf

ここでは、簡単な設定例を示したいと思います。 まず、cronのconfigureファイルcron.confを作成します。 スペース区切りで日時を指定、最後に実行コマンドを指定します。 行の最後に改行が必要なので、入れ忘れないように。

分 時 日 月 曜日 <実行コマンド> [改行]

あと、念のため、エラーになった時にエラーログを確認できるように、標準出力でエラーログを取っておきます。

0 11 * * * bash /home/pi/Desktop/BioRxivCurator/src/startup4RaspberryPi.sh > /home/pi/Desktop/error.txt 2>&1

cronのconfigureファイルを作成したら、以下のコマンドを実行します。

$ crontab ./cron.conf

設定内容を確認したい場合は、

$ crontab -e

で設定内容を確認・編集できます。

間違って登録してしまった場合、

$ crontab -r

で全ての設定を削除することができます(このコマンドの実行には注意が必要です)。

cron使用時の注意点

だいたいのエラーは、上記pythonへのパスに起因した問題です。

相対パスに注意

cronが実行するコマンドは、実行ユーザーのホームディレクトリで実行されます。

特に今回の場合、pythonのコードの中に相対パスを設定している箇所があるので、実行コマンド中で、cdでカレントディレクトリをソースコードの置いてあるディレクトリに移動しておきます。

$ cd /home/pi/Desktop/BioRxivCurator/src

Minicondaを使用している場合

python絶対パスで指定する必要があります。そうしないと、cronは標準でインストールされているpythonを使おうとします。

/home/pi/miniconda3/bin/python /.main.py --yaml_setting_file ./production.yaml

原因がわからないエラーの場合

cronで実行するコマンドの最後に、エラー出力先を指定しておくと良いです。

0 11 * * * bash /home/pi/Desktop/BioRxivCurator/src/startup.sh > /home/pi/Desktop/error.txt 2>&1

こうすることで、指定したファイルerror.txtにエラーログが出力される。

また、/var/log/syslog内にもcron実行時のログが残るので、そちらも参照すると原因の特定につながります。

cron.confが登録できない

コマンドの打ち間違え(こんなミスをするのは私だけかもしれませんが…)。

$ cron ./cron.conf
cron: can't lock /var/run/crond.pid, otherpid may be 3505: Resource temporarily unavailable

正しくは、cronではなくcrontabです。

$ crontab ./cron.conf

どうしようもなくなったら、とりあえず、リブート(再起動)してみましょう。

$ reboot

Windows Mixed RealityのImmersive Headsetの初期設定

概要

Acerから出たAcer Windows Mixed Reality Development Editionが届いたので、セットアップ方法をまとめました。

IMG_20170826_145612.jpg (2.6 MB)

設定方法

(1)HDMIケーブルとUSB3.0ケーブルをパソコンに差し込みます。

(2)しばらく待っていると、次のような画面が出てきます。 image.png (562.6 kB)

(3)次へを押していくと、接続しているパソコンのスペックが動作要件を満たしているかどうかチェックが行われます。問題なければ、次へを押します。 image.png (188.2 kB)

(4)HMDの説明が入り、ヘッドフォンジャックの位置などが示されます。 image.png (158.5 kB)

(5)身長を聞かれます(メートル法じゃない…)。 image.png (176.0 kB)

フィートとかインチでは身長がわからないので、メートル法を指定して自分の身長を入力します。 image.png (170.8 kB)

(6)フロアの設定が始まります。 image.png (477.6 kB)

HMDかぶって部屋の中央に立ち、開始ボタンを押します。 image.png (192.3 kB)

10秒間待つと、フロアが検出されましたと表示されます。 image.png (182.4 kB)

(7)続いて、フロアの境界を検知します。 image.png (664.1 kB)

できるだけ障害物を除いて、 image.png (669.3 kB)

腰のあたりにHMDを持ち、フロアの境界(壁)をスキャニングしていきます。 image.png (238.4 kB)

一周りすると、いかのように境界が設定されます(あんまりうまくない例です)。 image.png (262.3 kB)

(8)以上で基本的な設定は終わりで、 image.png (176.0 kB)

複合現実ポータルに入れます。 image.png (779.4 kB)

右クリックで、場所の移動。 キーボードのWindowsキーを押すと、メニューが出現します。 全体的な操作感は、HoloLensと似ています。 image.png (902.6 kB)

簡単ではありますが、以上になります。