2011年6月29日水曜日

2011 CVPR の Longuet-Higgins 賞は、あの論文です。

最も権威ある画像処理関係の学会の一つであるCVPR。

Longuet-Higgins 賞は、過去10年のCVPR論文のなかから「最も根源的なインパクトを与えた研究」に対して毎年授与される賞です。

今年の Longuet-Higgins 賞は、現代の顔認識アルゴリズムの基礎&定番を生み出した、あの Viola & Jonesの "Rapid Object Detection using a Boosted Cascade of Simple Features" に授与されました。まさに満を持してというか、当然の受賞という感じですね。

投稿者:寺田

Eclipse Indigo がリリースされてました。

ちょっと後追いになりますが、Eclipse の最新版、Indigoがリリースされたようです。

個人的には、GUIテストの自動化ツールに興味があります。(Jubulaベースとのこと)。

"Automated functional GUI testing for Java and HTML applications is included via Jubula"

投稿者:寺田

2011年6月22日水曜日

Google App Engine 1.5.1 リリース

GAEの1.5.1がリリースされたそうです。Google の blog のリリース記事を適当に訳してみました。



GoogleI/O 2011からそろそろ1ヶ月経ち、頃合いなので新しいリリースを行おうと思う。
詳細は下記の通り。

提供される変更点

Geolocation ヘッダー

App Engineは、あらゆるリクエストについて、その発信元アドレスを元に、(可能なかぎり)その国の識別情報をヘッダーを含むこととした。このヘッダは、"X-AppEngine-country"でタグ付けされており、開発者はこの情報を使ってカスタマイズすることができる。これにより、「あなたの地域を選択してください」などいうドロップダウンメニューが必要なくなると嬉しい。


Java および Python APIの更新

チャネル API (Presence サポート)

アプリのインバウンドサービスについて、チャンネルAPIに対するユーザの「存在」を検知するように設定できるようになった。これにより、ユーザがチャンネルに接続したり切断したりした際に何らかのアクションを取ることができる。

イメージAPI(WebP サポート)

イメージAPIは、WebP画像をサポートするようになった。まだ知らない人に説明すると、WebP は Googleがオープンソースとして今年初旬に公開した新しい画像フォーマットで、同じ画質ならばJPEGよりも39%以上ファイルサイズを小さくすることができる。(ロスあり圧縮)


新しいPython API群

ProtoRPC
ProtoRPCは、「明確に定義された使いやすい WebベースのRPCサービス」を構築できるオープンソースフレームワークである。サービスを定義する上では、Google Protocol Buffersを使うのと似ているが、ProtoRPCの目的は、開発者がWebサービスの定義を簡単にはじめられるようにすることである。これらのサービスは、もちろんスケールすることができるし、時間とともに発展することもできる。

データストア

SDKでの高速応答
高速応答データストアのリリース以来、我々は、アプリの開発を通じて、開発者が新しい一貫性モデルを理解しテストすることを手助けするようなツールを提供したいと考えていた。JavaおよびPython向け1.5.1SDKは、HRD一貫性モデルをエミュレートできるようになった。これにより、適切なSDKオプションを設定することにより、エンティティグループを横断するクエリは、最後に書きこまれたデータを必ずしも反映しない結果を返すようになる。開発者は、この一貫性モデルに対して、アプリケーションをより回復力のあるものとすることができるだろう。

全てのリリースと同様、1.5.1は小さな追加的な機能や多くのバグフィクスを含んでおり、その全リストは、PythonおよびJava用のリリースノートに記載されている。できれば、このリリースが有用であってほしい。どちらにせよ、何か考えるところがあれば気軽にApp Engine groupを通じて我々に連絡してほしい。


投稿者:てらだ

2011年6月21日火曜日

Androidで ツリー表示可能なリストビュー

はじめまして。アクアキャスト島田です。
アンドロイド端末のアプリを作成している者です。

仕事でGUIを考えているとき、「ツリー表示ができるビューはないのか!?」と思い探してみました。
GoogleCodeで検索すると出てきたのですが、やはり自分で作ってしまおうと思い作ってみました。

まずはコアとなるアダプタクラスです。(ビュークラスはListViewを使います)

import java.util.ArrayList;
import java.util.List;
import android.widget.BaseAdapter;

public abstract class BaseTreeAdapter extends BaseAdapter {
    TreeEntry rootEntry = null; 

    public TreeEntry add(Object entry) {
        if(rootEntry == null) rootEntry = new TreeEntry();
        return rootEntry.add(entry);
    }

    @Override
    public int getCount() {
      return rootEntry == null ? 0 : rootEntry.getCount();
    } 

 @Override
 public Object getItem(int position) {
  // position=0はrootEntryとなってしまうため  
  // インクリメントする  
  return rootEntry == null ? null : rootEntry.getItem(position + 1) ; 
 } 

 @Override
 public long getItemId(int position) {
  //TreeEntry treeEntry = (TreeEntry)getItem(position);
  //return treeEntry.getId();
  return 0;
 } 

