Webアプリ

最近の状況

前回の記事でWebアプリの開発のためにPythonのマイクロフレームワークであるFlaskについて学んでいますと書きましたが、ネット上のサンプルをいじりながら簡単なものなら書けるようになりました。ですので自分なりの知識の整理をするためにFlaskでつくったアプリと実際にHerokuで公開するまでを簡単にまとめてみたいと思います。

経緯について

自分の所属している研究室ではデータの解析をNgraphExcelで行っています。当研究室に代々受け継がれているデータ解析用のExcel等があるので皆さんそれを使ってらっしゃるのですが、Excelは重いしデータ解析の前に無駄な動作があるなと思っておりました。無駄な動作というのは生データの入れ替えをExcelでいちいち行わなければならないということです。入れ替えの動作自体は単純な作業なのですが、逆に言えば単純な作業ならプログラムに任せるべきでもあります。そこで生データの入れ替えを行うWebアプリを開発して公開しようと思い立ちました(データの入れ替えだけならそんなに難しくないだろうと思ったので笑)。

Flask Web app

つくったWebアプリですが自分で一からつくったわけではなく、ネット上のサンプルをいじりながら作りました。参考サイトは以下です。

qiita.com

この例では

<input type=text>

に入力された名前をHTMLに埋め込んで返すということを行っております。自分の場合だと十数行×十数行のタブ区切りのテキストデータなのでtextareaにする必要がありました。また変更後のデータも十数行×十数行となるためtextareaに埋め込んでレンダリングしようと考えました。また変更後のデータはコピペのみができたら良いのでreadonly=“true"にすることと、大量のデータはwrapされると読みにくいのでwrap="off"としています。pythonファイル側はデータのならび変えをするだけなのでなにか特別なことをしているわけではないです。なにはともあれとりあえず以下に載せます。

"""filename : main.py"""

# Flask などの必要なライブラリをインポートする
from flask import Flask, render_template, request, redirect, url_for
from operator import methodcaller
import pandas as pd


def group(dataframe):
    dataframe_col = dataframe.shape[1]
    RANGE = range(1, int(dataframe_col/4)+1)
    dv = ['Voltage_1 ({})'.format(i) for i in RANGE]
    dc = ['Current_1 ({})'.format(i) for i in RANGE]
    gv = ['Voltage_2 ({})'.format(i) for i in RANGE]
    gc = ['Current_2 ({})'.format(i) for i in RANGE]
    drain_v = dataframe[dv]
    drain_c = dataframe[dc]
    gate_v = dataframe[gv]
    gate_c = dataframe[gc]

    return pd.concat([drain_v, drain_c, gate_v, gate_c], axis=1)


def toNestedList(data):
    data = data.splitlines()
    splitedData = list(map(methodcaller("split", "\t"), data))

    return splitedData


# 自身の名称を app という名前でインスタンス化する
app = Flask(__name__)


# ここからウェブアプリケーション用のルーティングを記述
# index にアクセスしたときの処理
@app.route('/')
def index():
    title = "Transform data"
    # index.html をレンダリングする
    return render_template('index.html',
                           title=title)


# /post にアクセスしたときの処理
@app.route('/transformed', methods=['GET', 'POST'])
def transformed():
    title = "Data is transformed!"
    if request.method == 'POST': 
        data = request.form['textarea']  # データを取得
        NestedList = toNestedList(data)
        df = pd.DataFrame(NestedList[1:], columns=NestedList[0])
        df2 = group(df)

        return render_template('index.html',
                               title=title,
                               originalData=data,
                               transformedData=df2.to_csv(
                                          index=False, sep='\t'))
    
    else:
        # エラーなどでリダイレクトしたい場合はこんな感じで
        return redirect(url_for('index'))


if __name__ == '__main__':
    app.debug = True  # デバッグモード有効化
    app.run()  # どこからでもアクセス可能に

