20220127のPythonに関する記事は30件です。

Pythonで理解する計数抜取検査

0. はじめに 抜取検査の統計的な意味を理解する上で確率分布の理解は欠かせません。品質管理の本にのってる計数値に関する確率分布を見ることで統計的な意味の理解はできますが、身近な検査事例について自分で確率分布を書きアウトプットしながら理解することはより効果的です。ただ、手書きで確率分布を描くことは大変ですしエクセルであっても少し面倒です。 Pythonの統計関数モジュールscipy.statsは簡単に確率分布を描画できるため、グラフをかく煩わしさがなく効率的に検査の意味を理解できます。この記事では、Python統計関数モジュールscipy.statsを使ったわかりやすい計数抜取検査の解説を目指します。 1. 抜取検査と二項分布 抜取検査は、保証すべき単位(母集団)から一定数のサンプル(標本)だけを抜き取り検査することです。検査結果に対してある判断基準で合格・不合格を決めるわけですが、全数検査ではないため同じ品質レベルであっても、サンプルによって合格になったり不合格になったりします。 統計を使えば、ある不良率の時にどのくらいの確率で合格になるのか、どれくらいのサンプル数が適切なのかを設計することができます。それを理解するために、まずは基本となる二項分布について考えてみましょう。 合格品のネジ90本と不良品のネジ10本が入った袋があったとします。この中から10本を取り出す際に、1本ずつ取り出し取り出した後にそれを戻しよく混ぜ直します(復元抽出)。これを10回反復します。袋の中には合格品のネジ90本と不良品のネジ10本が、常に混ざった状態で入っているため、1回の取り出しで不良品のネジを取り出す確率Pは、 \begin{align} P &= 10/100 \\ &= 0.1 \end{align} となります。よって、取り出した10本の中にk個の不良品が含まれる確率は、 \begin{align} P(k) &= \frac{10!}{k!(10-k)!} (0.1)^k(0.9)^{10-k} \\ \end{align} となります。(0 ≦ k ≦ 10) 一般的な形で表した場合、不良率をp、独立したサンプリングをn回行った時に不良品がk個含まれる確率は以下であらわせます。 \begin{align} P(k) &= \frac{n!}{k!(n-k)!} (p)^k(1-p)^{n-k} \\ \end{align} このような反復試行で得られる離散型確率変数は二項分布となります。これにより不良率p、サンプリング数n、サンプリングの中の不良数kの関係がわかります。 Pythonを使って、二項分布を書き3つの関係についてみていきましょう。二項分布の描画には、以下の記事を参考にさせていただきました。 1. Pythonで学ぶ統計学 2. 確率分布[scipy.stats徹底理解] まず、ライブラリーを読み込みます。 import numpy as np import matplotlib.pyplot as plt from scipy import stats !pip install japanize-matplotlib import japanize_matplotlib 1-1 サンプルに含まれる不良の数・・・不良率pとサンプリング数n 固定 # 不良率 p = 0.1 # ネジを取り出す回数(サンプリング回数) n = 10 # 取り出す不良数 k = np.arange(0, 10) # array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) # pmfで成功回数ごとの確率を計算 binom_pmf = stats.binom.pmf(k, n, p) # 不良数=0~9, サンプリング回数=10, 不良率=0.1 # 可視化 plt.plot(k, binom_pmf, 'bo',ms=8) plt.vlines(k, 0, binom_pmf, colors='b', lw=3, alpha=0.5) plt.xticks(k) # x軸目盛 plt.xlabel("不良の数", fontsize=13) plt.ylabel("確率質量関数", fontsize=13) plt.show() # 不良率P、サンプリングn回の時に、不良品がk個含まれる確率 stats.binom.pmf(k, n, p) 取り出した10本のネジの中に含まれる不良数の確率分布を描くことができました。約73.6%は0本もしくは1本であることがわかります。3本出る確率はグッと減って5.7%です。 1-2 不良率の確率密度関数・・・不良数kとサンプリング数n 固定 # x軸の等差数列を生成 p = np.linspace(start=0, stop=1, num=100) # pmfで生存関数を生成 binom_sf = stats.binom.pmf(k=1, n=10, p=p) # 可視化 plt.plot(p, binom_sf) plt.xlabel("不良率P", fontsize=13) plt.ylabel("確率密度関数", fontsize=13) plt.show() ここでは、サンプル数を10個それに含まれる不良数を1個と固定し、不良率pを変化させたときの確率を描いています。不良率0.1のあたりでピークとなっていることがわかります。それぞれの不良率で、サンプル数10個不良率1個が発生する確率をつかむことができます。 2. OC曲線 抜取検査では、保証すべき単位(以下ロット)からのサンプリング結果をもとにロットの合否を判定します。合否判定には、不良がc個以下の時に合格、c+1個以上の時に不合格といった判定基準を設定することとなります。 合格基準である不良c個以下の確率は、0個の時の確率、1個の時の確率とc個までの確率を累積していけばもとまります。式で書くと次のようになります。 \begin{align} &P(≦c) = P(0)+P(1)+・・・ +P(c)& \end{align} では、この式を横軸を不良率、縦軸をロット合格率としてプロットしてみます。 2-1 不良率の累積分布関数・・・不良数kとサンプリング数n 固定 # x軸の等差数列を生成 p = np.linspace(start=0, stop=0.5, num=50) # cdfで累積分布関数を生成 binom_sf = stats.binom.cdf(k=1, n=10, p=p, loc=0) # 可視化 plt.plot(P, binom_sf) plt.xlabel("不良率P", fontsize=13) plt.ylabel("累積分布関数", fontsize=13) plt.show() このようにロットの不良率に対してロットの合格確率をプロットした累積確率曲線をOC曲線(operating characteristic curve)といいます。上に示したOC曲線の場合、合格基準をサンプル10個中不良数1個以下とした時に、不良率によってどのくらい合格するかを表しています。具体的に不良率0.1の時の合格率をみてみます。下記の図では、わかりやすいように不良率0.1に垂直線と水平線を引きました。 2-2 OC曲線・・・不良数kとサンプリング数n 固定 垂直線・水平線・合格率表記あり # x軸の等差数列を生成 p = np.linspace(start=0, stop=0.5, num=50) # cdfで累積分布関数を生成 binom_sf = stats.binom.cdf(k=1, n=10, p=p, loc=0) # 可視化 plt.plot(p, binom_sf) # cdfで、k=1, n=10  確率を求める pass_rate = stats.binom.cdf(k=1, n=10, p=0.1) plt.plot(0.1, pass_rate, 'bo') # 青色ドット plt.vlines(0.1, 0.0, pass_rate, lw=2, linestyles='dashed') # 垂直線 plt.hlines(pass_rate, 0, 0.1, lw=2, linestyles='dashed') # 水平線 plt.xlabel("不良率P", fontsize=13) plt.ylabel("累積分布関数", fontsize=13) plt.show() このように、不良率0.1の時の合格率は73.6%であることがわかります。 2-3 OC曲線・・・サンプリング数によるOC曲線の変化 n= 10, 15, 20, 25 サンプル数によってOC曲線はどう変わるかをみてみましょう。合格基準をサンプルn個中不良数1個以下とし、nを10, 15, 20, 25と変化させてみます。 # x軸の等差数列を生成 p = np.linspace(start=0, stop=0.6, num=50) # cdfで累積分布関数を生成 for i in range(10, 30, 5): binom_sf = stats.binom.cdf(k=1, n=i, p=p, loc=0) plt.plot(P, binom_sf, label=f"n={i}") plt.legend() plt.xlabel("不良率P", fontsize=13) plt.ylabel("累積分布関数", fontsize=13) plt.show() グラフからわかるように、nが増えるにつれて曲線が左へ移動していくことがわかります。不良率0.1の場合、n=10の時の合格率は73.6%でしたが、n=25では27.1%となります。サンプル数が増えたことでより正確に判定を行えるようになってくることがわかります。 3. 生産者危険と消費者危険 2-3で確認したように、サンプル数が増えると正確な検査を行うことができます。サンプル数をどんどん増やせば、最終的には全数検査となりサンプリングに起因する見逃しはなくなります。全数検査は確実な検査を行えますが、それだけ時間、人手、設備などが必要となってしまいます。製造現場では、抜取検査と全数検査を組み合わせて必要な品質を確保することとなります。 抜取検査をするさいに、サンプル数はどのように決めればいいのでしょうか。OC曲線では、曲線上の2点を決めることで生産者と消費者の両者に対しての品質保証状の取り決めをすることができます。下記の図において、αのロットの不良率は0に近く品質としては良いです。しかし、α分は不合格となってしまいます。このαを第1種の誤りといい、αが大きいと生産者が不利になるので生産者危険と言います。βのロットの不良率は高く品質としては悪いです。しかし、β分は合格となってしまいます。このベータを第2種の誤りと言い、βが大きいと消費者が不利になるので消費者危険と言います。一般的には、α = 5%、β = 10%でサンプル数を設計します。 4. おわりに ここまで統計関数モジュールscipy.statsで描画したグラフを用いて説明してきました。計数抜取検査の統計的な感覚をつかむには、身近な抜取検査の条件を想定してグラフを書くと感覚が掴みやすいです。Pythonを使うと簡単にグラフ描画ができますので、計数抜取検査を理解する際には試してみてください。 参考文献 谷津 進、宮川 雅巳 著 (1988) 経営工学ライブラリー6 品質管理、朝倉書店 参考サイト 抜取検査とOC曲線
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WSL2のファイル読み込み速度比較

初めに WSL2にUbuntu 20.04等をインストールするとVMwareなどと同様にWindowsにとっては一個のファイルで構成され、Windowsからは\\wsl$\Ubuntu-20.04のパスでWSL2内部のファイルシステムにアクセスできます。 一方、WSL2からもWindowsのファイルシステムに/mnt/c/のようなパス指定でアクセスできますが「非常に遅いのでWindowsへのファイルアクセスは避け、WSL内にファイルを置こう」とよく聞きます。どのくらい遅いんかいなということで測定してみました。 実行環境 OS CPU RAM Python 対象Drive Windows 10 Pro Core i7 4770K 32GB 3.8.10 venv SATA SSD(E:) 読み込むデータはPC用音楽ゲームでおなじみのBMSフォーマットでよく使われる音声ファイルOgg Vorbis(.ogg)を229,455個、データ総量約6.2GB WSL2のファイルext4.vhdxもC:ではなくE:に置いている E:\BMSにあるファイル・フォルダを全てWSL2内にもコピーしてある(/home/xxxxx/BMS) コード main.py import os import time def find_all_files(directory): for cur_dir, dirs, files in os.walk(directory): for file in files: yield os.path.join(cur_dir, file) if __name__ == '__main__': t1 = time.time() i=0; j=0 for file in find_all_files('e:/BMS'): # Windowのみ # for file in find_all_files('/mnt/e/BMS'): # WSL2からWindowsにアクセス # for file in find_all_files('/home/xxxxx/BMS'): # WSL2内のファイルにアクセス if file.endswith('.ogg') == True: f = open(file, 'rb') data = f.read() f.close() print(f'{file} ---> {len(data)} bytes') i += 1 j += len(data) t = time.time() - t1 print("Time={0}, count={1}, TLsize={2}".format(t, i, j)) 参考資料 結果 環境 時間 備考 WSL2 (/home/xxxxx/BMS) 106.4 PCを再起動した直後 WSL2 (/home/xxxxx/BMS) 30.7 ↑が終わった直後にもう一度 Windows (E:\BMS) 267.2 PCを再起動した直後 Windows (E:\BMS) 109.3 ↑が終わった直後にもう一度 WSL2 (/mnt/e/BMS) 913.2 早い順に並べると以下の通り。 WSL2内のみ > Windows > WSL2からWindowsにアクセス WSL2からWindowsファイルシステムへのアクセスはWSL2内に比べて9〜30倍くらい遅い WSL2内の処理の方がWindowsより2倍以上早い キャッシュが効く2回目実行はWSL2がさらに早い まとめ WSL2内で処理しましょう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

