kuroの覚え書き

96の個人的覚え書き

ADBキーボードをUSBに変換する(2020年版)

以前一度作成していたADB-USB変換アダプタをまた作ってみた。
以前はCJMCU-beetleというArduino Leonardo互換基板を使って作成したが、無駄に高いので今回はお手頃価格で入手できるArduino Pro MicroというSparkFun(
https://www.sparkfun.com
)というメーカーが出しているATMega 32U4を使ったLeonardo互換基板の中国製コピー商品。
Amazon.co.jp: KKHMF 2個 Leonardo Pro Micro ATmega32U4 5V/16MHz ブートローダ マイクロ USB Pro Mini 開発ボード Arduinoに対応: DIY・工具・ガーデン

1個あたり安いものだと500円くらいで入手できる。
ただし、製品によって仕様が微妙に違うことがあるらしいので、ここに書く情報はあくまで今回使った製品での記録ということになる。

その他用意したものは
ユニバーサル基板の切れっ端 1枚
どこのご家庭にも2−3個は転がっているADBポート(メス)1個
タクトスイッチ 1個
プルアップ用1kオーム抵抗 1個

機材
Mac mini
ADBケーブル(S端子ケーブル)
ADBキーボード



基本的には
github.com
こちらに書かれている通りでいい。

使用するピンはVCC,GNDそしてD0(data)の3本だけ。ADBキーボードの電源スイッチについては無視した仕様。
で、D0がこの基板でどこに相当するのかというところが唯一の問題となるわけだが、シルク印刷で3と番号が振ってあるところがD0に相当するらしい。
これに関しては
riv-mk.hateblo.jp
こちらの情報を参考にさせていただいた。

さて、基板へのファームウェアの書き込みであるが基本は
https://k-kuro.hatenadiary.jp/entry/20161230/p1
ここでやった時と同じようにすればいい。

ファームウェア
tmk_keyboard/converter/adb_usb/binary/adb_usb_rev1_unimap.hex
を使う。

まずまっさらのPro MicroをMacに接続し、Arduino.appを立ち上げる。
ツール>ボード
Arduino Leonardoを選択
ツール>シリアルポート
で/dev/cu.usbmodemHIDP1 (Arduino Leonardo)を選択
で、適当なプログラム(たとえばBlinkなど)を書き込んでみる。

そうするとAvrdudeのプログラムパスがわかるのであとはコマンドで直接書き込むという手順。

実際に書き込む際にはリセットをかけてから書き込まなければならないので、基板上のrstというピンを素早く2回gndに落とす。
結構やりにくいので、あらかじめここにタクトスイッチをつけておくといい。
設定を変えて焼き直す時も同様に接続してからリセットボタンをダブルクリックし、書き込みコマンドをすかさず実行、という手順となる。

タイミングが悪いとエラーが出て進まないので、これはタイミングを色々変えてやってみるしかない。

なお、一旦これで書き込んでしまうとArduino.appからシリアルポートが見えなくなるため、もう一度Arduinoとして使いたい場合はリセットボタンをダブルクリックしたら素早くシリアルポートの設定を合わせ直し、プログラムを書き込むという忙しいことをしなければならない。8秒しか猶予がなく、その間にシリアルポートを設定して書き込み開始までする必要があるため、長いプログラムのコンパイルをしていたら間に合わない。まずはBlinkなど短いプログラムでブートローダを回復させてから、落ち着いて長いプログラムに挑戦した方がいい。

なお、ここでもArduino.appを介さずにAvrdudeを直接使ってコマンドラインArduinoのスケッチを書き込んだほうがやりやすそうではある。

Flaskでhtmlファイルを読み込むとき、ブラウザのキャッシュが読み込まれてしまうのを回避するには

インタラクティブに生成されたhtmlファイルをFlaskで表示させようとしたところブラウザがキャッシュファイルを読みに行ってしまって、作り変えた結果が表示されない。

data = dir + "/data_" + datetime.datetime.now().strftime('%y%m%d%H%M%S') + ".html"
output_file(data)

そこでとりあえずhtmlを書き換えたら、毎回違う名前で保存し直して、読み込む側もそれを指定して読み込むようにする。

