端の知識の備忘録

技術メモになりきれない、なにものか達の供養先

【Python, 画像処理, napari】Pythonの画像ビューワ napariを使って動画のアノテーションツールを作ってみる

概要

動画から機械学習モデルを作るとき、一番時間を食うのがアノテーションではないでしょうか。

何枚か画像を抜き出してラベリングをするのですが、動きの速い動物の動画などだと前後のフレームを見比べないとその時どんな行動しているのかがわからなかったりする。

でもって、ラベリングツールとして有名ドコロだとMicrosoft謹製のVoTTやAWSのGroundTruthなどがあるかと思いますが、いずれもバウンディングボックスのアノテーションがメインで、意外にも単純な「フレームごとに何をしているのか、何が写っているか」みたいなシーン・フレームに対するアノテーションを動画で行うツールというのは私の知っている限りはない(誰か知っていたら教えてほしい)。

無いなら作るしか無い!ということで、たまたまDeepLabCut関連で知ったPythonの画像ビューワライブラリであるnapariを使って、超単純な動画アノテーションツールを作ってみたというお話。

もう作ってから数ヶ月経ってしまいましたが、あまりnapariに関する日本語の記事がないのでメモ程度に書いておきます。

まとめ

  • コードはこちら

github.com

  • napariはPythonを知っている人なら気軽にカスタマイズできる、高速でスタック画像に強い画像ビューワ。

    • daskを使って分散的に画像処理を行っているなどかなりパフォーマンスに気を使って設計されており、Z-stackや大きな画像の操作もキビキビしているモダンなビューワ
    • 複数の画像をレイヤーとして扱うことができ多重染色画像のマージした表示が可能だったり、立体画像の3Dレンダリングができたり、Drawingができたりバニラでも多機能なビューワ
    • 類似のものとして過去の資産もあるので未だにImageJは優れたツールだが、napariは積極的に更新が行われておりかなり勢いがあるように感じる。
  • すでにnapariを使った様々な作例があるので、それらを参考にすれば色々と作れそう

また、napariに関する日本語記事として、次の記事がとても参考になると思います(実は私もたまたまこれを見て触発されたので今更ながらブログを書いていたり)。

qiita.com

注意事項

先にも書いたとおり、とても開発が活発かつまだα版であり、今書いた内容がいつまで通用するかわかりません。

また、APIの破壊的な変更が起こりうるので、最新情報は公式のGitHubやドキュメントを参考にしてください。

Home - napari

github.com

作ったもの

GitHubのReadmeに書いたとおりではあるのですが、要は動画から↓こんな感じのCSVを作るためのもの。

frame, behavior
1, behaviorA
2, behaviorA
3, behaviorA
4, behaviorA
..., ...
98, behaviorA
99, behaviorA
100, behaviorB
101, behaviorB
102, behaviorB
..., ...

操作イメージとしては、

  • config.yamlファイルに開きたい動画ファイルとアノテーションしたい行動の名前を書き、python behavior_annotator.pyで起動
  • 行動の開始フレームで'f'キーを押し、フラグを設定
  • 行動の終わりまでスライダーを動かし、 1または2キーを押すことで、フラグのあるところからスライダーのあるところまでを一気にアノテーション

を繰り返すものです。意外にもこういうアノテーションができるものが私の知っている限り無かったんですよね。動物の行動は連続して行われるので、1枚ずつチマチマ行動A、行動A、行動A……って分類していくのはしんどくて、行動開始地点からまとめて指定できるのが欲しい機能でした。

f:id:hashicco:20210804225919p:plain

最後までラベリングが終わったら0キーを押すことで、pandas.to_csvによって上記のようなCSVが作成されます。

また、作成時の工夫として、napari標準の機能で動画を読み込むとものによっては非常に重い(この問題は作者たちも認識しているようで、いずれ改善されるかもしれません)ので、別の方が作られているOpenCVを使ったnapari用の動画ローダーであるnapari-videoを使用しています。

github.com

コードを見てもらえればわかるのですが、napariを使うことでこんなリッチな画像ビューワのGUIが数行で立ち上がり、合計数十行で自分のほしい機能を実装した動画アノテーションツールを作ることができるのです!napariすごい。

まあ私の作例は全然しょぼいしnapariの機能の1%も使えていないのですが。誰かの何かの参考になれば幸いです。

田舎住みの引きこもりがPS5を買うまでの数ヶ月

概要

結論、田舎の引きこもりがPS5を手に入れるには、とにかく抽選に応募し続ける他ない。近所の家電量販店でもきっと入荷はしているのであろうが、もはや調べるのが面倒。

まだヨドバシがある都会住みならカード作って確実に手に入れる手段もあっただろうが、残念ながらそれもできず。

ひたすらにノジマひかりTVショッピング(とたまにあみあみ)の抽選に応募し続けること約半年、ついに先日のノジマの第10回の抽選販売にて、FF7 RemakeとのセットのPS5通常版を買うことができた。やったね!

少しでも当選確率を上げるためにPS5版のFF7 Remakeセットを狙い始めて2回目での当選でした。PS4で一回やったゲームをフルプライスで買うのは正直嬉しくないが、倍率低そうだしIntergradeやりたいしでのターゲット選定である。

