スモールデータで使うPowerShellとmongoDB

最近良く耳にするmongoDB。NoSQL、スキーマレスって何? 良くわからないのでちょこっと使ってみることにしました。

PowerShellからmongoDBのAggregationを実行する(パイロット版)

mongoDBクライアントからの実行が便利なのでわざわざPowerShellから実行する必要もないのですが、他にやっている人もいないようなので試行錯誤の結果を公開してみます。使用するデータは前回と同じものです。

要は、mongoDBクライアントで使用したパラメータをBSON型にしてaggregateに渡せば良いみたいですが、変換の仕方がよくわからず苦労しました。オペレータの"$"をエスケープしたりしてようやく動くようになりました。
きっともっとスマートな方法があるはずなので、ひとまずパイロット版(とりあえず動きます版)として公開します。

# mongoDB定義
$driver_path = "C:\mongodb\driver\CSharpDriver-1.7.0.4714"
Add-Type -Path "$driver_path\MongoDB.Bson.dll";
Add-Type -Path "$driver_path\MongoDB.Driver.dll";

$dbName = "TestDB"
$connectionString = "mongodb://localhost/{0}?safe=true" -f $dbName
$db = [MongoDB.Driver.MongoDatabase]::Create($connectionString)
# collection名
$collectionName = "testcollection";
$collection = $db[$collectionName];

# "number" の合計
#db.testcollection.aggregate(
#	{  $group : { "_id" : null, "total" : { "$sum" : "$number" }} }
#)
[MongoDB.Bson.BsonDocument] $doc_total = @{
	'$group' = [MongoDB.Bson.BsonDocument] @{
		"_id" = 'null';
		"total" = [MongoDB.Bson.BsonDocument] @{
			"`$sum" = "`$number"}}
}

# aggregation 合計
$pipeline = $doc_total
$collection.aggregate($pipeline).ResultDocuments

# "number" の平均
#db.testcollection.aggregate(
#	{  $group : { "_id" : null, "average" : { "$avg" : "$number" }} }
#)
[MongoDB.Bson.BsonDocument] $doc_average = @{
	'$group' = [MongoDB.Bson.BsonDocument] @{
		"_id" = 'null';
		"total" = [MongoDB.Bson.BsonDocument] @{
			"`$avg" = "`$number"}}
}
# aggregation 平均
$pipeline = $doc_average
$collection.aggregate($pipeline).ResultDocuments

<実行結果>

Name                                                        Value
----                                                        -----
_id                                                         null
total                                                       50005000
_id                                                         null
total                                                       5000.5

Aggregation Framework は便利

mongoDBの集計機能、Aggregationフレームワークが便利です。

  • 使用例

テスト用のデータは、"number"というキーに1~10000を順番に入れて作った単純なものです。

> db.testcollection.find()
{ "_id" : ObjectId("519b6ec5fcb9577fd21fcbda"), "number" : 1 }
{ "_id" : ObjectId("519b6ec5fcb9577fd21fcbdb"), "number" : 2 }
{ "_id" : ObjectId("519b6ec5fcb9577fd21fcbdc"), "number" : 3 }
{ "_id" : ObjectId("519b6ec5fcb9577fd21fcbdd"), "number" : 4 }
{ "_id" : ObjectId("519b6ec5fcb9577fd21fcbde"), "number" : 5 }
.
.
.
{ "_id" : ObjectId("519b6ec6fcb9577fd21ff2e9"), "number" : 10000 }

> db.testcollection.count()
10000

# "number" の合計
> db.testcollection.aggregate(
{ $group : { "_id" : null, "total" : { "$sum" : "$number" }} }
)

# 結果
{ "result" : [ { "_id" : null, "total" : 50005000 } ], "ok" : 1 }

# "number" の平均
> db.testcollection.aggregate(
{ $group : { "_id" : null, "average" : { "$avg" : "$number" }} }
)

# 結果
{ "result" : [ { "_id" : null, "average" : 5000.5 } ], "ok" : 1 }

合計:50005000、平均:5000.5 でちゃんとあってます。


$group、$sum、$avg は、Aggregationオペレータと呼ばれるものです。
この他にもいろいろ便利なオペレータがそろってます。
Aggregation Framework Reference — MongoDB Manual 2.4.3

デジカメ画像のExifデータをPowerShellで取得してmongoDBに入れる(PowerShell Image Module)スクリプト

