一撃でウィンドウの内容をOCRする環境を構築した話

やったこと

もう大手各社のクラウドOCR APIの精度は十分実用的なレベルなので、単純にウィンドウの内容を投げればよい話なんだけれども、ウィンドウの内容全部をOCRしたかったり、あるいは矩形で選択したかったりするかもしれないので、スクリーンをキャプチャして、それら画像ファイルを生成するところまでは他のツールに任せることにして、「あるディレクトリ以下に画像ファイルが生成/移動されたら、それを自動でOCRする」作業をツールを書いて自動化しました。

成果物

github.com

インストール方法

zipファイルの中身を適当なところに解凍するだけです。

使い方

最小のコマンドは

cocr -d "PATH_TO_TARGET_DIRECTORY" -k "YOUR_GOOGLE_API_KEY"

-d で指定されたディレクトリを監視して、その中に画像ファイルが放り込まれたとき、自動で対応するjsonAPIを叩いたjsonレスポンスそのまま)、html(OCR結果を表示するHTMLファイル)を生成します。

-c オプションで結果をクリップボードに貼り付け。-s でhtmlファイルをブラウザで開く。-n で通知。他にもディレクトリ内の画像ファイルをまとめて処理する-b オプションなどがあります。

なお、使用しているGoogle Cloud Vision APIは月一定回数までは無料(2018/07 時点で最初の1000回は無料)ですが、-k オプションでのGoogle APIキーの指定は必須になります。APIキーを発行するときに、後で泣かないよう、適切な課金上限額を設定しておきましょう。

Authenticating to the Cloud Vision API  |  Cloud Vision API Documentation  |  Google Cloud

API キーの使用  |  ドキュメント  |  Google Cloud

イメージ

対象画像

f:id:ruby-U:20180713073812p:plain

クリップボード結果

f:id:ruby-U:20180713073741p:plain

HTMLを開いたところ

f:id:ruby-U:20180713074121p:plain

スクリーンキャプチャツールとの組み合わせ

SnapCrab www.fenrir-inc.com

GreenShot gigazine.net

などのツールの出力先を、cOCRの監視するディレクトリ以下に設定するだけです。あとはお好みに合わせて、スクリーンキャプチャツール側のキーバインドを設定すれば、「1ボタンで自動OCR、結果をクリップボードに保存」みたいなシステムが完成します。

PDFへの変換

cOCRは、Google Cloud Vision APIからのレスポンスをまるっとまるごとjsonファイルとして保存してるので、以下のツールとの組み合わせで、画像ファイルをOCRされたPDFに変換することも可能です。

github.com

github.com

Anki 2.1.0 (beta) で H.264 や MP3 の再生を可能にする方法

Anki のブラウザコンポーネントについて

まだ正式にリリースされていませんが、Anki 2.1.0 で Qt のバージョンが更新され、またブラウザ機能を提供するコンポーネントとして新しく QtWebEngine が導入されました。 Anki 2.1.0 beta42 では Qt, QtWebEngine のバージョンは 5.9.2 です。

Qt 5.9.2 Change Files - Qt Wiki

changes-5.9.2\dist - qt/qtwebengine.git - Qt WebEngine

 - Chromium Snapshot:
   * Security fixes from Chromium up to version 61.0.3163.79
    Including: CVE-2017-5092, CVE-2017-5093, CVE-2017-5095, CVE-2017-5097,
        CVE-2017-5099, CVE-2017-5102, CVE-2017-5103, CVE-2017-5107,
        CVE-2017-5112, CVE-2017-5114, CVE-2017-5117 and CVE-2017-5

とあるので、QtWebEngine 5.9.2 は Chromium 61 相当でしょうか。

一方で、Anki 2.0.x が使用しているブラウザコンポーネントは QtWebKit 2.2 で、これは 2011 年のリリース…。

QtWebKit 2.2.0 is released! - Qt Blog

QtWebKitFeatures22 – WebKit

QtWebKitRelease22 – WebKit

考えるのも嫌になるぐらい大昔の化石ですね。もう Anki 2.0.x のことは忘れてしまいましょう。

さて、Anki を最新のものにアップデートすれば、大抵のデッキはまず問題なくそのまま動作するのですが、以前から例えば Android 向けの実装である AnkiDroid では、より新しいブラウザコンポーネントが使用できていました。 極めて稀だとは思いますが、このような環境向けに、HTML5 Media API を使用する(Anki の [sound: xxx.mp3] タグではなく、JavaScriptでaudio/videoを直接操作している)デッキを作成し、運用している場合。または新しくこのようなデッキを導入する場合、最新のデスクトップ向け Anki でも、問題が生じるかもしれません。以下のような制限があるためです。

Qt WebEngine supports the MPEG-4 Part 14 (MP4) file format only if the required proprietary audio and video codecs, such as H.264 and MPEG layer-3 (MP3), have been enabled.

Qt WebEngine Features | Qt WebEngine 5.9