@app.route('/clustalw/bokeh/')
def clustalw_bokeh_index():
	user = g.user.name
	target = "./user/" + user + "/clustalw/*.html"
	file = glob.glob(target)[0]
	if os.path.exists(file):
		return render_template(file)

こんな感じでタイムスタンプをファイル名に入れておき、ターゲットのファイル名を読み取ってそれをrender_templateで受け渡す。

sequence alignmentをBokehを使ってインタラクティブに表示してみる

clustalwでsequenceのアライメントをとると、テキストで.alnというファイルが生成されるが、文字の並びとアスタリスクではわかりにくいことが多い。
なのでclustalxだとかMEGAだとかで表示するとカラフルに色分けで表示できるのでぱっと直感的に分かるのだが、いちいちファイルをアプリで開き直すとか、面倒くさいし。
ということでpythonでそういうのができないかやってみた。できたらそれをいつものウェブアプリに実装してやる。
Bioinformatics and other bits - A sequence alignment viewer with Bokeh and Panel
こちらの記事を参考に

import string
import numpy as np
from Bio import AlignIO
import panel as pn
pn.extension()
from bokeh.plotting import figure, output_file, save
from bokeh.models import ColumnDataSource, Plot, Grid, Range1d
from bokeh.models.glyphs import Text, Rect
from bokeh.layouts import gridplot

#output_file("out.html")
aln = AlignIO.read('Phylo.aln','clustal')
seqs = [rec.seq for rec in (aln)]
ids = [rec.id for rec in aln]    
text = [i for s in list(seqs) for i in s]
clrs =  {'A':'red','T':'green','G':'orange','C':'blue','-':'white'}
colors = [clrs[i] for i in text]
N = len(seqs[0])
S = len(seqs)
width = .4

x = np.arange(1,N+1)
y = np.arange(0,S,1)
xx, yy = np.meshgrid(x, y)

gx = xx.ravel()
gy = yy.flatten()

recty = gy+.5
h= 1/S

source = ColumnDataSource(dict(x=gx, y=gy, recty=recty, text=text, colors=colors))
plot_height = len(seqs)*15+50
x_range = Range1d(0,N+1, bounds='auto')
plot_width=800
fontsize="9pt"
if N>100:
    viewlen=100
else:
     viewlen=N

view_range = (0,viewlen)
tools="xpan, xwheel_zoom, reset, save"

p = figure(title=None, plot_width= plot_width, plot_height=50,
        x_range=x_range, y_range=(0,S), tools=tools,
        min_border=0, toolbar_location='below')

rects = Rect(x="x", y="recty",  width=1, height=1, fill_color="colors",
        line_color=None, fill_alpha=0.6)
p.add_glyph(source, rects)
p.yaxis.visible = False
p.grid.visible = False
p1 = figure(title=None, plot_width=plot_width, plot_height=plot_height,
                x_range=view_range, y_range=ids, tools="xpan,reset",
                min_border=0, toolbar_location='below')#, lod_factor=1)          
glyph = Text(x="x", y="y", text="text", text_align='center',text_color="black",
                text_font="monospace",text_font_size=fontsize)
rects = Rect(x="x", y="recty",  width=1, height=1, fill_color="colors",
                line_color=None, fill_alpha=0.4)
p1.add_glyph(source, glyph)
p1.add_glyph(source, rects)

p1.grid.visible = False
p1.xaxis.major_label_text_font_style = "bold"
p1.yaxis.minor_tick_line_width = 0
p1.yaxis.major_tick_line_width = 0

p = gridplot([[p],[p1]])

pn.pane.Bokeh(p)
#save(p)

できた。
f:id:k-kuro:20200902171838p:plain
Bokehって色々可能性を感じさせるね。

一番最初に
output_file("out.html")
と保存先を指定して
save(p)
でhtmlファイルとして保存してやるとwebアプリからも利用しやすいな。

f:id:k-kuro:20200902172541p:plain

系統樹とアライメントを一気に解析。ああなんて快適。

Arduino Pro micro その3 ATCG キーボード

さて、1キーのキーボードができたら今度はそれを複数にしていきたいわな。

ということで4キーに拡張してみた。

#include "Keyboard.h"

