粗大メモ置き場

個人用,たまーに来訪者を意識する雑記メモ

夫婦共働きにおける家計管理どうしてますか? 〜 我が家のケース 〜

はじめに

現在の日本にて夫婦共働きという家庭は結構あるかと思います。

しかし、肝心な家計管理の手法については下記のようなまとめサイトばかりで実際どのようにやりくりしているのかといった情報が少ないように思います。

www.smbc-card.com

この記事はひとまず自身のケースを備忘録として公開して、あわよくば他の方の情報も聞きたいという趣旨のもと書かれています。

目指す運用

前提

大前提として、

  • 夫婦共働き
  • 年収や労働時間に極端な差がない

こととします。また、ちゃんと話していませんが家計管理の目的は「支出の管理と貯蓄」にあるものとします。

家計管理に求めるもの

とりあえず家計管理のシステムに求めるのは下記の2点です。

  • 貯蓄や日常生活の支出については明確に管理したい
  • 私費や趣味に使ったお金はある程度相手からわからないようにしたい


また、原則として以下のような思想のもとやりくりを考えています。

  • 必須なもの・面倒なものは自動化
  • それ以外は人の手で調整できる余地を残す

家計管理サンプル

とりあえず、我が家でやっていることを下記に記します。

共有口座

f:id:ossyaritoori:20211027001659p:plain

個人で持つ口座の他に共有口座を一つ作って、そこから家賃などの固定費を支払うようにしています。

入金額はお互い相談して決めますが、固定費より多めに入れることで貯蓄用の口座としても振る舞えるようにしています。

良い点

  • 年ごとに決まった固定額を入れるだけなので管理が楽。
  • 個人の細かな出費を共有しなくても良いので気楽。

課題に感じる点

  • 結局、共有口座よりも個別口座にお金が溜まっている(いくら入れるか問題)。
  • 相手の個別口座や資産の状況についてあまり把握できない。
    • 年1で年間の収支を共有するイベントをするのが良いと思うがまだやってないのでわからない。

貯蓄用と固定費用の口座は分けるという意見もあるようですが、口座を増やすと管理がめんどくさそうなので今のところはやっていないです。

家のための支払いの集計

食べ物の買い出しなどを始めとする「家のための買い物」はお互いが気づいたときにそれぞれ行うためどうしても出費に差が出てしまいます。

会社での経費申請のように購入時のレシートをもとに集計して支払いの差額を可視化するようにしています。

f:id:ossyaritoori:20211027003118p:plain

※ Zaimの採用理由(クリックして展開) ちゃんと検討したわけではありませんが、下記のような理由でZaimを採用しました。

  • 著名なアプリで信用できると思った
  • Money forwardをすでに個人で使っていた
  • 集計結果をcsvでダウンロードする機能が無課金でできる(PCでの扱いが楽)

Tips

  • 共有Gmailアカウントを使うことで双方のデバイスから経費登録できる。
  • Zaim上でどちらの支払いか区別するためにカテゴリを2つに絞る。(例:夫の支払いは全て「車」カテゴリとする)

良い点

  • レシートを撮影するだけで全て登録できるので慣れれば早い

課題

  • 個人の収支管理(Money forward)と二度手間な感じがする
  • 物品を確認することをしないので悪意があれば容易に経費申請しまくれる(脆弱)

わからないこと

個別口座と共有口座の貯蓄の比率

大体の場合、個別口座と共有口座のそれぞれで貯蓄をしていくこととなるかと思いますがこれをどちらに振るべきかわかっていません。

共有口座はどちらかの名義で作られることが多いかと思いますが、場合によっては口座名義人へのパートナーからの「贈与」として税務署に目をつけられるケースもあるようです。(もっとずっと先の話な気もする。)

diamond.jp

style.nikkei.com

個別口座に貯めるとするとお互いにどれくらい貯めるかなどの合意をきちんと取る必要があるのが大変そうに感じます。

個別口座情報の共有(全体の支出をどう集計するか?)

今まで話したとおり、お互いに個別管理の口座には突っ込まないのが前提になっていますが、とはいえ家計全体の収支やその内訳は把握しておきたいわけで年1などで決算報告などをする必要があるイメージでいますがこのあたりを具体的にどうやるかまだ決めていません。

最低限、

  • 収入と支出額、投資口座の金額
  • 支出の内訳(カテゴリ毎)

は知りたいはずですがうまいこと細かい情報にマスクしつつ知りたい情報を共有する仕組みがあればいいなと思います。

調べると、今はOsidoriというアプリが無料でそのあたりの機能をそろえているそうなので気になっているところです。

www.osidori.co

おわりに

ということで家計管理@我が家のケースをご紹介しました。

共有口座とレシートで家計での経費管理をすることでそれなりに公平そうな仕組みにはできていそうですがもっと良い方法があるなら知りたいです。

一つ確実にオススメできるのは夫婦で何かと外部とやり取りする際、共有のGoogleアカウントがかなり便利(メール・TODOの共同編集やアプリ連携など)ということです。

オドメトリを連結している別の座標系に移す時の座標変換計算(ROS)

概要

下記のようなシチュエーションのオドメトリ変換を考えます。

Bodyに固定したセンサでとったodometryをbase_linkでのオドメトリに変換するのが目的です。 (ROSでよくあるシチュエーションだと思います。)

f:id:ossyaritoori:20210828003827p:plain

注意:速度変換の部分に自信がないです。詳しい方訂正・コメントお願いします。

tfを使った解法

ROSを使う人なら位置変換に関しては普通にROSのtfを

/base_link(q_0) -> /sensor_frame(q_0) -> /sensor_frame(q_c) -> /base_link(q_c)

のような感じでつないでlookupTransformで解決すれば位置の変換が取得できるとお思いになるかと思います。 一方で、速度(twist)の解決は私の知る限りサポートされていないように思います。

一応、lookupTwistというのがあるのですがこれはlookupTransformの結果を数値微分しているっぽいのであまり正確な値は期待できません。

また、複数のセンサがある場合などは複数の経路ができてtf treeの構造を壊しかねないのでエスケープのために余計なリンクをたくさん定義することになります。

自分で計算するときの数式

以上の課題を解決するために自分で計算していきます。

位置の変換

同次行列をつないでいけば、base_linkの座標系で見たOdometryのPoseは

 \displaystyle
\begin{bmatrix} R & t \\ 0 & 1 \end{bmatrix} = \begin{bmatrix} R_c & t_c \\ 0 & 1 \end{bmatrix}\begin{bmatrix} R_o & t_o \\ 0 & 1 \end{bmatrix}\begin{bmatrix} R_c & t_c \\ 0 & 1 \end{bmatrix}^{-1}

で計算できます。これを全て計算すると下記のようになります。

 \displaystyle
