肉メモ

f:id:stoikheia:20181023104838j:image

 

西友の980円肩ロース(厚手)。

QOL向上のため、この手の肉の目利き能力を上げる。

優秀部位の多い肉を選べるように研究するのだ。

肩ロースステーキを今まで散々バカにしてきたわけだが安さは偉大。

 

f:id:stoikheia:20181023104847j:image

 

明らかにスジ肉の部位は取り除いてカレー用にする。

 

f:id:stoikheia:20181023104856j:image

 

厚さ3ミリの鉄のフライパンで表裏メイラード反応が出るまで焼き上げてから濡れ布巾の上にパンを置いてアルミホイルで蓋をし蒸らすこと三分。

香りづけのガーリックも付着したまま一緒に焼く。

ガーリックは焼き上がりに取り除いて食さない。

(廃棄の肉汁とガーリックはカレーに入れた)

 

f:id:stoikheia:20181023104912j:image

皿に盛る都合で①③④⑤は鉛直方向を軸に180度回転状態。

焼き加減はたんぱく質凝固による柔らかさの違いが出やすいミディアムレア。

味付けは塩胡椒ガーリックのみ。

ソースなし。

 

f:id:stoikheia:20181023104928j:image

やわらか、ジューシー。

もっともステーキに向いている。

この部位は最後に投入したので、柔らかさは焼き加減によるところがあるかもしれない。

だが過去の経験からもこの部位が最もステーキに向いているのは薄々感づいていた。

どうやらここがロース芯と呼ばれる部位のようだ。

肩ロースからはこの程度の割合でしか取れない。とはいえ、今回の肉は比較的リブロース側の肩ロースと思われ、これでも多い方だと思う。

 

f:id:stoikheia:20181023104937j:image

やわらか、ややスジっぽい。

スジが口内に残る。

カレー行きにすべき。

①の部位とちゃんと分離するのが重要なことがわかる。

 

f:id:stoikheia:20181023105033j:image

やや硬い。スジっぽい。

太いスジが横方向に走っているので長いスジに当たりやすい。

 

f:id:stoikheia:20181023105043j:image

やわらか。ジューシーさは①に劣る。

右に行くほど脂っこく、柔らかい。

しかし、スジも増える。

(まじで増える。飲み込めないレベル。)

 

f:id:stoikheia:20181023105057j:image

コリコリとした歯ごたえ。

最も肉の味が濃い。

スジは左側に行くほどある。

左端は飲み込むのが辛いレベルでスジが残る。

縦にスジがあるのでナイフで切って短くできない。

食べきれず吐き出すスジ多し。

 

 

 

 

 

 

 

 

 

RICOH THETAで撮った写真をPTP-IPで取得する

前回シャッター操作をしたので、撮った写真の取得もしたくなることでしょう。
今回もRuby-PTP-IPをベースにして写真の取得シーケンスとコードを解説します。

THETAでの画像取得について


PTP対応機種では写真を撮ると、画像、サムネイル、メタデータが作成されます。
それらはまとめて一つの32bit整数が割り当てられます。
取得、変更、削除はその値を指定してアクセスすることになります。
これをオブジェクトハンドルといいます。一意の値です。


同一のオブジェクトハンドルに対して

  • 画像を取得する時はGetObject
  • サムネイルはGetThumb
  • メタデータはGetObjectInfo

と、オペレーションコードを変えてアクセスすることでそれぞれのデータを得る形です。


セッション内で取得したオブジェクトハンドルはそのセッション中でのみ一意性が保証されます。
まあ通常、セッションまたいでも同一ですが。


オブジェクトハンドルの取得についてですが、
前回のシャッター操作のようにInitiateCapture後はObjectAddedイベントが来ます。
そして、そのイベントの一つ目のパラメータに新しいオブジェクトハンドルが格納されています。
・・・が、THETAでは常に100が入っていて、そのオブジェクトハンドルでアクセスしてもエラーとして
レスポンスコード:PTP_RC_InvalidObjectHandle
が帰ってきます。(2014/08/24 : こちらのバグでした)


よって、今回のサンプルではTHETA内のオブジェクトハンドルを全て取得しています。
そしてTHETAではその末尾が最も新しいオブジェクトハンドルなのでその値を使用しています。

シーケンス


シーケンス図では長くなるので画像とサムネイルにおけるデータフェイズの記述を省略しています。







今回はイベントコネクションを使ったセッション中の通信は無いです。
THETAではイベントコネクションを開いていなくてもオペレーションリクエストを受付るらしいので、画像取得だけなら省略してしまってもいいでしょう。

コード


接続やセッションの部分は前回(修正版)と同じです。
今回はセッション中にサムネイルと画像を受信して保存しているだけです。
簡単ですね。

#!/bin/ruby