const int buttonPinA = 6;          // input pin for pushbutton
int previousButtonStateA = HIGH;   // for checking the state of a pushButton
const int buttonPinG = 7;          // input pin for pushbutton
int previousButtonStateG = HIGH;   // for checking the state of a pushButton
const int buttonPinC = 8;          // input pin for pushbutton
int previousButtonStateC = HIGH;   // for checking the state of a pushButton
const int buttonPinT = 9;          // input pin for pushbutton
int previousButtonStateT = HIGH;   // for checking the state of a pushButton


void setup() {
  // make the pushButton pin an input:
  pinMode(buttonPinA, INPUT_PULLUP);
  pinMode(buttonPinT, INPUT_PULLUP);
  pinMode(buttonPinC, INPUT_PULLUP);
  pinMode(buttonPinG, INPUT_PULLUP);
  // initialize control over the keyboard:
  Keyboard.begin();
}

void loop() {
  // read the pushbutton:
  int buttonStateA = digitalRead(buttonPinA);
  // if the button state has changed,
  if ((buttonStateA != previousButtonStateA)
      // and it's currently pressed:
      && (buttonStateA == HIGH)) {
    // type out a message
    Keyboard.print("A");
  }
  // read the pushbutton:
  int buttonStateT = digitalRead(buttonPinT);
  // if the button state has changed,
  if ((buttonStateT != previousButtonStateT)
      // and it's currently pressed:
      && (buttonStateT == HIGH)) {
    // type out a message
    Keyboard.print("T");
      }
  // read the pushbutton:
  int buttonStateC = digitalRead(buttonPinC);
  // if the button state has changed,
  if ((buttonStateC != previousButtonStateC)
      // and it's currently pressed:
      && (buttonStateC == HIGH)) {
    // type out a message
    Keyboard.print("C");
      }
  // read the pushbutton:
  int buttonStateG = digitalRead(buttonPinG);
  // if the button state has changed,
  if ((buttonStateG != previousButtonStateG)
      // and it's currently pressed:
      && (buttonStateG == HIGH)) {
    // type out a message
    Keyboard.print("G");
  }
  // save the current button state for comparison next time:
  previousButtonStateA = buttonStateA;
  previousButtonStateT = buttonStateT;
  previousButtonStateC = buttonStateC;
  previousButtonStateG = buttonStateG;
}

単純に4キー分を並べただけ。

f:id:k-kuro:20200830002441j:plain

キーボード用のスイッチを使ったところ結構チャタリングするのでdelay入れたほうが良さそう。

これで遺伝子のコードは書き放題だね。

Arduino Pro micro その2 ワンボタンキーボード

Arduino Pro microはArduino Leonardoの互換基板で、今回入手したのは更にそのコピー商品である。

このArduinoクローンの特徴としていわゆるArduino UNOなどスタンダードなシリーズがATMegaシリーズのAVRを使っているのと異なり、32U4というUSBインターフェイスを内蔵したチップを使っている。このチップのおかげでLeonardoシリーズはUSBデバイスとPCから認識されるため、自作キーボードクラスターでは自キー用コントローラとして重宝がられているのだ。

今回の目的もまさにそれなのだ。

ということでキーボード入力をまずはテストしてみる。

単純にデジタルピンにタクトスイッチの片足をつなげ、反対の足をGNDにつなぐだけの簡単回路でまずは試す。
デジタルピンをHIGHにしておいてGNDに落ちたらキー入力されたことになる。普通HIGHにするのにプルアップしておくのだが、内蔵抵抗でプルアップしておく便利機能もあるらしい。


f:id:k-kuro:20200829190505j:plain

#include "Keyboard.h"

const int buttonPin = 9;          // input pin for pushbutton
int previousButtonState = HIGH;   // for checking the state of a pushButton
int counter = 0;                  // button push counter

void setup() {
  // make the pushButton pin an input:
  pinMode(buttonPin, INPUT_PULLUP);
  // initialize control over the keyboard:
  Keyboard.begin();
}

void loop() {
  // read the pushbutton:
  int buttonState = digitalRead(buttonPin);
  // if the button state has changed,
  if ((buttonState != previousButtonState)
      // and it's currently pressed:
      && (buttonState == HIGH)) {
    // increment the button counter
    counter++;
    // type out a message
    Keyboard.print("You pressed the button ");
    Keyboard.print(counter);
    Keyboard.println(" times.");
  }
  // save the current button state for comparison next time:
  previousButtonState = buttonState;
}

