HPCメモ

HPC(High Performance Computing)に関連したりしなかったりすることのメモ書き

mocha であるイベントが発火した後に色々テストしたい時

テスト対象の関数がイベントベースの非同期関数で、あるイベント(ここでは"close"とします)が終わった後で何か値の検証をしたいって時に、今まで次のようなコードを書いていました。

it("should fire close", (done)=>
  hoge.on("close", ()=>{
    expect(huga).to.equal("piyo");
  });
});

これでも正常動作しているとテスト自体は問題無いんですが、expectでチェックしている項目がfailした時に、タイムアウトまで待たされる上に 落ちた場所も分かりません。

で、今まではテストにfailしたら、expectの前後にDEBUG printを入れてどのテストがfailなのか確認するところから作業してたんですが こんな感じでPromiseを使えば解決しました!!

it("should fire close", async ()=>{
  await new Promise((resolve)=>{
    hoge.on('close', resolve)
  })
  expect(huga).to.equal("piyo");
});

やってることはただの手動Promisifyなんですが、なんかasyncテストはPromiseを返す関数のものと思い込んでてなかなか思いつけなかったのがちょっとくやしい・・・

ただし、この形でもイベントが発火しなかった時は相変わらずタイムアウト待ちになるんですが、少なくともどこのテストでfailしたか一発で分かるようになったし タイムアウトになるのはイベントが発火しなかった時だけなので、今までよりずいぶんfail後の原因調査が捗ります。

浮動小数点数の比較

この記事は UdonTech Advent Calendar 2023の3日目の記事です。 なんか良い具合にネタが転がりこんできたので、もう一本穴埋め記事を投稿しときます :p

最近アドベントカレンダーを書いていたせいか、200イイネ突破みたいな感じでこちらの記事がfacebookのフィードに流れてきました。

qiita.com

この記事から派生して、浮動小数点数の比較ってどうやりゃ良いの?

qiita.com

というような記事も書かれていました。

元記事の是非はともかくとして浮動小数点数同士の比較については、こちらに良くまとまった記事があります。

Comparing Floating Point Numbers, 2012 Edition
randomascii.wordpress.com

10年ほど前の記事ですが、私が最初に見たのはもうちょっと前だったような気がします。

この記事では3つの比較方法について紹介されていて、ざっくりまとめると

  1. 二つの数の差の絶対値がマシンイプシロン(以降εとする)以下かどうか
  2. 二つの数の差の絶対値を、絶対値が大きい方の数の絶対値で割ったもの(相対誤差)がε以下かどうか
  3. ULP (一番下の1が立っているbit)でのとなりあう浮動小数点数の差 と比較して1ULP以下ならOK

注意する点として、0とある浮動小数点数との比較を行なう場合は、2、3の手法はほぼ無意味で1を使うべし といったあたりが挙げられてます。

じゃ実際のところ、数値計算ではどの判定方法を使っているかというと、たぶんどれも使ってません。

ここで挙げられている比較方法はテストなんかで、ある値が以前の値と等しいか?みたいな比較を行なう時には便利なんですが 数値計算の場合、「違う値が返ってきてるけど物理的には許容範囲」ということが多々あります。

というわけで、たいていの場合は1の絶対誤差を、物理的に意味のある許容誤差と比較するというのがよく用いられてるんじゃないかと。

AWSの請求関連のポリシー変更に対応する

この記事は UdonTech Advent Calendar 2023の10日目の記事です。

半年くらい前にAWSから "[Action required] Update your policies・・・" みたいなメールが来てたんですが、どうせたいしたことないだろうと高を括ってましたw

が、なんかよく見たらポリシーをアップデートしてないと Dec11以降は "AWS billing, Cost Management, Account consoles will be affected" とか書かれてるじゃないですか。 ちょっと本当にマズそうなので、(アドベントカレンダーのネタにもなるし)期限ギリギリでポリシー変更してみようと思います。

AWSからのメールに書かれてた、requiredなactionはこの2行だけでした。

The following policies need to be updated to include the new fine-grained actions:
Customer Managed|BillingFullAccess|arn:aws:iam::xxxxxxxxxxxx:policy/BillingFullAccess||