# デジカメ画像ファイル格納パス
$pathname = "C:\mongodb\imgs"
# jpegファイル一覧
$files = Get-ChildItem $pathname -Recurse | Where-Object {$_.Extension -eq ".jpg"}
# PowerShell Image Moduleのロード
Import-Module Image
# DB Driverパス指定
$driver_path = "C:\mongodb\driver\CSharpDriver-1.7.0.4714"
Add-Type -Path "$driver_path\MongoDB.Bson.dll"
Add-Type -Path "$driver_path\MongoDB.Driver.dll"

# mongoDB接続
$dbName = "ExifDb"; # DB名
$connectionString = "mongodb://localhost/{0}?safe=true" -f $dbName
$db = [MongoDB.Driver.MongoDatabase]::Create($connectionString)
# collection名
$collectionName = "ExifCollection";
$collection = $db[$collectionName];

foreach( $file in $files ){
	# 画像ファイル情報
	$filefullname = $file.fullname
	$dir_name = Split-Path $filefullname -Parent
	$file_name = Split-Path $filefullname -Leaf
	$file_LastWriteTime = $file.LastWriteTime
	$file_size = $file.Length
	
	# mongoDBに書き込む情報(ファイル情報)※doc.Add 行に " | Out-Null"をつけると速度UPする
	$doc = New-Object MongoDB.Bson.BsonDocument
	$doc.Add("FileFname", [MongoDB.Bson.BsonValue]::Create($file_name))
	$doc.Add("FileDir", [MongoDB.Bson.BsonValue]::Create($dir_name))
	$doc.Add("FileDate", [MongoDB.Bson.BsonValue]::Create($file_LastWriteTime))
	$doc.Add("FileSize", [MongoDB.Bson.BsonValue]::Create($file_size))
	
	# PowerShell Image Module を実行して画像Exif情報を取得する
	$ExifInfo = Get-Exif $filefullname
	$doc.Add("Manufacturer", [MongoDB.Bson.BsonValue]::Create($ExifInfo.Manufacturer))
	$doc.Add("Model", [MongoDB.Bson.BsonValue]::Create($ExifInfo.Model))
	$doc.Add("Width", [MongoDB.Bson.BsonValue]::Create($ExifInfo.Width))
	$doc.Add("Height", [MongoDB.Bson.BsonValue]::Create($ExifInfo.Height))
	$doc.Add("DateTaken", [MongoDB.Bson.BsonValue]::Create($ExifInfo.DateTaken))

	$collection.save($doc)

}

デジカメ画像のExifデータをPowerShellで取得してmongoDBに入れる(PowerShell Image Module)準備

先日、jhead.exeを使用してExifデータを取得する方法を記載しましたが、PowerShellで完結する方法がありました。
但し、この方法はvista以上のOSでしか動かないようです。当方は、Windows7 64bitで動作を確認しています。
追記:モジュール内でWia.ImageFileというCOMオブジェクトを使用しています。Windows Image Acquisition (WIA) 2.0 ライブラリをインストールすればXP等のOSでも使えるようです。
http://gallery.technet.microsoft.com/scriptcenter/7d7dcf67-5585-40b6-bab8-9484672e4f84

準備

http://gallery.technet.microsoft.com/scriptcenter/PowerShell-Image-module-caa4405a

  • Image.zipを解凍
  • PowerShellコンソールで以下コマンドで表示されるモジュールパスに配置する。

PS> $Env:PsModulePath

  • 通常以下ディレクトリとなる(WindowsPowerShell\Modulesのディレクトリがない場合は作る)

C:\Users\<ユーザ名>\Documents\WindowsPowerShell\Modules

  • 上記ディレクトリにImageディレクトリを配置する。

C:\Users\<ユーザ名>\Documents\WindowsPowerShell\Modules\Image

# PowerShell Image Moduleのロード
PS> Import-Module Image
# モジュール内スクリプト実行確認
PS> Get-Exif "xxxx.jpg"