streamlit_timelineを使って、streamlitで年表を作ってみた

はじめに Pythonの簡易Webアプリ作成ツールのstreamlitに、Timeline.jsと呼ばれる年表を作るJavaScriptのライブラリを組み込んだ、streamlit-timelineというライブラリがあります。 このTimelineの使い方を簡単に説明します。 インストール streamlitをまだインストールしていない場合 pip install streamlit steamlit_timlineをインストール pip install streamlit_timeline タイムラインの作り方 # Streamlit Timeline Component Example import streamlit as st from streamlit_timeline import timeline # use full page width st.set_page_config(page_title="Timeline Example", layout="wide") # load data with open('example.json', "r") as f: data = f.read() # render timeline timeline(data, height=800) このexample.jsonは、 https://timeline.knightlab.com/docs/json-format.html のフォーマットに従って書きます。 { "title": { "media": { "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/df/The_Fabs.JPG/150px-The_Fabs.JPG" }, "text": { "headline": "The Beatles", "text": "<p>The Beatles - Discography</p>", } }, "events": [ { "media": { "url": "https://upload.wikimedia.org/wikipedia/en/thumb/c/c0/PleasePleaseMe_audio_cover.jpg/150px-PleasePleaseMe_audio_cover.jpg" }, "start_date": { "year": 1963, "month": 1, }, "text":{ "headline":"<a href="https://en.wikipedia.org/wiki/Please_Please_Me">Please Please Me</a>", "text":"" } }, ... また、jsonファイルを読み込むだけでなく、辞書型のオブジェクトを json.dumps(dic) として、timelineの引数とする方法でも、実行が可能なので、動的にタイムラインを作りたい場合は、その方法のほうが良いです。 以下のURLに、動作のデモページを作っています。 こんな感じのインタラクティブなタイムラインが出来上がります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PyMC3用の環境構築

はじめに PyMC2系を利用するための環境は特に苦労なく構築できるのですが、PyMC3系の環境はすんなりいかなかったので備忘録的に残しておきます。 環境 項目名 バージョン Linux ディストリビューション Ubuntu 20.04.3 LTS Python 3.9.7 PyMC3 3.11.4 Python は pyenv で管理されていることを前提とします。 構築手順 Python のインストール theano からの要請で、共有ライブラリ libpythonX.Y.a (X.Y は Python のバージョン) が位置独立コードでなければならないため、コンパイルオプション -fPIC を指定してビルドします。 CFLAGS="-fPIC" pyenv install 3.9.7 pip を最新版にアップグレードしておきます。 pip install --upgrade pip PyMC3 のインストール pip install pymc3 NumPy のダウングレード NumPy 1.22 以降をインストールすると pymc3 インポート時に以下のようなエラーが出るため、ダウングレードをしておきます。 AttributeError: module 'numpy.distutils.__config__' has no attribute 'blas_opt_info' pip install 'numpy<1.22' 補足 Python のバージョンが古いと最新の PyMC3 がインストールされません。たとえば Python-3.6 系の場合、PyMC3 3.10.0 がインストールされます。この場合 import pymc3 as pm を実行すると以下のエラーが出ます。 ~/.anyenv/envs/pyenv/versions/3.6.15/lib/python3.6/site-packages/pymc3/stats/__init__.py in <module> 45 compare = map_args(az.compare) 46 ess = map_args(az.ess) ---> 47 geweke = map_args(az.geweke) 48 hpd = map_args(az.hpd) 49 loo = map_args(az.loo) AttributeError: module 'arviz' has no attribute 'geweke' Python のバージョンを変えたくないのであれば、arviz をダウングレードすることによって上記問題を解決することができます。 pip install 'arviz<=0.11.0'
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Windows10でpythonを用いた仮想環境の構築方法

仮想環境作成 python -m venv [name] 仮想環境立ち上げ ・Windows .[name]\Scripts\Activate ・Linux source .[name]/bin/activate 仮想環境から抜ける deactivate 仮想環境を削除 ・Windows rmdir /s [name] ・Linux rm -s [name] ライブラリエクスポート pip freeze > requirements.txt ライブラリインポート pip install -r requirements.txt 動画で確認する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PythonでSeleniumするときに書いておいたほうがいいコード

メモとして残しておきます。 scraping.py from selenium import webdriver from selenium.webdriver.common.by import By from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.service import Service options = webdriver.ChromeOptions() options.add_argument("start-maximized") # 画面表示を最大にする options.add_experimental_option("excludeSwitches", ["enable-logging"]) # よくわからん長文をコンソールに表示させない s = Service(ChromeDriverManager().install()) # これでwebドライバーのパスの指定不要、ブラウザのバージョンに合わせなくて済む driver = webdriver.Chrome(service=s, options=options) # ここまで定型句 driver.get("{WEBページのURL}") # 以下ご自由に
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

twitterAPIの取得に苦労したので情報共有します

twitterAPIの取得方法("Elevated"ライセンスまで) twitterAPIには"essencial"と"Elevated"のライセンスがある。 "essencial"ライセンスはパーミッションが"Read"のみで情報の取得しかできない。 一方、"Elevated"ライセンスはパーミッションが"Read&Write"でツイートやいいねができる。 APIの利用を開始すると最初は"essencial"ライセンスとなる。その際、英語の質問は特にされない。 "Elevated"ライセンスを取得するためにはディベロッパーポータルから申し込む必要がある。その際、英語の質問をされる。 英語の質問で気を付けたいことは下記2点 ・自動でいいねやリツイートをするとは言わないこと。 ・ツイッターの内容やコンテンツを外部で表示するといわないこと。 ・いかなる政府機関や公共団体にもAPIのデータは利用されないこと 自動でいいねを押すことは禁止されています。 検索で出てくる情報と今の登録方法には少し違いがあるようです。 tweepyは"Elevated"ライセンスでのみ利用できます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Anaconda3】仮想環境で起動したAtomからコードを直接実行する(RDKit)

以前、RDkitという化学系ライブラリを使用するためにPython3.7系の仮想環境を作成しました(RDkitがPython3.8以上に対応していないため)。このような仮想環境下でAtomエディタから直接スクリプトを実行する方法を調べたのでメモしておきます。 利用環境 Python: 3.8.8 (Anaconda3で導入) (RDkit用の仮想環境のPythonは 3.7.11) OS: Windows 10 エディタ: Atom ("script" パッケージ導入済み) ※ここでは既にRDkit用の仮想環境が準備できているものとします。仮想環境を構築する方法については過去のエントリを参考にして下さい。 手順 ① Anacondaパッケージに同梱されている「Anaconda Prompt」を起動 ② Anaconda Promptでactivate [仮想環境名]を実行して仮想環境を起動 環境一覧を取得したいときはconda info -eで表示できます。 # conda environments: # base * C:\Users\[ユーザー名]\anaconda3 rdkitenv C:\Users\[ユーザー名]\anaconda3\envs\rdkitenv ここでは仮想環境を rdkitenv という名前にしてあります。 ③ Anaconda Promptでatomを実行してAtomエディタを起動 ここでは仮想環境を rdkitenv という名前にしてあります。 ④ スクリプトを実行 "script" パッケージが導入済みなら「Ctrl + Shift + B」で実行できます。 問題が無ければ以下のプログラムが動くはずです。 sample.py from rdkit import Chem import sys print(sys.version_info) # sys.version_info(major=3, minor=7, micro=11, releaselevel='final', serial=0) # 周期表の取得(インスタンス化) pt = Chem.rdchem.GetPeriodicTable() # 1 ~ 9 番目までの元素記号を表示 for i in range(1,10): print(Chem.PeriodicTable.GetElementSymbol(pt, i)) # H # He # Li # Be # B # C # N # O # F 私のRDkit用の仮想環境 rdkitenv のPythonのバージョンは3.7.11なので、意図した環境で実行できていることが分かります。 後記 以上のことは単純に「仮想環境からAtomを起動することで仮想環境のバージョンのPythonが使われる」という理解で良いかと思います。私が知らないだけで実は常識だったのかは分かりませんが、参考になる日本語の記事を上手く見つけられなかったので個人的にまとめてみた次第です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PythonでSeleniumを使ってみる