サクッとサンプルを流用。今回ピンをd9にしたので変えたのはそこだけ。
タクトスイッチを押すたび、文字列とカウンターが入力されるという仕組み。
f:id:k-kuro:20200829190304p:plain

Arduino Pro micro

Arduino Pro micro を入手したのでとりあえずLチカの儀。

オンボードには電源直結のLEDとシリアルのインジケータLEDしか載ってないので、LチカするためにLEDを繋がねばならない。
で、普通Lチカのスケッチは13ピンを使ってるので13ピンがどれか探すと、13ピンが無いことがわかった。
仕方がないので9ピンに書き換えて試したところ、無事成功。
f:id:k-kuro:20200829180301j:plain

// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(9, OUTPUT);
}

// the loop function runs over and over again forever
void loop() {
  digitalWrite(9, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);                       // wait for a second
  digitalWrite(9, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);                       // wait for a second
}

次はスイッチ入力テストだな

しかしこのUSBコネクタは定評通りのヤワさだな。
実運用時にはもうちょっとマシなコネクタを別に用意したほうが良さそうだ。

ImageJのマクロをpythonで動かす

1000枚以上あるようなTIFF画像にImageJで一律の処理を行いたい。
とてもじゃないが手ではやってられないのでマクロを使って自動運転する。

f:id:k-kuro:20200827194949p:plain
File>New>Text Window
を開き、次のようなスクリプトを作成して、Runする。

from ij import IJ
from ij.io import DirectoryChooser
import os


def Analysis(imagepath):
    imp = IJ.openImage(imagepath)
    savefilepath = os.path.join(os.path.dirname(imagepath), "../bc/")
    filename = os.path.basename(imagepath)

    IJ.run(imp, "Median 3D...", "x=3 y=3 z=3") #ノイズを和らげる
    IJ.run(imp, "Enhance Contrast...", "saturated=0.1 normalize") #コントラストをつける
    IJ.saveAs(imp, "Tiff", savefilepath + filename) #出来上がったファイルを保存する
    imp.close()

srcDir = DirectoryChooser("Choose Folder").getDirectory()
IJ.log("directory: " + srcDir)

for root, directories, filenames in os.walk(srcDir):
    for filename in filenames:
        if filename.endswith(".tif"):
            path = os.path.join(root, filename)
            IJ.log(path)
            Analysis(path)

IJ.log("Finish")

ファイルの在処を聞いてくるので指定してやる
それだけ。

Raspberry pi にRTCをつけてネットワークのないところでも時計が狂わないようにする

温度ロガーとかタイムラスプカメラを設置する場所が必ずしもwifiの届くところでない場合、Raspberry Piの時計をちゃんと合わせるのは結構面倒なことだ。特にモニタとかキーボードとか全然使えなくて、本体とカメラのみで運用する場合どうしようもない。そこで、リアルタイムクロック(RTC)を載せてやることにした。

アマゾンで3個1,000円くらいで売られているDS3231と充電池が基板に載っただけのものを手に入れた。

コネクタも付いているのでGPIOに刺すだけでいいようだ。1番ピンに端を合わせて刺す。

www.raspberrypi.org
こちらの情報を元に設定をする。

pi@raspberrypi:~ $ sudo nano /boot/config.txt

dtparam=i2c_arm=on  #コメントアウトされているところを#を外す
dtoverlay=i2c-rtc,ds3231 

再起動

pi@raspberrypi:~ $ sudo hwclock --systohc

とやってシステムの時計をRTCの方に設定する。

pi@raspberrypi:~ $ sudo apt-get purge fake-hwclock

fake-hwclockをアンインストールする。

pi@raspberrypi:~ $ sudo nano /etc/udev/rules.d/85-hwclock.rules

# On the Raspberry Pi the RTC isn't available when systemd tries,
# set the time from RTC now when it is available.
KERNEL=="rtc0", RUN+="/sbin/hwclock --rtc=$root/$name --hctosys"

ファイルを作る。

以上。

pi@raspberrypi:~ $ i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- UU -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --

なおI2Cバスのアドレスを確認するとこんな感じ。

pi@raspberrypi:~ $ sudo hwclock -r
2020-08-26 16:52:26.248377+0900
pi@raspberrypi:~ $ date
2020年 8月 26日 水曜日 16:52:33 JST