require 'socket'

require './ruby_ptp_ip/ptp.rb'
require './ruby_ptp_ip/ptp_ip.rb'

# THETAのIPアドレスとPTP-IPのポート番号
ADDR = "192.168.1.1"
PORT = 15740

# THETAに送るレスポンダ情報
NAME = "Ruby_THETA_GetObject"
GUID = "ab653fb8-4add-44f0-980e-939b5f6ea266"
PROTOCOL_VERSION = 65536

# GUID文字列を整数の配列にする関数
def str2guid(str)
    hexes = str.scan /([a-fA-F0-9]{2})-*/
    hexes.flatten!
    raise "Invalid GUID" if hexes.length != 16
    hexes.map do |s|
        s.hex
    end
end

# パケットを書き込む関数
def write_packet(sock, pkt)
    sock.send(pkt.to_data.pack("C*"), 0)
end

# パケットを読み込む関数
def read_packet(sock)
    data = []
    data += sock.read(PTPIP_packet::MIN_PACKET_SIZE).unpack("C*")
    len = PTPIP_packet.parse_length(data)
    data += sock.read(len-PTPIP_packet::MIN_PACKET_SIZE).unpack("C*")
    PTPIP_packet.new(data)
end

# データフェイズの読み込み関数
def recv_data(sock, transaction_id)
    recv_pkt = read_packet(sock)
    raise "Invalid Packet : #{recv_pkt.to_s}" if recv_pkt.type != PTPIP_PT_StartDataPacket
    raise "Invalid Transaction ID" if recv_pkt.payload.transaction_id != transaction_id
    data_len = recv_pkt.payload.total_data_length_low
    data = []
    while recv_pkt.type != PTPIP_PT_EndDataPacket
        recv_pkt = read_packet(sock)
        raise "Invalid Packet : #{recv_pkt.to_s}" if recv_pkt.type != PTPIP_PT_DataPacket && recv_pkt.type != PTPIP_PT_EndDataPacket
        raise "Invalid Transaction ID" if recv_pkt.payload.transaction_id != transaction_id
        data += recv_pkt.payload.data_payload
    end
    raise "Invalid Data Size" unless data_len == data.length
    return data
end

# コマンドコネクション初期化関数
def init_command(sock)
    init_command_payload = PTPIP_payload_INIT_CMD_PKT.new()
    init_command_payload.guid = str2guid(GUID)
    init_command_payload.friendly_name = NAME
    init_command_payload.protocol_version = PROTOCOL_VERSION 
    init_command = PTPIP_packet.create(init_command_payload)

    write_packet(sock, init_command)
    read_packet(sock)#ACK
end

# イベントコネクション初期化関数
def init_event(sock, conn_number)
    init_event_payload = PTPIP_payload_INIT_EVENT_REQ_PKT.new()
    init_event_payload.conn_number = @conn_number
    init_event = PTPIP_packet.create(init_event_payload);
    
    write_packet(sock, init_event)
    read_packet(sock)
end

# データフェイズのあるオペレーションコード実行関数
def data_operation(sock, operation_code, transaction_id, parameters = [])
    op_payload = PTPIP_payload_OPERATION_REQ_PKT.new()
    op_payload.data_phase_info = PTPIP_payload_OPERATION_REQ_PKT::NO_DATA_OR_DATA_IN_PHASE
    op_payload.operation_code = operation_code
    op_payload.transaction_id = transaction_id
    op_payload.parameters = parameters
    op_pkt = PTPIP_packet.create(op_payload)
    write_packet(sock, op_pkt)

    data = recv_data(sock, transaction_id)
    recv_pkt = read_packet(sock)
    return recv_pkt, data
end

# シンプルなオペレーションコードの実行関数
def simple_operation(sock, operation_code, transaction_id, parameters = [])
    op_payload = PTPIP_payload_OPERATION_REQ_PKT.new()
    op_payload.data_phase_info = PTPIP_payload_OPERATION_REQ_PKT::NO_DATA_OR_DATA_IN_PHASE
    op_payload.operation_code = operation_code
    op_payload.transaction_id = transaction_id
    op_payload.parameters = parameters
    op_pkt = PTPIP_packet.create(op_payload)
    write_packet(sock, op_pkt)

    read_packet(sock)
end