前書き 初投稿記事です。 Seleniumというものがあることを知り、使ってみろと言われたため 備忘録的に残しおておこうと思います。 ゴール ・PythonとSeleniumのライブラリを使って自動でWebサイトを操作 ・必要な情報をログとして保存 準備物について 使用するもの ・Python v3.7.7 ・Selenium v4.0.0 ・ChromeDriver 96.0.4664.45 (多分) PythonもSeleniumもChromeDriverも触ったことがなかったので、それぞれの概要を以下にまとめます。 〇Pythonとは Pythonとはプログラム言語の事。 拡張性が高く、機械言語などで評価されている。 〇Seleniumとは Selenium(セレニウム)とは、ブラウザの操作を自動化するための物。 →このURLのこのボタン押して~みたいなことを自動で行ってくれる。 〇ChromeDriverとは プログラムとChrome(ブラウザ)をつなげる役割を果たすドライバー。 様々なドライバーがあるが、今回はchromeを使用する。 準備(インストールなど) Seleniumのインストール まずはSeleniumのライブリを落としてきましょう。 pip経由でインストールするのがよさそう pip install selenium ChromeDriverのインストール Googleで調べると出てきます。 使っているChromeのバージョンと合わせましょう。 ダウンロードしてきたchromedriver.exeの場所は控えておきましょう。後ほど使います。 seleniumインストール時に怒られた たまにcryptographyが~と怒られる時がありますが そういう時は python -m pip install --upgrade pip でpipを更新してあげると解決するかもしれません。 実践 ブラウザの立ち上げ 早速Seleniumを使ってみます。 以下のようなコードを打ってみてください from time import sleep from selenium import webdriver from selenium.webdriver.common.by import By driver = webdriver.Chrome("chromedriver.exeのPATH") driver.get('https://qiita.com') sleep(3) driver.close() driver.quit() 多分一瞬Qiitaのページが表示されて消えると思います。 それぞれなにをしているか解説していきましょう。 driver = webdriver.Chrome("chromedriver.exeのPATH") ここでは、webdriverのインスタンスを生成しています。 インスタンス生成の引数として、ドライバーのパスを入れています。 ※ドライバーのパスが通っていれば引数に指定する必要は無いみたいです。 driver.get('https://qiita.com') getの引数にURLを入れると、そのURLを開きます。 sleep(3) 待機。 driver.close() driver.quit() それぞれ、開いたブラウザを閉じ、 インスタンスを終了する。という処理になります。 これがないとブラウザが開きっぱなしなので、 開きっぱなしは困るよ…という人は記載するといいと思います。 これでブラウザを立ち上げることはできましたね。 ページ遷移と値取得 「ブラウザを開いたのはいいけど、何もできないよ…」 「コレじゃあテストもクソも無いやん!」 というワガママボーイズ・ガールズの声が聞こえてくるので、 ページ遷移する機能を追加してみます。 from time import sleep from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support import expected_conditions as EC driver = webdriver.Chrome(r"C:\driver\chromedriver\chromedriver.exe") driver.get('https://qiita.com') print('URLを開きました') element = WebDriverWait(driver, 30).until( EC.visibility_of_element_located((By.CSS_SELECTOR, 'div.st-NewHeader_mainNavigation')) ) mainNavigation = element.find_elements(By.CSS_SELECTOR, 'div.st-NewHeader_navigationTabContainer > a') mainNavigation[1].click() sleep(2) driver.close() driver.quit() 上記コードを実行すると、Qiitaの記事を開いた後、 「ホーム」から「タイムライン」に遷移するのでは無いでしょうか。 このコードのポイントは element = WebDriverWait(driver, 30).until( EC.visibility_of_element_located((By.CSS_SELECTOR, 'div.st-NewHeader_mainNavigation')) ) mainNavigation = element.find_elements(By.CSS_SELECTOR, 'div.st-NewHeader_navigationTabContainer > a') mainNavigation[1].click() です。 それぞれ、 element = WebDriverWait(driver, 30).until( EC.visibility_of_element_located((By.CSS_SELECTOR, 'div.st-NewHeader_mainNavigation')) ) この記述でやっていることは、「div.st-NewHeader_mainNavigationという要素が出現するまで待つ」ということをやっています。 WebDriverWaitにドライバーとタイムアウトの秒数を記述しています。 untilの中身は待機するまでの条件を記載しています。 ECには沢山のプロパティがありますが、今回は.visibility_of_element_locatedを使用しています。コレは特定の要素が出現するまで待つという命令です。 この引数にはセレクターを指定します。 By.CSS_SELECTOR, 'div.st-NewHeader_mainNavigation' という部分でセレクターを検索しています。 この書き方はいろんなところで使います。 で、要素が出現したら「element」にWebElementを入れています。 このWebElementというのは、 簡単に言うとSeleniumを用いてセレクター検索をした結果取得できる物 くらいに思っておいてください。 mainNavigation = element.find_elements(By.CSS_SELECTOR, 'div.st-NewHeader_navigationTabContainer > a') mainNavigation[1].click() ここでは先程のWebElementにfind_elementsというメソッドを用いて検索をかけています。 WebElementの中に該当のセレクターはあるかな。と検索しているわけです。 このセレクターはQiita上部のタブ群を指しています。 タブの中身は複数個あるため、検索結果が複数出てきます。 その中の一つを選択したいときは[1]をみたいな感じで指定することができます。 二行目では、取得したウェブエレメントの2つ目(1つ目)をClickしています。 ここは説明不要ですね。要素をクリックしております。 まとめ このようにSeleniumを使用します。 色々なメソッド、プロパティを使えば、いろいろなことができるので、 是非調べてみてください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python でウィンドウの閉じるボタンを無効化する

概要 Minecraftで遊んでいるとき、間違えてウィンドウの閉じるボタンを押してしまうと何の確認もなくゲームが終了してしまいます。これを防止するために閉じるボタンを無効化します。 前提 Microsoft Windows 10 Home Python 3.7.7 pip 21.3.1 pywin32 のインストール pip install pywin32 プログラム disableXbutton.py import win32con import win32gui def showXbutton(title: str) -> None: hwnd = win32gui.FindWindow(None, title) if not hwnd: print("couldn't find window. title:", title) return print(win32gui.GetWindowText(hwnd)) win32gui.GetSystemMenu(hwnd, True) def disableXbutton(title: str) -> None: hwnd = win32gui.FindWindow(None, title) if not hwnd: print("couldn't find window. title:", title) return print(win32gui.GetWindowText(hwnd)) hMenu = win32gui.GetSystemMenu(hwnd, True) hMenu = win32gui.GetSystemMenu(hwnd, 0) if not hMenu: return win32gui.DeleteMenu(hMenu, win32con.SC_CLOSE, win32con.MF_BYCOMMAND) def main(): titles = [] # titles に直接書いても良い with open("titles.txt", mode="r") as f: titles = f.readline() for title in titles: disableXbutton(title) # 無効化した閉じるボタンを再表示する関数 # showXbutton(title) if __name__ == '__main__': main() titles.txt Minecraft* 1.17.1 - Multiplayer (3rd-party Server) ウィンドウ名の調べ方 titles.txt に入力するウィンドウ名は、おそらく完全なものである必要があります。そのために、Visual Studio に同梱されている Microsoft Spy++(公式ドキュメント)を使用します。 1. ウィンドウ検索 を開き ファインダー ツール を調べたいウィンドウの上にドラッグし、OK を押します。 2. OK を押すと プロパティインスペクター が表示され、ウィンドウキャプション が調べたかったウィンドウ名となります。 実行 実行すると、閉じるボタンが灰色になり無効化されます。 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

logging 練習

logging 練習 from logging import ERROR, Formatter, Handler, getLogger, FileHandler, Formatter, DEBUG # loggerを作る logger = getLogger(__name__) # 手下のhandlerを作る(手下:handlerには属性があり、filehandler:ファイル作るやつ、とstreamhandler:コンソールに出すやつがいる) handler = FileHandler('log.txt',encoding='utf-8') # loggerに手下を割り当てる logger.addHandler(handler) # 手下の報告書フォーマット決める # reportForm = Formatter('[%(levelname)s] %(asctime)s - %(message)s (%(filename)s)') reportForm = Formatter('%(levelname)s,%(asctime)s,%(message)s,%(filename)s') # 報告書フォーマットでhandlerに指示する handler.setFormatter(reportForm) # 手下の報告基準を設定する handler.setLevel(DEBUG) # loggerの報告基準を設定する logger.setLevel(DEBUG) logger.debug('てすと') logger.error('えらー')
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

デッキ画像から駒名称を抜き出す【特徴点マッチング編】

はじめに こんにちは。逆転オセロニアのYouTubeチャンネル「まこちゃんねる」の中の人です。 本稿では、以前にも挑戦したデッキ画像から駒名称を抜き出すことを目標にします。 今回は特徴点マッチングを使った方法を利用してみます。 モチベーション 前回はテンプレートマッチングを使った方法を利用することで、テンプレート画像から駒名称を取得しました。が、この方法はテンプレート画像を用意する手間があるのが問題点でした。 そこで、今回はテンプレート画像を作らなくても良い手法で、かつ汎用性の高く、画像から駒名称を抜きだしたいと思ったのが始まりです。 特徴点マッチングとは? 2つの画像間で特徴点を検出し、似た特徴点をマッチングする手法です。テンプレートマッチングは回転や拡縮に弱かったのですが、特徴点マッチングは回転や拡縮にも強いのが特徴です。 特徴点を検出するアルゴリズムは、SIFTやSURF、AKAZEが有名らしいです。 マッチングの手法は、総当たり法(Brute-Force)か近似最近傍探索法(FLANN)を利用します。 処理の流れ 特徴点の検出 画像の特徴点(キーポイント)を検出する 特徴量の記述 各キーポイントの周辺の勾配情報から特徴量を記述する 特徴点のマッチング 画像間の特徴量を比較して特徴点を対応付ける 環境 macOS JupyterLab Python3.6 OpenCV 実装の流れ 今回はアルゴリズムにSIFTを使い、FLANNでのマッチングで実装していきます。 画像間の対応点の取得 特徴点検出と特徴量記述を実装する FLANNベースのマッチング処理を実装する マッチング結果から対応点を取得する 特徴点マッチングの判定 画像間の対応点を描画する 複数画像の判定に対応する 駒名称を出力する 画像間の対応点の取得 まず、SIFTで各画像の特徴点と記述子(特徴点の特徴量)を求めます。その後、FLANNと呼ばれる近似最近傍探索アルゴリズムを用いて、元画像の各特徴点と特徴が近い比較画像の記述子を求めます。求めた記述子同士の距離から特徴量として良いものを対応点として取得します。 特徴点検出と特徴量記述を実装する SIFT_create()で検出器を初期化し、detectAndCompute()で検出と記述を行います。 kp1、kp2には特徴点の各座標、des1、des2には各特徴点に対応する特徴量が格納されています。 import cv2 import numpy as np import glob import matplotlib.pyplot as plt %matplotlib inline # 元画像(顔画像)を読み込み、グレースケール変換する(-1はBGRA) img1 = cv2.imread("face/04524_神_A+_[高みへ挑む者]イリオット.png", -1) img1_gray = cv2.cvtColor(img1, cv2.COLOR_BGRA2GRAY) # 比較画像(デッキ画像)を読み込み、グレースケール変換する img2 = cv2.imread("deck.png") img2_gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY) # 検出器(SIFT)の初期化をする sift = cv2.SIFT_create() # キーポイントの決定と特徴量を記述する kp1, des1 = sift.detectAndCompute(img1_gray, None) kp2, des2 = sift.detectAndCompute(img2_gray, None) FLANNベースのマッチング処理を実装する 今回は記述子のマッチングアルゴリズムにFLANN(Fast Library for Approximate Nearest Neighbor)を利用します。indexとsearchパラメータを指定して初期化します。checksの値を大きくすれば精度が上がりますが、速度は遅くなります。flann.knnMatch(des1, des2, k=2)は、第引数des1(元画像の記述子)の各特徴点に対して、第二引数のdes2(比較画像の記述子)を比較していき、近似解2つをマッチング結果として取得します。つまり、元画像の各特徴点と対応するであろう比較画像の特徴点2つを取得しています。 FLANN_INDEX_KDTREE = 0 # INDEXパラメータを指定する index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5) # 木構造を再起的に辿る回数を指定する search_params = dict(checks = 50) # FLANNの初期化をする flann = cv2.FlannBasedMatcher(index_params, search_params) # 上位2つのマッチング結果を取得する matches = flann.knnMatch(des1, des2, k=2) マッチング結果から対応点を取得する des1の各特徴点に近似している2つの距離から特徴量として良いものを対応点としてgoodに格納していきます。0.7の値が小さほど対応点の検出は厳密になり、大きいほど誤検出してしまうので、調整する必要があります。 # 上位2つの距離を相対的に見て、特徴量として良いものを対応点とする good = [] for m, n in matches: if m.distance < 0.7 * n.distance: good.append(m) 特徴点マッチングの判定 マッチした特徴点の個数を閾値とし、閾値に満たない場合は元画像は比較画像に含まれないと判定することにします。今回であれば、特徴点が10個より多くマッチングすれば画像が含まれていると判定します。 画像間の対応点を描画する 閾値を超えた特徴点が10個を超えている時、画像間の特徴点同士を赤線で結び、元画像が比較画像のどの部分に含まれているのかを赤の矩形で描画する。 # 特徴点個数の閾値 MIN_MATCH_COUNT = 10 if len(good) > MIN_MATCH_COUNT: src_pts = np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1,1,2) dst_pts = np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1,1,2) M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0) matchesMask = mask.ravel().tolist() h,w = img1_gray.shape pts = np.float32([[0,0],[0,h-1],[w-1,h-1],[w-1,0]]).reshape(-1,1,2) dst = cv2.perspectiveTransform(pts, M) img2 = cv2.polylines(img2, [np.int32(dst)], True, (0,0,255), 3, cv2.LINE_AA) draw_params = dict(matchColor = (0,0,255,255), singlePointColor = None, matchesMask = matchesMask, flags = 2) img_output = cv2.drawMatches(img1, kp1, img2, kp2, good, None, **draw_params) cv2.imwrite("output.png", img_output) 複数画像の判定に対応する 元画像を複数枚に増やして順番に判定していく処理を実装します。目標は、比較画像(デッキ画像)に含まれる全駒分の5000クラス以上になるのですが、流石に時間がかかりすぎるので工夫しないとダメそうです。(単純ループで1回試してみましたが、2時間以上かかりました・・・)。 今回は単純にループすることで順次画像を判定してみたいと思います。元画像には以下の5クラスを使いました。被りますが、ループ版の完成コードを載せておきます。 04524_神_A+_[高みへ挑む者]イリオット 02018_魔_S+_[百億の牙]ベルゼブブ 00179_竜_A+_[地帝竜]ランドタイラント 00586_神_S+_[叡智の書]ヒアソフィア 00847_竜_A+_[地城竜]ランドタイラント import cv2 import numpy as np import glob import matplotlib.pyplot as plt %matplotlib inline # 比較画像(デッキ画像)を読み込み、グレースケール変換する img2 = cv2.imread("deck.png") img2_gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY) # 検出器(SIFT)の初期化をする sift = cv2.SIFT_create() # デッキ画像のキーポイントの決定と特徴量を記述する kp2, des2 = sift.detectAndCompute(img2_gray, None) # FLANNの初期化をする FLANN_INDEX_KDTREE = 0 index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5) search_params = dict(checks = 50) flann = cv2.FlannBasedMatcher(index_params, search_params) files = glob.glob("face/*.png") for idx, file in enumerate(files): img1 = cv2.imread(file, -1) img1_gray = cv2.cvtColor(img1, cv2.COLOR_BGRA2GRAY) # キーポイントの決定と特徴量を記述する kp1, des1 = sift.detectAndCompute(img1_gray, None) # 上位2つのマッチング結果を取得する matches = flann.knnMatch(des1, des2, k=2) # 上位2つの距離を相対的に見て、特徴量として良いものを対応点とする good = [] for m, n in matches: if m.distance < 0.7 * n.distance: good.append(m) print(len(good)) # 特徴点個数の閾値 MIN_MATCH_COUNT = 10 if len(good) > MIN_MATCH_COUNT: src_pts = np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1,1,2) dst_pts = np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1,1,2) M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0) matchesMask = mask.ravel().tolist() h,w = img1_gray.shape pts = np.float32([[0,0],[0,h-1],[w-1,h-1],[w-1,0]]).reshape(-1,1,2) dst = cv2.perspectiveTransform(pts, M) img2_tmp = img2.copy() img2_tmp = cv2.polylines(img2_tmp, [np.int32(dst)], True, (0,0,255), 3, cv2.LINE_AA) draw_params = dict(matchColor = (0,0,255,255), singlePointColor = None, matchesMask = matchesMask, flags = 2) img_output = cv2.drawMatches(img1, kp1, img2_tmp, kp2, good, None, **draw_params) cv2.imwrite(f"{str(idx)}.png", img_output) 駒名称を出力する 最後はテンプレート画像の時と同じように、駒名称を出力して目的を達成する。 # 駒名称リスト piece_name_list = [] files = glob.glob("face/*.png") for idx, file in enumerate(files): img1 = cv2.imread(file, -1) img1_gray = cv2.cvtColor(img1, cv2.COLOR_BGRA2GRAY) ... piece_name_list.append(file.split("_")[3].rstrip(".png")) # 駒名称を出力する print("\n".join(piece_name_list)) [高みへ挑む者]イリオット [地帝竜]ランドタイラント おわりに これで後は、前回スクレイピングした全駒の顔画像と突合していけば、自前でデータを作らなくても全自動でデッキの中の駒名を取得する事が可能になりました! また、前回のテンプレートマッチングの実装では、私の端末やオセロニアのデッキ仕様が変わったら全てが終わってしまう実装でしたが、今回は特徴量から求めているので、汎用的に利用していくことも可能です。ただし、全ての駒をチェックしていくとなると数時間かかるため、普段よく使われる駒だけをチェックするか、最低でも検索対象をA+とS+に絞らないとダメだなと思いました。次回はここら辺を改善していきたいと思います。DNN系の技術を用いたら精度高く早くなるのかな? 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EclipseでPythonのpip installを使う