延長保証の主張が強すぎるのと文字通りの兄弟分であるPCデポの悪行もあってあんまり好きではなかったノジマだが、誠実に人気商品を売ってくれるので最近は好印象。自作PCパーツも扱ってくれると嬉しいな!

ところでAmazonはさっさとボットに狩られるだけの定時販売をやめてほしい。そもそも平日の朝9時とかサラリーマンは仕事中で買えないし。

(21/10/18 追記)とか言っていたら10月くらいからAmazonもちゃんと抽選販売になりましたね。よかったよかった

PS5は大きい

今自宅にはPS3, PS4, PS5が揃い踏みしたということで、せっかくなので並べてみた。

f:id:hashicco:20210731142800j:plain

いや、PS5デカイな!PS3でもかなりPS4(スリム版)より大きいし重いと思っていたのだが、その倍近くあるだろうか。

まあ中に8core Zen2とRDNA2のGPUが入っていると考えればむしろコンパクトなのだろうか?電源ケーブルもメガネケーブルということは、中にそれらを動かすためのPSUもあるわけだし。

縦置き用のスタンドも付属するので、今回は縦置きにすることにした。下位互換品となったPS4は実家に送って、そろそろ限界に近いnasne専用機PS3をリプレースすることにしようと思う。

ともあれ、買えてよかった!まあ遊びたいゲームがまだあんまりないわけですが、とりあえずIntergradeやろう。

愚痴

新型ゲーム機を買うのに半年も応募し続けなければならないとは、なんだか嫌な時代になったものである。せめて普通にゲームをする人たちのもとに普通の値段で行き渡っているならば特に何も言うことはないのだが、転売屋に買い占められマージンを追加されてフリマサイトに並ぶさまはなんとも悲しい。

グラボ、CPU、プラモデルそしてゲーム機と、自分の趣味としている領分が荒らされているのは残念でならない。購入履歴を元にした優先販売などは新規の参入を阻む壁となり長期的に見るとコンテンツの寿命を縮めることにもなりかねないし、結局今の最善は抽選販売か受注生産なのだろうか。

転売問題を始めとした近年のなんとなく人同士の不和が漂う雰囲気を見るに、誰でも全世界に向けて意見を発信したり、今まで繋がりようの無かった人たちが繋がり合ったり、CtoCの商取引を行ったりがお手軽にできる環境が整ってしまうと、悲しいかなもはや人の良心に頼った運用というのは不可能な時代なのかもしれない。などとたかがゲーム機の購入一つを介してしんみりと考えてしまう。

Tensorflow-Directmlで`DXGI_ERROR_DEVICE_REMOVED`が出てしまうときの対処法

概要

前回もご紹介したTensorflow-Directmlに関するエラーについて。

この間久しぶりにTensorflow-Directmlを使ったところ、DXGI_ERROR_DEVICE_REMOVEDというエラーが出て学習前にコケてしまう事態に見舞われた。

Tensorflow-Directml の日本語記事はあまりないので、とりあえずメモ。

まとめ

  • 確認した環境は、RX6900XT, python3.7にtensorflow-directmlの各バージョン。

    • 1.15.5.dev210429, 1.15.5.dev210422, 1.15.4.dev201216, 1.15.3.dev200911 といろいろバージョンを試してみたが同じエラーが出てしまった。
  • 対処法は、DMLバイスを使う前に予め環境変数TF_DIRECTML_MAX_ALLOC_SIZEで割り当てるメモリの量を決めておくというもの。

python中で設定するなら、

import os
os.environ["TF_DIRECTML_MAX_ALLOC_SIZE"] = "8589934592" # ここの値はデバイスによって適切に書き換える。この例では8GB。

てな感じで環境変数をセットしてからtensorflow-directmlを使うとうまく動くことがわかりました。

多分メモリ確保の量を決定するプロセスでドライバのタイムアウトが起きてしまうために起こるのだと思う(完全なる憶測)。

なので、多分tensorflowのconfig.protoで使用メモリ量を予め決めておいてもこの問題は起きないのではないかと思う(完全なる憶測)。

参考URL

よくRadeonFF14をやってる人が遭遇する忌々しきDX11の致命的なエラーの対処法としてお馴染みの、 レジストリTdrDelayを調整するやり方もGitHubで提案されていましたが、残念ながらこれでは解決せず。

tensorflow-directml/Troubleshooting-Timeouts.md at directml · microsoft/tensorflow-directml · GitHub

結果的にこちらのページに書いてあるように、環境変数でメモリ量を決めてやると動くことがわかりました。

DXGI_ERROR_DEVICE_REMOVED Error · Issue #95 · microsoft/DirectML · GitHub

エラー出力

_feature_guard.cc:142] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2
2021-06-09 23:21:04.003571: I tensorflow/core/common_runtime/dml/dml_device_cache.cc:185] DirectML: creating device on adapter 0 (AMD Radeon RX 6900 XT)
2021-06-09 23:21:04.181556: I tensorflow/stream_executor/platform/default/dso_loader.cc:99] Successfully opened dynamic library Kernel32.dll
2021-06-09 23:21:08.022165: E tensorflow/core/common_runtime/dml/dml_heap_allocator.cc:53] The DirectML device has encountered an unrecoverable error (DXGI_ERROR_DEVICE_REMOVED). This is most often caused by a timeout occurring on the GPU. Please visit https://aka.ms/tfdmltimeout for more information and troubleshooting steps.
2021-06-09 23:21:08.022265: F tensorflow/core/common_runtime/dml/dml_heap_allocator.cc:53] HRESULT failed with 0x887a0005: hr