でもって、大量にドキュメントへのリンクを用意してくれてるんですが、ここは流行りに乗ってchatGPTに相談してみます。

https://chat.openai.com/share/177675dd-0e52-435d-b272-f7450de434fa

ふむ。JSONで新ポリシーのデータまでくれて至れり尽くせりですね。

さて、次はどうしましょうか? もしこの回答が有償サポートの窓口から返ってきたものなら言われるままに設定しても良さそうですが そこまで信用できる手順書でも無いので逐一検証してみましょう。

IAM ポリシーの特定 メールに記載されているポリシー BillingFullAccess がどの IAM ポリシーに関連しているかを特定します。IAM コンソールまたは AWS CLI を使用して確認できます。

こりゃそうですね。元々"BillingFullAccess"を設定していないIAMポリシーを変更したらセキュリティインシデントですw

IAM ポリシーの編集 該当する IAM ポリシーを開いて、新しい細かいアクションを追加します。具体的なアクションがメールに記載されているはずです。例えば、AWSの新しいサービスや機能に関連するアクションがあるかもしれません。

残念、具体的なアクションはメールに書いてくれてませんでした。 まーこのドキュメントにmappingを用意したよ! って書いてるんですが、元々 aws-portal:* とかいうクソデカ権限なので何を付ければ良いのやらさっぱり分かりませんo..rz

docs.aws.amazon.com

さて、どうしようかと思いながらドキュメントを見ていたら、「影響を受けるポリシーツール」なるものが提供されていました。

https://us-east-1.console.aws.amazon.com/poliden/home?region=us-east-1#/

初期状態では、ボタンの表示が一部隠れていますが「更新されたポリシーをコピー」の列を広げると「新しいポリシーをクリップボードにコピー」というボタンが現われます。

「新しいポリシーをクリップボードにコピー」をクリックしてポリシーのJSONデータをコピーして、右隣にある「IAMコンソールで編集」をクリックすると このポリシーの編集画面に行くので、元のJSONを削除してペーストしましょう。

保存すれば作業完了(のはず)です。

あれ?まだ手順書の検証をしてたはずなのに、作業が終わってしまった・・・

DDNSを捨てよIPアドレスを通知しよう

この記事は UdonTech Advent Calendar 2023の2日目の記事です。

   | \
   |∀ ゚) ダレモイナイ・・アナウメスルナラ イマノウチ
   |⊂
   |

一般のご家庭ではISP様の気分次第で*1割り当てられたIPアドレスが変わってしまうので、 外部からアクセスしたいんだけど、IPアドレスを知るためには同時に家庭内LANにも居ないといけないという ジレンマに悩まされていました。

解決策としてDDNS(Dynamic DNS)というのがこれまた古くから使われているんですが、年に数回外出先からsshで帰宅する程度にしか使わないので 名前を割り当てられるほどの大掛かりなサービス要らんのだけどなぁという気持ちもあって、なんとなくアレ使うの嫌なんですよね。

そもそも、一般に公開するサービスを提供するわけじゃないので誰にでも分かりやすいアドレスなんてものも不要だし、 むしろ名前でアクセスできると攻撃を受ける頻度が上がるだけじゃないだろうか(要出典)という気もします。

ところで、最近知ったのですが、一部のpublicなDNSに特殊なクエリを投げると自分の(DNS側から見た時の)IPアドレスを返してくれるそうです。

community.cisco.com

ということは、これを家庭内LANのマシン上で定期的に動かしつつ、IPアドレスが変わっていたら通知するシステムがあれば十分じゃないか!

ということで作りました :D

自分のIPアドレスを知る

これを実行するだけ。

dig myip.opendns.com @208.67.222.222 +short

+short オプションは、今回初めて知ったんですが便利ですね。 最初はsedでanswer sectionを抜き出して〜とかやってたんですが、このオプションをつければ単にIPアドレスだけ返してくれます。

通知する

そんなもんslackにDMで投げときゃ良いでしょ。 ってことで、こちらの記事を参考に incomming webhook を設定してcurlでポストします。