EclipseでPython pip installのやりかた 本記事は、Pythonをinstallしていること前提としています。 目次 1.get-pipをインストール 2.実行 get-pipをインストールする https://bootstrap.pypa.io/get-pip.py 1.リンクを右クリック 2.名前を付けてリンク先を保存 3.Eclipse.exeと同じ階層にget-pip.pyを配置 実行 1.Eclipseの設定 2.フィルター入力 →「インタープリンター」 3.Pythonインタープリター 4.pipで管理 5.install <インストールするもの>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

二つのDataFrameを比較して代入を行う方法(pd.mergeの使い方)

毎回書き方を忘れるので、以下、備忘録です。 DataFrame① df time_index data 0 2001 10 1 2002 20 2 2003 30 3 2004 40 3 2011 40 19 2020 50 DataFrame ② dx time_index data 0 2010 1 1 2011 20 2 2012 3 3 2013 4 10 2020 4 このDataFrame①とDataFrame②をmergeする。 df = pd.merge(df, dx, on='time_index', how='left') とすることで、 time_index data_x data_y 0 2001 10 nan 1 2002 20 nan 2 2003 30 nan 3 2004 40 nan 10 2011 40 20 19 2020 50 4 となる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ネットワークの中心性(次数中心性と近接中心性)

ネットワーク(グラフ)とは,以下のように頂点(ノード)が辺(エッジ,リンク)で結ばれたものです. このようなネットワークにおいて中心的なノードを見つけたいことがあります.人のつながりをネットワークとして捉えるならば,中心的な人は,その文脈に依存して,友人が多い人,うわさを広めてしまう人,感染症を拡げてしまう人,他人に影響力がある人,…と解釈することができます. ネットワークの中心的なノードを見つける時に,中心性という指標が使えます.中心性指標にもいくつかありますが,この記事では次数中心性,近接中心性の2つを説明します.媒介中心性,固有ベクトル中心性の2つについては後日記事を書いて説明します.これまでに書いたネットワークの記事 NetworkXによるネットワークの作成と可視化 NetworkXによるモデルからのネットワーク生成 も併せてご覧ください. 次数中心性 次数中心性は,単純に各ノードが持つリンクの数(=次数)です.具体的に以下のようなネットワークで次数中心性を求めてみましょう. ノード$v_i$の次数$k_i$が$v_i$の次数中心性となります.すなわち上のネットワークでは,$k_1=4,\, k_2=1,\, k_3=2,\, k_4=4,\, k_5=3,\, k_6=2$となります. Pythonのネットワーク分析ツールであるNetworkXで上記ネットワークの各ノードの次数中心性を求めてみましょう.NetowrkXでは,次数中心性は上記$k_i$を$N-1$で割って,正規化したものが出力されます.$N$はノード数です.したがって,$k_1=\frac{4}{5}=0.8,\, k_2=\frac{1}{5}=0.2,\, k_3=\frac{2}{5}=0.4,\, k_4=\frac{4}{5}=0.8,\, k_5=\frac{3}{5}=0.6,\, k_6=\frac{2}{5}=0.4$となります.具体的なコードは以下です. コード centrality import networkx as nx # NetworkXをインポート # ネットワーク生成 G = nx.Graph([(1, 2), (1,3), (1,4), (1,5), (3, 4), (4,5), (4,6),(5,6)]) nx.draw(G, with_labels=True) # ラベルをTrueにして番号の可視化 print('Degree Centrality', nx.degree_centrality(G)) print('Closeness Centrality', nx.closeness_centrality(G)) print('Betweenness Centrality', nx.betweenness_centrality(G)) print('Eigenvector Centrality', nx.eigenvector_centrality(G)) 出力結果は以下のようになります.まず上記の図と同じネットワークをNetworkXで生成し,4つの中心性指標をまとめて出しています.NetworkXの組み込み関数degree_centrality()にネットワークのオブジェクトGを渡すことで各ノードの次数中心性を求めてくれます.Degree Centralityの部分を見ていただくと,Degree Centrality {1: 0.8, 2: 0.2, 3: 0.4, 4: 0.8, 5: 0.6000000000000001, 6: 0.4}となっており,$k_1=0.8,\, k_2=0.2,\, k_3=0.4,\, k_4=0.8,\, k_5=0.6,\, k_6=0.4$に値がそれぞれ一致することが分かります. 結果 近接中心性 近接中心性は,あるノードから他の全てのノードへの距離(最短経路長)平均値の逆数をとったものです.平均的な距離が小さいほど中心性の値は大きくなってほしいので,逆数をとります.式で書くと,ノード$v_i$の近接中心性は, $$ l_i = \frac{\sum d_{ij}}{N-1}の逆数=\frac{N-1}{\sum d_{ij}} $$ となります.具体的にまた前の例と同じネットワークで考えてみましょう. まずノード$v_1$を始点として他の全てのノードへの距離(最短経路長)$d_{12},\, d_{13},\, d_{14},\, d_{15},\, d_{16}$を求めてみましょう.$v_1$と$v_2$,$v_1$と$v_3$,$v_1$と$v_4$,$v_1$と$v_5$は直接(リンク数は1つ)つながっているので$d_{12}=d_{13}=d_{14}=d_{15}=1$となります.$v_1$と$v_6$は間に2本のリンク($v_4$経由でも$v_5$経由でも良い)でつながっているので,$d_{16}=2$となります.したがって,ノード$v_1$の近接中心性$l_1$は, $$ l_1 = \frac{N-1}{\sum d_{ij}}=\frac{5}{1+1+1+1+2}\approx0.83 $$ となります.同様に求めると,$l_2=0.5,\, l_3=0.625,\, l_4\approx0.83,\, l_5\approx0.71,\, l_6\approx0.56$となります. ではこれもNetworkXの結果を見てみましょう.コードの中に既に書いてあるcloseness_centrality(G)の部分です.NetworkXの組み込み関数closeness_centrality()にGを渡せばよいです.結果のCloseness Centrality {1: 0.8333333333333334, 2: 0.5, 3: 0.625, 4: 0.8333333333333334, 5: 0.7142857142857143, 6: 0.5555555555555556}を見ると上で計算した$l_1, \ldots, l_6$と一致することが分かります. 動画解説 この記事の内容は私のチャンネルの以下の動画で解説していますので,よろしければ併せてご覧ください.もし今後とも役に立ちそうであればチャンネル登録もよろしくお願いします.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Python]Matplotlibで任意の日本語フォントを表示する。