特定のメディア(H.264, MP3 など)について、ライセンス上の制限のため、デフォルトでは再生することができません。この問題を回避するには、-proprietary-codecs オプションを付加してコンパイルした QtWebEngine が必要です。

コンパイルしたものはこちら。商用利用にはライセンスが必要です

コンパイル

環境

>perl -v

This is perl 5, version 26, subversion 2 (v5.26.2) built for MSWin32-x64-multi-thread

~略~

>python --version
Python 2.7.13

ソース

http://download.qt.io/archive/qt/5.9/5.9.2/single/ から http://download.qt.io/archive/qt/5.9/5.9.2/single/qt-everywhere-opensource-src-5.9.2.tar.xz を拾ってきます。

依存ツール

手順

ソースを解凍後、生成された qtwebengine ディレクトリに、依存ツール(bison, flex, gperf)を放り込みます。 開発者コマンドプロンプト for VS2015 で qtwebengine を開き、-proprietary-codecs オプションを付けて qmake を実行します。結果は以下のようなものです。

C:\Qt\qt-everywhere-opensource-src-5.9.2\qtwebengine>"C:\Qt\5.9.2\msvc2015\bin\qmake.exe" -- -proprietary-codecs

Running configuration tests...
Done running configuration tests.

Configure summary:

Qt WebEngine:
  Embedded build ......................... no
  Pepper Plugins ......................... yes
  Printing and PDF ....................... yes
  Proprietary Codecs ..................... yes
  Spellchecker ........................... yes
  WebRTC ................................. yes
  Using system ninja ..................... no

Qt is now configured for building. Just run 'nmake'.
Once everything is built, Qt is installed.
You should NOT run 'nmake install'.
Note that this build cannot be deployed to other machines or devices.

Prior to reconfiguration, make sure you remove any leftovers from
the previous build.

Proprietary Codecs ..................... yes になっていることを確認してください。確認できたら nmake するだけです。数時間以上かかります。気長に待ちましょう。

Anki コンポーネントの上書き

C:\Program Files (x86)\AnkiQt5WebEngineCore.dll などがありますので、これらを成果物に置き換えます。

以上です。お疲れ様でした。

マウスジェスチャーツール Crevice 3.2.184 をリリースしました

カッコいいロゴを頂いたのでテンション上がってそのまま18時間コーディングしたらメジャーバージョンが1つ上がっていました。

f:id:ruby-U:20171223133526p:plain

どうですか、超クールなのでは?? イエーイ🎉🎉

Crevice 3.2.184 リリースノート

1月にリリースしたCrevice 2.5 から Crevice 3 へメジャーバージョンが1つ上がり、いくつかの大きな変更が加わっています。

  • 新しいクールなアイコン! アプリケーションの外観をブラッシュアップしました。

  • ユーザースクリプトのキャッシュ機構を追加。Crevice 2.5 と比較して起動速度が最大で50倍高速になりました。

  • コマンドラインインターフェイスの追加。ユーザースクリプトの場所、タスクトレイアイコン非表示、プロセス優先度、詳細なログ出力などの設定を引数として指定できるようになりました。

特に頑張ったところは起動速度の高速化です。CreviceではユーザースクリプトC# Scriptを使っていて、Crevice 2.5 ではRoslynで毎回コンパイルしていたのですが、このリソースが無駄だし起動時間は遅くなるしいいことなしだったので、ユーザースクリプトコンパイルした後のアセンブリをうまいことキャッシュできないかなーと試行錯誤してたら偶然うまく行きました。雰囲気でやっている。

技術資料

以下、できたてホヤホヤのソースから抜粋して、Roslynでユルい C# Script をコンパイルして、アセンブリからそれを評価する方法を書いておきます。アセンブリに GetType して、 GetMethod して Invoke という資料はよく見かけますが、 C# Script 特有のトップレベルに変数がくるようなコードでうまくそれをやる方法が見当たらなかったので貴重な資料だと思います(ホントか?)