\begin{bmatrix} R & t \\ 0 & 1 \end{bmatrix} = \begin{bmatrix} R_c R_o R_c ^\top & -R_c R_o R_c ^\top t_c + t_c + R_c t_o \\ 0 & 1 \end{bmatrix}


ここで、 R_o,R_cが可換の時、具体的にはyaw回転しかしない自動車などのアプリケーションでカメラを水平に構えた時などは

 \displaystyle
\begin{bmatrix} R & t \\ 0 & 1 \end{bmatrix} = \begin{bmatrix} R_o  & - R_o t_c + t_c + R_c t_o \\ 0 & 1 \end{bmatrix} \mbox{(R_oとR_c が可換のケース)}

とすることができます。

どこかが違うtwistの変換 (Pose変換の微分で解く)

Poseの変換がわかったので上記の式を時刻qで微分することで、Twist変換に変換することができます。(よね?)

なんか上記の仮定が間違っている気がしてきました。 一応途中式は残しておきます。結果が少し異なるのですが何が違うのかちょっと自信がないので

何かがおかしい気がする導出

記述量削減のため  \frac{dR_x}{dq} = \tilde{\omega_x} R_x, \frac{dt_x}{dq} = v_x と記述することにします。 ここで \omega_xは角速度で、 \tilde{\omega_x}はその交代行列です。この辺の話は面白いのでぜひ参考文献をご覧あれ。

 R_o,t_o のみが時変なので微分は下記のように計算できます。

 \displaystyle
\begin{bmatrix} \tilde{\omega} R & v \\ 0 & 1 \end{bmatrix} = \begin{bmatrix} R_c \tilde{\omega_o} R_o R_c ^\top &  -R_c \tilde{\omega_o} R_o R_c ^\top t_c + R_c  v_o  \\ 0 & 1 \end{bmatrix}

角度の関係は

 \displaystyle
\tilde{\omega} R = \tilde{\omega} R_c R_o R_c ^\top =R_c \tilde{\omega_o} R_o R_c ^\top

を解いて

 \displaystyle
\tilde{\omega} =R_c \tilde{\omega_o}  R_c^\top

となります。角速度の関係は参考文献[2]から \omega = R_c \omega_oと簡単な形に求まります。

並進速度は下記の感じになります。

 \displaystyle
v = -R_c \tilde{\omega_o} R_o R_c ^\top t_c + R_c  v_o

また、R_o R_c が可換のケースでは下記のようになります。

 \displaystyle
\tilde{\omega} = \tilde{\omega_o}  \\
v = - \tilde{\omega} R  t_c + R_c  v_o \\
\mbox{(R_o R_c が可換のケース)}

並進速度の第一項は回転に伴うモーメントのような項ですね。

twistの変換 (多分こっちが正しい。)

twist(速度・加速度)の変換に自信がなく結構調べたのですが 調べる際によく出る例として下記の質疑があります。参考文献[4]のスライドがいい感じだと思われます。

physics.stackexchange.com

要約すると座標Aで見た速度を座標A'で見たときにどうなるかを表す式は

 \displaystyle
