テキストエディタMeryからOpenAI ChatGPTを使うマクロ

ここ数年はテキストエディタとしてMeryを使っています。最近はVisual Studio Codeをコーディングに使う事がめっきり多くなりましたが、軽量なテキストエディタはなかなか手放せません。さて、最近流行りのChatGPTですが、これをエディタの中で使うと便利ではないかと思い立ちMeryのマクロを作ってみました。質問したい内容を選択状態にしておきマクロを実行します。しばらくすると答えが帰ってきます。Pythonマクロなので若干の設定が必要なことと、OpenAI API Keyの取得が必要です。

import openai

openai.api_key = "---あなたのOpenAI API Key---"

text = window.document.selection.Text
lines = text.splitlines()

chat = []
content = {'system': '', 'assistant': '', 'user': ''}
role = 'user'

def flush_contents():
    global chat, content
    for key in content.keys():     
        if len(content[key]) > 0:
            chat.append({"role": key, "content": content[key]})
            content[key] = ''

for line in lines:
    role_headline = False
    for key in content.keys():
        if line == f'### {key}':    
            flush_contents()
            role = key
            role_headline = True
    if not role_headline:
        content[role] += line + '\n'    

flush_contents()

if len(chat) > 0:
    #response = openai.ChatCompletion.create(model="gpt-3.5-turbo", messages=chat) # gpt-3.5-turboを使う場合。
    response = openai.ChatCompletion.create(model="gpt-4", messages=chat) # gpt-4を使う場合。
    message = response["choices"][0]["message"]["content"].lstrip()    
    window.document.selection.Text = text + '\n\n### assistant\n\n' + message + '\n\n### user\n\n'
  • MeryマクロをPythonで記述できる様にする方法は次のリンクを参考にしました。 知っておくと便利なテキストエディター「Mery」の 10 個の小技

  • このマクロを実行するためには OpenAI API のキーを入手する必要があります。

  • 見出し### systemをつけると以下をsystem contentとして扱います。

  • 見出し### userをつけると以下をuser contentとして扱います。

  • 見出し### assistantをつけると以下をassistant contentとして扱います。

ユピテルY-300dPの画像から加速度データを得る

最近、ドライブレコーダーを買いました。 ユピテル YupiteruY-300dPです。 発売は2021年と少々古いのですが、性能が良く値段が手頃なので気に入っています。 このドラレコは衝突や急ブレーキ、急ハンドルなどで異常な加速度を検知した場合、 前後40秒のデータを別フォルダに格納する機能がついています。 その際に加速度の閾値を適切に決めないと頻繁にアラーム音とともに動作してしまうので 設定を調整する必要があります。 画像をPCで確認するプログラムとのことで、 調べてみると字幕データとしてフレームごとに記録されているようです。 というわけでPythonコードを書いてみました。

# AVI Audio Video Interface
# RIFF Resource Interchange File Format

import struct

#
# Read RIFF Header
#
def read_RIFF_header(f):
    buffer = f.read(12)
    riff, size, type = struct.unpack('<4sI4s', buffer)
    if riff != b'RIFF' or type != b'AVI ':
        raise ValueError('Not a valid AVI file')
    return size

def read_list(f, read_data=True):
    buffer = f.read(12)
    id, size, type = struct.unpack('<4sI4s', buffer)
    if id != b'LIST':
        raise ValueError('Not LIST.')
    if read_data:
        buffer = f.read(size - 4)
        if size % 2 == 1:
            f.read(1) # Read one more byte if size is odd.
        return size, type, buffer
    return size, type

def read_chunk(f, read_data=True):
    buffer = f.read(8)
    id, size = struct.unpack('<4sI', buffer)
    if read_data:
        buffer = f.read(size)
        if size % 2 == 1:
            f.read(1) # Read one more byte if the size is odd.
        return id, size, buffer
    return id, size

file = '~~~.avi'
f = open(file, 'rb')
#
# Check RIFF Header
#
size = read_RIFF_header(f)
size, type = read_list(f, read_data=False)
if type != b'hdrl':
    raise ValueError('list_type is not hdrl.')
#
# Main AVI Header
#
id, size, buffer = read_chunk(f)
if id != b'avih':
    raise ValueError('chunk_type is not avih.')
#
# Stream Header Lists
# fccType = 'vids', 'auds', 'txts'
#
for i in range(3):
    size, type, buffer = read_list(f)
    if type != b'strl':
        raise ValueError('list_type is not strl.')