今さらこの記事を書く意義は? これまでも同様の無いようでQiitaにも多数の記事が投稿され、Webでも検索すれば、数えきれないほどの資料が見つかりますが、あえて少し視点を変えて、解説させていただきます。 ここでは、特にGoogle ColaboratoryとWatson Studioという、基本的にデータを永続化出来ない環境でのMatplotlibを想定した日本語フォントを導入方法を紹介します。 Google Colaboratoryに関しては、Androidのユーザーであれば必ずと言っていいほど登録済みのGooleアカウントでサイトにアクセスして、利用規約に同意するだけで容易に新規ノートブックを作成していただけます。 IBM Cloudに関しては、アカウントお持ちで無い方が多いとは思いますが、サインアップは済んでいるものとして進めさせていただきます。 始めに、ご存じ無い方のためにWatson Studioを開始するまでの手順をかいつまんで解説しておきます。まずはログイン後のダッシュボードの画面の右上にあるリソースの追加をクリックします。 次のカタログの画面で、左側のカテゴリーのリストからAI / Machine Learningを選びます。 次のページで目的のWatson Studioが選べます。 作成画面に遷移しますと、任意でロケーションを変更可能ですが、右下の使用条件に同意さえすれば、無料プランで作成ボタンがクリック出来るようになります。 サービスが作成され、ページが変わったら、Launch in Cloud Pak for Dataというボタンをクリックします。 ようこその画面になりますので、新規にプロジェクトを作成します。 プロジェクトの作成では、差し当たり空のプロジェクトを選択します。 次は任意のプロジェクト名を入力すれば、作成ボタンがアクティブになります。 プロジェクトのトップページが表示されたら、プロジェクトに追加ボタンで資産タイプの選択が出現されるのでNotebookを選びます。 後はNotebook名を入力するだけです。ランタイムは特に高いスペックを必要としないのであれば、低いスペックにしておいたほうが、稼働時間を長くできます。 白紙のNotebookが表示されたら準備完了です。お疲れさまでした。早速コードを入力して実行してみましょう。 まず日本語フォント未登録だと、どうなるか... 単なるサンプルなので意味の無いデータですが、今回は以下のコードをセルに入力し、グラフを表示します。 import numpy as np x = np.arange(-np.pi, np.pi, 0.25) from matplotlib import pyplot as plt plt.plot(x, np.sin(x), marker='o', label=u'sinカーブ', color='gray') plt.plot(x, 2*np.sin(x), marker='x', label=u'sin2カーブ', color='red') plt.plot(x, np.cos(x), marker='.', label=u'円周率π', color='blue') plt.xlabel(u'スケール') plt.ylabel(u'ラジアン') plt.legend() plt.figtext(x=0, y=0, s=u'三角関数') plt.grid(axis='x', color='pink', alpha=0.5, linewidth=1, linestyle='--') plt.grid(axis='y', color='cyan', alpha=0.5, linewidth=1, linestyle=':') すると大量のエラーが出力された後、全角のフォントがいわゆる「豆腐」の状態となり、それぞれ以下のように表示されます。 Google Colaboratory Watson Studio Plotに日本語フォントを一括適用する 日本語フォントはそれぞれ好みのものを選択して構いませんが、ここでは直接リンクからZipファイルをダウンロードできるSource Han Code JPを採用します。 まず下記のコードでダウンロードしてから、目的のファイルを抽出し保存します。 import requests from zipfile import ZipFile from io import BytesIO from matplotlib import font_manager as fm font_url = 'https://github.com/adobe-fonts/source-han-code-jp/archive/2.011R.zip' font_zip = requests.get(font_url) font_set = BytesIO(font_zip.content) font_path = 'source-han-code-jp-2.011R/OTF/SourceHanCodeJP-Regular.otf' ZipFile(font_set, 'r').extract(font_path) font_set.close() さらに、フォントファイルをfont_managerに登録した上で、matplotlib.pyplotに既定のフォントとして一括適用します。 手っ取り早く日本語フォントを表示するためだけであれば、これでよいのではないかと思います。 from matplotlib import pyplot as plt, font_manager as fm fm.fontManager.addfont(font_path) font_prop = fm.FontProperties(fname=font_path) plt.rc('font', family=font_prop.get_name()) 実際に日本語フォントを表示してみる。 準備が出来たところで、冒頭のコードを同じように実行すると、それぞれ以下のように表示されます。 Google Colaboratory Watson Studio 個別にフォントの設定を調整する場合 グラフの見栄えにこだわるのであれば、font_propertiesというパラメータにmatplotlib.font_manager.FontPropertiesを渡すことにより、個別にフォントを調整することが出来ます。 下記にフォントのサイズや太さを変更するサンプルを示します。 x = np.arange(-np.pi, np.pi, 0.25) plt.plot(x, np.sin(x), marker='o', label='sinカーブ', color='gray') plt.plot(x, 2*np.sin(x), marker='x', label='sin2カーブ', color='red') plt.plot(x, np.cos(x), marker='.', label='円周率π', color='blue') font_prop.set_size(12.0) plt.xlabel('スケール', font_properties=font_prop) plt.ylabel('ラジアン', font_properties=font_prop) font_prop.set_size(18.0) font_prop.set_weight('extra bold') plt.figtext(x=0, y=0, s='三角関数', font_properties=font_prop) plt.grid(axis='x', color='pink', alpha=0.5, linewidth=1, linestyle='--') plt.grid(axis='y', color='cyan', alpha=0.5, linewidth=1, linestyle=':') font_prop.set_size(9.0) font_prop.set_weight('ultralight') plt.legend(prop=font_prop) Google Colaboratory Watson Studio 終わりに 毎回、フォントファイルの入手からの作業となることを念頭に、出来るだけ小さいオーバーヘッドで、かつ汎用的に利用出来るコードとなるよう、配慮したつもりです。 実際のところ、普段は下記の記事に書いているようにGoogle Cloud ShellでJupyter Labを利用しています。 そのためGoogle ColaboratoryもWatson Studioも最近はほとんどアクセスすることがありません。今回の記事を書くために久々に利用したので、少し新鮮に感じられました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】独自例外の作成

Pythonでの独自例外の作成方法と使い方を簡単にメモしておく。 独自例外の作成 Pythonで独自の例外クラスを定義する場合、Exceptionクラスを親クラスとして継承したクラスを作成する。 sample1 class MyException(Exception): pass また、例外オブジェクトを出力(print())したときに表示されるメッセージを例外クラスで指定することもできる。 sample2 class MyException(Exception): def __str__(self): return "例外クラス:MyException" さらに、__init__を使うことで、引数を受け取ることもできる。 独自例外を使う 独自例外もExceptionクラスから派生したクラスであるため、raise <例外名>を使って例外を投げることができる。 例外処理の方法は、他の例外と同じように行うことができる。 # sample1場合 try: raise MyException("例外が発生しました!") except MyException as e: print(e) # 例外が発生しました # sample2場合 try: raise MyException() except MyException as e: print(e) # 例外クラス:MyException
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

知らない人は損しているなと思うChromeDriverを自動更新するPythonライブラリ

記事はこちらに移管しましたmm
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

seleniumインストールに失敗したけどpipアップグレードして解消した

記事はこちらに移管しましたmm
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[py2rb] 多重継承の3

はじめに 移植やってます。 ( from python 3.7 to ruby 2.7 ) 多重継承 (Python) 移植するにあたり、多重継承はRubyのミックスインで行こうと思っていました。 __init__のないクラスをモジュールにする予定でしたが、これでは避けられないようですので、委譲により多重継承を表現できればと思います。 forwardable (Ruby) require 'forwardable' class A def initialize(s) @s = s end def putsa(o) puts "#{@s}は#{o}です" end def putsd() puts "呼び出されたクラスは#{self.class}です" end end class B def initialize(s) @s = s end def putsb(o) puts "#{@s}は#{o}です" end def putsd() puts "呼び出されたクラスは#{self.class}です" end end class C extend Forwardable def initialize(*args, **kwargs) @a = A.new(kwargs['A']) @b = B.new(kwargs['B']) end def_delegators :@a, :putsa, :putsd def_delegators :@b, :putsb, :putsd end c = C.new('A' => '好きな食べ物', 'B' => '好きな飲み物') c.putsa('みかん') c.putsb('コーヒー') c.putsd() p C.ancestors # output 好きな食べ物はみかんです 好きな飲み物はコーヒーです 呼び出されたクラスはBです [C, Object, Kernel, BasicObject] 学習したばかりのforwardableを使用した例です。 メソッドが重複した場合、後のdef_delegators :@b, :putsb, :putsdが優先されるようです。 また、ancestorsにクラスAもBも検索されませんので、superで呼び出したりとかはできないものと思われます。 delegate (Ruby) require 'delegate' class A def initialize(s) @s = s end def putsa(o) puts "#{@s}は#{o}です" end def putsd() puts "呼び出されたクラスは#{self.class}です" end end class B def initialize(s) @s = s end def putsb(o) puts "#{@s}は#{o}です" end def putsd() puts "呼び出されたクラスは#{self.class}です" end end class C def initialize(*args, **kwargs) @a = SimpleDelegator.new(A).new(kwargs['A']) @b = SimpleDelegator.new(B).new(kwargs['B']) end def method_missing(name, *args) [@b, @a].each do |x| if x.respond_to?(name) return x.method(name).call(*args) end end raise "メソッド #{name} が見つかりません" end end c = C.new('A' => '好きな食べ物', 'B' => '好きな飲み物') c.putsa('みかん') c.putsb('コーヒー') c.putsd() p C.ancestors # output 好きな食べ物はみかんです 好きな飲み物はコーヒーです 呼び出されたクラスはBです [C, Object, Kernel, BasicObject] もう一つのdeletagorを使用した例です。 メソッド数が多い場合は、こちらの方が楽かもしれません。 メモ Python の 多重継承の3 を学習した 百里を行く者は九十里を半ばとす
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

uvicorn ~~:app --reloadが途中で落ちる

解決 仮想環境を削除 pipenv --rm 開発用パッケージもインストール pipenv sync --dev
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonで複数の動画を自動圧縮

