goだけでグラフ書き
『病院の設備が一番わかる』なんて本を読んだ。例によって技術評論社のシリーズものだ。 図書館の司書はゆとり教育の世代の人だろうか。こういうゆとり本が多数揃えられていたぞ。
病院と診療所は何が違うか? 病院は20床以上かつ医師が3人以上いる所。それ以下は、 診療所だそうだ。診療所の中にも、有床の所と無床の所がある。町の医院とか、何とか クリニックは、無床の診療所という事になる。
昔は、決められた5科以上の専門科が ある病院を、総合病院と言ってたらしいけど、今はこの分類が無いとの事。だから、総合って ついてる病院は、それなりに老舗の扱いになるらしい。
医療機器を作っているメーカーは国内だと、オリンパス、テルモ、東芝メディカルが売り上げ高 ビッグスリーになる。以前、格安で入手した血圧計はフクダ電子製だったけど、ここもいろいろ やっているそうな。後はニプロとかが良く聞く名前だな。
病院設備で尤も巨大な物は、粒子線治療装置らしい。敷地120mX70mの所に、シンクロトロンを 設置し、これで加速した重粒子をガン細胞にぶつけて治療する。まだ、保険が利かない為、 治療費は敷地面積に比例して高くなるとか。くれぐれも、こういう設備のご厄介にはなりたく ないな。
面白い設備として、痛み計というのがあるそうな。痛みは患者の主観に頼る為、数値化が 難しかった。そこで開発されたのが、知覚感覚定量分析装置。患者の腕などに電極を2つ取り付け。 そこに通電。最初にビリビリ感じた点をゼロとし、序々に電圧を上げ、患部の痛みと同程度に なった事で、痛みを定量化ってのが、その原理らしい。電気でビリビリさせて敵を撃退する やつがあるけど、あれは、どれくらいの痛さなんでしょうね? 痛みの王様と言えば、尿道結石が筆頭になるらしいけど、スタンガンとの比較を求む。
後、TVドラマのご臨終ですって場面に登場する装置。ピーピー警告音が鳴って五月蝿いやつ。 生体情報モニターって言うそうな。心電図、心拍数、呼吸数、血圧、脈波、動脈血酸素飽和度 (SpO2)、呼吸曲線、体温等を同時に表示。心拍数の表示が一番大きかった。次は、SpO2。 心臓と肺の動きが死活問題って訳だな。こういうのも散りつけられたくないな。
冬に向かって、ご用心、ご用心。
読書メーター 7冊 / 1924頁 / 11000円
おもろい資料
去年のアドベントカレンダーから、面白い 記事を見つけた。
Golang + Raspberry Pi + LPS331AP で気圧・温度を測定してみた
Golang でコマンドライン Fuzzy Finder 「gof」作った
今年はいかに? 出揃ったらチェックの事 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を誘発しそうだな。