※Exceptionが発生し、"get-help about_signingと入力してヘルプを参照してください"というメッセージがでたら以下いずれかの方法で対応します。
1、Set-ExecutionPolicyでUnrestrictedに設定する。(実行の度に確認を求められるのでめんどくさい)
2、C:\Users\<ユーザ名>\Documents\WindowsPowerShell\Modules\Image内のファイルを右クリック[プロパティ]の下の方にある[ブロックを解除]を押して解除する。(実行しても安全とわかっているものはこれが簡単です)
3、 PowerShell 3.0 には、Unblock-File という専用のコマンドレットが用意されてます。
http://technet.microsoft.com/en-us/library/hh849924.aspx

mongoDBってビッグデータ用?

最近、良く耳にするmongoDB。NoSQL、スキーマレスって言われても良くわかんなぁ~いので環境作ってみました。
2、3日使ってみただけですが、mongoDBに惚れ込んでしまいました。

  • インストールが楽でびっくり

先のエントリにも書きましたがファイルの配置だけで完了します。

  • ユニークインデックスで重複削除ができる

写真データの整理に使おうと思ってます。ファイル名、ファイルサイズだけでなくExif情報もインデックスに加えればより安全な重複削除が可能になると思います。

RubyとかPythonとかで使うのが一般的なんでしょうけどPowerShellとも相性が良さそうです。とりあえずKeyとValueセットでmongoDBに放り込んでしまって、集計とかはmongoDBでやるというのがいいのかな?と思ってます。

  • Client用Guiが便利

DOSクライアントはやっぱり使いにくいんでClient用Gui使ってます。
MONGOVUE
http://www.mongovue.com/
データ取り込んだ後は、Guiで操作できるので楽です。

  • 今までだとExcel使ってやってたようなログの解析とかが早く、簡単にできそうです。
  • 地理空間のインデックスでデジカメのGPSデータを使って検索とかできそうです。
  • 流行ってるmongoDBを使いこなせば皆に自慢できます。

結論:ビックなデータにも使えますがスモールなデータにも使えます。

スキーマレスって何がいいの?

Exifデータはカメラの機種によって下記のように保有している項目数が違います。
新しい機種ほど項目が多いようです。

File name C:\mongodb\imgs\100323\IMG_5502.JPG
File size 678776 bytes
File date 2010:03:23 11:23:52
Camera make Canon
Camera model Canon PowerShot A620
Date/Time 2010:03:23 11:23:53
Resolution 1600 x 1200
Flash used Yes (auto, red eye reduction mode)
Focal length 29.2mm (35mm equivalent: 146mm)
CCD width 7.21mm
Exposure time 0.017 s (1/60)

  • PENTAX Optio WG-1 GPS(GPSをOFFにしていためGPSデータがとれていない)

File name C:\mongodb\imgs\101_0521\IMGP0866.JPG
File size 2093447 bytes
File date 2012:05:21 19:18:14
Camera make PENTAX
Camera model PENTAX Optio WG-1 GPS
Date/Time 2012:05:21 19:18:14
Resolution 4288 x 3216
Flash used Yes (manual)
Focal length 5.0mm (35mm equivalent: 28mm)
Exposure time 0.020 s (1/50)
Aperture f/4.2
ISO equiv 400
Whitebalance Auto
Metering Mode pattern
Focus range close
GPS Latitude ? ?
GPS Longitude ? ?
JPEG Quality 96

スキーマありのRDBに投入するためには事前の表定義が必要になりますが、だれかからもらったりする画像データにどんな項目があるのかはもらってみるまでわかりません。こんな時、スキーマレスなら事前の定義なしに配列のキーと値をセットを全部放り込んでおくことができます。とりあえず情報を取り込んでおいて使い方は後でゆっくり考えるような使い方が可能です。

mongoDB重複データの削除

PowerShellコンソールでファイル数をカウント。

  • 14ファイルあります。

PS > $files = Get-ChildItem $pathname -Recurse | Where-Object {$_.Extension -eq ".jpg"}
PS > $files.length
14

PowerShellスクリプトでmongoDBを更新。

  • mongoDBクライアントでカウントした結果も14です。

> db.exifcollection.count()
14

再度、同じファイル(14ファイル)をmongoDBに書き込む。

  • mongoDBクライアントでカウント。

> db.exifcollection.count()
28

重複データを削除します。

  • 重複する値があるキーに dropDups オプションをつけてユニークインデックスを作成すると重複データが削除されます。

> db.exifcollection.ensureIndex({"FileFname" : 1}, {unique : true, dropDups : true})

  • カウント。

> db.exifcollection.count()
14

これは、便利です。