goだけでグラフ書き

『病院の設備が一番わかる』なんて本を読んだ。例によって技術評論社のシリーズものだ。 図書館の司書はゆとり教育の世代の人だろうか。こういうゆとり本が多数揃えられていたぞ。

病院と診療所は何が違うか? 病院は20床以上かつ医師が3人以上いる所。それ以下は、 診療所だそうだ。診療所の中にも、有床の所と無床の所がある。町の医院とか、何とか クリニックは、無床の診療所という事になる。

昔は、決められた5科以上の専門科が ある病院を、総合病院と言ってたらしいけど、今はこの分類が無いとの事。だから、総合って ついてる病院は、それなりに老舗の扱いになるらしい。

医療機器を作っているメーカーは国内だと、オリンパス、テルモ、東芝メディカルが売り上げ高 ビッグスリーになる。以前、格安で入手した血圧計はフクダ電子製だったけど、ここもいろいろ やっているそうな。後はニプロとかが良く聞く名前だな。

病院設備で尤も巨大な物は、粒子線治療装置らしい。敷地120mX70mの所に、シンクロトロンを 設置し、これで加速した重粒子をガン細胞にぶつけて治療する。まだ、保険が利かない為、 治療費は敷地面積に比例して高くなるとか。くれぐれも、こういう設備のご厄介にはなりたく ないな。

面白い設備として、痛み計というのがあるそうな。痛みは患者の主観に頼る為、数値化が 難しかった。そこで開発されたのが、知覚感覚定量分析装置。患者の腕などに電極を2つ取り付け。 そこに通電。最初にビリビリ感じた点をゼロとし、序々に電圧を上げ、患部の痛みと同程度に なった事で、痛みを定量化ってのが、その原理らしい。電気でビリビリさせて敵を撃退する やつがあるけど、あれは、どれくらいの痛さなんでしょうね? 痛みの王様と言えば、尿道結石が筆頭になるらしいけど、スタンガンとの比較を求む。

後、TVドラマのご臨終ですって場面に登場する装置。ピーピー警告音が鳴って五月蝿いやつ。 生体情報モニターって言うそうな。心電図、心拍数、呼吸数、血圧、脈波、動脈血酸素飽和度 (SpO2)、呼吸曲線、体温等を同時に表示。心拍数の表示が一番大きかった。次は、SpO2。 心臓と肺の動きが死活問題って訳だな。こういうのも散りつけられたくないな。

冬に向かって、ご用心、ご用心。

読書メーター 7冊 / 1924頁 / 11000円

おもろい資料

去年のアドベントカレンダーから、面白い 記事を見つけた。

Golang + Raspberry Pi + LPS331AP で気圧・温度を測定してみた

Golang でコマンドライン Fuzzy Finder 「gof」作った

GoのツールチェインのCコンパイラを使う

Go言語の関数とメソッドのちょいネタ

Goで関数型プログラミング

今年はいかに? 出揃ったらチェックの事 and More

何かないか?

goで血圧管理プロは、一応決着をみた。けど、グラフ書きは、gnuplotと言う外注さん任せ。 入れてないと動かない。これって問題じゃん。折角goの出力がシングルバイナリーになってて、 どこでも動くはず、なんだけど、外注さんを用意しないといけないって、なんか負けた気がするぞ。

そこで、goだけでグラフが書けないか、探してみたよ。 そしたら、 Go でグラフを plot するパッケージを試した というのを試しておられる方が居た。どんなグラフが書けるかと言うと、 プロジェクトのHPにサンプルが載っていた。

こういう便利なのを探す時には、 goの詰め所を訪問するのがいいんかなあ。

plotinumを試す

早速、追試します。本体はplotinumっていう事で、まとめてあるんだけど、その他に いろいろ従属する物を取ってこないと、動かないようだ。

% go get code.google.com/p/plotinum/ 
% go get bitbucket.org/zombiezen/gopdf/pdf
% go get code.google.com/p/draw2d/draw2d
% go get code.google.com/p/freetype-go/freetype
% go get code.google.com/p/freetype-go/freetype/truetype
% go get code.google.com/p/go.image/tiff
% go get github.com/ajstarks/svgo