 public class TreeEntry {
  private List treeEntries = null;  
  private int depth = -1; // マイナスはルートオブジェクトのみ  

  private boolean isExpanded = false;
  private Object data = null;
  private TreeEntry() {   // ルートオブジェクト専用   
   isExpanded = true;
  }  

  private TreeEntry(TreeEntry parentEntry, Object data) {
   if(parentEntry != null) depth = parentEntry.depth + 1;
   this.data = data;
  }  

  public TreeEntry add(Object entry) {   
   if(treeEntries == null) treeEntries = new ArrayList();
   TreeEntry treeEntry = new TreeEntry(this, entry);
   treeEntries.add(treeEntry);
   return treeEntry;
  }  

  private int getCount() {
   if(treeEntries == null || !isExpanded) return 0;
   // 自分の持っているエントリの内、   
   // 開いている数を返す   
   int count = treeEntries.size();
   for(TreeEntry entry : treeEntries) count += entry.getCount();
   return count;  
  }  

  private TreeEntry getItem(int position) {
   if(position == 0) return this; // position=0は自分を返す
   if(treeEntries == null || !isExpanded) return null;
   for(int i = 0; position >= 0 && i < treeEntries.size(); ++ i) {
    TreeEntry entry = treeEntries.get(i);
    -- position;    // 子について、
    // エントリが取得できない場合、
    // 孫をpositionから引く  
    int count = entry.getCount();
    if(count >= position) {
     // 子以降で必ずアイテムが見つかる
     return entry.getItem(position);
    }
    position -= count;
   }
   // ここは、アイテム数より大きなpositionを
   // 指定された場合のみ
   return null;
  }  

  public void expand() {
   if(!hasChild()) return; // 子を持っていない場合、開けない
   if(isExpanded) return;
   isExpanded = true;
   notifyDataSetChanged();
  }

  public void collapse() {
   if(!isExpanded) return;
   isExpanded = false;
   notifyDataSetChanged();
  }

  public boolean isExpanded() {
   return isExpanded;
  }

  public int getDepth() {
   return depth;
  }
  
  public Object getData() {
   return data;
  }

  public boolean hasChild() {
   return treeEntries != null && !treeEntries.isEmpty();
  } 
 }

}

次に上記アダプタクラスから派生したクラスです。
public class MyTreeAdapter extends BaseTreeAdapter { 
 private LayoutInflater inflater = null;

 @Override
public View getView(int position, View convertView, ViewGroup parent) {
  if(convertView == null) {
   if(inflater == null) {
    inflater = (LayoutInflater)parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
   }
   convertView = inflater.inflate(R.layout.entry, null);
  }

  TreeEntry treeEntry = (TreeEntry)getItem(position);
  TextView text = (TextView)convertView.findViewById(R.id.text);
  Object data = treeEntry.getData();
  String str = (String)data;
  text.setText(str);
  text.setPadding(treeEntry.getDepth() * 20, text.getPaddingTop(), text.getPaddingRight(), text.getPaddingBottom());

  return convertView;
 }
}

最後にアクティビティクラスです。

public class Main extends Activity { 
 private Map expandMap = new HashMap();

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
  final ListView listView = (ListView)findViewById(R.id.list);

  listView.setOnItemClickListener(new OnItemClickListener() {

    @Override
    public void onItemClick(AdapterView parent, View view, int position, long id) {
     TreeEntry treeEntry = (TreeEntry)parent.getItemAtPosition(position);
     int depth = treeEntry.getDepth();
     if(treeEntry.isExpanded()) {
      expandMap.remove(depth);
      treeEntry.collapse(); 
     } else {
      if(treeEntry.hasChild()) {
       if(expandMap.containsKey(depth)) {
        expandMap.get(depth).collapse();
       }
       treeEntry.expand();
       expandMap.put(depth, treeEntry);
      }
     }
    }
   } );

  listView.setAdapter(new MyTreeAdapter());
  MyTreeAdapter adapter = (MyTreeAdapter)listView.getAdapter();

  TreeEntry root = adapter.add("1階層目-1");
  TreeEntry days = root.add("2階層目-1");
  days = root.add("2階層目-2");
  days.add("3階層目-1");
  days.add("3階層目-2");
  root = adapter.add("1階層目-2");
  days = root.add("2階層目-3");
  days.add("3階層目-3");
  days.add("3階層目-4");
  days.add("3階層目-5");
  days = root.add("2階層目-4");
  days.add("3階層目-6");
  days.add("3階層目-7");
  days.add("3階層目-8");
  days.add("3階層目-9");
 }
}

実行した画面をキャプチャーしました。

アダプタクラスだけで、できてしまいました。




投稿者:島田

2011年6月17日金曜日

Pythonのジェネレータと、RubyのFiber