【AWS App Runner + Flask】FF11のルート探索プログラムをウェブアプリにする

概要

実はSAAとSOAを最近取得できたのだが、構築経験に乏しすぎる耳年増なままDVAやらSAPを取っていくのは意味なさそうなので、色々経験を積んでいきたいと思う今日このごろ。

何かお題はないかと思っていたところ、こないだ作ったNetworkxでFF11のルート探索をするPythonプログラムがCLIベースだと微妙に使いにくいと思ったので、勉強がてらAWSパワーでウェブアプリにしてみることにした。

GitHub - bobfromjapan/FF11_route_finder

FF11のマップ移動が難しすぎるのでグラフ構造を使って解決してみる【NetworkX】 - 端の知識の備忘録

とりあえずまずは味気もなにもないHTMLテンプレートとFlaskを使ってローカルで動くようにしたところ、ちょうどAWSからApp Runnerなる新サービスが発表された。

aws.amazon.com

どうやらBeanstalkのようにEC2やらなんやらが立つスタックとしてではなく、HerokuみたいなPaaSとしてコードと簡単な設定ファイルを書くだけでお気軽にWebアプリを走らせられるというサービスらしい。

あまりにもタイムリーすぎて運命的な何かを感じてしまったので、兎に角一回Flaskで作ったFF11ルート探索Webアプリ、FF11_route_finder ~online~をApp runnerで動かしてみたというお話。

まとめ

  • Webアプリ化はFlaskでやってみた
    • これでフロントエンドも触ったことがあるということで、フルスタックエンジニアの仲間入りと言ってもいいかもしれない(冗談)
  • App Runnerは素人でも簡単にWebアプリをデプロイできる使いやすいサービス
    • GitHubにコードを上げておけばそこから勝手にPull→Buildしてくれるのはとても良い
    • ロードバランサーやオートスケーリングも勝手に作られるが、正直今回の用途じゃ過剰過ぎるのでこれならHerokuの無料枠で十分かも
    • お値段は最小構成(東京リージョン、1 vCPU $0.081 + 2GB RAM $0.009)で1時間$0.09。しかしリクエストを処理していないときはメモリ分の$0.009だけで済むらしいのでほぼ使われなければ月に700円程度
      • なんかのイタズラでオートスケーリングが発動するとデフォルトだと最大25台もインスタンスが動いてしまうぽいので、ちゃんと設定で減らしておくのをおすすめします。

App Runnerはお手軽で良いサービスなんだけども、やはり無料枠欲しいなあという感じ。誰も使わないであろうお遊びアプリで月700円取られるならビールを3本買ったほうがいいに決まっている。

ということで多分すぐ休止状態にしてしまうと思います。

こんな雑なアプリを公開状態にしておくのは怖いし。Flaskがレンダリングしてくれるものは対策してくれているらしいが、本当ならもうちょいちゃんとエスケープとか気にしなきゃならんのだろう。なにより、この辺の知識があやふやな私がお手軽PaaSで公開しているのは危ないだろうという判断である。

https://uh3dkqvvdz.ap-northeast-1.awsapprunner.com/uh3dkqvvdz.ap-northeast-1.awsapprunner.com

FF11_route_finder ~online~使いたいという奇特な人はGitHubクローンして自分でローカルなりで動かしてください! コードはこちら

github.com

コードの更新部分

せっかくウェブアプリにするならば、ということで英語でも検索できる機能を追加してみた。

ヴァナ・ディールの地名の和英変換はFF11 Wikiのこちらの表から対訳表(auxfunc/dict.csv)を作成し、グラフを作成するためのyamlファイルの日本語地名を愚直に置換するスクリプトを書いて、英語用のyamlファイルを作成した。

FF11 Wikiは転載、改変自由というのはありがたいことである。

あとは出発地・目的地を入力するフォームや歩きのみで検索するかどうかの選択ボタン、和英選択のラジオボタンを配置しただけの簡単なHTMLテンプレートを作成し、Flaskの簡単なコーディングを行うだけ。その際こちらのサイトを参考にいたしました。

【Flask】formからサーバーサイドにテキストや各種コントロール、ファイルを渡す | たぬハック

ここまで作れば

cd path/to/app
python application.py

を実行するだけでローカルで検索はできるようになる。お金もかからないし、正直ここまでで十分なはずであったのだが……。

f:id:hashicco:20210525223535j:plain

App Runnerで動かしてみる

ほんとに前項のFlaskのコードを書き終えた日くらいにちょうどAWSからApp RunnerのGAがアナウンスされた。

いくつかApp Runner使ってみた系の記事はあったのだが、それらを見るまでもなくコンソールで言われたとおりにポチポチしていくだけで簡単にデプロイできてしまった。

書くほどのものではないのだが何枚かスクショを貼ってメモを書いておこう。


  • 今回はGitHubのコードから行うので、GitHubAWSの連携が必要である。AWS Connector for GitHubというアプリをGitHubにインストールする作業が発生した。