#
# JUNK Chunks
#
for i in range(2):
    id, size, buffer = read_chunk(f)
    if id != b'JUNK':
        raise ValueError('chunk_type is not JUNK.')
#
# movi List
#
movi_size, type = read_list(f, read_data=False)
if type != b'movi':
    raise ValueError('listtype is not movi.')


current_size = 0
ax_list = []
ay_list = []
az_list = []
while current_size < movi_size:
    id, size, buffer = read_chunk(f)
    if id == b'02tx':
        f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, \
            ax, ay, az, n, speed, latitude, longitude, unix_time = struct.unpack('<13fI3fI', buffer)
        ax_list.append(ax)
        ay_list.append(ay)
        az_list.append(az)
        print(ax, ay, az, n, speed, latitude, longitude, unix_time)
    current_size += size

f.close()

私が購入したドラレコの型番はY-300dpなのですが、 LaBoon!!さんのサイトでは、 同様なハードが様々な型番(WDT700c, SN-TW9900d, SN-TW9800d, Y-300C, Y-300R, Y-210R, SN-TW99c, SN-TW84d)で販売されているとの事で、 ひょっとしたらこれらのドラレコでもデータが抽出できるかもしれません。

Pythonメモ

全くのところ、自分用のメモです。

始めの宣言

%matplotlib widget
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns; sns.set()

区切りの良いカラー番号

Pythonとは関係ありませんが、0から255まで等間隔に区切りたいだけです。17の倍数です。255を素因数分解すると、

255 = 17 * 5 * 3

となる訳で。

# 0から255まで4段階に分ける
0, 85, 170, 255,
# 0から255まで6段階に分ける
0, 51, 102, 153, 204, 255,
# 0から255まで16段階に分ける
0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255

matplotlibのデフォルトカラーコード

'tab:blue', 'tab:orange', 'tab:green', 'tab:red', 'tab:purple', 'tab:brown', 'tab:pink', 'tab:gray', 'tab:olive', 'tab:cyan'

グラフの描画

# seaborn-paper, seaborn-notebook, seaborn-talk, seaborn-posterの順で文字が大きくなる
with plt.style.context('seaborn-poster'):
    fig, axes = plt.subplots(2, 2, figsize=(8,6))
    ax = np.rabel(axes)
    ....
    plt.tight_layout()
    plt.show()

インタラクティブなmatplotlibグラフ表示時のショートカット

Command Shortcut(s)
Home/Reset 'h', 'r', 'home'
Back 'left', 'c', 'backspace'
Forward 'right', 'v'
Pan/Zoom 'p'
Zoom-to-rect 'o'
Save 's', 'ctrl+s'
Toggle fullscreen 'f', 'ctrl+f'
Toggle major grids 'g'
Toggle minor grids 'G'
Toggle x axis scale (log/linear) 'k', 'L'
Toggle y axis scale (log/linear) 'l'
Close Figure 'ctrl+w', 'cmd+w', 'q'
Constrain pan/zoom to x axis hold x when panning/zooming with mouse
Constrain pan/zoom to y axis hold y when panning/zooming with mouse
Preserve aspect ratio hold CONTROL when panning/zooming with mouse

好みのグラフにする

# グラフのサイズ
plt.rcParams['figure.figsize'] = (12, 5.5)
# 外枠を消す
#plt.rcParams['axes.spines.left'] = False
plt.rcParams['axes.spines.right'] = False
#plt.rcParams['axes.spines.bottom'] = False
plt.rcParams['axes.spines.top'] = False
# 左の目盛りを消す
plt.rcParams['ytick.left'] = False
# 水平グリッドを表示する
plt.rcParams['axes.grid.axis'] = 'y'
plt.rcParams['axes.grid'] = True
plt.rcParams['grid.linestyle'] = 'dotted'
# カラーマップを再定義する
colors = plt.cm.get_cmap('tab20c')(np.arange(0, 1, 0.05))
plt.rcParams['axes.prop_cycle'] = plt.cycler(color=colors)

Pythonによる分散分析

分散分析(Analysis Of VAriance; ANOVA)は3標本以上の差の検定です。

Pythonの統計パッケージには、scipy.statsやstatsmodelsがあります。最近はpingouinというものもあります。いずれもANOVAをサポートしています。それぞれのパッケージでの使用方法を説明します。

scipy.statsによるANOVA

from scipy import stats