概要 画面キャプチャでとった動画たちがHDDの容量を圧迫しているので、圧縮したい。 容量の小さい動画(既に圧縮済だったりする)も混ざっているので、容量の大きい動画だけピックアップして圧縮するようにする。 圧縮にはffmpegというソフトを使うのが簡単そう。 Pythonからの操作もできるから、Pythonで書くことにする。 ffmpegのインストール ここの記述に従ってffmpegをインストールしてパスを通す。 処理の流れ 動画ファイル名にスペースが入っているとエラーの原因になるので、これを取り除く。 カレントディレクトリ中の .mp4 のファイルすべてのパスのリストを取得。 for文で得られたリストを回す。動画の容量が1GB以上か判定し大きければ圧縮。 圧縮前の動画(思い通り圧縮できないかもしれないので一応とっておく)の名前を圧縮後と区別しやすいように変更。 コード import glob import os import subprocess #① videos = glob.glob('./*.mp4') #②ファイル名にスペースが入っているとエラーになるので、取り除いておく。 for video in videos: os.rename(video, video.replace(' ', '')) #③ videos = glob.glob('./*.mp4') count = 0 #ビデオの容量が1GB以上か判定し、大きければ圧縮 for video in videos: size = os.path.getsize(video) if size>1000000000: #④圧縮 subprocess.call('ffmpeg -i '+video+' -crf 18 '+video[:-3]+'_圧縮済.mp4', shell=True) #⑤元動画の名前を変更する。消しやすいように、まとまって配置させるため番号をつける。 os.rename(video, './'+str(count)+'_圧縮前_'+video[2:]) count += 1 ①まずは globでカレントディレクトリ内 mp4のパスすべてのリストを取得。 ②リストのパスを使って mp4ファイル名からスペースを取り除くループを回す。 ③ファイル名を変更したのでパスのリストも更新しておく。 ④ffmpegの圧縮はシェルコマンドで行う。Pythonからシェルコマンドを呼び出すには subprocess.callを使う。 crfの値を変えると動画の品質を指定できる。 ⑤圧縮前の動画ファイル名を分かりやすいように変えておく。番号を振っておくとソートしたとき並んで表示されるので成功確認した後で消すときにやりやすい。 0_圧縮前_元の名前.mp4 とか 1_圧縮前_元の名前.mp4 みたいになる。 結果 ものによっては容量が10分の1以下になった。 ffmpegってすごい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Windows11,Anaconda3上でYOLOv5を動かしてみる(GPU編)

目的 画像データから物体認識ができるYOLOv5をWindows上で試してみたので方法をまとめた。 前回はCPUのみで実行していたので、今回はGPUを使う方法を試してみた。 環境 CPU : Intel i7-10700F 8Core 16thread GPU : NVIDIA GeForce RTX 3060 (Mem 12GB) OS : Windows11 Anaconda : 3 Python : 3.9.7 YOLOv5のダウンロード ここはCPU編と同じ。 https://github.com/ultralytics/yolov5 から git clone するか、zipファイルをダウンロードして展開しておく。 PyTorchのバージョンを確認する https://pytorch.org/ のページへ行き、 Install ボタンをクリックすると START LOCALY の画面が表示されるので、 PyTorch Build:Stable (1.10.1) Your OS:Windows Package:Conda Language:Python Computer Platform:CUDA 11.3 と選択すると下記が表示されるのでメモっておく。 Run this command : conda install pytorch torchvision torchaudio cudatoolkit=11.3 -c pytorch Anacondaで環境作成 ANACONDA.NAVIGATORで仮想環境を新たに作成し、Terminalを開く。 そこでメモしておいたコマンドを下記のように実行することでPyTorchがインストールされる。 conda install pytorch torchvision torchaudio cudatoolkit=11.3 -c pytorch 他にも必要なパッケージをインストールしておく pip install opencv-python pip install pandas pip install requests pip install PyYAML pip install tqdm pip install matplotlib pip install seaborn 実行してみる CPU編の時に作成した test.py のディレクトリへ移動し、実行する。 ソースはこちら https://github.com/roxa-delphi/yolov5_test/blob/main/test.py cd (target-dirctory) python test.py すると下記のようにメッセージが出て、認識したものにマーカーが付いた状態で動画が表示された。ちゃんとGPUが認識出来ていることが分かる。 YOLOv5 2022-1-22 torch 1.10.1 CUDA:0 (NVIDIA GeForce RTX 3060, 12288MiB) 60秒の動画の解析時間は CPUのみ : 157秒 GPUあり : 40秒 と約3倍速いことを確認した。 GPUを使うことでリアルタイムに解析可能なことがわかった。 おまけ 下記の2つのスクリプトも作ったのでgithubへ置いた。 動画ファイルを読み込み物体認識を行った結果をフレーム毎にJPEGで保存する https://github.com/roxa-delphi/yolov5_test/blob/main/detectedFrameImage.py 動画ファイルを読み込み物体認識を行った結果を動画ファイルへ保存する https://github.com/roxa-delphi/yolov5_test/blob/main/detectedMovie.py 参考URL Windows11,Anaconda3上でYOLOv5を動かしてみる(CPU編) https://qiita.com/roxa/items/e735e8ceaeb326943674
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[py2rb] 辞書クラスの拡張2

はじめに 移植やってます。 ( from python 3.7 to ruby 2.7 ) 辞書クラスの拡張 (Python) from collections import defaultdict class BasicComposition(defaultdict): def __init__(self, *args, **kwargs): defaultdict.__init__(self, int) for k, v in list(self.items()): if not v: del self[k] def __missing__(self, key): return 'nil' def __setitem__(self, key, value): if isinstance(value, float): value = int(round(value)) elif not isinstance(value, int): print('Error') if value: super(BasicComposition, self).__setitem__(key, value) elif key in self: del self[key] d = BasicComposition() d.update({'A': 1, 'B': 2.1, 'C': 0}) d['D'] = 0 d['E'] = 1.2 for k, v in d.items(): print(k, v) print(d['F']) # output A 1 B 2.1 C 0 E 1 nil コメント欄にてご指摘いただいた通り、非整数を代入することが可能です。 運用で逃げようとも思いましたが、別件の多重継承をどうするかという話もありますので、考えてみました。 継承案 (Ruby) class BasicComposition < Hash def initialize(*args, **kwargs) self.default = 0 if args args.each do |x| self[x] += 1 end end if kwargs kwargs.each do |k, v| self[k] = v end end end def []=(key, value) if value.instance_of?(Float) value = value.round elsif value.instance_of?(Integer).! raise TypeError, 'IntegerもしくはFloat以外が指定されました。' end if value != 0 # reject 0's self.merge!({key => value}) elsif self.include?(key) self.delete(key) end end end d = BasicComposition.new d.merge!({'A' => 1, 'B' => 2.1, 'C' => 0}) d['D'] = 0 d['E'] = 1.2 d.each do |k, v| p [k, v] end puts d['F'] # output ["A", 1] ["B", 2.1] ["C", 0] ["E", 1] 0 これは、単に移植しただけですので、非整数の代入が可能です。 委譲案1 (Ruby) class BasicComposition def initialize(*args, **kwargs) @h = Hash.new(0) if args args.each do |x| @h[x] += 1 end end if kwargs kwargs.each do |k, v| @h[k] = v end end end def []=(key, value) if value.instance_of?(Float) value = value.round elsif value.instance_of?(Integer).! raise TypeError, 'IntegerもしくはFloat以外が指定されました。' end if value != 0 # reject 0's @h.merge!({key => value}) elsif self.include?(key) @h.delete(key) end end def merge!(**kwargs) if kwargs kwargs.each do |k, v| self[k] = v end end end def include?(key) @h.include?(key) end def [](key) @h[key] end def h @h end end d = BasicComposition.new d.merge!('A' => 1, 'B' => 2.1, 'C' => 0) d['D'] = 0 d['E'] = 1.2 d.h.each do |k, v| p [k, v] end puts d['F'] # output ["A", 1] ["B", 2] ["E", 1] 0 独習Ruby 450p に委譲の説明があります。 # class BasicComposition < Hash @h = Hash.new(0) Hashを継承するのではなく、内部に保持(has)します。 d.h.eachのところが格好悪いのですが、'B'の値が整数となり、'C' => 0が削除されました。 委譲案2 (Ruby) def merge!(**kwargs) # if kwargs # kwargs.each do |k, v| # self[k] = v # end # end end # output ["E", 1] 0 委譲案1のコードで、def merge!を空のメソッドにします。 結果として、merge!メソッドからの値変更をスルーすることにより、非整数の代入を防ぎます。 forwardable (Ruby) require 'forwardable' class BasicComposition extend Forwardable def initialize(*args, **kwargs) @h = Hash.new(0) if args args.each do |x| @h[x] += 1 end end if kwargs kwargs.each do |k, v| @h[k] = v end end end def_delegators :@h, :[], :include?, :each def []=(key, value) if value.instance_of?(Float) value = value.round elsif value.instance_of?(Integer).! raise TypeError, 'IntegerもしくはFloat以外が指定されました。' end if value != 0 # reject 0's @h.merge!({key => value}) elsif self.include?(key) @h.delete(key) end end def merge!(**kwargs) if kwargs kwargs.each do |k, v| self[k] = v end end end end d = BasicComposition.new d.merge!('A' => 1, 'B' => 2.1, 'C' => 0) d['D'] = 0 d['E'] = 1.2 d.each do |k, v| p [k, v] end puts d['F'] # output ["A", 1] ["B", 2] ["E", 1] 0 require 'forwardable'つよいですね。 d.eachになってスッキリしています。 @Nabetani さん、ご指摘ありがとうございました。 メモ Python の 辞書クラスの拡張2 を学習した 百里を行く者は九十里を半ばとす
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LSTM層を使おうとしてエラーが出てから使えるようになるまで

はじめに keras で時系列データの機械学習してみようと構築しているとLSTMでエラーが出ました。いろいろ調べた結果numpyのバージョンを下げることで解決しました。その過程をまとめます。 書いたコード ひとまず簡単なmodelだけ一旦作成しようと以下のコードをとりあえず実行。数字は適当です。 from tensorflow.keras.layers import Dense, LSTM, Input, Embedding, Dropout from tensorflow.keras.models import Model nunits = 365 embedding_size = 100 input_layer = Input(shape=(None,)) x = Embedding(1000, embedding_size)(input_layer) x = LSTM(nunits)(x) x = Dropout(rate=0.2)(x) out = Dense(365, activation='softmax')(x) model = Model(input_layer, out) model.summary() そうすると、 詰みました。 そこでnumpyのバージョンを下げればいいとの情報を耳にしたので、新たなLSTM用の仮想環境を作成し、そこでnumpy==1.19.1をインストール 仮想環境の作成、その他諸々のインストール conda create -n forLSTM python=3.7 ipykernel ターミナルでこのコマンドを実行してforLSTMという環境を作成 そして、 conda activate forLSTM これでforLSTMに侵入 もろもろインストールしていきます。 conda install numpy==1.19.1 conda install tensorflow conda install keras この状態で最初のコードを実行してみると きました! vscodeでセルコーディングをしている際は、右のinteractiveと書いてある部分(呼び方がわからない)の右上や、画面の左下などにどの環境にいるかが書かれているので、そこまでちゃんと移行していないとまたエラーが出るので注意です。 参考文献 [生成Deep Learning] ・David foster 著  ・松田晃一、小沼千絵 訳 ・オライリージャパン ・2020年発行
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

django-rest-framework-gisを触ってみよう