f:id:hashicco:20210525230351j:plain

  • デプロイトリガーは手動を選択。自動デプロイは追加でお金がかかるようである(1 USD/アプリケーション、月額)

  • 設定は別のファイルに記載しておくこともできるよう(App Runner configuration file examples - AWS App Runner)だが、今回はコンソールから設定することにした。

    • ランタイムはPython、構築コマンドはrequirements.txtを作っておいたので、pip install -r requirements.txtでOK

    • 開始コマンドは初めてだったので若干詰まった。公式ドキュメントより、今回application.pyがモジュール名なので、gunicorn application:appとすれば良いらしいことがわかった。

      • ちなみにうまくApp Runnerが起動しない場合、15分くらいでエラーでロールバックが行われる。ステータスはfailedになっていると設定項目の変更はできない(pauseまたはrunningでないとダメと言われる)ので、もうdeleteするしかなくなってしまった。

Running Gunicorn — Gunicorn 20.1.0 documentation

$ gunicorn [OPTIONS] [WSGI_APP] Where WSGI_APP is of the pattern $(MODULE_NAME):$(VARIABLE_NAME). The module name can be a full dotted path. The variable name refers to a WSGI callable that should be found in the specified module.

f:id:hashicco:20210525230812p:plain

  • 最後のサービスを設定ではAuto Scalingの設定だけ変更した。デフォルトだと最大25インスタンスまでスケールするようになっているので、万が一にでもDDoSを喰らおうものなら25倍もお金がかかってしまう。
    • Maxを1にしておけばリクエストが増えようがサービスは応答しなくなるだけで1台しか動かないはず。

ここまで設定すればあとは少し待つだけでWebアプリがAWSのどっかのサーバーで動いてインターネット経由でアクセスできるようになるわけである。

VPCもEC2もロードバランサーもRoute53も何も気にすることなくこれができるのはすごく魅力的。リクエストないときは最低限の課金というのもリーズナブルだし。App Runnerで費用・機能の要件を満たせるなら存分に使うべきサービスのような気がします。まあ会社じゃVPCでくくれない時点で使えなさそうなので私にとっては個人開発用ですね。

ただ、これだと楽すぎて正直なんの勉強にもならない!多分苦しみながらリソース建てたほうが何か得るものがあっただろうと思いました。

今後の展望

いつかは全体マップにルートの線書いたり、エリア別マップのリンクがついたりすると便利そうだなあと思っているがいつやるかはわからない。

あと、これならAmplifyとLambda、APIゲートウェイを合わせたほうがお安く済みそうだし、サーバーレスアーキテクチャの勉強になりそうなのでいつかはこっちも試してみたいと思います。

Arduino Leonardoでエアロバイクをコントローラーにしてヴァナ・ディールを駆け抜ける話

背景

田舎の引きこもり生活は健康に悪い。

東京で長距離通学をしていた頃は否応なしに人波を抜け、歩き、自転車に乗るトライアスロンをさせられていたのだが、田舎に来るとマジで動かない。

しかもコロナのせいで輪をかけて動かないものだからより健康に悪い。

流石にそれはまずいと思い筋トレとエアロバイクを日課にしていたのだが、最近はちょい残業が多かったせいでサボリ気味。すると元々痩せ型であった我が肉体にも徐々に余分な脂がつきはじめ……。

研究職もある程度は健康維持に務めねばならないということで、酒飲んでゲームしかしない休日に運動を無理やりブチ込む仕掛けはないものかと思案していたところ、1つの考えが湧いてくる。

今年一年は間違いなく現実世界よりもエオルゼアとヴァナ・ディールで歩いた量のほうが多いと断言できる。ならば、ゲームの世界をエアロバイクで移動できればよいのでは!?

……ということで、エアロバイクを漕げばW押して前進するようなものをArduino使って作ったろ!を実際にやってみたお話。

まとめ

注意:市販品に変な改造を施しているので、真似するときは自己責任で!

  • コードはこちら

GitHub - bobfromjapan/exercise_bike_as_a_controler: Arduino code for using an exercise bike to control video games.

  • アルインコのAFB4417というエアロバイクが今回犠牲になったブツ

    • こいつは速度・漕いだ距離とかを表示してくれるマイコン的なやつがついているので、ここから上手いこと漕いでいることを判定できる情報を取り出すことにした

    • 最初は出力の3.5mm プラグから何らか電圧とかで取れないかと思ったのだが、結局半田ごてを動員するはめになった

www.amazon.co.jp

  • 使うArduinoUSB入力をエミュレートできるArduino Leonardoを使用

参考URL

こちらのサイトを参考にさせていただきました。

Arduino電子工作の基本⑥ スイッチの状態を読み取る | Device Plus - デバプラ

Arduino Leonardo(Pro Micro)のHID(キーボード)機能を使う(ショートカットキー実行,コマンド実行) - Qiita

1. エアロバイクの改造

まずこのエアロバイク(AFB4417)では本体から3.5mmのプラグが伸びており、それを上部のサイクルコンピューター的な機械に差し込みペダルを漕ぐと、走行距離や速度が表示されるという機能がある。1万で買えた割には多機能である。

で、当初はこの3.5mmのプラグからペダル漕いだときにダイナモかなんかが動いて電圧が発生、それをサイクルコンピューターで読み取っているのかと思っていたのだが、テスターを当てながらいくら漕げども電圧表示は0Vのまま。