Crevice 2 の実装(ユルいC# Script にglobalsを与えて評価する)

ユーザースクリプトから Script を作ります。

private Script ParseScript(string userScript)
{
    var stopwatch = new Stopwatch();
    Verbose.Print("Parsing UserScript...");
    stopwatch.Start();
    var script = CSharpScript.Create(
        userScript,
        ScriptOptions.Default
            .WithSourceResolver(ScriptSourceResolver.Default.WithBaseDirectory(UserDirectory))
            .WithMetadataResolver(ScriptMetadataResolver.Default.WithBaseDirectory(UserDirectory))
            .WithReferences("microlib")                   // microlib.dll
            .WithReferences("System")                     // System.dll
            .WithReferences("System.Core")                // System.Core.dll
            .WithReferences("Microsoft.CSharp")           // Microsoft.CSharp.dll
            .WithReferences(Assembly.GetEntryAssembly()), // CreviceApp.exe
                                                          // todo dynamic type
        globalsType: typeof(Core.UserScriptExecutionContext));
    stopwatch.Stop();
    Verbose.Print("UserScript parse finished. ({0})", stopwatch.Elapsed);
    return script;
}

そしてこの Script にコンテキスト ctx を globals として与えて評価します。ctxLeftButton, MiddleButton のような設定のためのトークン、 ジェスチャー定義に関する @when 関数や、Config のようなインスタンスTooltip, Balloon のようなユーティリティ関数などをメンバーとして持ち、それを globals として与えることで、ユーザースクリプト評価時のコンテキストとしています。

public class UserScriptExecutionContext
{
    public readonly DSL.Def.LeftButton   LeftButton   = DSL.Def.Constant.LeftButton;
    public readonly DSL.Def.MiddleButton MiddleButton = DSL.Def.Constant.MiddleButton;
// 略
    public Config.UserConfig Config
    {
      get { return Global.UserConfig; }
    }

    public DSL.WhenElement @when(DSL.Def.WhenFunc func)
    {
        return root.@when(func);
    }

    public void Tooltip(string text)
    {
        Tooltip(text, Global.UserConfig.UI.TooltipPositionBinding(WinAPI.Window.Window.GetPhysicalCursorPos()));
    }

    public void Balloon(string text)
    {
        Balloon(text, Global.UserConfig.UI.BalloonTimeout);
    }
// 略
}
private IEnumerable<Core.GestureDefinition> EvaluateUserScript(Script userScript, Core.UserScriptExecutionContext ctx)
{
    var stopwatch = new Stopwatch();
    Verbose.Print("Compiling UserScript...");
    stopwatch.Start();
    var diagnotstics = userScript.Compile();
    stopwatch.Stop();
    Verbose.Print("UserScript compilation finished. ({ 0})", stopwatch.Elapsed);
    foreach (var dg in diagnotstics.Select((v, i) => new { v, i }))
    {
        Verbose.Print("Diagnotstics[{0}]: {1}", dg.i, dg.v.ToString());
    }
    Verbose.Print("Evaluating UserScript...");
    stopwatch.Restart();
    userScript.RunAsync(ctx).Wait();
    stopwatch.Stop();
    Verbose.Print("UserScript evaluation finished. ({0})", stopwatch.Elapsed);
    return ctx.GetGestureDefinition();
}

評価後 ctx にはマウスジェスチャーの設定が保存されています。

Crevice 3 の実装(ユルい C# Script から生成したアセンブリをロードして globals を与えて評価する)

ユーザースクリプトから Script を作ります。

private Script ParseScript(string userScript)
{
    var stopwatch = new Stopwatch();
    Verbose.Print("Parsing UserScript...");
    stopwatch.Start();
    var script = CSharpScript.Create(
        userScript,
        ScriptOptions.Default
            .WithSourceResolver(ScriptSourceResolver.Default.WithBaseDirectory(UserDirectory))
            .WithMetadataResolver(ScriptMetadataResolver.Default.WithBaseDirectory(UserDirectory))
            .WithReferences("microlib")                   // microlib.dll
            .WithReferences("System")                     // System.dll
            .WithReferences("System.Core")                // System.Core.dll
            .WithReferences("Microsoft.CSharp")           // Microsoft.CSharp.dll
            .WithReferences(Assembly.GetEntryAssembly()), // CreviceApp.exe
                                                          // todo dynamic type
        globalsType: typeof(Core.UserScriptExecutionContext));
    stopwatch.Stop();
    Verbose.Print("UserScript parse finished. ({0})", stopwatch.Elapsed);
    return script;
}

ここまでは以前の実装と同じです。次にアセンブリのバイナリを作ります。次回起動時にアプリケーションのバージョンやユーザースクリプトに変更がなければ、これをキャッシュとして使うことで起動時間を大幅に短縮することができます。

private UserScriptAssembly.Cache CompileUserScript(UserScriptAssembly usa, string userScriptCode, Script userScript)
{
    var stopwatch = new Stopwatch();
    Verbose.Print("Compiling UserScript...");
    stopwatch.Start();
    var compilation = userScript.GetCompilation();
    stopwatch.Stop();
    Verbose.Print("UserScript compilation finished. ({0})", stopwatch.Elapsed);

    var peStream = new MemoryStream();
    var pdbStream = new MemoryStream();
    Verbose.Print("Genarating UserScriptAssembly...");
    stopwatch.Restart();
    compilation.Emit(peStream, pdbStream);
    stopwatch.Stop();
    Verbose.Print("UserScriptAssembly generation finished. ({0})", stopwatch.Elapsed);
    return usa.CreateCache(userScriptCode, peStream.GetBuffer(), pdbStream.GetBuffer());
}

キャッシュをロードして、以前と同様にコンテキスト ctx を globals として与えて評価します。アセンブリに GetType("Submission#0") して、 GetMethod("") して得られた関数を Invoke するのですが、この時に ctxnew object[] { new object[] { ctx, null } } のような形で2番めの引数として渡すのがポイントです。

private IEnumerable<Core.GestureDefinition> EvaluateUserScriptAssembly(UserScriptAssembly.Cache cache, Core.UserScriptExecutionContext ctx)
{
    var stopwatch = new Stopwatch();
    Verbose.Print("Loading UserScriptAssembly...");
    stopwatch.Start();
    var assembly = Assembly.Load(cache.pe, cache.pdb);
    stopwatch.Stop();
    Verbose.Print("UserScriptAssembly loading finished. ({0})", stopwatch.Elapsed);
    Verbose.Print("Evaluating UserScriptAssembly...");
    stopwatch.Restart();
    var type = assembly.GetType("Submission#0");
    var factory = type.GetMethod("<Factory>");
    var parameters = new object[] { new object[] { ctx, null } };
    var result = factory.Invoke(null, parameters);
    stopwatch.Stop();
    Verbose.Print("UserScriptAssembly evaluation finished. ({0})", stopwatch.Elapsed);
    return ctx.GetGestureDefinition();
}

評価後 ctx にはマウスジェスチャーの設定が保存されています。

仕組み的に Crevice 2 と Crevice 3 でのユーザースクリプトの実行は全く同様の結果になる…はずなのですが、いまいち確信が持てないので失敗時にはフォールバックするようには実装しています。

private IEnumerable<Core.GestureDefinition> GetGestureDef(Core.UserScriptExecutionContext ctx)
{
    var userScriptCode = GetUserScriptCode();
    var userScript = ParseScript(userScriptCode);
    if (!Global.CLIOption.NoCache)
    {
        try
        {
            var cache = GetUserScriptAssemblyCache(userScriptCode, userScript);
            return EvaluateUserScriptAssembly(cache, ctx);
        }
        catch (Exception ex)
        {
            Verbose.Print("Error occured when UserScript conpilation and save and restoration; fallback to the --nocache mode and continume");
            Verbose.Print(ex.ToString());
        }
    }
    return EvaluateUserScript(userScript, ctx);
}

うまく動かないよーという方がもしいらっしゃいましたらご連絡ください。

DisplayPortディスプレイには妖精が住んでいる(Windowsのディスプレイ構成をプロファイルごとに切り替える)

以前からWindowsのディスプレイ周りには困惑していて、特にDisplayPort(以下DP)が絡んだ場合には、DPの電源オフ時にはデバイス自体がOS上でオフ扱いになるという仕様からほんとにもうどうしようもない挙動をします。ディスプレイがオフ扱いになる、あるいは復帰するタイミング、またはデフォルトのモニター解像度の存在などが渾然一体となり、休憩から帰った後にウィンドウが元の位置にあることは決して保証されません。そのサイズすらもです。

この問題の対処法はいろいろあります。とりあえず簡単に紹介します。

DPディスプレイの電源を切らずに運用する

シンプルに因果律から導き出されるアイディアです。しばらく放っておくとDPディスプレイはスタンバイ状態に入り、この状態への移行、この状態からの復帰では特に問題は発生しません。 この解決策は良さそうに見えますが、席を立った後、あなたのディスプレイがまだ光を放っていることに気づいた親切な同僚の存在について考察してみましょう。

プログラムから即座にディスプレイをスタンバイ状態に移行する方法を以下に紹介します。あなたのディスプレイのインジケーターがスリープ時にモンスターたちを惹きつけないことを祈ります。

gist9167e2b52ab8033414fd70357079070e

モニターの解像度に関するレジストリ値を変更する

モニターを省電力から復帰させると解像度やウィンドウのサイズが小さくなるのを解決する : プログ 塵の雨日記: DisplayPort接続時のディスプレイのオンオフによるウィンドウの再配置について PC用4Kモニタでウインドウが小さく左上に偏る問題をレジストリ編集で解決

多くの方の直った!という声が寄せられていますが、この方法はマルチディスプレイ環境でウィンドウが別ウインドウに移動してしまう問題については効果がありません。

DPケーブルをハックする

DisplayPortの切断を回避するアダプター - Qiita DisplayPort接続でモニターの電源を切ると認識が外れるのを何とかできる? - Aquablews_apps.dump()

私はこの方法を試していませんが、コストと互換性問題について納得できれば試す価値はあるでしょう。

ウィンドウ位置を管理するツールを使う

あるディスプレイにウィンドウを集めるもの、ウィンドウの状態を保存・復元するものなどいろいろありますが、多くのツールが、異なるDPIを含むマルチディスプレイ環境で正常に動かず、ひとまず断念しました。いいツールがあったら教えてください。

ディスプレイ構成をプロファイルごとに切り替える

試行錯誤の後、最終的にこの方法にたどり着きました。要するに、悪いDPディスプレイは能動的に取り外して、DPディスプレイがオフ扱いになるタイミングを制御しようというものです。前述のレジストリ値を使う方法と組み合わせて、この問題を多少改善することができます。

1. Display Changer IIでWindowsのディスプレイ構成を保存・復元する

Display Changer II « 12noon

例えば以下のコマンドでProfile1.xmlにディスプレイの設定を保存し、復元することができます。

dc2.exe -create="Profile1.xml"
dc2.exe -configure="Profile1.xml"

このxmlファイルはその時点でのWindowsのディスプレイの設定を保存したものなので、グラフィックドライバの更新などでデバイス名などに大きな変更が加わった場合はその都度作り直す必要があります。

2. コマンドをタスクとして登録する

dc2.exe -configure="Profile1.xml" のようなコマンドを管理者権限のタスクとして、タスクスケジューラに登録しておきます。こうすることで、通常権限のAutoHotKeyアプリケーションからコマンドを実行できます。 ここでは例として、SwitchDisplayProfile1, SwitchDisplayProfile2, SwitchDisplayProfile3の3つのプロファイルを作成しました。

3. AutoHotKeyからディスプレイのプロファイルを切り替える

AutoHotKeyスクリプトには前述のDisplayStandBy.exeと管理者権限タスクSwitchDisplayProfile1, SwitchDisplayProfile2, SwitchDisplayProfile3を起動するフック、Ctrl+Shift+Alt+F9, Ctrl+Shift+Alt+F10, Ctrl+Shift+Alt+F11, Ctrl+Shift+Alt+F12を記述しました。

gist869012765c79d16d2604292c890c5f54

Amazonの過去の売れ筋商品データ(月間)を取得する

Amazonの商品コードを大量に集める必要があり、いろいろ方法を調べていると、次のようなものを見つけた。なかなか有用だと思うので取得方法を纏めておく。

過去の売れ筋商品データ(月間)

f:id:ruby-U:20171111085507p:plain

対象年月と商品カテゴリを選択してボタンを押せばデータへのリンクが表示される。

f:id:ruby-U:20171111085512p:plain

データのURLは固定されているようで、認証なども必要ない。好きなように取ってくればよさそうだ。

中身は普通のTSVで、1行のヘッダあり。なお、これは2011-01の本カテゴリのデータ。懐かしい…。 f:id:ruby-U:20171111090205p:plain

2011-01から2017-09までのデータを集めたものをダウンロードできるようにしておくので、面倒な人はこちら(Google Drive)

なお、注意点として、カメラカテゴリの他にも一部データに抜けがある。DVDカテゴリの2011-11, 2011-12など。恐らく元データが存在しない。

以下のスクリプトで作成した。

gist.github.com

GPUが使えて仮想環境を認識するJupyter環境のDockerfileを書いた

Jupyterが立ち上げるだけでプログラミング環境ができあがる各種の便利なDockerイメージを出してくれているが、GPUが使えない。ので、無理やり使えるようにしてみた。 Dockerfileの継承元のFROMを書き換える必要があったので強引な感じになった。 以下ではcudaのDockerイメージを元にしているが、jupyterを元にcudaをインストールしたほうが完了までの時間が短いはず。

なお、TensorflowがGPUの使えるJupyter入りのDockerイメージをリリースしてくれている。なので、Jupyter本家に拘らなければこんなことをする必要はほぼない…が、まあせっかく書いたので。

適当に継ぎ接ぎしてDockerfileを作る

mkdir ~/Documents/cuda-based-jupyter-datascience-notebook
cd ~/Documents/cuda-based-jupyter-datascience-notebook

wget https://raw.githubusercontent.com/jupyter/docker-stacks/master/base-notebook/Dockerfile -O base-notebook
wget https://raw.githubusercontent.com/jupyter/docker-stacks/master/minimal-notebook/Dockerfile -O minimal-notebook
wget https://raw.githubusercontent.com/jupyter/docker-stacks/master/scipy-notebook/Dockerfile -O scipy-notebook
wget https://raw.githubusercontent.com/jupyter/docker-stacks/master/datascience-notebook/Dockerfile -O datascience-notebook

cat << "EOF" > Dockerfile
# Merging nvidia/cuda and jupyter/datascience-notebook DockerFiles.
FROM nvidia/cuda:8.0-cudnn6-devel-ubuntu16.04

MAINTAINER rubyu

EOF
cat << "EOF" >> Dockerfile

# jupter/base-notebook
EOF
sed -e '1,8 s/^/# /g' base-notebook >> Dockerfile
cat << "EOF" >> Dockerfile

# jupter/minimal-notebook
EOF
sed -e '1,6 s/^/# /g' minimal-notebook >> Dockerfile
cat << "EOF" >> Dockerfile

# jupter/scipy-notebook
EOF
sed -e '1,5 s/^/# /g' scipy-notebook >> Dockerfile
cat << "EOF" >> Dockerfile

# jupter/datascience-notebook
EOF
sed -e '1,5 s/^/# /g' datascience-notebook >> Dockerfile
cat << "EOF" >> Dockerfile

USER $NB_USER

RUN jupyter notebook --generate-config && \
    jupyter serverextension enable --py jupyterlab --sys-prefix && \
    pip install environment_kernels && \
    echo "c.NotebookApp.kernel_spec_manager_class = 'environment_kernels.EnvironmentKernelSpecManager'" >> ~/.jupyter/jupyter_notebook_config.py
EOF

最後のRUNの部分がJupyter Labとconda createで作る仮想環境のための設定。

Dockerイメージ作成に必要なファイルをダウンロードしておく

wget https://github.com/jupyter/docker-stacks/raw/master/base-notebook/fix-permissions -O fix-permissions
wget https://github.com/jupyter/docker-stacks/raw/master/base-notebook/jupyter_notebook_config.py -O jupyter_notebook_config.py
wget https://github.com/jupyter/docker-stacks/raw/master/base-notebook/start-notebook.sh -O start-notebook.sh
wget https://github.com/jupyter/docker-stacks/raw/master/base-notebook/start-singleuser.sh -O start-singleuser.sh
wget https://github.com/jupyter/docker-stacks/raw/master/base-notebook/start.sh -O start.sh
sudo chmod ugo+x fix-permissions
sudo chmod ugo+x jupyter_notebook_config.py
sudo chmod ugo+x start-notebook.sh
sudo chmod ugo+x start-singleuser.sh
sudo chmod ugo+x start.sh

実行できる権限がないとダメ。ここではwgetしているが、普通はgit cloneするのでハマることはないはず。

コンテナを立ち上げる

sudo nvidia-docker build -t rubyu/datascience-notebook-gpu:1.0 .

sudo nvidia-docker run -d --name notebook-gpu -p 8888:8888 --restart=always -v /storage/samba:/home/jovyan rubyu/datascience-notebook-gpu:1.0 start.sh jupyter lab --NotebookApp.token='XXXX'

これで8888ポートでJupyter Labが起動している。

本題には関係ない話ですが、Jupyterのhome(/home/jovyan)をまるごとホストのディレクトリに割り当てて、かつそれをSamba経由でアクセスできるようにするのが好きです。Windowsで処理する必要がある時にいちいち転送する必要がなくなるので。なんやかんやでGUIWindowsが使いやすい(個人の感想です)

アタッチしてconda createで仮想環境を作る

docker exec -it notebook-gpu /bin/bash

conda create -n your-new-envronment python=3.5 jupyter tensorflow-gpu && conda clean -tipsy

作成された仮想環境はJupyterから見えて、カーネルとして選択することができる。

Dockerfile

# Merging nvidia/cuda and jupyter/datascience-notebook DockerFiles.
FROM nvidia/cuda:8.0-cudnn6-devel-ubuntu16.04

MAINTAINER rubyu


# jupter/base-notebook
# # Copyright (c) Jupyter Development Team.
# # Distributed under the terms of the Modified BSD License.
# 
# # Ubuntu 16.04 (xenial) from 2017-07-23
# # https://github.com/docker-library/official-images/commit/0ea9b38b835ffb656c497783321632ec7f87b60c
# FROM ubuntu@sha256:84c334414e2bfdcae99509a6add166bbb4fa4041dc3fa6af08046a66fed3005f
# 
# MAINTAINER Jupyter Project <jupyter@googlegroups.com>

USER root

# Install all OS dependencies for notebook server that starts but lacks all
# features (e.g., download as all possible file formats)
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update && apt-get -yq dist-upgrade \
 && apt-get install -yq --no-install-recommends \
    wget \
    bzip2 \
    ca-certificates \
    sudo \
    locales \
    fonts-liberation \
 && apt-get clean \
 && rm -rf /var/lib/apt/lists/*

RUN echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && \
    locale-gen

# Install Tini
RUN wget --quiet https://github.com/krallin/tini/releases/download/v0.10.0/tini && \
    echo "1361527f39190a7338a0b434bd8c88ff7233ce7b9a4876f3315c22fce7eca1b0 *tini" | sha256sum -c - && \
    mv tini /usr/local/bin/tini && \
    chmod +x /usr/local/bin/tini

# Configure environment
ENV CONDA_DIR=/opt/conda \
    SHELL=/bin/bash \
    NB_USER=jovyan \
    NB_UID=1000 \
    NB_GID=100 \
    LC_ALL=en_US.UTF-8 \
    LANG=en_US.UTF-8 \
    LANGUAGE=en_US.UTF-8
ENV PATH=$CONDA_DIR/bin:$PATH \
    HOME=/home/$NB_USER

ADD fix-permissions /usr/local/bin/fix-permissions
# Create jovyan user with UID=1000 and in the 'users' group
# and make sure these dirs are writable by the `users` group.
RUN useradd -m -s /bin/bash -N -u $NB_UID $NB_USER && \
    mkdir -p $CONDA_DIR && \
    chown $NB_USER:$NB_GID $CONDA_DIR && \
    fix-permissions $HOME && \
    fix-permissions $CONDA_DIR

USER $NB_USER

# Setup work directory for backward-compatibility
RUN mkdir /home/$NB_USER/work && \
    fix-permissions /home/$NB_USER

# Install conda as jovyan and check the md5 sum provided on the download site
ENV MINICONDA_VERSION 4.3.21
RUN cd /tmp && \
    wget --quiet https://repo.continuum.io/miniconda/Miniconda3-${MINICONDA_VERSION}-Linux-x86_64.sh && \
    echo "c1c15d3baba15bf50293ae963abef853 *Miniconda3-${MINICONDA_VERSION}-Linux-x86_64.sh" | md5sum -c - && \
    /bin/bash Miniconda3-${MINICONDA_VERSION}-Linux-x86_64.sh -f -b -p $CONDA_DIR && \
    rm Miniconda3-${MINICONDA_VERSION}-Linux-x86_64.sh && \
    $CONDA_DIR/bin/conda config --system --prepend channels conda-forge && \
    $CONDA_DIR/bin/conda config --system --set auto_update_conda false && \
    $CONDA_DIR/bin/conda config --system --set show_channel_urls true && \
    $CONDA_DIR/bin/conda update --all --quiet --yes && \
    conda clean -tipsy && \
    fix-permissions $CONDA_DIR

# Install Jupyter Notebook and Hub
RUN conda install --quiet --yes \
    'notebook=5.2.*' \
    'jupyterhub=0.8.*' \
    'jupyterlab=0.28.*' \
    && conda clean -tipsy && \
    fix-permissions $CONDA_DIR

USER root

EXPOSE 8888
WORKDIR $HOME

# Configure container startup
ENTRYPOINT ["tini", "--"]
CMD ["start-notebook.sh"]

# Add local files as late as possible to avoid cache busting
COPY start.sh /usr/local/bin/
COPY start-notebook.sh /usr/local/bin/
COPY start-singleuser.sh /usr/local/bin/
COPY jupyter_notebook_config.py /etc/jupyter/
RUN fix-permissions /etc/jupyter/

# Switch back to jovyan to avoid accidental container runs as root
USER $NB_USER

# jupter/minimal-notebook
# # Copyright (c) Jupyter Development Team.
# # Distributed under the terms of the Modified BSD License.
# 
# FROM jupyter/base-notebook
# 
# MAINTAINER Jupyter Project <jupyter@googlegroups.com>

USER root

# Install all OS dependencies for fully functional notebook server
RUN apt-get update && apt-get install -yq --no-install-recommends \
    build-essential \
    emacs \
    git \
    inkscape \
    jed \
    libsm6 \
    libxext-dev \
    libxrender1 \
    lmodern \
    pandoc \
    python-dev \
    texlive-fonts-extra \
    texlive-fonts-recommended \
    texlive-generic-recommended \
    texlive-latex-base \
    texlive-latex-extra \
    texlive-xetex \
    vim \
    unzip \
    && apt-get clean && \
    rm -rf /var/lib/apt/lists/*

# Switch back to jovyan to avoid accidental container runs as root
USER $NB_USER

# jupter/scipy-notebook
# # Copyright (c) Jupyter Development Team.
# # Distributed under the terms of the Modified BSD License.
# FROM jupyter/minimal-notebook
# 
# MAINTAINER Jupyter Project <jupyter@googlegroups.com>

USER root

# libav-tools for matplotlib anim
RUN apt-get update && \
    apt-get install -y --no-install-recommends libav-tools && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

USER $NB_USER

# Install Python 3 packages
# Remove pyqt and qt pulled in for matplotlib since we're only ever going to
# use notebook-friendly backends in these images
RUN conda install --quiet --yes \
    'nomkl' \
    'ipywidgets=7.0*' \
    'pandas=0.19*' \
    'numexpr=2.6*' \
    'matplotlib=2.0*' \
    'scipy=0.19*' \
    'seaborn=0.7*' \
    'scikit-learn=0.18*' \
    'scikit-image=0.12*' \
    'sympy=1.0*' \
    'cython=0.25*' \
    'patsy=0.4*' \
    'statsmodels=0.8*' \
    'cloudpickle=0.2*' \
    'dill=0.2*' \
    'numba=0.31*' \
    'bokeh=0.12*' \
    'sqlalchemy=1.1*' \
    'hdf5=1.8.17' \
    'h5py=2.6*' \
    'vincent=0.4.*' \
    'beautifulsoup4=4.5.*' \
    'protobuf=3.*' \
    'xlrd'  && \
    conda remove --quiet --yes --force qt pyqt && \
    conda clean -tipsy && \
    # Activate ipywidgets extension in the environment that runs the notebook server
    jupyter nbextension enable --py widgetsnbextension --sys-prefix && \
    fix-permissions $CONDA_DIR

# Install facets which does not have a pip or conda package at the moment
RUN cd /tmp && \
    git clone https://github.com/PAIR-code/facets.git && \
    cd facets && \
    jupyter nbextension install facets-dist/ --sys-prefix && \
    rm -rf facets && \
    fix-permissions $CONDA_DIR

# Import matplotlib the first time to build the font cache.
ENV XDG_CACHE_HOME /home/$NB_USER/.cache/
RUN MPLBACKEND=Agg python -c "import matplotlib.pyplot" && \
    fix-permissions /home/$NB_USER

USER $NB_USER

# jupter/datascience-notebook
# # Copyright (c) Jupyter Development Team.
# # Distributed under the terms of the Modified BSD License.
# FROM jupyter/scipy-notebook
# 
# MAINTAINER Jupyter Project <jupyter@googlegroups.com>

USER root

# R pre-requisites
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
    fonts-dejavu \
    gfortran \
    gcc && apt-get clean && \
    rm -rf /var/lib/apt/lists/*

# Julia dependencies
# install Julia packages in /opt/julia instead of $HOME
ENV JULIA_PKGDIR=/opt/julia

RUN . /etc/os-release && \
    echo "deb http://ppa.launchpad.net/staticfloat/juliareleases/ubuntu $VERSION_CODENAME main" > /etc/apt/sources.list.d/julia.list && \
    apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3D3D3ACC && \
    apt-get update && \
    apt-get install -y --no-install-recommends \
    julia && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/* && \
    # Show Julia where conda libraries are \
    echo "push!(Libdl.DL_LOAD_PATH, \"$CONDA_DIR/lib\")" >> /usr/etc/julia/juliarc.jl && \
    # Create JULIA_PKGDIR \
    mkdir $JULIA_PKGDIR && \
    chown $NB_USER $JULIA_PKGDIR && \
    fix-permissions $JULIA_PKGDIR

USER $NB_USER

# R packages including IRKernel which gets installed globally.
RUN conda config --system --add channels r && \
    conda install --quiet --yes \
    'rpy2=2.8*' \
    'r-base=3.3.2' \
    'r-irkernel=0.7*' \
    'r-plyr=1.8*' \
    'r-devtools=1.12*' \
    'r-tidyverse=1.0*' \
    'r-shiny=0.14*' \
    'r-rmarkdown=1.2*' \
    'r-forecast=7.3*' \
    'r-rsqlite=1.1*' \
    'r-reshape2=1.4*' \
    'r-nycflights13=0.2*' \
    'r-caret=6.0*' \
    'r-rcurl=1.95*' \
    'r-crayon=1.3*' \
    'r-randomforest=4.6*' && \
    conda clean -tipsy && \
    fix-permissions $CONDA_DIR

# Add Julia packages
# Install IJulia as jovyan and then move the kernelspec out
# to the system share location. Avoids problems with runtime UID change not
# taking effect properly on the .local folder in the jovyan home dir.
RUN julia -e 'Pkg.init()' && \
    julia -e 'Pkg.update()' && \
    julia -e 'Pkg.add("HDF5")' && \
    julia -e 'Pkg.add("Gadfly")' && \
    julia -e 'Pkg.add("RDatasets")' && \
    julia -e 'Pkg.add("IJulia")' && \
    # Precompile Julia packages \
    julia -e 'using HDF5' && \
    julia -e 'using Gadfly' && \
    julia -e 'using RDatasets' && \
    julia -e 'using IJulia' && \
    # move kernelspec out of home \
    mv $HOME/.local/share/jupyter/kernels/julia* $CONDA_DIR/share/jupyter/kernels/ && \
    chmod -R go+rx $CONDA_DIR/share/jupyter && \
    rm -rf $HOME/.local && \
    fix-permissions $JULIA_PKGDIR $CONDA_DIR/share/jupyter


USER $NB_USER

RUN jupyter notebook --generate-config && \
    jupyter serverextension enable --py jupyterlab --sys-prefix && \
    pip install environment_kernels && \
    echo "c.NotebookApp.kernel_spec_manager_class = 'environment_kernels.EnvironmentKernelSpecManager'" >> ~/.jupyter/jupyter_notebook_config.py

Logicoolマウスの敏感なホイールをソフトウェアでどうにかする

小型かつ、標準でL, M, R, W系x4, X1, X2に割り当てがあるLogicool m546をポチったんですが、ホイールが敏感すぎて、美少女紙芝居ゲーで遊ぶのと、ホイールでのマウスジェスチャが辛かったので対策しました。ブラウジングにはいいんですが。もうちょっとホイールのカチカチ感が強いといいバランスかもしれません。

var VWManager = new VerticalWheelManager(
    400, // Duration in milliseconds for suppression of wheel events given after once it have emitted.
    6    // Max number of the wheel events which will be suppressed in the duration for suppression.
);
@do((ctx) =>
{
    if (VWManager.Up.Check())
    {
        // do something
    }
}

VerticalWheelManagerのコンストラクタに渡す値は「連続するホイールメッセージを無効にする期間(ミリ秒)」, 「その期間内でも一定数以上連続すれば有効にする」です。あとはVerticalWheelManager.Up.Check()VerticalWheelManager.Down.Check()を叩けば、truefalseの判定の結果が返ります。

以下、マウスジェスチャツール CreviceAppの設定ファイルサンプルです。M325, M545, M546あたりの軽いホイール。または高速スクロール対応機種にも便利かもしれません。

gist6de4bf1c5f7d936bc2df2873173dec65

このぐらいの処理をマウスジェスチャツール本体ではなく、設定側でどうにかできるというのはなかなか便利です。