qiita.com

anton0825.hatenablog.com

デプロイメント

crontab -e

IPアドレスを調べてファイルに書き出し、前のIPアドレスとdiffとって違ってたらslackに投げるシェルスクリプトを作っておいて毎時5分くらいに動かします。

修正版

これで完成!!と思いきや、なんか定期的にIPアドレスが変わっていないのに通知が来る現象が起きました。

直前のIPアドレスとのdiffをとった時のログを見ると

1c1,4
< 192.168.0.1
---
> 
> ; <<>> DiG 9.10.6 <<>> myip.opendns.com @208.67.222.222 +short
> ;; global options: +cmd
> ;; connection timed out; no servers could be reached

あ、time outしとる・・・

というわけで、digが非ゼロで終了したら1分待ってリトライするようにして完成です(たぶん)((今のところ連続して同じIPアドレスが通知されたことは無いので、大丈夫そうだけど単にdigがエラーを返してないだけという可能性もあるので・・・)

需要は無さそうですが一応最終版のスクリプトを貼っておきます。

function onDetectNewGlobalIP(){
  #save difference 
  diff ${OUTPUTDIR}/globalIP.txt  ${OUTPUTDIR}/globalIP_new.txt

  #post to slack
  curl -X POST -H 'Content-type: application/json'\
   --data '{"text":"'$(cat ${OUTPUTDIR}/globalIP.txt)'"}' ${WEBHOOK_URL}
  rt=$?
  rm  ${OUTPUTDIR}/globalIP.txt 
  mv ${OUTPUTDIR}/globalIP_new.txt ${OUTPUTDIR}/globalIP.txt 
  return $rt
}

{
dig myip.opendns.com @208.67.222.222 +short  > ${OUTPUTDIR}/globalIP_new.txt
dig_rt=$?

error_count=0
while [ $dig_rt != 0 ]
do
  sleep 60
  dig myip.opendns.com @208.67.222.222 +short  > ${OUTPUTDIR}/globalIP_new.txt
  dig_rt=$?
  error_count=$(( ${error_count} + 1))
  if [ ${error_count} -gt  ${MAX_RETRY} ];then
    echo "dig failed more than ${MAX_RETRY} in a row"
    exit 1
  fi
done


if [ -f  ${OUTPUTDIR}/globalIP.txt ];then
  diff -q ${OUTPUTDIR}/globalIP_new.txt ${OUTPUTDIR}/globalIP.txt 
  if [ $? -ne 0 ];then
    echo 'global IP address is changed!'
  
    onDetectNewGlobalIP
  else
    echo 'global IP address is not changed!'
    rm ${OUTPUTDIR}/globalIP_new.txt 
  fi
else
  echo 'global IP address file does not exist'
  onDetectNewGlobalIP
fi

}  > ${OUTPUTDIR}/$(date +%Y%m%d-%H%M%S).log

OUTPUTDIR, WEBHOOK_URL, MAX_RETRYはそれぞれPC上のファイル置き場、slackに通知するためのwebhookのURL、MAX_RETRYはdigがエラーを返した時に リトライを行なう数です。

cronにこのスクリプトを仕込んで動かしつつ、cronで毎日1週間前のログを消すといった感じで運用していますが、今のところ快適に使えてます。

大前提としてルーターの設定などで外部からのsshアクセスは捌けるようにしておく必要がありますが、これで半年ほど使っていて特に問題なさそうです。

唯一の欠点は、淡々とIPアドレスが並んだslackの画面に往年の某掲示板を思い出してしまうところでしょうかね?

*1:嘘です、何かしらのアルゴリズムに基いて切り替わるはず

macvimからVimR(neovim)へ移行しました。

マカーになって以来、エディタはずっとmacvimを使ってたんですが、

という謎の現象が起きてて、しかもこれ明確に発生条件が絞り込めたのは、 行末から右移動しようとしたとき だけですが、 これ以外の状況でもちょくちょくフリーズしてました。

profileを取ると、よく槍玉に挙げられてるHighlight_Matching_Pairが最上位に居たんですが、こちらで紹介されていた paren-match pluginを導入しても挙動は変わらず*1

qiita.com

そもそも、プラグインを無効化したり、.vimrcを読まないで起動しても同じ現象が起きてたので 駄目元でneovimを導入してみたら、あっさり解決しましたo......rz

というわけで、急遽 macvimからneovim(のフロントエンドであるところのvimR)へ移行することにしたので作業内容をまとめておきます。

インストール

brew install vimr

設定の移行

こちらの記事を参考に

qiita.com

まずは、.vimrc をそのままneovimのinit.vimへコピーします。

mkdir -p ~/.config/nvim
cp ~/.vimrc  ~/.config/nvim/init.vim

続いて(私もこちらの記事の方と同じくPlugでプラグイン管理をしているので)、plug.vimをコピーします。 *2

mkdir -p ~/.local/share/nvim/site/autoload/
cp ~/.vim/autoload/plug.vim ~/.local/share/nvim/site/autoload/

ここで一旦、vimRを起動するとcolor schemaは効いてないは、 横に勝手にファイラーが追加されてていんたーねっとには公開し難いはと散々な状態です。

が、まぁとりあえず :PlugInstall しときましょう。

続いて、 settings メニューへ行き

  • General タブ -> After Last Window Closes を Quit に変更
  • Toolsタブ -> File BrowserとMarkdown Previewのチェックを外す
  • Appearanceタブ -> Default Fontを Monaco の18ポイントに変更

と変更します。

ここまでやって気付いたけど、これ .gvimrc に書いた設定が読まれてないだけみたいですね。

というわけで.gvimrcに入れていた設定のうち、フォントまわり以外を突っ込みます。

if has("gui_vimr")
  " カラースキームの設定
  set t_Co=256
  let g:solarized_termtrans=0
  let g:solarized_bold=0
  let g:solarized_underline=0
  let g:solarized_italic=0
  let g:solarized_visibility='high'
  set background=light
  colorscheme solarized

  "カーソル形状
  set guicursor=a:blinkon0
  " コマンドラインの高さ(GUI使用時)
  set cmdheight=1
  set laststatus=2

  " ビジュアル選択(D&D他)を自動的にクリップボードへ (:help guioptions_a)
  set guioptions+=a

  set mouse=a

  "Input Method関係の設定 (IME on時の余計な改行とかを防ぐ)
  set iminsert=0
  set imsearch=-1

endif

set guioptions+=a は効いてないっぽいですが、ビジュアル選択した状態で Ctrl-Cすれば普通にコピーできるので、当面はyankと使い分けていきます。

プラグインの変更

さて、これでもうエディタとしては問題無く使えるのですが ついでに前述の記事で紹介されてたプラグインを入れてみましょう。

markdown-preview

まず、previmを markdown-preview に変更します。

次の行*3を追加して、:PlugInstallします。

Plug 'iamcco/markdown-preview.nvim', { 'do': 'cd app && npm install' }

それから、previm関連のプラグイン(私の場合はprevimとopen-browser)の行を削除して、:PlugClean します。

これで適当なmarkdownファイルを開いて :MarkdownPreview すると、htmlでレンダリングされた結果がブラウザで表示されます。終了する時は :MarkdownPreviewStop としてください。

vimRにはmarkdownを表示する機能がもともとついていますが、画像が表示されなかったり、エディタと表示サイズが(縦方向に)揃えられてしまって、見難いなど色々と難がありました。しかも、こちらのプラグインを使うとmarkdownファイル側のカーソル位置に追従してhtmlの表示位置を変更してくれます!!!

previmにも無かった機能ですが、これがあるとめっちゃ便利そうなのでこの機に乗り換えてみました。

AquaSKKの設定

VimRでAquaSKKから日本語入力していると、文字種切り替え(q,l, C-jなど)がVimRにもわたってしまうようで qやら改行やらが入った文章を後から直す羽目に、ちょっと面倒です。

macvimでも何か設定してたような気がするんですが、思い出せないのでこちらの記事にあった空文字挿入の設定を追加してみます。

hamaco.hatenablog.jp

まず、VimRのbundleIDを調べるために、ターミナルでlsappinfoします。

lsappinfo info VimR
"VimR" ASN:0x0-0x1146145: 
    bundleID="com.qvacua.VimR"
    bundle path="/Applications/VimR.app"
    executable path="/Applications/VimR.app/Contents/MacOS/VimR"
    pid = 84650 type="Foreground" flavor=3 Version="20230103.174333" fileType="APPL" creator="????" Arch=ARM64 
    parentASN=ASN:0x1-0x14aa8: 
    launch time =  2023/04/18 14:27:23 ( 1 hours, 1 minutes, 51.2144 seconds ago )
    checkin time = 2023/04/18 14:27:23 ( 1 hours, 1 minutes, 51.0431 seconds ago )
    launch to checkin time: 0.171407 seconds

もし表示されなかったら、引数無しで実行してlessにでも流して、それっぽいエントリを探してくださいw

さて、bundleIDが "com.qvacua.VimR"だと分かったので、aquaSKKの環境設定を開いて 互換性タブに行き [追加]ボタンをクリックしてから新しい行に"com.qvacua.VimR"を入力します。 あと、空文字挿入の列にチェックを入れれば設定完了です。

これで、VimR上で日本語の文字種を切り替えたり、英語-日本語を行き来しても 余計な文字が入力されることはなくなりました。

おわりに

という感じで、急遽macvimからVimRへ乗り換えてみたのですが これまであったストレスフルな挙動が全部解消して、めっちゃ楽になりました。

あとは、gitのmergetoolとか色々設定していないのがあるんですが まぁその辺は公式のドキュメントにもあるので、おいおいやっていこうかと思います。

*1:プロファイラの結果は変わっていたので、正常に機能はしている

*2:記事中では、Neovim用のコマンドを実行=neovim用のディレクトリにplug.vimを再ダウンロードしているようですが、 vim用と同じものなので同じマシン内でコピーしました

*3:公式のドキュメントだとyarnを使えってなってるけど、npm installでももちろん大丈夫

sesami4で快適スマートロック生活

この記事は UdonTech Advent Calendar 202220日目の記事です。

突然ですが、1ヶ月ほど前に事務所を借りました。といってもワンルームの賃貸マンションを借りて週2〜3日そちらで作業するようにしただけなので、殺風景なことこの上ない状態です。

しかし、こんな部屋でも鍵をかけ忘れたりすると不用心ですね。しかも、毎日何回も出入りするようなところではないので、一旦かけ忘れると数日は開きっぱなしということもありえます。

そんなお悩みを解決するのがこちらの商品です!

SESAME4jp.candyhouse.co

いわゆるスマートロックというものですね。端的に言うと、遠隔で操作&状態が取得できる鍵です。

この手のデバイスではSwitchBotが有名ですが、あちらと比べてお値段半分以下、WebAPIも公開されてるのでまーソフト面で何か問題があっても最悪なんとかできるでしょう。*1*2*3

購入したものは、次の4つです。

  • sesami 4
  • wifiモジュール
  • sesami BoT x2

sesami4 を玄関の鍵に取り付け、BoTの方はオートロック用のインターフォンにつけてこちらの開錠をさせます*4

まず、玄関用のsesami4からセットアップします。

内容物はこんな感じ

サムターンみたいなのがついてますが、これの裏側が本物のサムターンを両側から挟む治具みたいになってます。

電池のとこに絶縁用のフィルムがはさまってるので、取り外してまずは動作確認をします。

スマホにsesamiアプリをインストールして鍵とペアリング(?)すると、最初に角度の設定(どのポジションで開錠、施錠とするか)をするように誘導されますが 後から変更できるので適当に設定してとりあえず動かしてみましょう。

youtu.be

無事に動くことが分かったので、実際にサムターンのところに取り付けます。 この部屋の鍵はちょっと特殊で、エントランスと各部屋のドアが1枚のカードキーで開けられるようになっています。

サムターンのとこに鍵穴が付いてるものと違ってカードキーを入れる部分が上側についていて微妙に高さが合わないのでこちら側から取り付けるのは諦めて 左側を思いっきり嵩上げして付けることにしました。

だいたい15mmくらい上げる必要があったのですが、近所のホームセンターを物色してたら厚み7mmの磁石(2個入り)が売られてたので、2段重ねにした上に両面テープをつけてsesami 4をのせました。

この状態でも、スマホで鍵の開閉ができてわざわざ鍵を取り出さなくても便利!! と言えなくもないのですが、BTで鍵と接続しないと状態も分からないし操作もできないのでさらにwifiモジュールを追加します。

USBの充電アダプタに挿して電源コンセントにつなぐと勝手に起動するので、スマホアプリからペアリングします。 その後、アプリのwifiモジュールの画面に行くとペアリング済の他のデバイスが表示されるので、そこからwifiモジュール経由で制御するデバイスを選びます。 (スクショ撮り忘れてたので記憶を頼りに書いてます。間違えてたらすみませんm(__)m)

設定が終わると、スマホとデバイスのBT接続が切れたら自動的にインターネット&wifiモジュール経由で制御してくれるようになります。

これで、デバイスから離れた場所に居てもネットさえ繋ればいつでも鍵がかかってるか確認できるようになりました。

しかし、このマンションはオートロックなので、これだけでは鍵を持たずに来たらエントランスで誰かが通るまで延々待ち続ける不審人物になってしまいます :p

エントランスのロックを解除するには

  1. エントランス側で部屋番号を押してインターホンを鳴らす
  2. 部屋の端末で受話器を持ち上げる
  3. 開錠ボタンを押す

という手順を踏む必要があります。

1は、訪問者(自分も含む)がやるので問題無いとして、部屋側では

インターホンが鳴ったら、受話器を持ち上げて、開錠ボタンを押す

という仕組みを作れば、スマホのみで部屋に入れるようになります。

まず、簡単に済む開錠ボタンの方から解決しましょう。 これは単に、sesami BoTをボタンのところに貼り付ければOKです。

youtube.com

しかし、ちょっとボタンを押した後アームの動きを止めるのが遅いようで、モニターが壁からちょっと浮いてしまってますね。

販売元に動画を添えて止めるタイミングを設定できないのかと相談してみたところ、ボタンにクッションシールなどを貼って高くしてみてはとのアドバイスをいただけました。

推測ですが、HW的には何かに当たったら止めるだけの機構しかないので、当たるタイミングを早くしてやればその分止まるのも早くなりますよってことだと思います。

実際にクッションシールを貼ってみたところ多少は改善できたので、開錠ボタンの方はこれで完成としました。

最後に面倒な受話器を持ち上げる処理です。

公式サイトのサポートには、ユーザ事例として凄い力技が紹介されてますがちょっとこれを真似する勇気はありません :(

最初の方で紹介した、競合(?)製品のswitch BoTの方はボタンを押すアームが中心部分にあるので、単に受話器を掛けておく部分に貼りつければ操作できるようです。 しかし、sesami botの場合本体の側面にアームがついているので、アームをフックボタンの位置に合わせて固定するのは至難の技です。

電話機側は、凹凸が激しかったり(受話器をひっかけるためのものなので下手に回避するとアームが届かなくなる)、曲面だったり(アームの初期位置が仰角になってしまってこちらもしっかりフックボタンを押せなくなる)といった問題があります。

それなら壁にステーでもつけて固定すれば良いんですが、壁面から4cmちょっと浮かさないといけないので、コの字ステーだと結構重たくなりそうです。

契約した直後に壁にがっつりと穴をあけるのもどうかと思うし、そもそもちょうど良い長さのコの字ステーを探すのが難しそうです。

そこでふと思い出したのが、自宅で使っている無印良品の壁につけられる家具シリーズです

www.muji.com

これなら壁への取り付けは押しピンなので、一応賃貸許されそうな範囲*5の傷で済む上に、耐荷重も十分です。

この棚を受話器の横に設置して、側面にL字ステーを取り付けて受話器の方に伸ばしてSesami BoTを設置するための足場を作るような感じで無事に設置できました。

ついでに棚の上に受話器も置いておけるので、一石二鳥です :)

実際に開錠してる時の様子を、室内から撮影するとこんな感じになってます。

youtu.be

まだいくつか課題は残ってますが、とりあえずこれで

  • 遠隔地から鍵がかかってるかどうか確認する
  • 遠隔地から鍵の開閉をする
  • スマホひとつ持っていけば、鍵が無くても部屋に入れる

といった状態にできました :D

*1:とはいえ、webサービス側で問題があったらどうしようもないですが

*2:一応AndroidiOS用のBT接続ライブラリは用意されているので気合があればAndroidスマホにTaskerでも入れて近くに置いとけばなんとかなるかも

*3:この記事を書いてる時に初めて気付いたんですが、switch botだったらラズパイからBT接続で制御できるライブラリがあったっぽい https://github.com/OpenWonderLabs/SwitchBotAPI

*4:部屋の契約直後によっしゃスマートロック導入だーと意気込んでSESAMI4とwifiモジュールを買った後、実際に入居してセットアップしたらオートロックの方も解除しないと外から開けられないことに気付いて、BoTを買い増しし、さらにBoT1台では受話器が上げられないので、ボタンを押してもエントランスの鍵が開けられないことに気付いてさらに買い増しといった経緯を経て買ったので、送料が3回分もかかってますorz

*5:本当に許されるかどうかは退去時に大家さんとご相談ください

M1 macにlabelImg をインストール

最近、何の因果かYOLOで遊ぼうというイベントの講師をしないかというお誘いをいただきまして、分不相応とは思いつつもやらせていただいてます。

で、そろそろ次回使うアノテーションツールの準備をしようと思ったんですが

VoTTはno longer being maintained!とか言ってるし

github.com

CVATはdocker前提のようなのでたぶんwindows勢が死ぬし

openvinotoolkit.github.io

VIAはclient sideのJSで動いてるらしいので、(データ数が増えた時とか、長時間作業した時)イマイチ信用がおけない*1

www.robots.ox.ac.uk

ということで、安定のlabelImgをインストールしようとしたんですが、M1 macだとすんなりとは導入できません。 READMEに書いてあるとおり、pipでインストールしようとすると、次のようなエラーが出ます。

% pip3 install labelImg                             
DEPRECATION: Configuring installation scheme with distutils config files is deprecated and will no longer work in the near future. If you are using a Homebrew or Linuxbrew Python, please see discussion at https://github.com/Homebrew/homebrew-core/issues/76621
Requirement already satisfied: labelImg in /opt/homebrew/lib/python3.9/site-packages (1.8.5)
Collecting pyqt5
  Using cached PyQt5-5.15.6.tar.gz (3.2 MB)
  Installing build dependencies ... done
  Getting requirements to build wheel ... done
  Preparing metadata (pyproject.toml) ... error
  error: subprocess-exited-with-error
  
  × Preparing metadata (pyproject.toml) did not run successfully.
  │ exit code: 1
  ╰─> [29 lines of output]
      Traceback (most recent call last):
        File "/opt/homebrew/lib/python3.9/site-packages/pip/_vendor/pep517/in_process/_in_process.py", line 156, in prepare_metadata_for_build_wheel
          hook = backend.prepare_metadata_for_build_wheel
      AttributeError: module 'sipbuild.api' has no attribute 'prepare_metadata_for_build_wheel'
      
      During handling of the above exception, another exception occurred:
      
      Traceback (most recent call last):
        File "/opt/homebrew/lib/python3.9/site-packages/pip/_vendor/pep517/in_process/_in_process.py", line 363, in <module>
          main()
        File "/opt/homebrew/lib/python3.9/site-packages/pip/_vendor/pep517/in_process/_in_process.py", line 345, in main
          json_out['return_val'] = hook(**hook_input['kwargs'])
        File "/opt/homebrew/lib/python3.9/site-packages/pip/_vendor/pep517/in_process/_in_process.py", line 160, in prepare_metadata_for_build_wheel
          whl_basename = backend.build_wheel(metadata_directory, config_settings)
        File "/private/var/folders/cp/mpvg43bj17xfjdsvlclvhf1m0000gn/T/pip-build-env-m96blqiu/overlay/lib/python3.9/site-packages/sipbuild/api.py", line 51, in build_wheel
          project = AbstractProject.bootstrap('pep517')
        File "/private/var/folders/cp/mpvg43bj17xfjdsvlclvhf1m0000gn/T/pip-build-env-m96blqiu/overlay/lib/python3.9/site-packages/sipbuild/abstract_project.py", line 83, in bootstrap
          project.setup(pyproject, tool, tool_description)
        File "/private/var/folders/cp/mpvg43bj17xfjdsvlclvhf1m0000gn/T/pip-build-env-m96blqiu/overlay/lib/python3.9/site-packages/sipbuild/project.py", line 594, in setup
          self.apply_user_defaults(tool)
        File "project.py", line 63, in apply_user_defaults
          super().apply_user_defaults(tool)
        File "/private/var/folders/cp/mpvg43bj17xfjdsvlclvhf1m0000gn/T/pip-build-env-m96blqiu/overlay/lib/python3.9/site-packages/pyqtbuild/project.py", line 70, in apply_user_defaults
          super().apply_user_defaults(tool)
        File "/private/var/folders/cp/mpvg43bj17xfjdsvlclvhf1m0000gn/T/pip-build-env-m96blqiu/overlay/lib/python3.9/site-packages/sipbuild/project.py", line 241, in apply_user_defaults
          self.builder.apply_user_defaults(tool)
        File "/private/var/folders/cp/mpvg43bj17xfjdsvlclvhf1m0000gn/T/pip-build-env-m96blqiu/overlay/lib/python3.9/site-packages/pyqtbuild/builder.py", line 67, in apply_user_defaults
          raise PyProjectOptionException('qmake',
      sipbuild.pyproject.PyProjectOptionException
      [end of output]
  
  note: This error originates from a subprocess, and is likely not a problem with pip.
error: metadata-generation-failed

× Encountered error while generating package metadata.
╰─> See above for output.

note: This is an issue with the package mentioned above, not pip.
hint: See above for details.

エラーメッセージを参考にいろいろググってみたところ

  • labelImgは pyqt5に依存
  • M1 mac用はpyqt6以降で対応

という状況のようでした・・・o..rz

こちらのnoteではpython3.7にダウングレードしたらいけたよ!って話ですが

note.com

さらにぐぐってたら、githubのこちらのissueにpyside6ブランチを試してみなってあったので、これでやってみようと思います。

note.com

> git clone https://github.com/tzutalin/labelImg.git
> cd labelImg
> git checkout pyside6
> pip3 install pipenv
> pipenv run pip install pyside6 lxml
> pipenv run make pyside6
> pipenv run python3 labelImg.py

これで、何の問題もなく、labelImgが起動しました。

最後にApplicationsフォルダにナイスなアイコンを表示できるようにインストールしておきましょう。

初めにsetup.pyがmain branch用のままなので、依存パッケージとしてpyqt5が入っているのをpyside6に変更します

> sed -i -e 's/pyqt5/pyside6/' setup.py

続いて、READMEの[Optional]と書かれた行のコマンドをちょっと変えて順に実行していきます。

> rm -rf build dist
> python3 setup.py py2app -A
> mv "dist/labelImg.app" /Applications

これでlaunchpadを開くと、素敵なアイコンが表示されてるはずです

あれ・・・???

この状態でも画像は無いですが、一応起動はしてくれます。 でもpipenv run python3 labelimg.pyで起動してた時は日本語表示だったのが、英語表示になってしまいましたorz

うーん、イマドキのpythonのパッケージャーとか良く分からんのでとりあえずpipenvから起動する方式でやることにします。

issueを眺めてるとqt6関連のissueが乱立してて、はたしてこのブランチでやるのが今後主流になるかどうか分かりませんが、とりあえず現時点ではこれで動かすのが一番簡単そうですね。

*1:実際に評価したわけではないので、やってみたら特に問題はでないかもしれませんが・・・