サイクルコンピューターには電池を2本入れて動作させるので、どうやら電圧をかけないと漕いでもなんの変化も現れないらしい。なんかこんなの高校の物理のときやった気がするが、今回の目的を達するために原理まで理解する必要はないのでとりあえず置いておくことにする(詳しい人いたら教えてください!)。要はサイクルコンピューターばらして、どっかの端子から漕いだときに何らかの変化を得られればいいわけである。

というわけで、まだ買ってから一年経っていないこいつを躊躇なくバラすことにした。実験には犠牲が不可欠!

中身はこんな感じ。非常にシンプルな基盤だが、私は別にこういうのに強いわけではないのでここもまた実験的手法でなんとかする。いくつかなんか取れそうな端子にテスターを当てながらエアロバイクを漕いで電圧変化が起こる組み合わせを探してみることに。

f:id:hashicco:20210509135355j:plain

すると、3.5mm端子の刺さっている部分の2極にテスターを当てながら漕いでると電圧が変化することがわかった。具体的には漕いでないときは電池由来の3V前後の電圧がかかっているのだが、ペダルを漕ぐと特定の場所を過ぎるときに電圧が下がる。ただ、下がる量は不安定だし漕いでいるときずっと下がるわけではない。

まあ一瞬でも下がってくれればそれをArduinoのAnalogピンに繋いで電圧が閾値を下がったことを検知できるはずなので、ここに無理やり導線をはんだ付けして信号を取り出すことにした。

で、出来上がったのはこんなもの。Amazonで30mも買ってしまって余っていたオーディオケーブルを導線として使ったが、太すぎて取り回しが悪かったので普通の27AWGくらいの導線を使うことをおすすめする。

酒飲みながらやったせいで、めちゃめちゃはんだ付けが雑……。気が向いたら直そうと思う。

f:id:hashicco:20210509141611j:plain

f:id:hashicco:20210509141618j:plain

この導線をArduinoのアナログピンに指すことで、エアロバイクを漕ぐことによる電圧変化を測れるわけである。

2. Arduinoのコードを書く

魔改造は前項までで、ここから先は至って普通のArduino工作です。

流石にペダル漕いで前にすすめるだけでは使い物になりませんので、横への方向転換用にハンドル部分に'A'と'D'のボタンもつけることにした。

配線はこんな感じ。'A'と'D'のボタンはfritzingの都合上ブレッドボードに挿してますが、実際には導線(オーディオケーブル)を2mくらいつけて、エアロバイクのハンドル部分に雑にマスキングテープで取り付けてます。実際の写真は後でのお楽しみということで。

f:id:hashicco:20210510205306p:plain

コードはこちら。'A'と'D'のボタンについてはシンプルに押されたかどうかをプルアップの変化で読み取ります。Keyboard.pressで'A'と'D'押したことにする。ピンで言うと7と8を利用。

エアロバイクの電圧変化はAnalogピンを利用。適当に閾値を3.3Vとしてそれを下回ったときにKeyboard.pressで'W'キーを入力するという感じ。更に、先程述べたように漕いでるとき常に電圧が下がる感じではないので、過去5回分の移動平均をとりそれが閾値を下回るか否かを判定基準とすることにした。

また単押しだと進む距離が短すぎて超漕がないとミリも進まないマゾ設定になってしまうので、press後Delayを入れて100ms押した状態にする。

#include <Keyboard.h>
const int A_PIN = 7;
const int D_PIN = 8;
double v_logs[5] = {3.3,3.3,3.3,3.3,3.3}; 
double thresh = 3.3;


void setup(){
    pinMode( A_PIN, INPUT_PULLUP );
    pinMode( D_PIN, INPUT_PULLUP );
    Serial.begin( 9600 );
}

void loop(){
    int value_a, value_d;
    double value_w = analogRead(A0);
    value_w *= 5;
    value_w /= 1024;

    int i;
    double sum = 0 ;
  
    for (i = 0; i < 4; i = i + 1) {
    v_logs[i] = v_logs[i+1];
    sum += v_logs[i];
    //Serial.println(v_logs[i]);
    }
    
    v_logs[4] = value_w; 
    sum += value_w;
  
    if(((sum/5) < thresh) && (sum > 10)){
      Keyboard.press( 0x77 );
      delay(100);
    } else {
      Keyboard.release( 0x77 );
    }
    
    value_a = digitalRead( A_PIN );
    if ( value_a == LOW ){
      Keyboard.press( 0x61 );
    } else {
      Keyboard.release( 0x61 );
    }

    value_d = digitalRead( D_PIN );
    if ( value_d == LOW ){
      Keyboard.press( 0x64 );
    } else {
      Keyboard.release( 0x64 );
    }

    
}

3. ヴァナ・ディールを走ってみる

てなわけでコーディングも配線も終わったのでいざ実物を!

f:id:hashicco:20210510214602j:plain

……はい汚い。こんなの部屋においてあったら長年の友達でも少し引く。後ろの自作のプラモ塗装乾かしスペースやらプチプチの巻きつけられたダンベルやら相まって怪しい部屋度が50%増し。独身寮だから許される何かが出来上がりました。

せめて導線にもっと目立たない細い配線を使ったり、フレームに這わせたり、なによりマスキングテープは使わないようにしたりすればもうちょいマシでしょうか。