おいらは、うぶで取り寄せておいて、srcの中の該当物をWindowsに転送。そこで、上記 コマンドを叩いて、pkgを作っておいた。だって、hgなんてWindowsに入っていないんだもん。

動作確認は、Webに載ってた極小サンプルで試した。 折れ線グラフが書ければ、取り合えずOK。念入りにチェックしたかったら、plotinum/plotutil の中に入っているmain.goを走らせればよい。

C:\homes\godev\src\lineg>ls example_*
example_errpoints.eps         example_stackedAreaChart.eps
example_errpoints.jpg         example_stackedAreaChart.jpg
example_errpoints.pdf         example_stackedAreaChart.pdf
example_errpoints.png         example_stackedAreaChart.png
example_errpoints.svg         example_stackedAreaChart.svg
example_errpoints.tiff        example_stackedAreaChart.tiff

こんな風に(無駄と思える程)多用な形式で、出力してくれる。ファイルに落としてくれる のはいいんだけど、その場でリアルタイムに出来栄えを確認出来ないかねぇ。いちいちfirefox なりにファイルをドロップするのは、ちと辛いぞ。

えと、それについて解答します。Windows、Mac、LinuxっといろいろなBSD用に、X Windows相当の ハンドリングはやりたく無かったんです。(本当は、これら3機種を揃えるのが出来なかったりして) ですから、キャンバスは規格になってるファイル形式にしました。

今だったら、HTMLでキャンパスがサポートされてますから、そいつを目掛けて描画するのも 有りかも知れません。言いだしっぺの法則で、やってみませんか。と、質問したのを期に 勧誘されそうよ。

マニュアルは何処だ?

上のサンプルを試しているうちに、どうやって出力ファイル形式を変えればいいの?って疑問が 出てきた。

そんなのソース嫁。以上、終わり。

じゃ、ねたが続かないので、どうやら、Saveの引数の中で指定するファイル名のサフィックス で、出力形式が決まるようだ。それはそれでいいんだけど、多分山のようにある関数やらの 当たりをどうやって見つける?

ものは試しと、Saveを例に、本体側を探してみた。そしたら、plot.goに答えが出てた。

// Save saves the plot to an image file.  Width and height
// are specified in inches, and the file format is determined
// by the extension.  Supported extensions are
// .eps, .jpg, .jpeg, .pdf, .png, .svg, and .tiff.
func (p *Plot) Save(width, height float64, file string) (err error) {
    :

やっぱり、予想通りでしたよ。親切に沢山コメントが入っている。これって、まさか文芸的 プログラミングなんでしょうか。コードと解説を隣合わせに置いておけ。そうすれば、 コードに変更に伴って、解説も容易に更新できる。えと、これを唱えた人は、TeXの発明者 だったかな。

このコメントだけを抜き出して、読ませてくれるのが、godocだ。

sakae@uB:~/godev/src/lineg$ godoc code.google.com/p/plotinum/plot

とか、やると、画面にドキュメントが表示される。Webから見たいとかなら、-httpオプションを 付けて、godocを起動すれば良い。ぐぐる様からの標準パッケージと、独自に入れた パッケージと、分け隔てなく表示してくれる。

追加したものだけを見たいなんて時は、

sakae@uB:~/godev$ godoc -goroot='/home/sakae/godev' -http=192.1.1.1:6060

のように$GOPATH相当を指定してやれば良い。多少文句は言われるけど、ずっとパッケージを探し易くなる。 pkg/を辿って行けば説明が、src/を辿れば、ソースが見られる。

Webからモニター

言い出しっぺの法則をやってみる。グラフを書いたら、それをWebからモニターする。Go本に 載ってたのを土台にしたよ。グラフも例に載ってたもの。

package main

import (
        "code.google.com/p/plotinum/plot"
        "code.google.com/p/plotinum/plotter"
        "code.google.com/p/plotinum/plotutil"
        "fmt"
        "net/http"
        "os"
)

func pg() {
        p, _ := plot.New()
        p.Title.Text = "Plot example"
        p.X.Label.Text = "X"
        p.Y.Label.Text = "Y"
        plotutil.AddLinePoints(p, "", plotter.XYs{{0, 0}, {2, 4}, {4, 16}, {8, 64}})
        width := 5.0
        height := 4.0
        p.Save(width, height, "zzz.png")
}

func web() {
        http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
                fmt.Fprint(w, `
<html>
<body>
<img src="/zzz.png" width="500" height="400" alt="plot sample">
</body>
</html>`)
        })
        http.HandleFunc("/zzz.png", func(w http.ResponseWriter, r *http.Request) {
                http.ServeFile(w, r, "zzz.png")
        })
        err := http.ListenAndServe(":8080", nil)
        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }
}