はじめに この記事はMIERUNE Advent Calendar 2021 21日目の記事です(すでに年が明けていますが・・・泣)。前回はGeoDjangoを触ってみよう!という記事を書いたので、今回はdjango-rest-framework-gisを触ってみようというテーマにしました。前回の記事はちょっと重めになったので、今回はライトめに書きます。 実際の業務の中では、DjangoはAPIサーバーとして利用することがほとんどで、GISデータを扱う際にはこちらのモジュールを導入することが多い印象です。 django-rest-framework-gisとは DjangoでREST APIを実装する際には、Django REST framework(DRF)というサードパーティ製のライブラリを使います。そのDRFに地理空間機能を追加したものがdjango-rest-framework-gisになります。 ソースコードはこちらになります。 前提 この記事は、Django公式ドキュメントのGeoDjangoチュートリアルを実行したソースコードに対して、DRFおよびdjango-rest-framework-gisを追加してみて、その機能を試してみようという趣旨で書いています。 対象 DRFはある程度触ったことがある django-rest-framework-gisははじめて バージョン python_version = "3.8" django = "==3.2.6" psycopg2-binary = "==2.9.1" Dockerのバージョン $ docker version --format '{{.Server.Version}}' 20.10.11 ソースコード/ 環境 以下に実行したソースコードを載せておきます。前回の記事で紹介したリポジトリに、django-rest-framework-gisを含めたREST APIの機能を追加しています。 https://github.com/selfsryo/GeoDjangoOfficialTutorial 環境はそのままDockerでDjango + PostGIS環境を構築したものを使います。 以下のような構成になっています。 GeoDjangoOfficialTutorial ├── .gitignore ├── docker-compose.yml ├── geodjango │ ├── .env.sample │ ├── Dockerfile │ ├── Pipfile │ ├── Pipfile.lock │ ├── entrypoint.sh │ ├── geodjango │ │ ├── __init__.py │ │ ├── asgi.py │ │ ├── settings.py │ │ ├── urls.py │ │ └── wsgi.py │ ├── manage.py │ └── world │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── data │ │ ├── Readme.txt │ │ ├── TM_WORLD_BORDERS-0.3.dbf │ │ ├── TM_WORLD_BORDERS-0.3.prj │ │ ├── TM_WORLD_BORDERS-0.3.shp │ │ └── TM_WORLD_BORDERS-0.3.shx │ ├── load.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── serializerss.py │ ├── tests.py │ └── views.py └── postgres ├── .env.db.sample ├── Dockerfile └── sql └── init.sql データは前回の記事で用意したShapefileを使います。 やってみよう 準備 Pipenv環境の中にDRF、django-rest-framework-gis、django-filterをインストールします。 $ cd .../GeoDjangoOfficialTutorial/geodjango $ pipenv install djangorestframework djangorestframework-gis django-filter その後、コンテナを一度削除します。 $ cd .. $ docker compose down -v settings.pyのINSTALLED_APPに'django.contrib.gis'と、先ほど作成したworldを追加します。なお、自分の場合はDockerを使っているので、いくつかの値を環境変数としてgeodjango/.envから読み込むようにしています。DATABASESの設定値もDockerのPostGISに接続するようにしています。以上を踏まえ、settings.pyは以下のようになりました。 import os from pathlib import Path BASE_DIR = Path(__file__).resolve().parent.parent SECRET_KEY = os.environ.get("SECRET_KEY") DEBUG = int(os.environ.get("DEBUG", default=0)) ALLOWED_HOSTS = [] INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'django.contrib.gis', # 追加 'django_filters', 'rest_framework', 'rest_framework_gis', # 作成したアプリケーション 'world', ] シリアライザ 次にserializers.pyを作成します。アプリケーションの直下の、world/serializers.pyという階層にして作成します。 world ├── data │ ├── Readme.txt │ ├── TM_WORLD_BORDERS-0.3.dbf │ ├── TM_WORLD_BORDERS-0.3.prj │ ├── TM_WORLD_BORDERS-0.3.shp │ └── TM_WORLD_BORDERS-0.3.shx │ ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ └── __init__.py ├── models.py ├── serializerss.py ├── tests.py └── views.py 内容は以下のようにします。今回はGeoFeatureModelSerializerというクラスを使いました。(公式のREADMEはこちら) from rest_framework_gis.serializers import GeoFeatureModelSerializer from world.models import WorldBorder class WorldBorderSerializer(GeoFeatureModelSerializer): class Meta: model = WorldBorder geo_field = 'mpoly' auto_bbox = True fields = ('__all__') GeoFeatureModelSerializerはModelSerializerのサブクラスですが、以下のような特徴があります。 geo_fieldの定義が必要 GeometrySerializerMethodFieldとして地理空間的な処理を行った上で指定することも可能 GeoJSON形式でレスポンスを返す id_fieldにてidを含むか含まないか、および別フィールドを指定可能(デフォルトはpk) auto_bboxにてgeo_field(およびbbox_geo_field)から計算されたbboxの値をレスポンスに含むか含まないか指定可能 GeoJSONのpropertiesはカスタム可能 今回は、auto_bboxをTrueにしてみました。 ビュー 次にviews.pyは以下のようにしました。ビューはDRF標準のModelViewSetを使います。 from rest_framework import viewsets from rest_framework_gis.filters import DistanceToPointFilter, InBBoxFilter from rest_framework_gis.pagination import GeoJsonPagination from world.serializers import WorldBorderSerializer from world.models import WorldBorder class MyPagination(GeoJsonPagination): page_size_query_param = 'page_size' class WorldBorderViewSet(viewsets.ModelViewSet): queryset = WorldBorder.objects.all() serializer_class = WorldBorderSerializer pagination_class = MyPagination filter_backends = (DistanceToPointFilter, InBBoxFilter) distance_filter_field = 'mpoly' bbox_filter_field = 'mpoly' bbox_filter_include_overlapping = True 以下django-rest-framework-gisの特徴となる点です。 pagination_classにGeoJsonPaginationを指定 DRFのページネーション(PageNumberPaginationなど)とはレスポンスの形式が一部異なり、GeoJSON形式になる フィルタリングのクラスを指定できる DistanceToPointFilterの場合、指定した距離に含まれるインスタンスを返す distance_filter_fieldで基準となるフィールドの指定が必要 InBBoxFilterの場合、指定されたbbox内に完全に含まれるインスタンスを返す bbox_filter_fieldで基準となるフィールドの指定が必要 bbox_filter_include_overlapping = Trueでbbox内に完全に含まれなくとも重なっていればそのインスタンスを返す URL 次にurls.pyを更新します。今回は以下のようにします。 from django.contrib import admin from django.urls import include, path from rest_framework import routers from world.views import WorldBorderViewSet router = routers.DefaultRouter() router.register('border', WorldBorderViewSet) urlpatterns = [ path('admin/', admin.site.urls), path('world/', include(router.urls)), ] BrowsableAPI 設定ができたのでDRFのBrawsableAPIから確認します。Dockerのコンテナを立ち上げます。 $ docker compose up --build -d その後データのインサートを行います。前回の記事で作成したスクリプトを実行します。 $ docker container exec -it geodjango python3 manage.py shell -c "from world import load; load.run()" ブラウザで確認してみます。単体のGETをしてみます。ブラウザから以下にアクセスします。 以下のように、GeoJSONの形式でレスポンスが返ってきていることが確認できます。 一部省略しましたがpropetiesとbboxも含まれています。 続いてページングの確認です。以下のURLを開きます。 http://localhost:8000/world/border/?page_size=5 DRFが持つページネーションクラスとは違った形式でレスポンスが返ってきていることが確認できます。 これに、距離でフィルタをかけてみます。以下にアクセスします。 http://localhost:8000/world/border/?page_size=5&dist=1&point=138.729952,35.359455 座標を富士山に指定したので、日本のインスタンスが取得できます。 最後にbboxでフィルタをかけてみます。以下にアクセスします。 http://localhost:8000/world/border/?page_size=5&in_bbox=122.935257,24.250832,153.96579,45.486382 日本のインスタンスが持つbboxを指定しましたが、bbox_filter_include_overlapping = Trueにしているので"count": 7となっています。 このように、アクセスするエンドポイントを変えることで取得する地物をフィルタリングすることができました。 おわりに 基本的な内容でしたが、体系的にdjango-rest-framework-gisを触ることができたので良い勉強になりました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

lambda 関数作るとき【完全自分用メモ】

1. 概要 lambda関数作成するとき、忘れてググるやるまとめる 2. 目次 1. 概要 2. 目次 2.1. layer の追加 3. 参考 2.1. layer の追加 pip install -t {dir_name} {package_name} でパッケージをdir_nameにインストールする dir_name を python にする事で、lambda関数内でパスを指定する必要がなくなる zip -r9 layer.zip python で zip化する AWS コンソールからレイヤーの作成→zipをアップロードする arn を取得し、レイヤーの追加でarnを指定する 3. 参考 Lambda レイヤーの作成と共有 【AWS】LambdaにrequestsライブラリをLayerで追加する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DeepLやーる(Windows 10、Python3.6)

はじめに DeepLやーる 開発環境 Windows 10 PC Python3.6 実装 1.無料版に登録 2.認証キーをコピーする 3.下記プログラムを実行 test.py import requests auth_key = "<insert your api key>" text = "Hello, world" target_lang = "JA" url = f"https://api-free.deepl.com/v2/translate?auth_key={auth_key}&text={text}&target_lang={target_lang}" headers = {"Content-Type": "application/x-www-form-urlencoded"} response = requests.post(url, headers=headers) print(response.json()) 4.実行結果 (py36) D:\PythonProjects\DeepL>python test.py {'translations': [{'detected_source_language': 'EN', 'text': 'ハロー、ワールド'}]} お疲れ様でした。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Django】IIS 利用時に python を all users でインストールしなかった場合の対処

はじめに IIS に Django をデプロイして 画面を一回もお目見えすることなく 500 エラーが出た場合、タイトルの通り python のインストール設定が原因で起きている可能性があります 先に結論を記述しますが、その場合の対処策として 2パターン 方法があります  パターン①:python をアンインストールしてから再度、適切な設定でインストールし直す  パターン②:適切な権限を付与して対処する (アンインストールしない) アンインストールして再度インストールしてもいいですが、環境パスが変わるなど、すでにローカルで動かしているシステムに影響があるかないか調べるのもメンドクサイですよね… 今回は パターン② 適切な権限を付与して対処する方法を共有したいと思います 下記の環境を想定しています - Windows 10 - Python 3.9.7 - Django 3.2.10 もくじ 原因となったインストール手順 権限不足時のエラー画面 対処方法 原因となったインストール手順 ここで「Install Now」を押してしまっている… 「Install Now」でインストールする場合、インストールされる場所はユーザーフォルダ配下となる IIS にユーザーフォルダ配下の python にアクセスする権限がないためエラーとなってしまう 最初から IIS で利用する目的の場合は、「Customize installation」を選択して 「all users」にチェックを入れる必要がある (デフォルトでチェックが入っている) ※ちなみに、Windows Server でインストールする場合は、デフォルトで False になっているので注意してください 権限不足時のエラー セットアップが終わり、いざ接続って時に下記のエラーが出る これだけではさすがに分からないですね… 対処方法 python の実行ファイル (python.exe) があるフォルダに対して、IIS のユーザー権限を付与する 下記、作業順に記載 [1] ユーザーの python 実行フォルダを開く   C:\Users\*****\AppData\Local\Programs\Python\Python39 [2] Python39 に対して右クリック -> 「プロパティ」を選択 [3] 「セキュリティ」タブ -> 「編集」を選択 [4] 「追加」を選択後、「IIS_IUSRS」と入力し「名前の確認」を押下 [5] 「適用」して完了 おわりに python を入れ直すって記事はいくつかあったが、そのままの状態で対処する記事が見つからなかった 探し方が甘いだけかもしれませんが… アンインストールはしたくないなぁって思いながら探している方の助力になれば幸いです
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Blockly Python Code Generator(2)