で、肝心のゲーム内の移動に関しては問題なくできることを確認。アルテパ砂漠をラプトルに乗って走る冒険者をエアロバイクで操作するのはなかなか趣があって面白かったし、何よりしっかり運動になりました。

これからもこれ使って寿命を伸ばしつつ健康的にヴァナ・ディール生活を送っていこうと思いました。めでたしめでたし。

Kensingtonの人差し指トラックボール ExpertMouseとSlimBladeを比較する

概要

私は研究室・職場ではトラックボールをずっと使っている。かれこれ4年以上になろうか。

最初はOrbitTrackball with Scroll Ringを使い人差し指トラックボールにハマり、数カ月後には無線のExpertmouseを買っていた。

Amazon | Kensington ExpertMouse ワイヤレストラックボール K72359JP 【日本語パッケージ】 | Kensington | トラックボール 通販

Expertmouseは素晴らしく使いやすく、そこから3年以上ずっと使い続けていた。

Expertmouseには概ね満足であったが、Kensingtonのトラックボールの最上位といえばSlimBladeというイメージが頭について離れない。しかし無線バージョンがないということで購入は見送っていた。

まあ最近は職場でほとんどデスクトップしか使っておらず無線に拘る必要もないかと改心したのと、Paypay祭りで少し安く(実質7000円くらい)買えそうだったのでSlimBladeを衝動買いしたのであった。

Amazon | ケンジントン 【正規品・5年保証付き 日本語パッケージ】 SlimBlade Trackball 72327JP | Kensington | マウス 通販

使い始めて1ヶ月位経ったし、そろそろまともにレビューできそうなのでExpertmouseとSlimBlade2つのKensingtonトラックボールの比較を書いてみようと思う。

まとめ

ということで、1万近い価値は十分あるトラックボールであると断言できる。ちなみに4000円くらいで買えるOrbitTrackballも入門用としてとてもいいぞ。ぜひScroll ring付きをおすすめしたい。

Amazon | ケンジントン 【正規品・5年保証付き 日本語パッケージ】 OrbitTrackball with Scroll Ring 72337JP | Kensington | マウスパッド 通販

  • Expertmouseのいいところ

    • 無線版がある
    • クリック押しやすい
    • スクロールリングがある
  • SlimBladeのいいところ

    • 圧倒的に掃除がしやすい!
    • ボールをひねってスクロールするのは面白い(使いやすいとは言ってない)

必ずしもSlimBladeは上位版ではなく、正直単純な使いやすさならExpertmouseのほうが上だったというのが両方使ってみての感想。

しばらく使っていればなれる程度ではあるが、クリックに関してはSlimBladeが一枚板?的なデザインで作っているせいで、中心から離れた端の部分は可動域が少なく押しにくい。手の大きさや普段どんなポジションで指をおいているかによるが、Expertmouseのどこを押しても同じ感覚で押せるのに慣れていると最初のうちは押しにくさを感じると思う。気になる人は一度ヨドバシなどで実機を触ってから買ったほうが良いだろう。

SlimBladeの特徴であるボールをひねると音が出てそれでスクロールできるというのは面白いギミックだが、長いスクロールなどはやはり径が大きいリングのほうが使いやすい。

ただなんと言っても掃除がしやすいというのは大きなメリットである。多分EM使いの人なら皆わかると思うのだが、ExpertMouseはすごい汚れが溜まりやすいという欠点がある。掃除を怠り数日使ったあとのExpertmouseの支持球とか見ちゃうと正直他人の使うトラックボールとか汚くて触りたくないよね……もしくは私が手垢すごいだけ?

Expertmouseと違ってスクロールリングがない、支持球の構造が違う、ボール受けるところの下が空いているということで、ティッシュ1枚だけでらくらくお掃除できる。

掃除がしやすいというこの一点だけでも乗り換える価値があるということで、今はメインでSlimBladeを使っています!

ギャラリー

玉の大きさはおんなじ。というわけで今まで大事にボナンザ(フッ素コーティング剤)で磨き続けてきたExpertmouseのボールを使うことにした。

比較にトラックボールの中では一番売れているであろうLogicoolのM570も並べてみるとこんな感じ。フットプリントはExpertmouseとSlimBladeであまり違いはないのだが、傾斜がSlimBladeはだいぶ浅め。

なのでどちらかに慣れてからもう片方を触ると、それなりに違和感を感じることだろう。自分もSlimBlade触り始めて数日は左クリックがかなり押しにくく感じていたが、クリックする場所や力が調整できてくると特に問題なく使えるようになった。

f:id:hashicco:20210505151301j:plain

玉の入っているカップ部分はこんな感じ。Expertmouseとは人工ルビー支持球の構造が異なり、壁面に埋め込まれるような形になっているため、ここに手垢やホコリが溜まりにくい。

更にExpertmouseでは底面にあったレーザーの発光部がSlimBladeでは側面に存在するので、これもまたゴミが溜まりにくい構造になっている。

Expertmouseは分解してブラシを使わないと掃除ができなかったのがティッシュ1枚でさっと拭けるのはとても楽です(3回目)。

f:id:hashicco:20210505151233j:plainf:id:hashicco:20210505151250j:plain