func main() {
        pg()
        fmt.Println("http://localhost:8080/")
        web()
}

Go本によると、文字列の指定方法は3つあるとかで、そのうちのバッククォートを使って、 index.html相当を設定してみた。ヒアドキュメントって、コードの中にあると微妙だな。 これが似合うのは、shellスクリプトだけだな。

ルーチィングが2種設定されてる。/ のリクエストでindex.htmlが返り、次のブラウザー からの要求は、zzz.pngをクレって事なんで、その要求にも答えられるように設定してある。 簡単に書いてあるけど、その裏には、、、Web旧人類の悲劇が。。。。

zzz.pngを返せばいいんでしょ。だったら、ioユーティリティ内にある、ReadFile関数でも 使って、pngファイルを一気読みするか。えと、返り値の型はバイト配列だから、文句を 言われないように、stringにキャストして、それを出力すればいいんだな。

こういうファイルはブラウザーに図データだよって、ちゃんと伝えなきゃいかん。えと、 それには、htmlのheadタグで、Content-Type: image/png とかするんだったな。ああ、Content-Length も、伝えなきゃいかんな。それをheadタグ内に書いたけど、ブラウザー側で見ると、さっぱり 反映されてない。念入りにtcpdumpしても、そんなパケットは流れていない。

godocの出番で、headタグに設定するには、ちゃんとした関数を経由しないと駄目みたい。 w.Header().Set(key, value)を使うみたい。面倒臭いなと思いつつ、流儀に従いましたよ。 それで、ブラウザー側にヘッダー情報が渡ってくるようになったけど、pngの図は出ず。

で、もう一度godocを見直したら、

func ServeFile

func ServeFile(w ResponseWriter, r *Request, name string)

ServeFile replies to the request with the contents of 
the named file or directory. 

さらりと書いてある関数に目がとまったって訳。これ一つで、ファイル(or directory)を読み込んで、 適当なヘッダーを付けて、ブラウザーに送り出してくれる。httpも便利さの為に進歩してんな。 あれ、htmlのbody部は、人間が読める文字だけで構成しないとRFC違反になるんかな。

先の失敗の原因は、binaryデータを強引に送り付けちゃったからかな。Base64でエンコード しとけば良かったんかな。htmlと言っても、Mailのプロトコルそっくりだからなあ。そんな 事は頭の片隅に残っているけど、すっかり忘れてしまったわい。この代償は、httpパッケージが 4Mも喰っている事で支払われています。まあ、apache内蔵と思えば間違いないか。

ついでなんで、進歩の足跡を見ておく。net/http/fs.goに証拠が有った。最終的に、 serveContentの中で、ヘッダーを付けて、送り出してる。送りたいファイルのサフィックスを見て、 Content-Typeを設定したり、ファイルサイズを見て分割したりと、頑張ってくれているのね。

グラフのサイズは5x4インチに設定した。インチ表示って事はアメリカ製だな。アメリカは 日本に対して、日本の各種規制は貿易障壁だと文句を言うけど、お前こそいまだにインチ なんていう田舎の単位を使ってて、恥ずかしくないのか。TPP交渉は怒鳴り合いの修羅場だ そうだけど、日本も負けじと言い返せ。日本は、これから尺貫法に移行するぞとな。