個人的に悩んだのはHTMLのtextareaから取得するのはtsvではなくただのstring型なのでどうやってpandasのDataFrameに読み込ませるかということですね。最初はそもそもファイルをアップロードしてもらう方式を取ろうかとも考えていたのですが、HTML5についてあまり良く知らなかったため諦めました笑 結局stringデータとなったものをsplitを二回して2次元配列とし、それをDataFrameとするように実装しました。

また出力に関しても悩んだのですがこれはpandas公式ドキュメントを読むと、pandas.DataFrame.to_csvメソッドで引数を与えないと文字列として返すようになるのを利用しました。

この他にindex.htmlとlayout.htmlも書く必要があります。以下に記載します。

<!-- index.html -->
{% extends "layout.html" %}
{% block content %}
  <!-- Form
  ================================================== -->
<div class="form">
  <div class="container">
    <div class="row">
      <div class="col-md-12">
      <h2>Transform data for mobility of linear or saturation region</h2>
      <h4><a href="https://www.youtube.com/watch?v=xAH6VFGCX6Q">Tutorial video</a></h4>
        <form action="/transformed" method="post">
          <h3>Original data</h3>
          <textarea class="form-control" rows="5" id="textarea" name="textarea" placeholder="Original data" wrap="off">{% if originalData %}{{originalData}}{% endif %}</textarea>
          <button type="submit" class="btn btn-default">Transform</button>
        </form>
        <h3 for="comment">Transformed data</h3>
        <form>
          <textarea class="form-control" rows="5" id="textarea2" placeholder="Transformed data" readonly="true" wrap="off">{% if transformedData %}{{transformedData}}{% endif %}
          </textarea>
        </form>
      </div>
    </div>
  </div>
</div>
{% endblock %}
<!-- layout.html -->
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    {% if title %}
        <title>{{title}}</title>
    {% else %}
        <title>Bootstrap 101 Template</title>
    {% endif %}
    <!-- Bootstrap -->
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
    <![endif]-->
  </head>
  <body>
    {% block content %}{% endblock %}
    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script> 
    </body>
</html>

layout.htmlは上に挙げた参考サイトのものをほとんどそのまま引用してます。参考サイトの方もBootstrapの公式をそのまま使ったというようなことが書いてあったはずです。layout.html内の{% block content %}{% endblock %}にindex.htmlの内容を埋め込むようなかたちでレンダリングしているようです。index.htmlの<form action=/transformed method="POST">でボタンを押すといった動作が検知されたときにどこに移動先のURLを示すようです。

たぶんまだ色々書いたほうが良いのでしょうが、文章ですべてを説明しようとするのはなかなか難しいですね。main.pyのFlaskのルーティングのこととかの説明は他サイトを見て頂くと分かるかと思われます。質問していただければ自分も分かる範囲で答えさせて頂きます。

長くなったのでHerokuでデプロイするところはまた別記事で書きます。それでは

Remove all ads

Flask勉強中

先日

バイト帰りにメールチェックをしているとはてなブログからメールが届いていました。いつもどおりフォローしてるはてなブロガーの新記事が出たよというお知らせメールかと思いきや、一ヶ月ぐらい何も書いてないけどそろそろ書けば?という催促メールでした。

というわけで催促されたので大した実りもない勉強中のことを書きます。

前フリ

最近はwebアプリをデプロイしたいためwebアプリケーションフレームワークについて勉強しています。今まではフルスタックのフレームワークの方が将来的にも使う頻度が高いのかな−という理由からDjangoについて勉強していました。しかしフレームワークというものについても知らない人間がネットの情報を漁りつつデプロイまでたどり着くということは到底不可能に近く、自分の飽き性もあって全く勉強は進んでいませんでした。そこでPythonで有名なフレームワークとしてDjangoとは別にFlaskという名前をよく見かけることに気づきました。(そもそもなんでPythonなのかというとPythonしか書けないからです…)Flaskはマイクロフレームワークと呼ばれており、非常に軽量なフレームワークだそうです。設定ファイルが一つだけで良いらしいということから初心者である自分はこちらから学び始めた方が良いのではと考え、現在学んでおります。

所感