Blockly Python Code Generator(2) 前回(Blockly Python Code Generator)では、Pythonコードを表示するのみでしたが、今回はそれにファイルに保存する機能を追加します。 File API File APIはHTML5で追加された機能です。 ウェブアプリケーションからのファイルの使用 作成したオブジェクトをファイルとしてダウンロードするためのURLを作成することができます。 const objectURL = window.URL.createObjectURL(fileObj); 上記で取得したURLは下記関数で開放します。 URL.revokeObjectURL(objectURL); Blob JavaScriptで生データを使用するためのオブジェクトとして、Blobが準備されています。 Blob テキストを保存する場合は、以下のように設定します。 const blob = new Blob([txt], { type: 'text/plain' }); このデータはFile APIに渡すことができます。 const blob = new Blob([txt], { type: 'text/plain' }); const url = URL.createObjectURL(blob); File 保存 File APIで作成した一時的なURLに対してクリックイベントを起こすことにより、ファイル保存のトリガを実行します。 ただし、JavaScriptで直接ローカルにファイル保存することはできません。 ユーザー確認が必要になります。 具体的には以下のような処理になります。 const url = URL.createObjectURL(blob); const a = document.createElement("a"); document.body.appendChild(a); a.download = 'sample.py'; a.href = url; a.click(); a.remove(); URL.revokeObjectURL(url); まとめ 前回(Blockly Python Code Generator)のファイルをベースに変更します。 ファイル保存用のボタンを追加します。 (今回は機能を実装していませんが、XMLのSave/Loadボタンも作っています。) HTML index.html <!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>Blockly Sample</title> <link rel="stylesheet" href="./styles.css"> <link rel="stylesheet" href="./code.css"> <script src="../blockly/blockly_compressed.js"></script> <script src="../blockly/blocks_compressed.js"></script> <script src="../blockly/msg/js/en.js"></script> <script src="../blockly/python_compressed.js"></script> <script src="./code.js"></script> </head> <body> <table> <tr> <td> <h1> <p>Blockly Code Generator</p> </h1> </td> </tr> <tr> <td> <table> <tr id="tabRow" height="1em"> <td id="tab_blocks" class="tabon">Blocks</td> <td class="tabmin tab_collapse">&nbsp;</td> <td id="tab_python" class="taboff">Python</td> <td class="tabmin tab_collapse">&nbsp;</td> <td id="tab_xml" class="taboff">XML</td> <td class="tabmax"> <button id="trashButton" class="notext" title="..."> <img src='../blockly/media/1x1.gif' class="trash icon21"> </button> <button id="saveButton" class="text" title="srcSave"> Save </button> <button id="loadButton" class="text" title="srcLoad"> Load </button> <button id="writeButton" class="text" title="writePython"> Write </button> </td> </tr> </table> </td> </tr> <tr> <td height="99%" colspan=2 id="content_area"> </td> </tr> </table> <div id="blocklyDiv"></div> <div id="content_blocks" class="content"></div> <pre id="content_python" class="content prettyprint lang-py"></pre> <textarea id="content_xml" class="content" wrap="off"></textarea> </body> </html> JavaScript ボタン機能を追加し、ファイル保存の機能を追加しました。 code.js var Code = {}; /** * Blockly's main workspace. * @type {Blockly.WorkspaceSvg} */ Code.workspace = null; Code.loadBlocks = function (defaultXml) { try { var loadOnce = window.sessionStorage.loadOnceBlocks; } catch (e) { // Firefox sometimes throws a SecurityError when accessing sessionStorage. // Restarting Firefox fixes this, so it looks like a bug. var loadOnce = null; } if ('BlocklyStorage' in window && window.location.hash.length > 1) { // An href with #key trigers an AJAX call to retrieve saved blocks. BlocklyStorage.retrieveXml(window.location.hash.substring(1)); } else if (loadOnce) { // Language switching stores the blocks during the reload. delete window.sessionStorage.loadOnceBlocks; var xml = Blockly.Xml.textToDom(loadOnce); Blockly.Xml.domToWorkspace(xml, Code.workspace); } else if (defaultXml) { // Load the editor with default starting blocks. var xml = Blockly.Xml.textToDom(defaultXml); Blockly.Xml.domToWorkspace(xml, Code.workspace); } else if ('BlocklyStorage' in window) { // Restore saved blocks in a separate thread so that subsequent // initialization is not affected from a failed load. window.setTimeout(BlocklyStorage.restoreBlocks, 0); } }; Code.bindClick = function (el, func) { if (typeof el == 'string') { el = document.getElementById(el); } el.addEventListener('click', func, true); el.addEventListener('touchend', func, true); }; Code.getBBox_ = function (element) { var height = element.offsetHeight; var width = element.offsetWidth; var x = 0; var y = 0; do { x += element.offsetLeft; y += element.offsetTop; element = element.offsetParent; } while (element); return { height: height, width: width, x: x, y: y }; }; Code.TABS_ = ['blocks', 'python', 'xml']; Code.TABS_DISPLAY_ = [ 'Blocks', 'Python', 'XML', ]; Code.selected = 'blocks'; Code.tabClick = function (clickedName) { // If the XML tab was open, save and render the content. if (document.getElementById('tab_xml').classList.contains('tabon')) { var xmlTextarea = document.getElementById('content_xml'); var xmlText = xmlTextarea.value; var xmlDom = null; try { xmlDom = Blockly.Xml.textToDom(xmlText); } catch (e) { var badXml = "XML のエラーです:\n%1\n\nXML の変更をやめるには「OK」、編集を続けるには「キャンセル」を選んでください。" var q = window.confirm(badXml.replace('%1', e)); if (!q) { // Leave the user on the XML tab. return; } } if (xmlDom) { Code.workspace.clear(); Blockly.Xml.domToWorkspace(xmlDom, Code.workspace); } } // Deselect all tabs and hide all panes. for (var i = 0; i < Code.TABS_.length; i++) { var name = Code.TABS_[i]; var tab = document.getElementById('tab_' + name); tab.classList.add('taboff'); tab.classList.remove('tabon'); document.getElementById('content_' + name).style.visibility = 'hidden'; } // Select the active tab. Code.selected = clickedName; var selectedTab = document.getElementById('tab_' + clickedName); selectedTab.classList.remove('taboff'); selectedTab.classList.add('tabon'); // Show the selected pane. document.getElementById('content_' + clickedName).style.visibility = 'visible'; Code.renderContent(); Blockly.svgResize(Code.workspace); }; Code.renderContent = function () { var content = document.getElementById('content_' + Code.selected); var saveButton = document.getElementById('saveButton'); var loadButton = document.getElementById('loadButton'); var writeButton = document.getElementById('writeButton'); saveButton.style.display = "none"; loadButton.style.display = "none"; writeButton.style.display = "none"; // Initialize the pane. if (content.id == 'content_xml') { saveButton.style.display = ""; loadButton.style.display = ""; var xmlTextarea = document.getElementById('content_xml'); var xmlDom = Blockly.Xml.workspaceToDom(Code.workspace); var xmlText = Blockly.Xml.domToPrettyText(xmlDom); xmlTextarea.value = xmlText; xmlTextarea.focus(); } else if (content.id == 'content_python') { writeButton.style.display = ""; Code.attemptCodeGeneration(Blockly.Python); } else { } }; /** * Attempt to generate the code and display it in the UI, pretty printed. * @param generator {!Blockly.Generator} The generator to use. */ Code.attemptCodeGeneration = function (generator) { var content = document.getElementById('content_' + Code.selected); content.textContent = ''; if (Code.checkAllGeneratorFunctionsDefined(generator)) { var code = generator.workspaceToCode(Code.workspace); content.textContent = code; } }; /** * Check whether all blocks in use have generator functions. * @param generator {!Blockly.Generator} The generator to use. */ Code.checkAllGeneratorFunctionsDefined = function (generator) { var blocks = Code.workspace.getAllBlocks(false); var missingBlockGenerators = []; for (var i = 0; i < blocks.length; i++) { var blockType = blocks[i].type; if (!generator[blockType]) { if (missingBlockGenerators.indexOf(blockType) == -1) { missingBlockGenerators.push(blockType); } } } var valid = missingBlockGenerators.length == 0; if (!valid) { var msg = 'The generator code for the following blocks not specified for ' + generator.name_ + ':\n - ' + missingBlockGenerators.join('\n - '); Blockly.alert(msg); // Assuming synchronous. No callback. } return valid; }; var onClickWrite = function () { var pythonArea = document.getElementById('content_python'); const blob = new Blob([pythonArea.textContent], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); document.body.appendChild(a); a.download = 'sample.py'; a.href = url; a.click(); a.remove(); URL.revokeObjectURL(url); } Code.init = function () { var container = document.getElementById('content_area'); var onresize = function (e) { var bBox = Code.getBBox_(container); for (var i = 0; i < Code.TABS_.length; i++) { var el = document.getElementById('content_' + Code.TABS_[i]); el.style.top = bBox.y + 'px'; el.style.left = bBox.x + 'px'; // Height and width need to be set, read back, then set again to // compensate for scrollbars. el.style.height = bBox.height + 'px'; el.style.height = (2 * bBox.height - el.offsetHeight) + 'px'; el.style.width = bBox.width + 'px'; el.style.width = (2 * bBox.width - el.offsetWidth) + 'px'; } // Make the 'Blocks' tab line up with the toolbox. if (Code.workspace && Code.workspace.getToolbox().width) { document.getElementById('tab_blocks').style.minWidth = (Code.workspace.getToolbox().width - 38) + 'px'; // Account for the 19 pixel margin and on each side. } }; window.addEventListener('resize', onresize, false); var writeButton = document.getElementById('writeButton'); writeButton.addEventListener('click', onClickWrite, false); var toolbox = document.getElementById("toolbox"); var options = { toolbox: toolbox, // scrollbars: true, grid: { spacing: 25, length: 3, colour: '#ccc', snap: true }, media: '../Blockly/media/', zoom: { controls: true, wheel: true } }; Code.workspace = Blockly.inject('content_blocks', options); Code.loadBlocks(''); Code.tabClick(Code.selected); Code.bindClick('trashButton', function () { Code.discard(); Code.renderContent(); }); for (var i = 0; i < Code.TABS_.length; i++) { var name = Code.TABS_[i]; Code.bindClick('tab_' + name, function (name_) { return function () { Code.tabClick(name_); }; }(name)); } onresize(); Blockly.svgResize(Code.workspace); } /** * Discard all blocks from the workspace. */ Code.discard = function () { var count = Code.workspace.getAllBlocks(false).length; if (count < 2 || window.confirm(Blockly.Msg['DELETE_ALL_BLOCKS'].replace('%1', count))) { Code.workspace.clear(); if (window.location.hash) { window.location.hash = ''; } } }; Promise.all( ["toolbox.xml"].map(async file => { return fetch(file).then( (res) => { return res.text(); } ); }) ).then((xmls) => { xmls.forEach((xml) => { var parser = new DOMParser(); var doc = parser.parseFromString(xml, "application/xml"); document.body.appendChild(doc.documentElement); }); }).then(() => { Code.init(); }); 作成したPythonコードを保存するところまで実装できました。 XMLのSave/Loadを実装することで、ブロックの保存もできるようになると思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む