x = [43, 55, 57, 72, 51, 52, 48, 46, 58, 54]
y = [53, 44, 54, 51, 68, 64, 45, 67, 49, 56]
z = [77, 55, 67, 54, 46, 75, 65, 57, 49, 61]
print(stats.f_oneway(x, y, z))

結果

F_onewayResult(statistic=1.6463168290164742, pvalue=0.21152459574247603)

statsmodelsによるANOVA

import pandas as pd
import statsmodels.api as sm
from statsmodels.formula.api import ols

x = [43, 55, 57, 72, 51, 52, 48, 46, 58, 54]
y = [53, 44, 54, 51, 68, 64, 45, 67, 49, 56]
z = [77, 55, 67, 54, 46, 75, 65, 57, 49, 61]

values = x + y + z
groups = ['x'] * len(x) + ['y'] * len(y) + ['z'] * len(z)
data = pd.DataFrame({'values': values, 'groups': groups})

lm = ols('values ~ groups', data=data).fit()
sm.stats.anova_lm(lm, typ=2) # Type 2 Anova DataFrame

結果

               sum_sq    df         F    PR(>F)
groups     271.666667   2.0  1.646317  0.211525
Residual  2227.700000  27.0       NaN       NaN

pingouinによるANOVA

import pandas as pd
import pingouin as pg

x = [43, 55, 57, 72, 51, 52, 48, 46, 58, 54]
y = [53, 44, 54, 51, 68, 64, 45, 67, 49, 56]
z = [77, 55, 67, 54, 46, 75, 65, 57, 49, 61]

values = x + y + z
groups = ['x'] * len(x) + ['y'] * len(y) + ['z'] * len(z)
data = pd.DataFrame({'values': values, 'groups': groups})

print(pg.anova(data, dv='values', between='groups'))

結果

   Source  ddof1  ddof2         F     p-unc       np2
0  groups      2     27  1.646317  0.211525  0.108694

Pythonによる級内相関係数の計算

検者内・検者間の信頼性を表す指標として級内相関係数ICC; Intraclass correlation)というのがあります[1, 2]。Pythonの統計パッケージにはscipy.statsstatsmodelsがありますが、ICCは実装されていない様です。

最近の統計パッケージpingouinにはありますので紹介します。

pingouinはpipでインストールできます。

pip install pingouin

サンプルコードは以下のとおりです。

import pandas as pd
import pingouin as pg

A = [1,1,3,1,1,2,1,2,1,1]
B = [2,1,3,1,3,2,1,3,3,3]
C = [2,3,3,1,1,1,1,2,3,3]
D = [2,3,3,1,1,2,1,2,3,1]
E = [2,3,3,3,3,2,1,2,3,1]
ratings = A + B + C + D + E
raters = ['A'] * len(A) + ['B'] * len(B) + ['C'] * len(C) + \
         ['D'] * len(D) + ['E'] * len(E)
targets = list('abcdefghij') * 5
data = pd.DataFrame({'targets':targets, 'raters':raters, 'ratings':ratings})
icc = pg.intraclass_corr(data=data, targets='targets', 
                         raters='raters', ratings='ratings')
print(icc.set_index('Type'))

出力結果は以下のとおりです。

                   Description       ICC         F  df1  df2      pval  \
Type                                                                     
ICC1    Single raters absolute  0.266854  2.819923    9   40  0.011519   
ICC2      Single random raters  0.280000  3.221007    9   36  0.005798   
ICC3       Single fixed raters  0.307576  3.221007    9   36  0.005798   
ICC1k  Average raters absolute  0.645380  2.819923    9   40  0.011519   
ICC2k    Average random raters  0.660377  3.221007    9   36  0.005798   
ICC3k     Average fixed raters  0.689538  3.221007    9   36  0.005798   

              CI95%  
Type                 
ICC1   [0.03, 0.64]  
ICC2   [0.05, 0.64]  
ICC3   [0.06, 0.67]  
ICC1k   [0.13, 0.9]  
ICC2k   [0.21, 0.9]  
ICC3k  [0.23, 0.91]  

[1] Shrout, P. E., & Fleiss, J. L. (1979). Intraclass correlations: uses in assessing rater reliability. Psychological bulletin, 86(2), 420. https://pdfs.semanticscholar.org/b8d4/7b0c0b12dd77543e82e6bf6636ddd335cfea.pdf

[2] 医療系データのとり方・まとめ方、対馬栄輝・石田水里、東京図書 (2013年). https://www.amazon.co.jp/dp/4489021437