Djangoを学んでいるときに感じていた「今自分は何を書いているのだろう」という疑問は少ないです。Flaskは簡単なものならあまりプログラムを書く必要がありません。(十数行程度で書けるときもあります)その程度の分量なら自分でもがんばれば読めるし、理解できます。なのでとりあえずFlaskでWebアプリを自由に作れる程度までは頑張って勉強していきたいと思います。

Remove all ads

Anacondaから標準のPythonに

Raspberry Pi 3にPythonの環境を構築してて思ったのですが、環境構築ってAnacondaじゃなくてもよくない?と思ったのでAnacondaから標準のPythonに移行しました。環境はMacで行っております。

まずhomebrewをインストールします。この手順は他サイトで紹介されていると思うので探してみてください。

brew install python3

とするとPython 3系がインストールできます。Pythonがインストールできたらpip3コマンドが使えるので必要なライブラリをそれぞれインストールしたらおしまいです。自分はnumpy, scipy, matplotlib等をインストールしました。

pip3 install numpy scipy matplotlib

こんな感じです。

仮想環境も作れると思ったのでつくてみたのですがなんかエラーが出ました。めげずにもう一回同じコマンドを打つと仮想環境が構築できました。結果仮想環境が作れたのでいいのですが、なぜエラーがでるのか少し気になりますね。

Pythonの仮想環境構築

最近Raspberry Pi 3でwebアプリを作ろうと奮闘しております。システムにインストールされているPythonと分別する意味でも仮想環境下にPython環境を構築したほうが良いという情報をよく目にしたため、仮想環境の構築と日頃使っているライブラリのインストールを行いました。環境はUbuntu MATE 16.04で行っております。

そもそも仮想環境とは

あくまで自分の理解の範疇で書きます。間違っている点も多々あると思いますのでその点はご了承ください。

そもそもPCでコマンドが使用できるのは/bin以下に使用したいコマンドのバイナリファイルがあるためです。普通に使用する分にはいわゆる普通のインストール(バイナリファイルが/bin以下に格納される)をすれば良いです。しかし/bin以下にインストールされたコマンドはシステム全体で利用されるためシステムのPython環境を汚染します。具体的にはライブラリのコンフリクト等が起きるのではないかと思われます。こういった事態を避けるためにシステムとはまた別の環境を作り、その環境にPythonをインストールすることでシステムのPython環境を汚染することなくPython環境を構築することができます。

Ubuntuに仮想環境を構築する

現在Raspberry Pi 3にUbuntu MATE 16.04をインストールしサーバーとして使っています。このマシンにPythonの仮想環境を構築しました。Ubuntu MATE 16.04にはデフォルトでPython3.5が入っており、Python3.5ではvenvというモジュールを用いて仮想環境を構築します[1]。仮想環境を構築したいディレクトリに移動、もしくはディレクトリを作製し、そのディレクトリ以下に仮想環境を構築します。以下ではhogeというディレクトリを作製し、そこに仮想環境を構築しています。

mkdir ~/hoge
python3 -m venv ~/hoge

これで~/hoge以下に仮想環境が構築されました。この仮想環境をアクティベートするには

source ~/hoge/bin/activate

コマンドを打ちます。 するとプロンプトが

user@machine:~$

から

(hoge) user@machine:~$

となります。UbuntuのシステムにはPython2とPython3のどちらもインストールされているのでPythonのバージョンを指定して起動するためにpythonとするとPython2がpython3とするとPython3が起動するようになっています。しかし構築した仮想環境をアクティベートすると仮想環境下にはPython3のみがインストールされており、pythonと打つだけでPython3が起動するようになります。同様にシステムのPython環境ではpipでPython2のライブラリのインストールが、pip3でPython3のライブラリのインストールができますが、仮想環境下ではpipでPython3のライブラリがインストールされます。自分はここで詰まったので一応書いておきます。

またよく使っているPythonライブラリのnumpy, matplotlibをインストールしました。

(hoge) user@machine:~$ pip install numpy

とするとnumpyがインストールされました。 同様に