# 
# ここから処理
# 
TCPSocket.open(ADDR, PORT) do |s|

    # コマンドコネクション
    recv_pkt = init_command(s)
    raise "Initialization Failed (Command) #{recv_pkt.payload.reason}" if recv_pkt.type == PTPIP_PT_InitFailPacket
    p @conn_number = recv_pkt.payload.conn_number
    p @guid = recv_pkt.payload.guid
    p @name = recv_pkt.payload.friendly_name
    p @protocol_version = recv_pkt.payload.protocol_version

    TCPSocket.open(ADDR, PORT) do |es|

        # イベントコネクション
        recv_pkt = init_event(es, @conn_number)
        raise "Initialization Failed (Event) #{recv_pkt.payload.reason}" if recv_pkt.type == PTPIP_PT_InitFailPacket
        print "Command/Event Connections are established.\n"
        
        @transaction_id = 1
        @session_id = 1

        # セッション開始
        recv_pkt = simple_operation(s, PTP_OC_OpenSession, @transaction_id, [@session_id])
        raise "Open Session Failed #{recv_pkt.payload.response_code}" if recv_pkt.payload.response_code != PTP_RC_OK
        @transaction_id += 1
        
        # オブジェクトの列挙
        # 1つ目のパラメータはストレージID. 0xFFFFFFFFのときは全てのストレージから列挙
        # 2つめのパラメータはフォーマットID. 0xFFFFFFFFの時は全てのフォーマット.
        # 3つめのパラメータはオプション.場合によって異なるが、今回の全列挙の場合は0.
        recv_pkt, data = data_operation(s, PTP_OC_GetObjectHandles, @transaction_id, [0xFFFFFFFF, 0xFFFFFFFF, 0])
        raise "GetObjectHandles Failed #{recv_pkt.payload.reason_code}" if recv_pkt.payload.response_code != PTP_RC_OK
        offset = 0
        @object_handles, offset = PTP_parse_long_array(offset, data)
        p @object_handles #オブジェクトハンドルのダンプ
        @transaction_id += 1

        print "GetThumb...\n"
        # 最後に撮ったサムネイルの取得
        # 一番目のパラメータにObjectHandleを格納
        recv_pkt, data = data_operation(s, PTP_OC_GetThumb, @transaction_id, [@object_handles[-1]])
        raise "GetThumb Failed #{recv_pkt.payload.reason_code}" if recv_pkt.payload.response_code != PTP_RC_OK
        File.open("./theta_thumb.jpg", "wb") do |f|
            f.write(data.pack("C*"))
            print "Saved!\n"
        end
        @transaction_id += 1

        print "GetObject...\n"
        # 最後に撮った写真の取得
        # 一番目のパラメータにObjectHandleを格納
        recv_pkt, data = data_operation(s, PTP_OC_GetObject, @transaction_id, [@object_handles[-1]])
        raise "GetObject Failed #{recv_pkt.payload.reason_code}" if recv_pkt.payload.response_code != PTP_RC_OK
        File.open("./theta_pic.jpg", "wb") do |f|
            f.write(data.pack("C*"))
            print "Saved!\n"
        end
        @transaction_id += 1

        # セッション終了
        recv_pkt = simple_operation(s, PTP_OC_CloseSession, @transaction_id) 
        raise "Close Session Failed #{recv_pkt.payload.response_code}" if recv_pkt.payload.response_code != PTP_RC_OK
        @transaction_id += 1

    end

end


プログラム的にサムネイル、画像を取得できるようになれば定期的な撮影とアップロードとかを自動で出来ますね。
RICOHのページにアップする以外にもウェブページに埋め込んだり、アプリに埋め込んだりという選択肢もあるので、以下のプログラムと組み合わせるといいかもです。

ブラウザで見る場合は
Haga氏のビューワ(github)
https://github.com/thaga/IOTA
akokubo氏のビューワ(github)
https://github.com/akokubo/ThetaViewer

Oculus持っている人は
MobileHackerzの中の人のOculus用ビューワ
http://mobilehackerz.jp/contents/Review/RICOH_THETA/Oculus

とかあります。

RICOH THETAでRuby-PTP-IPの紹介

RICOH THETAってPTP-IPで制御できるんですね↓
http://mobilehackerz.jp/contents/Review/RICOH_THETA/WiFi_Control


昔作ったPTP-IPのライブラリがあるのでRubyからシャッターを動作させてみます。
今後も時間を作って、写真の取得などの記事も書きたいですね。 →書いた