\begin{pmatrix}
v_{A'} \\ 
\omega_{A'}
\end{pmatrix} = \begin{pmatrix}
R_{A'A} & \hat{t}_{A'A}R_{A'A} \\ 
0 & R_{A'A}
\end{pmatrix} \begin{pmatrix}
v_{A} \\ 
\omega_{A}
\end{pmatrix}

とかけるというものです。

自分の変数に書き下すと下記の通りになります。vは並進tを時間微分したものです。

 \displaystyle
\begin{pmatrix}
v \\ 
\omega
\end{pmatrix} = \begin{pmatrix}
R_c & \tilde{t}_cR_c \\ 
0 & R_c
\end{pmatrix} \begin{pmatrix}
v_{o} \\ 
\omega_{o}
\end{pmatrix}

ここで、 \tilde{\omega}は角速度ベクトルの交代行列であり、 \dot{R_x} = \tilde{\omega_x} R_x のように微小回転行列を表せます。 この辺は結構面白いのでリー代数や三次元回転についての記述を参照してください。(参考文献1)

展開すると並進速度は外積の性質などを用いて、

 \displaystyle
v = \tilde{t}_cR_c \omega_o   + R_c v_o\\
= t_c \times R_c \omega_o   + R_c v_o\\
=  - \omega \times t_c + R_c v_o\\
= - \tilde{\omega} t_c + R_c v_o

とかけます。

  • 導出

導出の元になる数式は下記のようになっています。

 \displaystyle
\begin{bmatrix} \tilde{\omega} & v \\ 0 & 1 \end{bmatrix} = \begin{bmatrix} R_c & t_c \\ 0 & 1 \end{bmatrix}\begin{bmatrix} \tilde{\omega}_o & v_o \\ 0 & 1 \end{bmatrix}\begin{bmatrix} R_c & t_c \\ 0 & 1 \end{bmatrix}^{-1}

角度について展開すると

 \displaystyle
\tilde{\omega} =R_c \tilde{\omega_o}  R_c^\top

となります。角速度の関係は参考文献[2]から \omega = R_c \omega_oと簡単な形に求まります。

並進速度は下記のように書けます。

 \displaystyle
v = -R_c \tilde{\omega_o}  R_c ^\top t_c + R_c  v_o 
= -\tilde{\omega} t_c + R_c v_o

補足

  • 交代行列について

  \omega = \begin{bmatrix} r , p ,  y \end{bmatrix} ^\top の時、

 \displaystyle
\tilde{\omega} = \begin{bmatrix}  0 & -y & p\\ y & 0 & -r\\ -p & r &0  \end{bmatrix}

この行列は下記の性質を持ちます。

 \displaystyle
\tilde{\omega}^\top  = - \tilde{\omega}
  •  \omega = R_c \omega_o の導出

スマートな解釈としては参考文献[3]の式(8)あたりを見てもらうと良いですが、実は以下の2式をゴリゴリ成分計算することでも求まります。

 \displaystyle
\tilde{\omega} =R_c \tilde{\omega_o}  R_c^\top

f:id:ossyaritoori:20210828131106p:plain
rpyの角度から算出する三次元回転行列('sxyz')

まとめ

ということで機体から  R_c,t_cの位置に取り付けられたセンサのオドメトリ R_o,t_o,\omega_o,t_oから得られる機体のオドメトリ R,t

 \displaystyle
R = R_c R_o R_c ^\top \\
t = -R_c R_o R_c ^\top t_c + t_c + R_c t_o  \hspace{5mm}= -R t_c + t_c + R_c t_o\\
\omega =R_c \omega_o    \\
v = -R_c \tilde{\omega_o}  R_c ^\top t_c + R_c  v_o  \hspace{5mm} = - \tilde{\omega}  t_c + R_c v_o


で表されます。上から順に計算するなら右側の別解を使ったほうがスムースかと思われます。


なお、回転がYawしかないような特殊ケースでR_o,R_cが可換の場合は

 \displaystyle
R = R_o  \\
t = - R_o t_c + t_c + R_c t_o \\
\omega =R_c \omega_o    \\
v = - \tilde{\omega}  t_c + R_c v_o

となります。

正直Twistの速度の変換は下記の数式と異なるためどこか間違えている気がしないでもないのでご指摘よろしくおねがいします。

参考文献

フォーマット適当ですが下記の文書が参考になります。

[1] 金谷先生 「3次元回転: パラメータ計算とリー代数による最適化」

[2] 角速度ベクトルと回転行列の時間微分【力学の道具箱】 | スカイ技術研究所ブログ

[3] ベクトルの成分表示と座標変換【力学の道具箱】 | スカイ技術研究所ブログ

[4] http://www.eeci-institute.eu/pdf/M5-textes/M5_slides4.pdf

matplotlibのインタラクティブなプロットを作る覚書(スクロールでズーム、ドラッグで移動)

背景

オレオレGUIを作る際にインタラクティブなPlotがしたいという動機です。

インタラクティブなPlotについては下記が結構ボリュームがあって良いと思います。

qiita.com

サンプルコード①:スクロールで拡大縮小、ドラッグで移動

下記StackOverflowの議論からコードをもらってPython3用に改変しました。

stackoverflow.com

動作は下記の2つで、それぞれ関数が割り当てられています。

  • スクロールで拡大縮小
  • クリック&ドラッグで移動
from matplotlib.pyplot import figure, show
import numpy

class ZoomPan:
    def __init__(self):
        self.press = None
        self.cur_xlim = None
        self.cur_ylim = None
        self.x0 = None
        self.y0 = None
        self.x1 = None
        self.y1 = None
        self.xpress = None
        self.ypress = None


    def zoom_factory(self, ax, base_scale = 2.):
        def zoom(event):
            cur_xlim = ax.get_xlim()
            cur_ylim = ax.get_ylim()

            xdata = event.xdata # get event x location
            ydata = event.ydata # get event y location

            if event.button == 'down':
                # deal with zoom in
                scale_factor = 1 / base_scale
            elif event.button == 'up':
                # deal with zoom out
                scale_factor = base_scale
            else:
                # deal with something that should never happen
                scale_factor = 1
                print(event.button)

            new_width = (cur_xlim[1] - cur_xlim[0]) * scale_factor
            new_height = (cur_ylim[1] - cur_ylim[0]) * scale_factor

            relx = (cur_xlim[1] - xdata)/(cur_xlim[1] - cur_xlim[0])
            rely = (cur_ylim[1] - ydata)/(cur_ylim[1] - cur_ylim[0])

            ax.set_xlim([xdata - new_width * (1-relx), xdata + new_width * (relx)])
            ax.set_ylim([ydata - new_height * (1-rely), ydata + new_height * (rely)])
            ax.figure.canvas.draw()

        fig = ax.get_figure() # get the figure of interest
        fig.canvas.mpl_connect('scroll_event', zoom)

        return zoom

    def pan_factory(self, ax):
        def onPress(event):
            if event.inaxes != ax: return
            self.cur_xlim = ax.get_xlim()
            self.cur_ylim = ax.get_ylim()
            self.press = self.x0, self.y0, event.xdata, event.ydata
            self.x0, self.y0, self.xpress, self.ypress = self.press

        def onRelease(event):
            self.press = None
            ax.figure.canvas.draw()

        def onMotion(event):
            if self.press is None: return
            if event.inaxes != ax: return
            dx = event.xdata - self.xpress
            dy = event.ydata - self.ypress
            self.cur_xlim -= dx
            self.cur_ylim -= dy
            ax.set_xlim(self.cur_xlim)
            ax.set_ylim(self.cur_ylim)

            ax.figure.canvas.draw()

        fig = ax.get_figure() # get the figure of interest

        # attach the call back
        fig.canvas.mpl_connect('button_press_event',onPress)
        fig.canvas.mpl_connect('button_release_event',onRelease)
        fig.canvas.mpl_connect('motion_notify_event',onMotion)

        #return the function
        return onMotion


fig = figure()

ax = fig.add_subplot(111, xlim=(0,1), ylim=(0,1), autoscale_on=False)

ax.set_title('Click to zoom')
x,y,s,c = numpy.random.rand(4,200)
s *= 200

ax.scatter(x,y,s,c)
scale = 1.1
zp = ZoomPan()
figZoom = zp.zoom_factory(ax, base_scale = scale)
figPan = zp.pan_factory(ax)
show()

出力は下記のようになります。

f:id:ossyaritoori:20210726210806p:plain
サンプルとして用いる散布図グラフ。ホイールで拡大縮小、ドラッグで移動ができます。

ズーム動作

関数の最初にzoom用の関数を定義して、下記のコードでコールバック関数として渡しているのがわかります。

fig.canvas.mpl_connect('scroll_event', zoom)

そして、受け手の関数では変数eventから得られるスクロールの上下に関する情報をもとに図の拡大縮小を行っています。

ドラッグ動作

一方ドラッグ動作では、クリックされたときと離されたとき、マウスをドラッグしたときの動作にそれぞれ関数を割り当てて、 コールバックを呼んでいます。

# attach the call back
        fig.canvas.mpl_connect('button_press_event',onPress)
        fig.canvas.mpl_connect('button_release_event',onRelease)
        fig.canvas.mpl_connect('motion_notify_event',onMotion)

その他の動作

つまり、どんなイベントがあるかだけ把握すれば適切なコールバック関数を渡してあげることでいろいろな動作ができるということです。

では、実際にどんなイベントがあるかについては下記公式ページを参照してください。

matplotlib.org

次のサンプルではkey_press_eventを使います。

GUI動作は組み合わせで行うことが多いのでサンプルコードのようにクラスを定義してその中でどのボタンがホールドされているかなどの変数を保持しておくと捗ると思います。

サンプルコード②:スクロールで左右ズーム、Ctrl押しながらのスクロールで上下ズーム

先程のコードを時系列Plot用に改良しました。

主な動作としては下記の通りになります。

  • スクロール:X軸のみズーム
  • Ctrl+スクロール:Y軸のみズーム
  • ドラッグ:並行移動
  • 「r」キー:描画範囲リセット
from matplotlib.pyplot import figure, show
import numpy
import matplotlib
matplotlib.use('TKAgg')

class ZoomPan:
    def __init__(self,ax):
        self.press = None
        self.cur_xlim = None
        self.cur_ylim = None
        self.x0 = None
        self.y0 = None
        self.x1 = None
        self.y1 = None
        self.xpress = None
        self.ypress = None

        self.ctrl_press = False

        self.ax = ax
        self.orig_xlim = ax.get_xlim()
        self.orig_ylim = ax.get_ylim()


        self.zoom_factory(ax,base_scale=1.1)
        self.ctrl_key(ax)
        self.pan_factory(ax)

    def zoom_factory(self, ax, base_scale = 2.):

        def zoomX(event,scale_factor):
            cur_xlim = ax.get_xlim()
            xdata = event.xdata # get event x location
            new_width = (cur_xlim[1] - cur_xlim[0]) * scale_factor
            relx = (cur_xlim[1] - xdata)/(cur_xlim[1] - cur_xlim[0])

            ax.set_xlim([xdata - new_width * (1-relx), xdata + new_width * (relx)])
            ax.figure.canvas.draw()

        def zoomY(event,scale_factor):
            cur_ylim = ax.get_ylim()
            ydata = event.ydata # get event y location
            new_height = (cur_ylim[1] - cur_ylim[0]) * scale_factor
            rely = (cur_ylim[1] - ydata)/(cur_ylim[1] - cur_ylim[0])

            ax.set_ylim([ydata - new_height * (1-rely), ydata + new_height * (rely)])
            ax.figure.canvas.draw()

        def zoom(event):
            if event.button == 'down':
                # deal with zoom in
                scale_factor = 1 / base_scale
            elif event.button == 'up':
                # deal with zoom out
                scale_factor = base_scale
            else:
                # deal with something that should never happen
                scale_factor = 1
                print(event.button)

            ####### Switch zoom X or Y #########
            if self.ctrl_press:
                zoomY(event,scale_factor)
            else:
                zoomX(event,scale_factor)

        fig = ax.get_figure() # get the figure of interest
        fig.canvas.mpl_connect('scroll_event', zoom)

        return zoom

    def ctrl_key(self,ax):
        def onPress(event):
            #print(event.key)
            if event.inaxes != ax: return
            if event.key == "control":
                self.ctrl_press = True
            elif event.key == "r": # reset zoom
                ax.set_xlim(self.orig_xlim)
                ax.set_ylim(self.orig_ylim)
                ax.figure.canvas.draw()

        def onRelease(event):
            #print(event.key)
            if event.inaxes != ax: return
            if event.key == "control":
                self.ctrl_press = False
        
        fig = ax.get_figure() # get the figure of interest

        # attach the call back
        fig.canvas.mpl_connect('key_press_event',onPress)
        fig.canvas.mpl_connect('key_release_event',onRelease)

    def pan_factory(self, ax):
        def onPress(event):
            if event.inaxes != ax: return
            self.cur_xlim = ax.get_xlim()
            self.cur_ylim = ax.get_ylim()
            self.press = self.x0, self.y0, event.xdata, event.ydata
            self.x0, self.y0, self.xpress, self.ypress = self.press

        def onRelease(event):
            self.press = None
            ax.figure.canvas.draw()

        def onMotion(event):
            if self.press is None: return
            if event.inaxes != ax: return
            dx = event.xdata - self.xpress
            dy = event.ydata - self.ypress
            self.cur_xlim -= dx
            self.cur_ylim -= dy
            ax.set_xlim(self.cur_xlim)
            ax.set_ylim(self.cur_ylim)

            ax.figure.canvas.draw()

        fig = ax.get_figure() # get the figure of interest

        # attach the call back
        fig.canvas.mpl_connect('button_press_event',onPress)
        fig.canvas.mpl_connect('button_release_event',onRelease)
        fig.canvas.mpl_connect('motion_notify_event',onMotion)

        #return the function
        return onMotion


fig = figure()

ax = fig.add_subplot(111, xlim=(0,1), ylim=(0,1), autoscale_on=False)

ax.set_title('Click to zoom')
x,y,s,c = numpy.random.rand(4,200)
s *= 200

ax.scatter(x,y,s,c)
scale = 1.1
zp = ZoomPan(ax)
show()

key_press_event を使ったフラグ管理と注意点

コントロールキーを押しているか管理するためにkey_press_eventを使っています。

キーが押されたらフラグを立てて、キーを話したらフラグを下ろすという2つのコールバックを定義しています。 その他に「r」を押した際に描画範囲をリセットする機能も同時に書いています。

ハマったバグ
Macで開発しているときにハマったのがOS Xのバックエンドだとcontrolキーが押されたかチェックできない問題です。

python - Close pyplot figure using the keyboard on Mac OS X - Stack Overflow

上記の質疑のようにバックエンドをTKAggに変えてことなきを得ました。ちょっと画質が荒くなる感じがしてあまり好きではありませんが。。。

import matplotlib
matplotlib.use('TKAgg')

これ以外にもキーの同時押しのときはバックエンドによって出てくる値が変わるなどこのあたりは結構気をつけることがありそうです。

スマホ外付けの望遠レンズで月は撮れるか(OpenCVで実倍率を検証)

概要

スマホの外付けレンズというのが果たして実用に堪えるのか前から気になっていたので夏休みに買ってみて検証してみました。

先に所感をまとめると以下のとおりです。

  • クリップは固定に不安
  • 望遠とマクロはそこそこ楽しい
  • 真面目にやるなら三脚は必須
  • 中華のズーム倍率は信用してはいけない

また,一応スマホでも月は撮れます。

f:id:ossyaritoori:20210723185405p:plain
Pixel4,3.7倍と望遠レンズ(約10倍)で撮影した上弦の月

スマホと外付けレンズ

検証に用いたのは下記のレンズキットです。

自称望遠22倍,マクロ,広角,魚眼レンズを備えているということでした。

また,スマホとしてPixel4とHuaweiのNova3を用いました。

レンズを使ってみての感想

100円レンズだと曇ったり周辺がぼやけたりするそうですが,全般的にレンズをつけて著しく画質が劣化するということはなかったです。

ただ,広角も魚眼レンズもほしい場面があまりなく後述のクリップの手間を考えても持ち運んで気軽にスマホにつけるというような運用ではないと思いました。

レンズ おすすめ度 感想
広角 広角で撮りたいシーンが自撮りくらいしか思いつかない。
魚眼 ちゃんと魚眼になるけど何に使うのか不明。
マクロ スマホでマクロ撮影できるのは意外と楽しい。位置合わせも楽。
望遠 ちゃんとセッティングすれば結構遊びがいがある。が,準備がだるい。

f:id:ossyaritoori:20210723190338p:plain
昔買ったルースをマクロで撮影。生活感のない撮影対象が他になかった。

クリップの使用感

商品写真の通り,クリップにレンズをはめてスマホのカメラと位置合わせをすることで撮影ができます。 これは正直慣れが必要で,

  • 位置合わせが難しい(特に望遠レンズ)
  • 望遠レンズ着用時は自重でズレが起きやすい

という問題があるため,特に望遠レンズで「正しくはめて」「正しく目標物に向ける」というプロセスが非常に撮影において時間がかかります。

もう少しきちんとした三脚があるとこのあたりの安定度が全然違うので,望遠レンズを使う方は今後のカメラ購入も見越して用意しておいたほうがいいと思います。

冒頭の画像はセッティングに5分程度かかっており,初めての場合はもっと掛かると思ったほうが良さそうです。

望遠倍率が表記と違う問題

商品説明では22倍ズームとあるのですが正直そんなにズームしている感じはしませんでした。 口コミでも倍率が低めとあったのでOpenCVを使って確認してみます。

Gistソースコード

特徴点のマッチングを用いてレンズの有無の画像の間の倍率を計算すると大体9.72倍と出ました。 22倍とは…

f:id:ossyaritoori:20210723183532p:plain
望遠レンズなし(左)と有り(右)。SIFT特徴点のマッチング結果を線で示しています。

この手の商品あるあるとして,1つのコア技術の製造元に対して外装をちょっと変更していろんな業者が売るという構造になっている事が多いので他の似た製品でも望遠レンズの倍率はせいぜい10倍弱になっているというのはあると思います。

製品仕様くらいはきちんと記述してほしいです。

参考:一眼レフで撮るとこうなる

いろいろあってSONYのα6400というカメラを手に入れました。

で、これで月を撮るとこんな感じになりました。

f:id:ossyaritoori:20210723010837p:plain
α6400の付属レンズで撮った月(3.8倍、JPEG画質)。一眼レフはやはり違った。

一番感動したのは手ブレ補正か倍率低さのおかげか三脚なしの手持ちでちゃんと撮影できたことです。 スマホの設定だと基本的に露光が長くなるので三脚なしはありえないのでこのあたりの撮影の手間は断然こちらの方が楽でした。

ちなみにコンデジで撮ると下記のような感じになります。

ossyaritoori.hatenablog.com

まとめ

ということで今回の所感です。

  • 安い外付けレンズでもスマホで月を撮れるが設備投資(特に三脚)が必要。
  • マクロと望遠意外は特に用がなさそう。
  • 望遠倍率だいたいサバ読んでる。
  • 一眼レフはいいぞ。

Python,PyQtで簡単にGUIを作る ② Splitterを使った配置とcsvのPlotter

目標

PythonでちゃっちゃとGUIを作ることを目標にしています。

前回に引き続き下記の2つを満たすようなものを作成していきます。

  • 複数ある要素を選択
  • 選択した要素をグラフに随時プロット

ossyaritoori.hatenablog.com

f:id:ossyaritoori:20210718014237p:plain
今回つくるやつです。左でcsvのHeaderを選択して右の領域に該当する値がPlotされます。

今回やること

ざっくりと下記のことをやっていきます。

  • Splitterを用いた配置
  • pandasと組み合わせて数値をPlot

Splitterを用いた配置

SetGeometryなどを用いずにSplitterという仕切りを使ってWidgetを配置する方法を用います。

下記のチュートリアルを見てみましょう。

www.finddevguides.com

使い方はVboxたちと似通っていて定義後はaddWidgetでWidgetを追加できます。

#from PyQt5.QtWidgets import *
      splitter1 = QSplitter(Qt.Horizontal) #横向きにStackする
      textedit = QTextEdit()
      textedit2 = QTextEdit()
      splitter1.addWidget(textedit2)
      splitter1.addWidget(textedit)
# 中略
# 最後に予め作成したQVboxlayoutに登録
      vbox.addWidget(splitter1)

また,SplitterにSplitterを追加できるため下記のようにプログラムを書けばいろいろ追加できます。

f:id:ossyaritoori:20210718103855p:plain
上がSplitter1で分けた領域,そしてSplitter2ではSplitter1と下のWidgetを分けている

#!/usr/bin/python3
# -*- coding: utf-8 -*-

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *

"""
Qt splitter のチュートリアル
https://www.finddevguides.com/Pyqt-qsplitter-widget

Example:Qwidgetから継承
SplitterはaddWidgetで部品を追加可能。Splitter自身も追加可能
基本的に一つの領域に一つの部品を配置できる。
"""

class Example(QWidget):

   def __init__(self):
      super(Example, self).__init__()

      self.initUI()

   def initUI(self):

      hbox = QHBoxLayout(self)
      
      # フレームを作る
      topleft = QFrame()
      topleft.setFrameShape(QFrame.StyledPanel)
      bottom = QFrame()
      bottom.setFrameShape(QFrame.StyledPanel)

      # 水平に分割
      splitter1 = QSplitter(Qt.Horizontal)
      textedit = QTextEdit()
      textedit2 = QTextEdit()
      textedit3 = QTextEdit()
      splitter1.addWidget(textedit2)
      splitter1.addWidget(textedit)
      splitter1.setSizes([100,200])

      splitter2 = QSplitter(Qt.Vertical)
      splitter2.addWidget(splitter1)
      splitter2.addWidget(textedit3)

      # Widgetを追加
      hbox.addWidget(splitter2)

      # レイアウト適用
      self.setLayout(hbox)
      QApplication.setStyle(QStyleFactory.create('Cleanlooks'))

      # Windowサイズを規定
      self.setGeometry(300, 300, 300, 200)
      self.setWindowTitle('QSplitter demo')
      self.show()

def main():
   app = QApplication(sys.argv)
   ex = Example()
   sys.exit(app.exec_())

if __name__ == '__main__':
   main()

csvファイルを読んでPlotする

前回の記事と合わせると冒頭で示したようなcsvファイルから数値列を読んでPlotするGUIが作れます。

流れは下記のとおりです。

  • csvを読み込んでPandasのDataFrameに落とす
  • 数値列のみを抽出してColumn名をリスト化
  • GUIを作成,選択したColumnリストをもとにPlot

データとして使うCSVはAtushi Sakai大先生のリポジトリから拝借してきました。

github.com

pandasで非数値列を弾く方法

CSVを読んでPlotするのは良いですがその際,非数値が混じってエラー終了するのは避けたいです。

下記のようにselect_dtypesを使えば非数値列を排除できて安全です。

        # dfの非数値の列のみ選択
        self.df = df.select_dtypes(include='number')

コード

#!/usr/bin/python3
# -*- coding: utf-8 -*-

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import pandas as pd 
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas


"""
Qt splitter のチュートリアル
https://www.finddevguides.com/Pyqt-qsplitter-widget

Example:Qwidgetから継承
SplitterはaddWidgetで部品を追加可能。Splitter自身も追加可能
基本的に一つの領域に一つの部品を配置できる。
"""

class Example(QWidget):
    def __init__(self,filename=None):
        super(Example, self).__init__()
        
        self.loadcsv(filename)
        self.initUI()
    
    def loadcsv(self,filename):
        df = pd.read_csv(filename)
        # 非数値の列のみ選択
        self.df = df.select_dtypes(include='number')
        print(self.df)
        self.columnlist = self.df.columns # list

    def initUI(self):
        hbox = QHBoxLayout(self)
    
        # list/figure作成
        self.create_listwidget()
        self.create_figure()

        # 左側作成
        splitter_l = QSplitter(Qt.Vertical)
        splitter_l.addWidget(self.add_label("Plotする要素を選択"))
        splitter_l.addWidget(self.listWidget)
        # 右側作成
        splitter = QSplitter(Qt.Horizontal)
        splitter.addWidget(splitter_l)
        splitter.addWidget(self.FigureCanvas)
        #splitter.setSizes([100,200])

        # Widgetを追加
        hbox.addWidget(splitter)

        # レイアウト適用
        self.setLayout(hbox)
        QApplication.setStyle(QStyleFactory.create('Cleanlooks'))

        # Windowサイズを規定
        #self.setGeometry(300, 300, 300, 200)
        self.setWindowTitle('CSV plotter demo')
        self.show()

    def create_listwidget(self):
        # リスト作成
        self.listWidget = QListWidget()
        self.listWidget.setSelectionMode( #複数選択可能にする
            QAbstractItemView.MultiSelection
        )
        for col in self.columnlist:
            item = QListWidgetItem(col)
            self.listWidget.addItem(item)
        self.listWidget.itemClicked.connect(self.printItemText)
    
    def create_figure(self):
        # Figureを作成
        self.Figure = plt.figure()
        self.FigureCanvas = FigureCanvas(self.Figure)  # FigureをFigureCanvasに追加
        self.axis = self.Figure.add_subplot(1,1,1) # axを持っておく



    def add_label(self,text):
        # ラベルを作る
        label=QLabel(self)
        label.setText(text)
        label.adjustSize()
        return label


    def printItemText(self):
        # 選択したアイテムを表示
        items = self.listWidget.selectedItems()
        self.selected_columns = []
        for i in range(len(items)):
            self.selected_columns.append(self.listWidget.selectedItems()[i].text())

        self.plot_selected_columns()

    # Plotをアップデート
    def plot_selected_columns(self):
        self.axis.cla() # リセットを掛ける必要がある。
        df = self.df[self.selected_columns]
        df.plot(ax=self.axis)
        plt.grid(); 
        self.FigureCanvas.draw()


def main():
   app = QApplication(sys.argv)
   ex = Example("samplecsv.csv")
   sys.exit(app.exec_())

if __name__ == '__main__':
   main()

余談:Checkbox形式への変更

Listwidgetは結構見た目があれなので自分的にはCheckboxのほうが好きだったりします。 下記のように設定すればCheckboxを導入できます。

    # Checkbox Widgetの作成
    def add_checkbox(self,name):
      chxlist = QCheckBox(name, self)
      chxlist.stateChanged.connect(self.checklist_clicked)
      return chxlist
    
    #  クリックされたCheckboxをみてリストに追加 or 削除
    def checklist_clicked(self, state):
        checked_name = str(self.sender().text())
        if state == Qt.Checked: # if checked
            # add to the list
            self.clicked_list.append(checked_name)
        else: # if unchecked
            # remove from list
            self.clicked_list = [x for x in self.clicked_list if not x == checked_name]

余談2: Checkboxをまとめて導入

Checkboxをまとめて導入したいときは,上記のように一個一個Widgetを作るのではなく,VboxLayoutを用意してその中に入れていくのが良いです。

ちょっと長めの記述になります。

    def create_checkboxes(self):
        vbox = QVBoxLayout()
        for col in self.columnlist:
            vbox.addWidget(self.add_checkbox(col))
        # 箱を用意する
        frame = QFrame(self) # Frame定義
        frame.setFrameShape(QFrame.StyledPanel) # 形状決定。パネル。
        vbox.addStretch(1) # 行間を定義
        frame.setLayout(vbox)
        return frame

補足:Checkboxに変更した版のGUI

以上をまとめると下記のような感じになります。

f:id:ossyaritoori:20210718115306p:plain
記述量は増えますが自分はこっちのほうがスッキリしていて好みです。

#!/usr/bin/python3
# -*- coding: utf-8 -*-

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import pandas as pd 
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas


"""
Qt splitter のチュートリアル
https://www.finddevguides.com/Pyqt-qsplitter-widget

Example:Qwidgetから継承
SplitterはaddWidgetで部品を追加可能。Splitter自身も追加可能
基本的に一つの領域に一つの部品を配置できる。
"""

class Example(QWidget):
    def __init__(self,filename=None):
        super(Example, self).__init__()
        
        self.loadcsv(filename)
        self.initUI()
    
    def loadcsv(self,filename):
        df = pd.read_csv(filename)
        # 非数値の列のみ選択
        self.df = df.select_dtypes(include='number')
        print(self.df)
        self.columnlist = self.df.columns # list

    def initUI(self):
        hbox = QHBoxLayout(self)
    
        # list/figure作成
        #self.create_listwidget()
        self.create_figure()

        # 左側作成
        splitter_l = QSplitter(Qt.Vertical)
        splitter_l.addWidget(self.add_label("Plotする要素を選択"))
        #splitter_l.addWidget(self.listWidget)
        splitter_l.addWidget(self.create_checkboxes())
        # 右側作成
        splitter = QSplitter(Qt.Horizontal)
        splitter.addWidget(splitter_l)
        splitter.addWidget(self.FigureCanvas)
        #splitter.setSizes([100,200])

        # Widgetを追加
        hbox.addWidget(splitter)

        # レイアウト適用
        self.setLayout(hbox)
        QApplication.setStyle(QStyleFactory.create('Cleanlooks'))

        # Windowサイズを規定
        #self.setGeometry(300, 300, 300, 200)
        self.setWindowTitle('CSV plotter demo')
        self.show()

    def create_listwidget(self):
        # リスト作成
        self.listWidget = QListWidget()
        self.listWidget.setSelectionMode( #複数選択可能にする
            QAbstractItemView.MultiSelection
        )
        for col in self.columnlist:
            item = QListWidgetItem(col)
            self.listWidget.addItem(item)
        self.listWidget.itemClicked.connect(self.printItemText)
    
    def create_figure(self):
        # Figureを作成
        self.Figure = plt.figure()
        self.FigureCanvas = FigureCanvas(self.Figure)  # FigureをFigureCanvasに追加
        self.axis = self.Figure.add_subplot(1,1,1) # axを持っておく

    def create_checkboxes(self):
        vbox = QVBoxLayout()
        for col in self.columnlist:
            vbox.addWidget(self.add_checkbox(col))
        # 箱を用意してはる
        frame = QFrame(self) # Frame定義
        frame.setFrameShape(QFrame.StyledPanel) # 形状決定。パネル。
        vbox.addStretch(1) # 行間を定義
        frame.setLayout(vbox)
        return frame

    def add_checkbox(self,name):
        chxlist = QCheckBox(name, self)
        chxlist.stateChanged.connect(self.checklist_clicked)
        return chxlist

    #  クリックされたCheckboxをみてリストに追加 or 削除
    def checklist_clicked(self, state):
        checked_name = str(self.sender().text())
        
        try:
            self.clicked_list
        except:
            self.clicked_list = []

        if state == Qt.Checked: # if checked
            # add to the list
            self.clicked_list.append(checked_name)
        else: # if unchecked
            # remove from list
            self.clicked_list = [x for x in self.clicked_list if not x == checked_name]
        
        self.selected_columns = self.clicked_list
        self.plot_selected_columns()


    def add_label(self,text):
        # ラベルを作る
        label=QLabel(self)
        label.setText(text)
        label.adjustSize()
        return label


    def printItemText(self):
        # 選択したアイテムを表示
        items = self.listWidget.selectedItems()
        self.selected_columns = []
        for i in range(len(items)):
            self.selected_columns.append(self.listWidget.selectedItems()[i].text())

        self.plot_selected_columns()

    # Plotをアップデート
    def plot_selected_columns(self):
        self.axis.cla() # リセットを掛ける必要がある。
        df = self.df[self.selected_columns]
        df.plot(ax=self.axis)
        plt.grid(); 
        self.FigureCanvas.draw()


def main():
   app = QApplication(sys.argv)
   ex = Example("samplecsv.csv")
   sys.exit(app.exec_())

if __name__ == '__main__':
   main()

TODO

いくつか目処は立っていますが下記の要素を徐々に追加していくとそれっぽくなるものと思われます。

Python,PyQtで簡単にGUIを作る ① リスト表示とグラフPlot

モチベーション

身の回りのありとあらゆる便利パッケージにGUIがあったらな,と思うことが多いためやり方をメモします。

初回はひとまず

  • リストの複数を選択
  • matplotlibの図をリストの選択に応じて表示

ができるようにします。

サンプルを通した理解

わかりやすいサンプルコードを介して理解を深めるのが手っ取り早いです。 今回の目的には下記の内容が大部分で合致しているので参考にしました。

qiita.com

配置

構造としてはまずはQwidgetというキャンバスを用意して,QVboxlayoutというコンテナの中にグラフのプロットであったり,チェックボックスであったり具体的な要素を詰めていくという構造をとります。(理解がUpdateされ次第Updateします。)

f:id:ossyaritoori:20210717170350p:plain

超最低限に書くと下記のようになります。

# 初期化のサンプル
class Test(QtWidgets): # QWidgetを継承
    def __init__(self):
        super().__init__() # 継承元のInit
        vbox = QtWidgets.QVBoxLayout(self) #Vboxlayoutの定義。縦にオブジェクトを並べる。(QHboxなら横に) 
        vbox.addWidget( QtWidgets.QListWidget() ) # リスト表示Widgetを追加。
        vbox.addWidget( QtWidgets.QLabel(self) ) # ラベルWidgetを追加。(テキスト設定していないからこの段階では無)
        # ...このように追加していって
        self.setLayout(vbox) # VboxlayoutをWidgetにはめ込む

他にオブジェクトを配置する手法としては最後の部分はsetGeometryなどでゴリゴリ配置しても良さそうですし,

        # 配置
        self.setGeometry(0,0,900,600)
        self.FigureWidget.setGeometry(200,0,700,600)
        self.FileList.setGeometry(0,0,200,600)

配置をこりたい人は,Qt Designer というGUIがあるのでそれでレイアウトを作ったほうがいいと思います。

www7a.biglobe.ne.jp

自分は今はSplitterという機能を使って画面を分割して配置するようにしています。また次回にメモします。

イベントの設定

配置したオブジェクトたちにはイベントが起きたときにどうするかという設定をすることができます。

# インデント略
self.listWidget = QtWidgets.QListWidget()
self.listWidget.itemClicked.connect(self.printItemText) #クリックされたらprintItemText関数を呼ぶ

# 呼ばれる側の関数
    def printItemText(self):
        # 選択したアイテムを表示
        items = self.listWidget.selectedItems()
        x = []
        for i in range(len(items)):
            x.append(str(self.listWidget.selectedItems()[i].text()))
        print (x)

実行

Qtにおいては、QApplicationというクラスがメインのアプリケーションを定義するので, 最後に作ったWidgetクラスを表示する際には下記のようにします。

#アプリケーション本体のインスタンスを作る
app = QtWidgets.QApplication(sys.argv)    #アプリケーションのインスタンスを作る
Test.show()                        #作ったWidgetを表示する
app.exec()                          #アプリケーションのメッセージループを開始する

小ネタ

大体のオブジェクトは配置してから初期設定などをする必要があります。 そうするとaddWidgetでの配置が見づらくなるので関数化して一行で表せるようにしたほうがはかどります。

ラベルテキスト追加

ラベルオブジェクトを配置するならこうします。

    def add_label(self,text):
        # ラベルを作る
        label1=QtWidgets.QLabel(self)
        label1.setText(text)
        label1.adjustSize()
        return label1

リストでの複数選択

QListWidgetでSelectionModeを設定すると複数選択可能にできます。

毎回リセットするなら ExtendedSelection,トグルするなら MultiSelectionがおすすめです。 下記のように書きます。

        self.listWidget = QtWidgets.QListWidget()
        self.listWidget.setSelectionMode( #複数選択可能にする https://doc.qt.io/qt-5/qabstractitemview.html
            QtWidgets.QAbstractItemView.MultiSelection # QtWidgets.QAbstractItemView.ExtendedSelection 
        )

参考: QAbstractItemView Class | Qt Widgets 5.15.5

リストで番号を選択してPlotするサンプル

ということでリストで番号を選択してPlotするサンプルを下記に載せます。

f:id:ossyaritoori:20210717182134p:plain
選択した順番に下の図にPlotされます。

# -*- coding: utf-8 -*-
"""
Stackoverflowの文章より原型を拝借
QtのListwidgetで複数選択をしてPlotする。
"""

from PyQt5 import QtWidgets, QtCore
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas

class Test(QtWidgets.QDialog):
    def __init__(self, parent=None):
        super(Test, self).__init__(parent)
        self.layout = QtWidgets.QVBoxLayout()
        # リスト作成
        self.listWidget = QtWidgets.QListWidget()
        self.listWidget.setSelectionMode( #複数選択可能にする
            QtWidgets.QAbstractItemView.MultiSelection
        )
        self.listWidget.setGeometry(QtCore.QRect(10, 10, 211, 291))
        for i in range(10):
            item = QtWidgets.QListWidgetItem("%i" % i)
            self.listWidget.addItem(item)
        self.listWidget.itemClicked.connect(self.printItemText)

        # Figureを作成
        self.Figure = plt.figure()
        self.FigureCanvas = FigureCanvas(self.Figure)  # FigureをFigureCanvasに追加
        self.axis = self.Figure.add_subplot(1,1,1) # axを持っておく

        self.layout.addWidget(self.add_label("Ctrlを押しながらで複数選択"))
        self.layout.addWidget(self.listWidget)
        self.layout.addWidget(self.FigureCanvas)
        self.setLayout(self.layout)


    def add_label(self,text):
        # ラベルを作る
        label1=QtWidgets.QLabel(self)
        label1.setText(text)
        label1.adjustSize()
        return label1


    def printItemText(self):
        # 選択したアイテムを表示
        items = self.listWidget.selectedItems()
        x = []
        for i in range(len(items)):
            x.append(int(self.listWidget.selectedItems()[i].text()))

        print(x)
        self.update_Figure(x)

    # Figure
    def update_Figure(self,x):
        self.axis.cla() # リセットを掛ける必要がある。
        self.axis.plot(x,'-o')
        plt.grid(); plt.legend(["selected number"])
        self.FigureCanvas.draw()

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    form = Test()
    form.show()
    sys.exit(app.exec_())

m1 mac 環境構築メモ① 入力の設定やRemap、基本操作など

3月末にM1 macを買ってウキウキだったものの、なんだかんだWSL2にハマってしまって放置していました。 とりあえずまともにタイピングできる環境を整えたのでメモします。

機体と環境について

  • 2021年度版 m1 macbook air
    • 16GB, 512GB SSD
    • JIS配列キーボード

電池の持ちが一昔前のガラケーレベルなのが最も顕著に恩恵を感じます。 今はGoogle Colabなどで作業環境共同化もできるので回線さえあれば外出時でもそれなりに色々できます。

記事内での設定内容

  • Terminalをショートカットで開くようにする
  • キーボード設定
    • ファンクションキーをFnなしで動かす
    • JISキーボードをUS入力風に変更する
    • 単語移動を変更
  • 装飾キーまわりのメモ

ターミナルをショートカットで開く。

⌘スペースでWin+rのように呼び出しメニューを出せるのでそこでターミナルとうてばデフォルトでもキーボードだけでターミナルを起動できます。

ただ、やっぱCtrl+Alt+T的なので一発で開きたい場合はちょっと回り道をする必要があります。

Automatorというアプリでサービスというのを作成してそれにショートカットを割り振るというのがよく使われているようです。

https://ry0.github.io/blog/2015/10/21/open-terminal-shortcut/#gsc.tab=0

コンフリクトがない限りいくつでもショートカットは設定できます。 まっさらな環境だと普通にcontrol+command+Tを割り振れました。

キーボード設定

基本的なところ

基本的なところは箇条書きにて失礼します。

KarabinerでJISキーボードをUSキーボードに設定

下記の記事の通りにKarabinerをインストールします。

https://qiita.com/canonrock16/items/0c090276b2f1fd8eb0ab

初回はキーボードの識別に時間を取られてちょっとビビります。 その後、権限を求められるのでそのあたりをきちんと設定したらOKです。

f:id:ossyaritoori:20210605164845p:plain
Karabinerの設定画面

私の設定は以下の通り。

変換前 JISの該当キー 変換後 USの該当キー
international1 grave_accent_and_tilde(`)
international3 backslash \
caps_lock 左下の奴 left_command 左⌘
right_command 右⌘ right_control 右^

右のシフトも使わない気がしているので適当に変換しようかなとは思っています。

Karabinerでその他ショートカット作成

Karabinerでは複数キーの組み合わせもRemapすることができます。 これはComplex modificationsという箇所から設定できインターネット上からインポートしてくることができます。

https://ke-complex-modifications.pqrs.org/?q=option%20%2B%20arrows

例えばWindowsでやっていた操作を一通りMacで動かせるようにするには下記の設定検索からWindows shortcuts on macOS というのを検索してImportすれば良いです。

自分の場合単語移動をcontrolでできるように下記を入れました。

Exchange control + arrows keys with option + arrows keys

装飾キーまわり

Macを使うにあたりちょっと混乱したのがWindowsであったCtrl/Alt/Winキーの挙動がMacではあまり対応していないことです。

⌘はAltとCtrlの機能を兼ねたような挙動を持っていますが肝心なときにどっちかわからなかったりします。
Pythonを雑に起動して終了しようと思ったら画面分割されたりとか、Chromeで新規タブは⌘Tで出すのにタブ移動は^Tabだったりするのでちょっと困ります。

単語移動関連

WindowsやLinudxではCtrlで単語移動できますが、macではOptionで移動する必要があります。(自分はKarabinerの設定で変更しました。)

とりあえずデフォルトでのvscodeでの挙動を下記メモします。

option 上下 行を保持して移動
option 左右 単語移動
command 左右 行頭行末移動
command 上下 文頭文末移動
control 左右 ワークスペース切り替え
control ウィンドウ選択
control 最近開いたファイル表示?

使いにくいと思った部分は混乱がない範囲でRemapして行けばいいと思います。

また、日本語の単語移動を実現するにはその都度設定が必要のようです。 VScodeの場合はJapanese Word Handerを入れれば良さそうでした。

スクリーンショット

スクリーンショット自分の場合、ファイルに保存する必要はなくてクリップボードに入れば良いことが多いです。 下記を参考にしました。

https://tamoc.com/mac-screenshot-window/#i-3

結論、特定領域・Windowをクリップボードにコピーするには下記のとおりやればいいです。

  • control ⌃+command ⌘+shift ⇧+4 で自由領域Cropを起動
  • Spaceを押してWindowの選択をする

controlを押さなければ普通にデスクトップに保存されるようです。

その他

プログラム関係 - ターミナルの名前を変更 - https://code-graffiti.com/how-to-change-the-prompt-display-on-the-mac-terminal/ - Xcodeをインストール - homebrewのインストール - VSCODE - https://stackoverflow.com/questions/30065227/run-open-vscode-from-mac-terminal

今後もなにかあれば追加していくと思います。