(hoge) user@machine:~$ pip install matplotlib

とするとmatplotlibがインストールされるかと思いきやエラーが出ました。

エラーログを読んでみるとfreetypepngというライブラリが必要だけど入ってないよという内容でした。ここで注意するべきはfreetypepngPythonのライブラリではなく、システムにインストールするライブラリです。ここでも自分は詰まりました…笑

sudo apt install freetype png

とするとどうもそのような名前のライブラリはないよということを言われました。 そこで[2]を参考にpngを、[3]を参考にfreetypeをインストールするとmatplotlibのインストールができました。

さらっと書いてはいますがmatplotlibのインストールするだけで2, 3日かかってます笑

参考と轍づくりのために書いておきました。それでは。

[1] 28.3. venv — 仮想環境の作成 — Python 3.6.1 ドキュメント
[2] Ubuntu15.04でpipでMatplotlibがインストールできない件 - python3.4から始めるプログラミング日記
[3] matplotlibのインストールでつまづいた話 - Qiita

Raspberry Pi 3 & ubuntu server

ラズパイ3にubuntuサーバーを導入して自宅サーバを構築しようと試みているのですが、再起動するとLinux kernel starting…という表示から起動しなくなります。正直ここまでハードウェアに近いことに興味があまり持てないのでこういうトラブルはやる気が削がれる一方です。公式が出してるubuntu MATEをサーバーみたいに使うほうが初心者にはいいのでしょうか。Pythonでwebアプリをつくってみたいだけなのに、そのはるか手前で躓いていて果たしていつその目標を達成できるのやら…

ブログと言うかTwitterで呟けばいいようなことではあるのですが、書いてみました。

2017/05/09追記 ubuntu MATEをインストールしてコンソールモードで使うことにしました。公式が出しているだけあってwifiもはじめから認識しているのでハードウェア的なことに煩わされないで済みます。初心者にはこっちの方がいいかもしれないですね。

新しいAnaconda

Pythonの環境づくりにpyenvとAnacondaを利用しております。一年ほど闇雲にインストールしたままでPythonを利用してきたのですが、さすがにそのまま使い続けるのもどうかと思ったので新しくインストールし直しました。

そして先日組んだGUIプログラムを起動させようとするとなんと起動しないではないですか。エラーメッセージを読んでみるとどうやら新しいAnacondaにはPyQt4が入っていないようでPyQt5に完全移行されたみたいです。それならとPyQt4と書いてある部分をPyQt5に書き換え起動させようとするとこれもまた起動しません。なんでかなーと思いちょっと調べてみるとどうやらPyQt4とPyQt5ではインスタンスのある場所が変わっているようで、widget系のインスタンスはQtWidgetsというところに移動したようです。なのでQtGuiから呼び出していた部分をQtWidgetsに書き換えたところ無事起動できましたとさ。

それだけの記事なのですが一応道標として…

keynoteの色の指定

大学の研究においてデータプロットはpythonのmatplotlibを用いています。matplotlibは使い慣れていることもあってわりと便利なのですが、ピーク検出等を全自動で行うとどうしても精度が良くない時があります。そんなときはデータを見つつマーカーをつけたりと手動でグラフ作成を行っており、そのときの画像編集はkeynoteを使っています。しかしそういった場合matplotlibのプロットした線とマーカーの色が異なってしまうことが多々あります。そこでkeynoteで16進数での色指定ができないかと調べていたところ意外とあっさりできました。

やり方としてはまず何かしらのオブジェクトを追加します。そしてそのオブジェクトがアクティブなときにカラーパネルのようなところをクリックします。

f:id:jinpei0908:20170426225941p:plain

次に2つめのタブのRGB sliderを選択します。一番下のHex Colorのところに16進数で値を入力するだけです。

f:id:jinpei0908:20170426225954p:plain

またmacにはdigital color meterというアプリがプリインストールされており、マウスカーソルで指した部分の色を16進数等で表示してくれます。この2つを組み合わせることで同じ色を作り出すことができます。