5x4ってアスペクト比で、5:4って事ですかね。近頃は、16:9だかが流行っているのかな。 これ、テクノロジーの都合から来てて美しくないな。美しい比は、黄金比か。あれは、ギリシャが 発祥だったから、日本の比はどうだ。白金比の方が高そうだぞ。

紙の規格では、A系列(A4紙とか)と日本の役所が大好きなB系列(B5紙みたいに)とかある。そう言えば、B5 サイズのノートPCなんてのが有ったな。極めて日本的。最近のスマホ業界は、画面の大きさ の新規性で売ってて、そんな事でしかユーザーを繋ぎ留められなくなっちゃったね。

ああ、腕に付けたり、眼鏡に仕込んだりバージョンが有るか。どうせなら、脳味噌に仕込んで、 惚け老人のサポートなんてのがいいぞ。これ、日本で絶対に売れるから、やってみなはれ。

無駄話をしちゃったわい。プログラムを実行したら、ブラウザーからリロードすれば、最新の グラフを見られるよ。

オイラーの所のうぶには、ブラウザーを入れていない。じゃ、pngファイルの閲覧はどうする? ちゃんとしたビューワーを入れてもいいんだけど、毎日使う訳では無いし。上の簡易サーバを うぶ上で動かしておいて、Windows側から、firefoxなりでアクセスするのが順当か。

まてまて、うぶには万能editorであるemacs君が居るではないか。彼なら何とかしてくれるんじゃ? 調べてみたら、image-diredでpngファイルを画像閲覧出来る事が分かった。あるいは、 doc-view-modeをイネーブルにしといて、pngファイルを開いてあげる。実際にやって みたら、ちゃんとemacsのフレームの中に表示してくれたぞ。あんたは偉い!!

Windowsのemacsでも同様にってんで、 windows版emacs24.3で画像を表示する を参考に、png要dllを入れたよ。便利だなあ。

組み込み

折角良い物に出会ったので、前回作ったプログラムに組み込んでおく。

func toXYs(av []int) plotter.XYs {
    pts := make(plotter.XYs, len(av))
    for i, v := range av {
        pts[i].X = float64(i)
        pts[i].Y = float64(v)
    }
    return pts
}

func pg(ttl string, ds AAy) {
    p, _ := plot.New()
    p.Title.Text = ttl
    p.X.Label.Text = "Date"
    p.Y.Label.Text = "Value"
    p.Add(plotter.NewGrid())
    plotutil.AddLines(p, "Hi", toXYs(w(hi, ds)))
    plotutil.AddLines(p, "Low", toXYs(w(low, ds)))
    //  plotutil.AddLines(p, "Pls", toXYs(w(pls, ds)))
    width := 6.0
    height := 4.0
    p.Save(width, height, "zzz.png")
}

importは省略しちゃったけど、まあいいか。toXYsは、グラフパッケージで要求される型に 変換するルーチン。タイトルは呼び出し元から供給する事にした。出力ファイル名は、 コードの中で決め打ちしてるけど、変えた方がいいかな。

どう変える? 一案は、指定したコマンドラインから貰ってくる方法。-am -tl 30 -pg と 指定されたなら、am-tl30.png とか。

それじゃ、呼び出し元の方

    if *bpg && strings.Index(args, ":pg:") >= 0 {
        ttl := ""
        switch {
        case strings.Index(args, ":am:") >= 0:
            ttl = "at Wakeup"
        case strings.Index(args, ":pm:") >= 0:
            ttl = "at Night"
        default:
            ttl = "All day's"
        }
        pg(ttl, z)
    }

flagの初期設定部分は省略しちゃったけど、自明だから許してね。swrtchの中で設定した 変数は、外に伝わらない仕様なのね。だから、switchのブロックに突入する前に、宣言しといた。

Python 3系 移行計画