Rubyは以前ちょっと仕事で使ってました。最近は GAE を齧ったりしているので Python を良く使っています。
私の頭のなかでは、両者はそんなに大きく違いません。使ってみた時の「感触」がとても近いと思っています。周辺技術も含めた実用性で少し Python が先行している感じでしょうか。

Rubyは使用頻度が減りましたが、いまも気になる言語です。Rubyist Magazineもちょくちょく覗いています。
今回は Rubyist Magazine からのネタです。下記の記事が非常に面白くて分かりやすい内容でした。
「関数型」と「手続き型」の発想を対比しながら Fiberと Procを説明しています。

Fiber と Proc ―― 手続きを抽象化する二つの機能

Python には「ジェネレータ」という便利な機能があります。これと同じことをしているのが Rubyの Fiberということになります。上記記事の説明によると、Fiberの概念(≒ジェネレータの概念)は関数型の発想ということになります。なるほど。

2つの対比的な概念を並行して説明することで理解しやすくする、というのは上手い説明の手法の一つですね。ジェネレータについていまいちすっきりしてない人は、上記を読んでみると参考になるかもしれません。

投稿者:てらだ

2011年6月16日木曜日

やはりAndroidのタッチ入力は、まだまだです:ムービーあり

開発でAndroid端末に触れることがよくありますが、どうしても気になるのは iPhone/iPad に較べてタッチ入力の感触が良くないことです。

この問題を定量的に調べた、ちょっと面白いデータを見つけたのでご紹介します。

ロボットアームを使って端末のスクリーン上を正確になぞり(ここでは斜めの線を繰り返し描いています)、
その軌跡が入力にどのように反映されるかを見ようというアプローチです。お絵かきソフト風のテストプログラムで入力結果を画像として見せています。

その結果が下記の画像です。



ご覧のとおり、iPhone以外の(一世代前ですが)Android端末の結果は悲惨です。
入力の位置精度も更新レート(毎秒何回位置情報を取り込めるか)も両方良くない感じですね。

新世代の端末や、Android3.xではもう少し改善していると思いますが・・・

下記のムービーは、その計測の様子です。




投稿者:てらだ

2011年6月15日水曜日

手元にあるcsvファイルをGAEにアップロードする方法

表題の件、覚書として書いておきます。

GAE(Google App Engine)のデータストアには2種類あります。
1.High Replication Datastore
2.Master/Slave Datastore

アプリケーション登録時に明示的に指定しないと1が自動的に選択されます。
一度選択してしまうと、後で変更はできません。

ここで示すデータのアップロードは1では動作しませんでした。
2を選択した場合のお話です(本当は1で動作させたいのだけれど)。


【1】以下のようなフォルダ構成を考えます。
    application/
       main.py
       app.yaml
       models.py
       bulkloader/
           link.csv
           loaders.py

main.pyはアプリ本体を記述したファイル、app.yamlは設定ファイルです。

【2】まず最初に、設定ファイルapp.yamlに以下3行を追加します(アプリ本体の設定は省略します)。
    - url: /remote_api
      script: $PYTHON_LIB/google/appengine/ext/remote_api/handler.py
      login: admin

ここで注意しなければならない行は
          script: $PYTHON_LIB/google/appengine/ext/remote_api/handler.py
です。$PYTHON_LIBは一字一句このまま記述しなければなりません。
勘違いして自分の環境変数に置き換えてはいけません(置き換えるとはまります)。

【3】link.csvは2つのカラムfromidとtoidを持ち、これらはint型であるとします。
このとき、models.py内に以下を実装します。

    from google.appengine.ext import db

    class Link(db.Model):
        fromid = db.IntegerProperty()
        toid = db.IntegerProperty()


【4】続いて、これに対応したloaderクラスをloaders.pyに実装します。

    from google.appengine.tools import bulkloader
    import sys
    import os.path
    sys.path.append(
            os.path.abspath(
                    os.path.dirname(
                            os.path.dirname(
                                    os.path.realpath(__file__)
                            )
                    )
            )
    )

    from models import *

    class LinkLoader(bulkloader.Loader):

        def __init__(self):

            bulkloader.Loader.__init__(
                self
              , "Link"
              , [
                    ("fromid", int)
                  , ("toid", int)
                ]
            )


    loaders = [LinkLoader]

models.pyが見えるように、sys.pathには1つ上のパスを追加しています(もっと良い書き方があると思うけど)。
末尾にグローバルな配列loadersを定義します。


【5】この2つのクラスがcsvからデータストアへの変換法則を決めます。
models.pyはアプリ本体でも参照するファイルなので、bulkloaderフォルダにはおかずにapplicationフォルダに
おきました。

【6】アプリ本体をアップロードします。
    $> appcfg.py update application

【7】次にデータをアップロードします。bulkloaderフォルダに入ったあと以下を実行します。

    $> appcfg.py upload_data --config_file=loaders.py --filename=link.csv \
      --kind=Link ..

    このコマンドは、link.csv内のデータを、クラスLinkとLinkLoaderを使って、bigtable内に格納します。

文責:くまだ