【R】 三井住友・楽天のカード明細とUFJの口座明細をRで読み込んで社会人1年目の収支を見てみる

背景

早いもので働き始めて一年も経ってしまったようだ。

学生の時と違うことといえば、とにもかくにもお金が入ってくること。

生まれてきてから溜め込んできた分の欲望を無計画に吐き出すかのように、この1年はとにかく何も考えずにお金を使ったものである。グラボを買った月などはカードの引き落しがその月の給料より高かった気がする。

高い買い物は記憶に残るものの、あまり出費として気に留めない細かい買い物も明らかに増えたし、その「細かい買い物」に該当する金額の幅も広がった。飲むお酒もストロング系からちゃんとした生ビールに、ウイスキーもトリスから酒屋で適当なスコッチを選り取り見取り好きに買い、味の違いを楽しむなんて具合に。酒を趣味にするだなんて少し洒落た話以外、もっと卑近でナードなサムシングの話をするならば、DLSiteの買い物がマジで増えた

いくら使ったか怖くてちゃんと計算してなかったけれども、流石に2年目以降無計画というのは人生楽しけりゃどうにでもなれ思想の私としてもいずれ底しれぬ不安を感じ夜もおちおち寝てられない状態になるのは目に見えているので、Rを使って収支をしっかり可視化してみようというお話。

まとめ

  • 三井住友のアマゾンカード、楽天カード、あと三菱UFJの口座明細のRでの処理方法を書いています

  • 少なくとも私の使っている金融機関で出力できるCSVは基本的にクソUTF-8かと思ったらBOM付きだったり、ひとつのCSVに2つのカードの情報が入っていてヘッダー的な行が途中唐突に挿入されていたり。

    • あと金融機関ごとで全く列に格納される情報もフォーマットも異なるので、それぞれ別々に処理するコードを書かなければならない。標準化とかしないんですかね……。
  • パソコン&ゲームオタクは意外に安上がりな人種。新卒1年目で欲望のままに買い物をしてもちゃんと貯金ができるようだ。

    • なにより会社の寮にいるので光熱費食費住居費が合わせて5万以内というのがでかい。これがなければ貯金額は半分くらいになっただろう
    • 趣味に生きたい人間は、会社を選ぶときにきちんと住宅補助や寮の有無を確認したほうが良いだろうと思いました

CSVをとってくる

それぞれの会員ページからCSVが出力できる。UFJは期間指定で一括で落とせたが、三井住友のVpassと楽天カードはそれぞれ月ごと。

下記のようなフォルダ構成にして置いておきます。

---- rmdファイル
    |---- vpass
    |    L 202004.csv, 202005.csv... ..., 202104.csv
    |---- rakuten
    |    L 202004.csv, 202005.csv... ..., 202104.csv
    |---- UFJ
         L xxxxxxx.csv

CSVの読み込み・前処理

ここが一番大事。楽天カード三井住友カードは12ヶ月分を順繰りに読んでマージ、一つのdfにします。

使う libraryはtidyverseだけで良いので、library(tidyverse)しておく。

三井住友の場合

エンコーディングshift-jisなのでread.csvではfileEncoding="cp932"を指定します。

多分3つの中で一番イケてないcsv。ヘッダーがなく(!)各列になんの情報が入っているのかわからない上、idでの支払いもこんな感じで唐突に挟まる。

支払い者情報のある行だけカンマの数が違うので、そもそも列数が違うエラーが出て読み込みすらままならない。極めてクソである

xx xx 様,xxxx-xxx-xxxx-x***,Amazonマスタークラシック
2020/03/26,xxxx,1628,1,1,1628,
2020/03/30,xxxx,2192,1,1,2192,
xx xx 様,xxxx-xxx-xxxx-x***,三井住友カードiD
2020/03/19,xxxx/iD,267,1,1,267,
2020/03/23,xxxx /iD,3448,1,1,3448,

そのため、読み込みのときは利用日の列が数字で始まるか否かで判定して、filterで唐突に現れる支払い者情報行を弾くことにする。

てなわけで、こんな感じで読み込んでみた。いらない列はselectで省きます。

df_smbc <- data.frame()

file_list <- list.files("smbc/", pattern="csv", full.names=T)

for( i in file_list){
  df_tmp <- read.csv(i,col.names = c("利用日","利用名","利用金額", "item1", "item2","支払総額","note"),skip=1,sep=',', fileEncoding = "cp932") %>% 
            filter(str_detect(利用日, "^[0-9]" )) %>% 
            select(-item1, -item2, -note)

  df_smbc <- rbind(df_smbc, df_tmp)
}

このdf_smbcから

#総額
sum(as.integer(df_smbc$利用金額))
#アマゾンだけ
sum(as.integer(filter(df_smbc, 利用名=="AMAZON.CO.JP")$利用金額))

とかすれば使った金額がわかる。

ちなみに手取り給料6.5ヶ月分くらい使っててビビったのは内緒。やはりちゃんと可視化するのは大事

楽天カードの場合

こちらもなかなかクソなCSVエンコーディングは何ということでしょうUTF-8 with BOM。使いにくいからやめてほしい。

でもまだヘッダーがあるぶんSMBCよりも随分マシである。まあ他のdfと列名の整合性を取るため、自分で列名はつけますが。