今、Windowsに入っているPythonは、2系のやつだ。こいつを入れた時は、喜びいさんでvPython とかwxPythonとかも一緒したけど、その後全く使っていない。500Mも容量を喰っていた。 でも、古すぎてgo getがこっそり使うhgすら入れられない低落。

2系にはすっぱりとおさらばして、3系にしたいぞ。そうすれば、3系推進委員会から褒められる かな。それに使いもしないアプリでDisk圧迫も無くなるだろう。

それをやるには、今やってるgoのアプリが完全にPythonの血圧アプリを置き換えられるように する事。だって、もし3系にするのに手間取った時にもgoのアプリで代用出来るから。 と、風が吹けば桶屋が儲かる的な発想をしたんであります。

それには、入力系とCSVファイルの更新を出来るようにしておかねばなるまい。ちまちまと 書いてみた。

func savecsv(csf string) {
    buf, _ := ioutil.ReadFile(csf)
    _ = ioutil.WriteFile("Backup.csv", buf, 0666)
    fd, err := os.OpenFile(csf, os.O_CREATE|os.O_WRONLY, 0666)
    failOnError(err)
    defer func() {
        fd.Close()
    }()
    w := csv.NewWriter(fd)
    for _, el := range bld {
        rows := make([]string, 0)
        for _, v := range el {
            rows = append(rows, strconv.Itoa(v))
        }
        w.Write(rows)
    }
    w.Flush()
}

一応、決め打ちでBackupを取っておいた。読み出してから書き込んでいるだけなんで、大した 手間じゃないけど、一発でコピー出来る関数を何故提供しないんだろう? 部品を供給 するから、組み合わせて使ってねっていう、schemeの思想、もとえunix哲学の意思?

メモリー上の整数配列をASCIIに戻してから、書き込み。最初rowsをクリアしないで使って いたら、大変な事になってた。そこで、ループを回る度にmakeを使って、rowsを作ったよ。 やれやれ、マーシャリングも大変だ。

func ire(ym int) {
    var a, b, c, d string
    var rs [4]int
    for {
        a, b, c, d = "", "", "", ""
        fmt.Printf("%d> ", ym)
        fmt.Scanln(&a, &b, &c, &d)
        if a == "fin" {
            break
        }
        rs[ymdh], _ = strconv.Atoi(a)
        rs[hi], _ = strconv.Atoi(b)
        rs[low], _ = strconv.Atoi(c)
        rs[pls], _ = strconv.Atoi(d)
        if rs[0] == 0 || rs[1] == 0 || rs[2] == 0 || rs[3] == 0 {
            fmt.Println("Bad")
            continue
        }
        rs[ymdh] += (ym * 10000)
        bld = append(bld, rs)
    }
    savecsv(dbfile)
}

こちらは、データをキーパンチする関数。csvファイルを読み込んだ直後に、コマンドオプションを 見て、呼び出せばよい。

キー入力に先立って、変数エリアをクリアしてるのは、無駄な改行で入力有りと誤認する のを防ぐ為。Scanlnを呼び出すと裏でパーサーが走って、スペース区切りで単語を分割 して、それぞれに変数に割り当ててくれる。受け取る変数の型を整数って指定しておけば、 文字列から整数に直すのもやってくれる。今回は、finいう入力終了マークも入力させる 必要があったので、文字列入力にした。

Atoiで、文字列を整数に変換させる時、数値以外を入力しちゃって エラーになる事がある。その都度調べるのが正統だろうけど、行数が無駄に増えるので、 変換が終了した時、まとめてチェック(失敗すると、結果が零になる性質を利用)してる。

これは、零と言うデータは、バイタル(生体)・サイン的に有り得ないという性質を逆手に取った ものだ。だって、脈拍が零なんて、お前は既に死んでいる、、、ですからね。

データに零がありうるなら、その都度チェックするしかないだろうな。それとも、エラーを トラップして、頑張って処理を継続させる? これは、面倒そうで、Bugを誘発しそうだな。