ちゃんと設定されている。

ベイジアンネットワーク解析で遺伝子発現の制御関係を網羅的に調べたい

マイクロアレイや、次世代シークエンサーによる発現データセットが大量にあると、すべての遺伝子同士の制御関係(上下関係)が描けるかもしれない。
ということで方法を模索するとネットワーク解析というものに行き着く。ところが、世の中に出ている遺伝子発現関係の論文でネットワーク解析というと大体は共発現ネットワーク(co-expression network)であるらしく、制御関係を示すネットワークではないらしい。
そういう関係性を推論するにはベイジアンネットワーク(bayesian network)解析を行うといいらしい。しかし、どうやらこいつは膨大な計算量になるらしいこともあちこちに書かれている。とりあえず、手元の計算機資源だけで小規模にでも試してみることはできないだろうかと、また、せっかくなのでpythonでライブラリがなかろうかと探してみたところ
Peblというのが見つかった。
pypi.org

ところがこいつは開発がだいぶ前に止まっていて、python3ではインストールさえさせてもらえなかった。

$ python3 -m pip install pebl
Collecting pebl
  Using cached pebl-1.01.tar.gz (2.5 MB)
    ERROR: Command errored out with exit status 1:
     command: /home/linuxbrew/.linuxbrew/opt/python@3.8/bin/python3.8 -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'/tmp/pip-install-3u4r784s/pebl/setup.py'"'"'; __file__='"'"'/tmp/pip-install-3u4r784s/pebl/setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' egg_info --egg-base /tmp/pip-pip-egg-info-assg8ojs
         cwd: /tmp/pip-install-3u4r784s/pebl/
    Complete output (6 lines):
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/tmp/pip-install-3u4r784s/pebl/setup.py", line 38
        except DistutilsPlatformError, x:
                                     ^
    SyntaxError: invalid syntax
    ----------------------------------------
ERROR: Command errored out with exit status 1: python setup.py egg_info Check the logs for full command output.

いちおうpython2環境では使えなくもなさそうなのだけど、MacではSegmentation Fault:11で落ちるとかいろいろ問題がありそうなので、他を当たる。

$ python
Python 2.7.13 (v2.7.13:a06454b1afa1, Dec 17 2016, 12:39:47) 
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from pebl import data
>>> from pebl.learner import greedy
>>> dataset = data.fromfile("pebl-tutorial-data1.txt")
>>> dataset.discretize()
>>> learner = greedy.GreedyLearner(dataset)
>>> ex1result = learner.run()
Segmentation fault: 11

bayespy
github.com
これは行けそう。とりあえずチュートリアルでもやってみるかね。

import numpy as np
np.random.seed(1)
data = np.random.normal(5, 10, size=(10,))
from bayespy.nodes import GaussianARD, Gamma
mu = GaussianARD(0, 1e-6)
tau = Gamma(1e-6, 1e-6)
y = GaussianARD(mu, tau, plates=(10,))
y.observe(data)
from bayespy.inference import VB
Q = VB(mu, tau, y)
Q.update(repeat=20)
import bayespy.plot as bpplt
bpplt.pyplot.subplot(2, 1, 1)
bpplt.pdf(mu, np.linspace(-10, 20, num=100), color='k', name=r'\mu')
bpplt.pyplot.subplot(2, 1, 2)
bpplt.pdf(tau, np.linspace(1e-6, 0.08, num=100), color='k', name=r'\tau')
bpplt.pyplot.tight_layout()
bpplt.pyplot.show()

f:id:k-kuro:20200818175006p:plain

さて、これは何なんだろうね?w

なおインストールしたまま実行すると

Traceback (most recent call last):
  File "bay1.py", line 13, in <module>
    Q.update(repeat=20)
  File "/home/linuxbrew/.linuxbrew/Cellar/python@3.8/3.8.5/lib/python3.8/site-packages/bayespy/inference/vmp/vmp.py", line 163, in update
    t = time.clock()
