2012/07/16

DP2 Merrillをもって散歩するのが楽しすぎる件

連休も終わってしまうので、昨日に引き続きSigma DP2 Merrillのレビューっぽいことをしておきます。色の不具合とか質感すげーという話は昨日したので、今日は個人的につぼったポイントを紹介します。

まず画角について。35mmカメラ換算で約45mmという、いわゆる標準レンズの画角なのですが、これがやっぱり使いやすいです。最近はD90+カラースコパー20mmという変態構成で散歩することが多くて、これはこれでボーっと眺めてる景色を四角い画面に収めたいときには都合よくて楽しいのですが、意識的に「何か」を眺めてるときの光景を切り出すのにちょうどいいのは、やっぱり標準レンズだったんだなーと思い出してきました。

10数年前、お金ない学生のころはフィルムカメラに標準レンズをつけて歩くしかなかったわけですが、そのころの感覚もこんなでした。そんなおっさんがDP2 Merrillを使うと、当時の自分には切り取れなかったような光景が切れるんだから、楽しくないわけがないです。

SDIM0127

SDIM0143

SDIM0025

SDIM0055

コンデジっぽくない操作性も評価したい点です。いちばんアクセスしやすい位置に取り付けられているジョグダイヤルには、撮影時には露出とシャッタースピードを変更する機能が割り当てられています。これ、すごく使いやすい。さらに、ホワイトバランスの変更、ISOの変更、MFへの切り替え、露出補正、AEロックといった、個人的に「必須」な操作へのアクセスがおそろしく簡便です。スチル写真を撮る人向けに特化した潔い設計で好感度高いし、実際、直感的な操作体系になるようまとめられてるなあと感じます。

コンデジっぽくないといえば、49mm径のフィルターが装着できるのもポイント高いです。ペンタックスSMCタクマーの標準で利用してたフィルターが軒並み使えるので大興奮です。まあでも、PLフィルターの効果を液晶ファインダーで確認するとか、あんまり正気の沙汰ではない感じもしますが、こまけえこたーどうでもいいんだよ。

DP2 Merrill with PL filter

最後にまたノスタルジックな話。DP2 Merrillで撮影後、Sigma Photo Proでの現像が楽しすぎるのですが、これもやっぱりフィルムの現像と暗室作業をしていたころを思い出します。フィルムの場合、現像と焼付けの時点ではじめて撮影時の妄想が形になるわけですが、DP2 Merrillで撮影したデータもPCで現像していると液晶ファインダーのプレビューでは気がつかなかったような絵が出てくることがあって、これって暗室で印画紙に絵が浮き上がってくる感覚に似てます。もっとも、DP2 Merrillの液晶ファインダーは正直なところ「撮ってすぐ細部を確認」のような使い方をするにはストレスフルなので、それで現像時の驚きが増えてるだけかもしれません。とはいえ、DP2 Merrillで撮影した写真の現像作業は楽しすぎて時間泥棒なのは事実です。

SDIM0128

SDIM0136

SDIM0137

SDIM0027

2012/07/15

Sigma DP2 Merrillがきた

Sigma DP2 Merrill を手に入れました。2月の発表と同時に酒を控えるなどしてこつこつ資金を捻出してきたおかげで発売当日にヨドバシカメラで購入することができました。発売日がちょうど夏休中だったのも幸いでした。日ごろの行いがよかったに違いありません。

さっそく、ヨドバシカメラ秋葉原店の前にあるスターバックスで試し撮りです。

DP2 Merrill

なんか暗いのはスタバの店内だからですが、右側のコーナーが緑がかってるのはなぜだろう。このflickr上の写真はカメラ内現像したJPEGデータですが、X3F形式のデータをIrfanViewで閲覧しても、SIGMA Photo Pro 5で処理しても、やはり緑がかってしまいます。ちょっと気になります。

この四隅が緑がかる現象は、こんな白バックの写真で現れるようです。これを見ると、四隅が緑というだけでなく、白バックの中心も赤みがかりますね。