ただ分割払いに対応するためなのか、"X月支払金額","X月繰越残高"という列があり、これが月によって変わるのがめんどい。

"利用日","利用店名・商品名","利用者","支払方法","利用金額","支払手数料","支払総額","X月支払金額","X月繰越残高","新規サイン"
"2020/05/27","xxxx","本人","1回払い","40000","0","40000","40000","0","*"
"2020/05/13","xxxx","本人","1回払い","5200","0","5200","5200","0","*"

また、ebayの買い物とかで為替が発生すると、下記のような列が挿入されることがあり邪魔なので、先程同様 利用日の列が数字で始まるか否かのfilterを行います。

"2020/11/20","xxxx利用国USA","本人","1回払い","1000","0","1000","1000","0","*"
"","現地利用額      1000.000変換レート   1.000円","","","","","","","",""

てなわけでエンコーディングと削る列以外はほぼSMBCとおんなじ感じ。

df_rakuten <- data.frame()

file_list <- list.files("rakuten/", pattern="csv", full.names=T)

for( i in file_list){
  df_tmp <- read.csv(i,col.names = c("利用日","利用名","利用者","支払方法","利用金額","支払手数料","支払総額","支払金額","繰越残高","新規サイン"),sep=',', fileEncoding = "UTF-8-BOM") %>% 
            filter(str_detect(利用日, "^[0-9]" )) %>% 
            select(-支払方法, -利用者, -支払手数料, -支払金額, -繰越残高, -新規サイン)  

  df_rakuten <- rbind(df_rakuten, df_tmp)
}
sum(as.integer(df_rakuten$利用金額))

で、楽天カードの利用額もわかる。

こちらが給料2.5ヶ月分でしたので、カードで合わせて給料9ヶ月分も使っていたらしい。怖いね!

三菱UFJの場合

これは12ヶ月分まとめて出せるので、for使わなくて良くて楽ちん。エンコーディングshift-jis。ちゃんとヘッダーもある。

しかし、こいつのめんどくさいところは金額に3桁ごとにカンマが入っているところである。仕方がないのでgsubでカンマを空白に置換し、as.numericで数値化した新規の列を作成することにした。

また、給料や賞与の摘要内容が空になってしまっていたので、if_elseを使って空のときは摘要の内容を入力することにしてみた。

さらに、時系列で残高を折れ線で書いてみたかったので、 gsubで /-にした後as.DateでRで日付として扱えるようにした日付列をdate列として追加した。

df_ufj <- read.csv("ufj/0208339_20210414213549.csv",col.names = c("日付","摘要","摘要内容","支払い金額","預かり金額","差引残高","メモ","未資金化区分","入払区分"), skip=1,sep=',', fileEncoding = "cp932") %>% 
          mutate(payment=as.numeric(sub(",","",支払い金額)), 
                 income=as.numeric(gsub(",","",預かり金額)),
                 balance=as.numeric(gsub(",","",差引残高)),
                 date=as.Date(gsub("/","-",日付)), 
                 item=as.character(if_else(摘要内容=="", 摘要, 摘要内容))
                 )

可視化!

金額わかるのは微妙なので適宜目盛は消していますが、ggplotで可視化。

  • カードの支払いを積み上げ棒グラフで

最初そのままsmbc_dfをグラフにしたら項目数が多すぎて何が何やらわからないグラフになってしまったので、総額のうち占める割合が3%以下の利用名の項目をis_samatsu=Trueとして判定、sonotaという項目に全部ひとまとめにすることに。

ちょっと列名つけるのめんどかったのでperとかperperとかいう適当な列が途中生成されていますが、そこはご愛嬌。

tmp <- df_smbc %>% mutate(per = round(as.integer(利用金額)/sum(as.integer(df_smbc$利用金額))*100, 3)) %>%
                                    group_by(利用名) %>% 
                                    summarise(perper = sum(per))  %>% 
                                    mutate(is_samatsu = if_else(perper<3, TRUE, FALSE))

new_df_smbc <- merge(df_smbc, tmp) %>% filter(is_samatsu==FALSE)

new_df_smbc <- rbind(new_df_smbc, c("others", "9999/99/99", sum(as.integer(filter(merge(df_smbc, tmp), is_samatsu==TRUE)$利用金額)), sum(as.integer(filter(merge(df_smbc, tmp), is_samatsu==TRUE)$利用金額)),"sonota" ))

ggplot(new_df_smbc, aes(x="", y=利用金額, fill=利用名)) + geom_col()

最初めちゃめちゃ使ってたと思っていた例のサイトは、せいぜいヨドバシと同じくらいしか使ってなかったらしい。よかったよかった!

いやでも少なくともRTX3080とRyzen5 5600X買った淀と同じということは、150000近くもいかがわしい何かに使っていたということか?……いや深くは考えないことにしておこう。

f:id:hashicco:20210420222409p:plain

  • 残高の変化を折れ線で

普通にdf_ufjdatebalance列を使って描くだけ。

ggplot(filter(df_ufj, balance!=""), aes(date, balance))  + geom_line() + geom_point()

時々散財の影が見え落ち込みを見せるものの、全体的には右肩上がりの楽しげなグラフである。

f:id:hashicco:20210420221615p:plain

一年頑張った証拠として、皆様もRで収支をグラフ化してみてはいかがでしょうか?