ライブラリは、この記事を書くまではruby_ptp_responderと言うプロジェクト名でしたが少しキャッチーにruby_ptp_ipへと変更しました。
こちら(github
https://github.com/stoikheia/ruby_ptp_ip
に置いてあります。
今見るとmoduleでまとめていなかったりするのがムズムズしますね。


Rubyでどうやって動かすのか、という点に興味ある方は最小コードでのシャッター制御をGOROman氏が公開しているので↓
https://gist.github.com/GOROman/7596186#file-thetatest-rb
そちらのほうがまずいいかもです。


まあー、自分がやることは既に無い感じですけど、
ライブラリの紹介ついでの記事にします。

(2014/08/23修正)

シーケンス

THETAのシャッターを動作させる手順は、以下のシーケンスです。
(青いノートはRuby-PTP-IP上での説明です)


より詳細に知りたい人は
PTP(PIMA15740-2000)とPTP-IP(CIPA_DC-005-2005)を読むと良いです。








THETA側がレスポンダと呼ばれる、機能を提供する側です。
要求を出すこっち側はイニシエータと呼ばれます。


コマンド用とイベント用それぞれ2つのコネクションを確立します。


上の図では緑の矢印がコマンドコネクション、
赤の矢印がイベントコネクションでの送受信として見てください。


イベント用のコネクションを開いた後、
InitCommandRequestのAckで取得したコネクションIDをInitEventRequestで渡し、
コマンドとイベントそれぞれのコネクションの対応をTHETAに教えます。


GetDeviceInfoはリクエストを送った後にデータフェイズになるオペレーションコードです。
DeviceInfoがデータパケットで送られてきた後、データフェイズが完了してからレスポンスパケットが帰ってきます。
DeviceInfoの取得は必須ではないです。


OpenSessionでセッションを開始後、シャッターを動作させるのですが
コマンドには同期コマンドと非同期コマンドがあり、
THETAのシャッターを開始するInitiateCaptureは非同期コマンドです。
THETAがコマンドを受け取った後に、
イベント用のコネクションからObjectAddedイベント、続いてCaptureCompleteイベントが来ます。


シャッター操作はコネクション確立、データフェイズ、セッション、非同期イベントと、PTP-IPの要素が全て入っているのでPTP-IPの学習にもってこいですね。

コード

このライブラリはレスポンダという、いわばTHETA側の機能を実現するために作ったものですが、PTP-IPの命令とパケットタイプはすべて実装してあるのでTHETAに命令するイニシエータとしても使えます。


汎用性のためにプリミティブなライブラリになっているので、PTP-IPのパケットやデータをSocketなどで送受信する処理は使用者が書くようになっていますが、そこはRubyなら簡単に書ける部分でしょう。


ライブラリの使い方全般として、
送信したい場面に応じて

  1. 各種payloadクラスのインスタンスを作る
  2. それをPTPIP_packetクラスのcreateメソッドに渡してパケットのインスタンスを作る
payload = PTPIP_payload_HOGE.new()
payload.aaaa = bbbb #データ格納とか
packet = PTPIP_packet.create(payload)

パケットクラスはto_dataメソッドでバイト配列を作成するので
それをpack("C*")でStringにしてSocketに書き込みます。

write_socket(packet.to_data.pack("C*")) #to_dataの時点ではバイト配列なのでpackする

逆に受信したデータは

  1. unpack("C*")バイト配列にする
  2. PTPIP_packet.new()に渡す
packed_data = read_socket()
packet = PTPIP_packet.new(packed_data.unpack("C*"))
p packet.payload.cccc #データに応じたペイロードが格納されている

という流れです。


GUIDは適当につくりました。


縦に長いコードですが、

  • 値を入れる
  • 送る
  • 受信

の繰り返しなだけなので内容は簡単だと思います。

#!/bin/ruby

require 'socket'

require './ruby_ptp_ip/ptp.rb'
require './ruby_ptp_ip/ptp_ip.rb'

# THETAのIPアドレスとPTP-IPのポート番号
ADDR = "192.168.1.1"
PORT = 15740

# THETAに送るレスポンダ情報
NAME = "Ruby_THETA_Shutter"
GUID = "ab653fb8-4add-44f0-980e-939b5f6ea266"
PROTOCOL_VERSION = 65536

# GUID文字列を整数の配列にする関数
def str2guid(str)
    hexes = str.scan /([a-fA-F0-9]{2})-*/
    hexes.flatten!
    raise "Invalid GUID" if hexes.length != 16
    hexes.map do |s|
        s.hex
    end
end

# パケットを書き込む関数
def write_packet(sock, pkt)
    sock.send(pkt.to_data.pack("C*"), 0)
end

# パケットを読み込む関数
def read_packet(sock)
     data = []
     data += sock.read(PTPIP_packet::MIN_PACKET_SIZE).unpack("C*")
     len = PTPIP_packet.parse_length(data)
     data += sock.read(len-PTPIP_packet::MIN_PACKET_SIZE).unpack("C*")
     PTPIP_packet.new(data)
end

# データフェイズの読み込み関数
def recv_data(sock, transaction_id)
    recv_pkt = read_packet(sock)
    raise "Invalid Packet : #{recv_pkt.to_s}" if recv_pkt.type != PTPIP_PT_StartDataPacket
    raise "Invalid Transaction ID" if recv_pkt.payload.transaction_id != transaction_id
    data_len = recv_pkt.payload.total_data_length_low
    data = []
    while recv_pkt.type != PTPIP_PT_EndDataPacket
        recv_pkt = read_packet(sock)
        raise "Invalid Packet : #{recv_pkt.to_s}" if recv_pkt.type != PTPIP_PT_DataPacket && recv_pkt.type != PTPIP_PT_EndDataPacket
        raise "Invalid Transaction ID" if recv_pkt.payload.transaction_id != transaction_id
        data += recv_pkt.payload.data_payload
    end
    raise "Invalid Data Size" unless data_len == data.length
    return data
end

# コマンドコネクション初期化関数
def init_command(sock)
    init_command_payload = PTPIP_payload_INIT_CMD_PKT.new()
    init_command_payload.guid = str2guid(GUID)
    init_command_payload.friendly_name = NAME
    init_command_payload.protocol_version = PROTOCOL_VERSION 
    init_command = PTPIP_packet.create(init_command_payload)

    write_packet(sock, init_command)
    read_packet(sock)#ACK
end

# イベントコネクション初期化関数
def init_event(sock, conn_number)
    init_event_payload = PTPIP_payload_INIT_EVENT_REQ_PKT.new()
    init_event_payload.conn_number = @conn_number
    init_event = PTPIP_packet.create(init_event_payload);
    
    write_packet(sock, init_event)
    read_packet(sock)
end

# データフェイズのあるオペレーションコード実行関数
def data_operation(sock, operation_code, transaction_id, parameters = [])
    op_payload = PTPIP_payload_OPERATION_REQ_PKT.new()
    op_payload.data_phase_info = PTPIP_payload_OPERATION_REQ_PKT::NO_DATA_OR_DATA_IN_PHASE
    op_payload.operation_code = operation_code
    op_payload.transaction_id = transaction_id
    op_payload.parameters = parameters
    op_pkt = PTPIP_packet.create(op_payload)
    write_packet(sock, op_pkt)

    data = recv_data(sock, transaction_id)
    recv_pkt = read_packet(sock)
    return recv_pkt, data
end

# シンプルなオペレーションコードの実行関数
def simple_operation(sock, operation_code, transaction_id, parameters = [])
    op_payload = PTPIP_payload_OPERATION_REQ_PKT.new()
    op_payload.data_phase_info = PTPIP_payload_OPERATION_REQ_PKT::NO_DATA_OR_DATA_IN_PHASE
    op_payload.operation_code = operation_code
    op_payload.transaction_id = transaction_id
    op_payload.parameters = parameters
    op_pkt = PTPIP_packet.create(op_payload)
    write_packet(sock, op_pkt)

    read_packet(sock)
end

# 
# ここから処理
# 
TCPSocket.open(ADDR, PORT) do |s|

    # コマンドコネクション
    recv_pkt = init_command(s)
    raise "Initialization Failed (Command) #{recv_pkt.payload.reason}" if recv_pkt.type == PTPIP_PT_InitFailPacket
    p @conn_number = recv_pkt.payload.conn_number
    p @guid = recv_pkt.payload.guid
    p @name = recv_pkt.payload.friendly_name
    p @protocol_version = recv_pkt.payload.protocol_version

    TCPSocket.open(ADDR, PORT) do |es|

        # イベントコネクション
        recv_pkt = init_event(es, @conn_number)
        raise "Initialization Failed (Event) #{recv_pkt.payload.reason}" if recv_pkt.type == PTPIP_PT_InitFailPacket
        print "Command/Event Connections are established.\n"

        @session_id = 1
        @transaction_id = 1

        # DeviceInfoの取得とパースと表示
        recv_pkt, data = data_operation(s, PTP_OC_GetDeviceInfo, @transaction_id)
        dev_info = PTP_DeviceInfo.create(data)
        print "Device Info :\n"
        p dev_info.to_s
        raise "GetDeviceInfo Failed #{recv_pkt.payload.response_code}" if recv_pkt.payload.response_code != PTP_RC_OK
        @transaction_id += 1
        
        # セッション開始
        recv_pkt = simple_operation(s, PTP_OC_OpenSession, @transaction_id, [@session_id])
        raise "Open Session Failed #{recv_pkt.payload.response_code}" if recv_pkt.payload.response_code != PTP_RC_OK
        @transaction_id += 1

        # キャプチャ開始
        # [0,0]はストレージIDとキャプチャフォーマット
        # それぞれ0の時はデバイス側が判断する.
        recv_pkt = simple_operation(s, PTP_OC_InitiateCapture, @transaction_id, [0,0])
        raise "Initiate Capture Failed #{recv_pkt.payload.response_code}" if recv_pkt.payload.response_code != PTP_RC_OK
        @transaction_id +=1
        @event_transaction_id = recv_pkt.payload.transaction_id

        print "Capturing...\r"

        
        # ObjectAddedイベントの待機
        recv_pkt = read_packet(es)
        raise "Invalid Event Packet" unless recv_pkt.type == PTPIP_PT_EventPacket
        raise "Invalid Event Code" unless recv_pkt.payload.event_code == PTP_EC_ObjectAdded

        print "Object Added!\n"

        # CaptureCompletedイベントの待機
        recv_pkt = read_packet(es)
        raise "Invalid Event Packet" unless recv_pkt.type == PTPIP_PT_EventPacket
        raise "Invalid Event Code" unless recv_pkt.payload.event_code == PTP_EC_CaptureComplete

        print "Capture Completed!\n"

        # セッション終了
        recv_pkt = simple_operation(s, PTP_OC_CloseSession, @transaction_id) 
        raise "Close Session Failed #{recv_pkt.payload.response_code}" if recv_pkt.payload.response_code != PTP_RC_OK
        @transaction_id += 1

    end

end

イベントのトランザクションIDはめちゃくちゃな値が入っているのでエラーチェックに使えなかったです。(2014/08/23 : こちらのバグでした。)

THETAのPTP-IPでのデバイス情報

上のコードでもDeviceInfoをダンプしているので確認できると思いますが表にしておきます。
ベンダ独自のコードもあるので、そのあたりまだ発掘しがいがありそうです。


使えるオペレーションコード

オペレーションコード
PTP_OC_GetDeviceInfo 0x1001
PTP_OC_OpenSession 0x1002
PTP_OC_CloseSession 0x1003
PTP_OC_GetStorageIDs 0x1004
PTP_OC_GetStorageInfo 0x1005
PTP_OC_GetNumObjects 0x1006
PTP_OC_GetObjectHandles 0x1007
PTP_OC_GetObjectInfo 0x1008
PTP_OC_GetObject 0x1009
PTP_OC_GetThumb 0x100A
PTP_OC_DeleteObject 0x100B
PTP_OC_InitiateCapture 0x100E
PTP_OC_GetDevicePropDesc 0x1014
PTP_OC_GetDevicePropValue 0x1015
PTP_OC_SetDevicePropValue 0x1016
???? 0x1022

使用されるイベントコード

イベントコード
PTP_EC_ObjectAdded 0x4002
PTP_EC_DevicePropChanged 0x4006
PTP_EC_StoreFull 0x400a
PTP_EC_CaptureComplete 0x400d

使用できるデバイスプロパティコード

バイスプロパティコード
PTP_DPC_BatteryLevel 0x5001
PTP_DPC_ExposureBiasCompensation 0x5010
PTP_DPC_DateTime 0x5011
PTP_DPC_CaptureDelay 0x5012
???? 0x502C
???? 0xD006
???? 0xD801
???? 0xD802
???? 0xD803
???? 0xD805
???? 0xD806
???? 0xD807

可能なキャプチャフォーマット

キャプチャフォーマット
PTP_OFC_Association 0x3001
PTP_OFC_EXIF_JPEG 0x3801

(と出ているものの、InitiateCaptureで0(デバイスまかせ)以外を指定するとParameterNotSupportedになりました)

可能なイメージフォーマット

イメージフォーマット
PTP_OFC_Association 0x3001
PTP_OFC_EXIF_JPEG 0x3801

sinf()の結果がVCとclang/gccで違う件について


VCとclang/gccで単精度浮動小数点数での三角関数の結果が微妙に違う。

まず以下のコード。

main.c

#define my_sinf(x) (float)sin((double)(x))

int main() {
    int tmp;
    float val;
    float ret_sin, ret_sinf, ret_my_sinf;

    tmp = 0x3e75565e;
    memcpy(&val, &tmp, 4);
    
    ret_sin = sin((double)val);//倍精度用
    ret_sinf = sinf(val);//単精度
    ret_my_sinf = my_sinf(val);//自作単精度

    printf("ret_sin : 0x%08x\n", *((int*)&ret_sin));
    printf("ret_sinf : 0x%08x\n", *((int*)&ret_sinf));
    printf("ret_my_sinf : 0x%08x\n", *((int*)&ret_my_sinf));

    return 0;
}

VCの結果

> cl.exe main.c
ret_sin : 0x3e72ff38
ret_sinf : 0x3e72ff38 <--- 最下位桁が8
ret_my_sinf : 0x3e72ff38

clang/gccの結果

$ clang --std=c99 -o test main.c
ret_sin : 0x3e72ff38
ret_sinf : 0x3e72ff39 <--- 最下位桁が9
ret_my_sinf : 0x3e72ff38


この結果の違いから、
VCにおいては sinf() は my_sinf() と同様に
内部で倍精度浮動小数点数で計算したあとで単精度にして返しているのに対し、
clang/gccでは、内部も単精度浮動小数点数で計算しているであろうというのがわかる。
(手元にある別のlibcのソースを見たら内部が単精度だったし(適当))


C言語の仕様としては計算の精度までは指定していないためこれは仕方が無いのですが、
同じ計算なのにビルド環境によって結果が違うというのは
まじめな計算をしている時には厄介なバグになるので注意ですね。実際死んだ。

こういうとき、いつもなら気晴らしにVCを叩くところだけど
さすがに精度がいいほうを叩くのはなんか気が引けるっすね

GASで記述したWebアプリ上で画面遷移

仕事じゃなく、さらに維持費とかが考えに無い知人からの
「こういうの欲しい。」
といった相談は、作ったら相手に投げっぱなしでおkな
Google SitesGoogle App Script (GAS)で対応する事が多い。


一応、この2つについて少し紹介すると
GoogleSitesはWYSIWYGでWebページを作れるサービスで、
GASはGoogleDriveのサーバー上で動くjavascriptのこと。
GASはGoogleDrive上でスプレッドシートなんかを処理するのに利用される事もあるけど、今回の話しはGASを使ってGoogleDrive上で動くWebアプリケーションを記述する機能についてのお話。


で、今回はちょっと複雑な入力を処理する事になったので、
GAS内で画面遷移を作る必要があった。

この場合の画面遷移とは、
同一URL内で、(ページ遷移をせずに)UIを切り替える事。

例えば、最初の画面から"次へ"ボタンを押すと
次となる画面が表示されるような形のことだ。

まず、GASにはUiInstanceというUI(ボタンやラベル、テキストボックス)
を保持するルートとなるオブジェクトがあり、全てのUIは
このオブジェクトから生成され、UiInstance自身にaddされることで
表示される画面を構成できる。

こんな感じ

//リクエスト
function doGet() {
    var app = UiApp.createApplication(); //UiInstance作成
    app.add(app.createButton("hoge")); //ボタン追加
    return app; //hogeボタンのある画面が表示される
}


画面をルートから入れ替えるにはUiInstance単位で操作すればいいのでは?
つまり新しくUiInstanceを作成してセットすればいいというのが筋というか、
そういう感じだろう!
と思ってリファレンスを調べていたけど全く手応えが無い。

そしてこの方法ではエラーが発生。

function eventHandler(e) {
    var app = UiApp.getActiveApplication();//既存の画面を取得
    /* ...  */
    var new_app = UiApp.createApplication();//新しい画面を作成
    /* ...  */
    //  ヤバげなerror!
}

現状のGASでは複数のUiInstanceを作成する事が想定されていない様子。


ということで、
画面遷移があるにもかかわらず一つのUiInstanceを使い回すことになると、
画面を移動するたびにUIを全て削除して追加して...
といった冗長なコードを書かないとならなくなる。


海外のフォーラムでもUiInstanceの切り替えができないことについて
相談があったりしたのでそれを読んだ感じ、
画面全体を占めるPanelを作って
それにUIを載せていけばPanel単位で管理出来るよというアイデアがあった。


と言う事で以下に画面遷移のサンプル。

  • 一つの画面に対してPanel自体を生成、破棄して実現
  • 画面の切り替えは一つのPanelを継続して使用する。clear()して新しい画面のUIをそのPanelに改めて構築することで実現

という実装の2パターン。


サンプルコード(GoogleDrive)
サンプルコード(Gist)

サンプル実行


ちなみにUiInstance自身にはclear()が無いのがキモで、
UIをひとまとめに管理するには
なにかしらのコンテナとなるUIが必要ということになっています。
Gridでもいいんだけど今回はそれがPanelだということでした。

無い!無い!(git)

共有リポジトリからgit cloneしてきたら作業ファイルが無い。

.git以下はちゃんと中身が詰まっているのに何故?
というのが今日やられたところ。


.gitはあるからrevertしてみようとか、pullするといいんじゃね?とかやってみたけどgitさんはエラーを返すばかり。

ギブアップして共有リポジトリに問題が無いかlogを見ようとしたら、原因へのとっかかりを得る事が出来た。

$ cd shared_repo.git
$ git log
fatal: bad default revision 'HEAD'
$ cat HEAD
ref: refs/heads/master

多分、共有リポジトリの作り方が悪かったんだ。
(と、そのときは考えた)

この共有リポジトリは大元の作業ローカルから

$ git clone --bare /hoge/huga shared_repo.git

してきたもの。

最初に空の共有リポジトリを作って、ローカルにcloneして変更を加えていったものではなかった。


そして、元作業ローカルに移動してHEADを確認ついでにbranchを確認したときに根本の問題が判明した。

$ git branch
* (no branch)

oh... masterブランチが無い(何故?)

今まで延々とno branchにコミットして今に至るらしい。

no branchの状態を是正すればきっとうまくいく。

$ git branch master
$ git branch
* (no branch)
  master
$ git checkout master
$ git branch
* master

と、(当然ながら)masterを作って切り替えるだけでいままでのlogも全てmasterに移行できた。


どういう経緯でno branchのみとなったのかは不明でした。


このあと、共有リポジトリを作り直し、そこから再び最初の目的であったcloneをしたら作業ファイルがちゃんと展開されていました。


おしまいっと。

昨日twitterで日中に流れていた話題。

インクリメントは前置きの方がパフォーマンスが良くなるというアレをjavascriptの世界で議論していた。
話題としては、「前置きの方が早くなるよね」「でもJITで最適化が効きそうだよね」としつつ、前置きが無難。という結論だったかんじ。


夜になるとvolatileの話題のほうが盛り上がって忘れ去られていって残念だったけど

JavaScriptでインクリメントは変数の前、後ろどちらの方が高速?
こちらでベンチマークが紹介されていました。

実測すると・・・

Chrome

Safari

iPhone


となり、意外や意外。
Chromeでは後置きの方が圧倒的に早いという結果になった!
(*原因は後述)


前置きが他のブラウザより酷く遅いというわけではなく、
後置きがめちゃくちゃ早い!

ここまで差が出るのは

javascriptエンジンがどんな最適化をしている影響か気になるよね。
v8の生成する実行コードを見たくなった。


それにあたってまずエンジン単体での
スクリプトの実行時間(とコンパイル時間)を計る。


ここを参考にlibv8を導入して、
Hello,World!を少しいじってclock()関数で計測。

#include <iostream>

#include <v8.h>


static const std::string preparation_code = "var data = new Array(500 * 1000);var index = data.length - 1;while (index--) data[index] = index;";

static const std::string prefix_increment = "for (var index = 0, len = data.length; index < len; ++index) {data[index] = data[index] * 2;}";
static const std::string suffix_increment = "for (var index = 0, len = data.length; index < len; index++) {data[index] = data[index] * 2;}";

void run_preparation(void);
void run_prefix(void);
void run_suffix(void);
void run_script(const std::string &test_name, const std::string &js);


int main (int argc, const char * argv[])
{
    run_prefix();
    run_suffix();
    return 0;
}

void run_preparation() {
    
    v8::HandleScope handle_scope;
    
    //preparation
    v8::Handle<v8::String> source = v8::String::New(preparation_code.c_str());
    v8::Handle<v8::Script> script = v8::Script::Compile(source);
    v8::Handle<v8::Value> result = script->Run();
}

void run_prefix() {
    run_script("prefix-test", prefix_increment);
}


void run_suffix() {
    run_script("suffix-test", suffix_increment);
}

void run_script(const std::string &test_name, const std::string &js) {
    
    clock_t start, end;
    
    v8::HandleScope handle_scope;
    
    v8::Persistent<v8::Context> context = v8::Context::New();
    
    context = v8::Context::New();
    
    v8::Context::Scope context_scope(context);
    
    //preparation
    run_preparation();
    
    //increment
    v8::Handle<v8::String> source = v8::String::New(js.c_str());
        
    start = clock();
    v8::Handle<v8::Script> script = v8::Script::Compile(source);
    end = clock();
    std::cout << test_name << " : compile = " << end - start << std::endl;
    
    start = clock();
    v8::Handle<v8::Value> result = script->Run();
    end = clock();
    std::cout << test_name << " : execute = " << end - start << std::endl;
    
    //dispose
    context.Dispose();
}

結果

環境はMacBookAir(i7) / v8(リビジョン: 10833)

500*1000を100回試行して平均をとる。
途中、後置きと前置きの実行順を入れ替える。

prefix-test : compile = 95
prefix-test : execute = 11551
suffix-test : compile = 93
suffix-test : execute = 12158

あれぇ?だいたいこんな感じで有意差は微妙という結果になった。
前置きの方が微妙に早いかな?
ちなみにindex=index+1も試したけどこちらも有意差は無かったです。


ちなみに以下の傾向があった
1)
前置き、後ろ置き共に
先に実行した場合は後で実行する場合より10%程度遅くなる。
(別のコンテキスト使っているのに・・・メモリブロック確保とかしているのかな?)

2)
コンテキストを継続して
初期化ー>コンパイルー>実行ー>実行・・・
の形で実行を複数回行うと2回目以降の実行時間が倍くらいになった。


ここで、Chrome側のJITのやり方に原因があるのか、
Chromeのv8って仕様が違うバージョン?と悩んだところで。

ベンチの実装により差が出ているぽい

らしいです。
JSでi++と++iどっちが速い?

ええ、ベンチマークがどんな挙動をしているのか
調べておくのは基本ですね:p


それでも、このケースでChromeだけ後置きが早くなる理由は気になるところです。