FM-2 WILDCAT 1/144 Sweet

色について気になるポイントが実はもうひとつあって、それは紫のノイズが出ることです。この写真の二階中央部分に、目立たないですが紫の帯が出現してます。

SDIM0038

露出をアンダーにして現像し、等倍で切り出したのがこちら。

SDIM0038-1-close

この紫ノイズは、SPPで「X3 Fill Light」というパラメータを引き上げて遊んでいるときに初めて気がついたので、そもそもこんなむちゃくちゃな光加減で写真を撮ろうと思わなければいいだけの話ですが、白バックが緑がかるのは撮影時にちょっと警戒しないといけなそう。

難癖は以上でおしまい。実際のところ、このカメラを持って散歩に出かけるのは最高に楽しいです。

まず、とにかくディテールがすごいと噂のFoveonなわけですが、確かにすごいです。意味もなく芝生の写真とか撮りたくなります。

SDIM0044

あと、質感が気持ち悪いくらいすごい。パンケーキの上で溶けかけのバターとか、

SDIM0037

科学博物館の鯨のひれのぬめっとした感じとか、

SDIM0047

オオムラサキの羽の粉っぽさとか。

オオムラサキ

ただしAFでのピント合わせは遅いです。それもあって、オオムラサキはシャッタースピード稼ぐために被写界深度を浅めにしか設定できず、いまいちピントあってないです。とはいえ、フォーカスをいつでもマニュアルへと一瞬で変更できるので、こういうシビアな局面でなくてAFだとおっつかない場合には地味にうれしいです。うどん屋でテーブル上の透明なオブジェ越しに落ち着きのない子どもを撮影とか、AFでどうやって撮るんだという。

SDIM0085

あとノイズについてですが、結論からいうと気にしなくていいと思います。FoveonはノイズがひどいのでISOを上げられないという噂を聞いてたので、けっこう警戒しながら撮影してたのですが、かなり暗い場面で動きのある被写体をディテールは気にしないから雰囲気だけ切り取りたいという気分だったので、思い切ってISO6400に設定し、距離を頼りにMFでシャッターを押してみました。その場で液晶ディスプレイのプレビューを見たときは、盛大なノイズに苦笑いしたのですが、うちに帰ってSPPに読み込ませたら、ほとんどノイズなんて目立たなくなりました。こういう写真を気軽に撮れるのは本当にうれしい。

SDIM0091

あと、カタログに「撮影時には気がつかなかったようなものさえ撮れている」とか何とか書いてあった気がするのですが、本当でした。たまたま撮った風景写真に知り合いが写りこんでるのを自宅に帰ってからPC上で眺めていて発見してしまうくらい。それだけ解像度はすごいです。

2012/07/06

時系列データのグラフをjQueryとFlotで描く

JSONの時系列データがたくさん手に入りました。なにせJSONなんだし、JavaScriptを使ってブラウザにインタラクティブなグラフを簡単に描けそうな気がします。そこでとりあえず折れ線チャートを描くことにしました。むかしjQuery用のFlotというライブラリを使ったことがあったので、今回もこれを使おうと思ったのですが、完全に使い方を忘れていた!そういえば、むかし使ったときも、API.txtという英文がだらだら書かれたテキストファイルしかドキュメントがなくて面倒だったのを忘れていました。そこで今回は備忘録を残しておくことにしたしだい。

なお、Flotはべつに時系列の折れ線チャートを描くためのライブラリではないので、ほかにもいろいろなチャートが描けるはずですが、とくに調べていないので知りません。あくまでも時系列データの折れ線チャートを描くことが目標の備忘録です。

下準備

描画したい時系列データは、こんな1つの配列として用意します。

mydata = [[1309446000000, 6],
          [1312124400000, 9],
          [1314802800000,18],
          [1317394800000,23],
          [1320073200000,27],
          [1322665200000,27],
          [1325343600000,31],
          [1328022000000,35],
          [1330527600000,38],
          [1333206000000,44],
          [1335798000000,49],
          [1338476400000,50]]