AttributeError: module 'time' has no attribute 'clock'
[rnaseq@mn-rx1330m3 temp]$ nano bay1.py 
[rnaseq@mn-rx1330m3 temp]$ python3 bay1.py 
Traceback (most recent call last):
  File "bay1.py", line 13, in <module>
    Q.update(repeat=20)
  File "/home/linuxbrew/.linuxbrew/Cellar/python@3.8/3.8.5/lib/python3.8/site-packages/bayespy/inference/vmp/vmp.py", line 163, in update
    t = time.clock()
AttributeError: module 'time' has no attribute 'clock'

こういうエラーが出るので
"/home/linuxbrew/.linuxbrew/Cellar/python@3.8/3.8.5/lib/python3.8/site-packages/bayespy/inference/vmp/vmp.py"
の中にあるtime.clock()をすべてtime.process_time()に書き換えてやる(python3.8以上の場合、3.7以下ならエラーは出ないみたい)

コードを読みほぐしてみる

import numpy as np
np.random.seed(1)
data = np.random.normal(5, 10, size=(10,))

まずNumpyでランダムなデータセットを作成しているな。
Gaussian distribution(正規分布する)平均5、SDが10な10個の数値を生成している。

from bayespy.nodes import GaussianARD, Gamma
mu = GaussianARD(0, 1e-6)
tau = Gamma(1e-6, 1e-6)
y = GaussianARD(mu, tau, plates=(10,))

ここでmodelを設定している。平均とSDを推定するモデルね。f:id:k-kuro:20200818181629p:plain
うーむガンマ分布ってなんだったっけ。f:id:k-kuro:20200818182306p:plain

とりあえずbayespy.nodesにいろいろな分布について用意されているので、ここを自分の考えるモデルに合わせて変えてくれってことね。

さあ、モデルができたらそれに当てはめてみるよ。

y.observe(data)

事後分布を推定する。

from bayespy.inference import VB
Q = VB(mu, tau, y)
Q.update(repeat=20)

どうやらここで推論エンジンをつかうのだが、このライブラリではVB(variational Bayesian 変分ベイズ)エンジンしか実装していないようだ。
以下はq(µ)とq(タウ)をグラフにしている。

モデルをどうやってでっち上げるのか、ってところが問題だな。というかこれからネットワークをプロットするのは無理かも。

正規分布しているかの検定

例によってwebアプリ拡張。

統計処理をするにあたって、データが正規分布しているかどうかによってその後の処理が分岐する事が多い。
なので、まずは正規分布かどうかを確定させる必要がある。
Shapiro-Wilk testで判定。Q–Q plot, quantile-quantile plotも参考に。

@app.route('/shapiro', methods=['GET', 'POST'])
def shapiro_index():
    user = g.user.name
    form=StatForm()
    dir = "./user/" + user + "/ttest"
    if os.path.exists(dir):
        shutil.rmtree(dir)
    if not os.path.exists(dir):
        os.makedirs(dir)
    input = dir + "/input.txt"
    output = dir + "/output.txt"

    if request.method == 'POST':
        if 'file' not in request.files:
            flash('No file part')
            return redirect(request.url)
        file = request.files['file']
        if file.filename == '':
            if form.samp_t.data:
                samp = form.samp_t.data.strip()
                f = open(input, 'w')
                f.write(samp)
                f.close()
            else:
                flash('No data input')
                return redirect(request.url)
        if file and allowed_file(file.filename):
            file.save(input)

    if os.path.exists(input):
        if form.data_f.data=='csv':
            df = pd.read_csv(input)
        if form.data_f.data=='tsv':
            df = pd.read_table(input)
        dfs1 = df.iloc[:, form.index_col.data].dropna()
        data = np.array(dfs1.values.tolist())
        header = df.columns
        record = df.values.tolist()
        results = stats.shapiro(data)
        stat_fig = dir + "/stat_" + str(time.time()) + ".png"
        plt.figure(figsize=(5,5))
        stats.probplot(data, dist="norm", plot=plt)
        plt.savefig(stat_fig)
        img_url = "../../static/" + stat_fig

        return render_template('/tools/shapiro.html', form=form, results=results, header=header, record=record, img_url=img_url)

    return render_template('/tools/shapiro.html', form=form)

f:id:k-kuro:20200817195854p:plain
Sepal lengthはこのデータでは微妙に正規分布から外れている(P<0.05)
f:id:k-kuro:20200817200010p:plain
Sepal widthはP>0.05なので正規分布と言えるらしい。