要素である長さ2の配列が、それぞれ [日時, データ値] を表しています。

第一要素の日時が 1309446000000 のようなでっかい数値なのは、UNIX時間をミリ秒単位で表したものだからです。これ以外の書式でFlotに日時を指定するすべはありません。もし「2012年7月4日の値は12で、2012年7月5日の値は0で、……」だったら、 [[1341327600000, 12], [1341414000000, 0], ...] という配列を用意するわけです。Gaucheで用意するならこんな感じ。( construct-json-string は、S式からJSON表記の文字列をよろしく作ってくれる関数です。)

(use srfi-19)
(use gauche.sequence)
(use rfc.json)
(define (localtime->jstime str)
  (* 1000
     (time->seconds
      (date->time-utc
       (string->date str "~Y年~m月~d日")))))

(define mydata
  #(#("2012年7月4日" 12) #("2012年7月5日" 0)))

;; (construct-json-string mydata) の出力ではだめ

(construct-json-string
 (map-to <vector>
         (lambda (v) (vector (localtime->jstime (ref v 0)) (ref v 1)))
         mydata))

ちなみに、日時に限らず、Flotが受け付けてくれる値データは数値のみです。文字列の値データは与えないこと。

とりあえず描く

おおざっぱなノリとしては、HTMLページ中にグラフの出力場所を作っておいて、 jQuery.plot(出力場所, [データ列], オプション) とすれば、それっぽい折れ線チャートができるという仕組みです。

<html>
<head>
  <script language="javascript" type="text/javascript"
    src="http://code.jquery.com/jquery-latest.js"></script>
  <script language="javascript" type="text/javascript"
    src="flot/jquery.flot.js"></script>
</head>
<body>
  <div id="placeholder" style="width:600px;height:300px;"></div>
</div>
<script>
var options = { xaxis : 
                 { mode : "time"}
              };
var mydata = [
              [1309446000000, 6],
              [1312124400000, 9],
              [1314802800000,18],
              [1317394800000,23],
              [1320073200000,27],
              [1322665200000,27],
              [1325343600000,31],
              [1328022000000,35],
              [1330527600000,38],
              [1333206000000,44],
              [1335798000000,49],
              [1338476400000,50]
             ];
$.plot($("#placeholder"), [mydata], options);
</script>
</body>
</html>

描画する場所は、この例では <div id="placeholder" style="width:600px;height:300px;"></div> です。このように、必ず縦と横の長さを明示してやります。

スクリプトから、この描画場所を参照するときは、この例のようにセレクタを jQuery() に渡してjQueryオブジェクトを作り、それを渡します。グラフを表示するだけなら、 $.plot("div#placeholder",...) のようにセレクタを直接指定するだけでもかまわないようですが、あとでjQueryオブジェクトの bind() メソッドを使ってインタラクティブな機能を付け足したいので、jQueryオブジェクトにしておきます。

$.plot() の3つ目の引数である options は、上記の例では {xaxis : { mode : "time"}} です。「x軸( xaxis )のデータの種類( mode )は日時( "time" )だよ」と意味です。こう指定すれば、元データの第一要素が日時データとして解釈され、x軸にそれっぽいラベルが適当な間隔でふられます。描画の際のオプションはほかにもいろいろ指定できますが、時系列データをプロットするときによく使いそうなものだけメモしておきます。ほかは公式のテキストで「Customizing the axes」とか検索して調べる。

項目オプション値の例意味
xaxismode"time"x軸の値を日時とみなす。
timeformat"%y/<br>%m"日時ラベルの書式。htmlタグ使える。
ticks10x軸に表示するラベルの数。画面に入らないと適当にふり直してくれる。
[]x軸にラベルを表示しない。
tickSize[1, "day"]1日おきにラベルをふる。
gridclickabletrueグラフをクリックしてイベント飛ばせるようになる。
hoverabletrueグラフにマウスオーバーでイベント飛ばせるようになる。

あらかじめ用意するデータ列 mydata はそもそも配列ですが、それをさらに [mydata] のように配列で括ってから $.plot() に渡している点に注意。この配列に複数のデータ列 [mydata1, mydata2] を指定すれば、ひとつのチャートに複数の折れ線を重ねてプロットできます。

var mydata1 = [
               [1309446000000, 6],
               [1312124400000, 9],
                ...                // 長いので省略
              ];
var mydata1 = [
               [1309446000000, 0],
               [1312124400000, 10],
                ...                // 長いので省略
              ];
$.plot($("#placeholder"), [mydata1, mydata2], options);

さて、この例のように $.plot() に引数として描画の際のオプションを渡すのでは、それぞれの線の色を好みに応じて変えたり凡例を指定したりすることはできません。次はその方法です。

凝ったグラフ用にデータを準備する

ここまでの例では、jQuery.plot()の第二引数に、 [[日時,データ], ...] というカタチをしたデータ列そのものを指定してました。実はこれは手抜きなやり方で、本当はデータ列とそのメタ情報をJavaScriptのオブジェクトとしてまとめて指定します。

var myseriese1 = {
                   label : "rapid",                   // データ列の名前
                   data  : mydata1,                   // データ列
                   color : "rgba(255, 0, 0, 0.8)",    // チャートの色
                   ...                   // ほかはドキュメント参照
                 }

var myseriese2 = {
                   label : "human",                    // データ列の名前
                   data  : mydata2,                    // データ列
                   color : "#rgba(255, 0, 0, 0.8)".    // チャートの色
                   ... 
                 }
$.plot($("#placeholder"),
       [myseriese1, myseriese2],
       $.extend(true, {}, options, { legend : { position : "nw" }}));

color は、まあ、線の色です。 label のほうは、指定した文字列が凡例に使われます。凡例は、デフォルトではグラフ領域の右上に表示されますが、これを左上に移動したいときは、この例のようにしてオブジェクト options を拡張( jQuery.extend() )してやります。 "nw" は「北西」ですね。

外部のファイルからJSONデータを読み込む

Flotがデータ列としてとるオブジェクトは、見ての通りまんまJSONです。そこで外部のファイルに保存してあるJSONのデータを読み出してグラフにしたい。これにはjQueryの jQuery.getJSON() メソッドを使います。

JSONがFlotのデータ列として適当な形式になってるとは限らないので、たとえばこんな内容のファイル mydata.json があったとしましょう。

[{
   "entry" : {
               "code"  : "rapid",
               "title" : "うさぎ",
               "data"  : [[1309446000000, 6],
                          ...                    // 省略
                          [1338476400000,50]]
             }
 },
 {
   "entry" : {
               "code"  : "human",
               "title" : "ヒト",
               "data"  : [[1309446000000, 6],
                          ...                    // 省略
                          [1338476400000, 1]]
             }
 },
 ... // 省略
]

"data" をデータ列として使い、 "title" を凡例用のラベルとして使い、チャートを書きたいとします。

var options = { xaxis: { mode: "time" },
                legend : { position : "nw" }
};

$.getJSON("mydata.json", function (json) {
  var target1 = getEntry(json, "rapid");
  var target2 = getEntry(json, "human");

  var plotdata = [{label : target1.title , data : target1.data},
                  {label : target2.title , data : target2.data}];

  $.plot($("#placeholder"), plotdata, options);
});

function getEntry (json, codestr) {
  i=0;
  while (i < json.length) {
    if (json[i].entry.code == codestr) {
      return json[i].entry}
    ++i}
  return false;
};

出力は前の例とほとんど一緒なので省略。

インタラクティブなグラフにしたい

いままでの要領でまず素朴なグラフを描き、その描画領域のjQueryオブジェクト(いまの例では $("#placeholder") )の bind() メソッドを使うことで、グラフにさまざまな機能を付け足すことができます。たとえば、上の例で $.getJSON() に渡している関数の中身に以下のコードを付け足すことで、「グラフ上をマウスでドラッグした範囲にズーム」という芸当ができるようになります( jquery.flot.selection.js が必要なのでHTMLファイルのヘッダ部分に呼び出しを追加しておくこと)。

  $("#placeholder").bind("plotselected", function (event, ranges) {
      $.plot($("#placeholder"), plotdata,
             $.extend(true, {}, options, {
                 xaxis: { min: ranges.xaxis.from, max: ranges.xaxis.to }
             }));
  });

bind の第一引数に指定できるイベントには、この例で使った "plotselected" のほか、 "plothover""plotclick" などがあります。名前のとおり、選択した領域に対するアクション、マウスオーバー時のアクション、マウスクリック時のアクションを関数として指定できます。ほかにもありそうだけど調べてません。このへんはドキュメントにはあまり解説がなくて、むしろ公式サンプルで遊びながらソースを参照しましょう。下の画面程度の完成度でよければ、サンプルからのコピペ駆動でそれほど苦もなく実現できます。

<html>
<head>
  <script language="javascript" type="text/javascript"
    src="http://code.jquery.com/jquery-latest.js"></script>
  <script language="javascript" type="text/javascript"
    src="flot/jquery.flot.js"></script>
  <script language="javascript" type="text/javascript"
    src="./flot/jquery.flot.selection.js"></script>
</head>
<body><div>
  <div id="placeholder" style="width:600px;height:300px;"></div>
  <br>
  <div id="overview" style="width:400px;height:150px"></div>
</div>
<script>
var options = { xaxis: { mode: "time", timeformat: "%y年<br>%m月"},
                legend : { position : "nw" },
                selection: { mode: "x" },
                grid: { hoverable: true, clickable: true }
};

$.getJSON("mydata.json", function(json) {
  var target1 = getEntry(json, "rapid");
  var target2 = getEntry(json, "human");

  var plotdata = [{label: target1.title , data: target1.data},
                  {label: target2.title , data: target2.data}];

  var plot = $.plot($("#placeholder"), plotdata, options);

  var overview = $.plot($("#overview"), plotdata, {
      series: {
          shadowSize: 0
      },
      legend: { show: false },
      xaxis: { mode: "time" },
      yaxis: { ticks: 1, min: 0, autoscaleMargin: 0.1 },
      selection: { mode: "x" }
  });
  
  var previousPoint = null;
  $("#placeholder").bind("plothover", function (event, pos, item) {
    if (item != null && previousPoint != item.datapoint) {
      previousPoint = item.datapoint;
      $("#tooltip").remove();
      var d = new Date(item.datapoint[0]),
          Y = d.getFullYear(),
          M = d.getMonth() + 1,
          r = item.datapoint[1].toFixed();
      showTooltip(item.pageX, item.pageY,
          "~" + Y + "年" + M + "月" + ":" + r + "体");
    } else {
      $("#tooltip").remove();
      previousPoint = null;
    }
  })
  
  $("#placeholder").bind("plotselected", function (event, ranges) {
    var plot = $.plot($("#placeholder"), plotdata,
                      $.extend(true, {}, options, {
                        xaxis: { min: ranges.xaxis.from, max: ranges.xaxis.to }
                      }));
    overview.setSelection(ranges, true);
  });
  $("#overview").bind("plotselected", function (event, ranges) {
    plot.setSelection(ranges);
  });
});

function getEntry (json, codestr) {
  var i=0;
  while (i < json.length) {
    if (json[i].entry.code == codestr) {
      return json[i].entry}
    ++i}
  return false;
};

function showTooltip(x, y, contents) {
  $('<div id="tooltip">' + contents + '</div>').css( {
    position: 'absolute',
    display: 'none',
    top: y + 5,
    left: x + 5,
    border: '1px solid #fdd', 
    padding: '2px',
    'background-color': '#fee',
    opacity: 0.80
  }).appendTo("body").fadeIn(200);
};

</script>
</body>
</html>