Hatena::ブログ(Diary)

hkobの覚え書き

2016-04-27

haml による Web サイトの構築

| 23:10


訳あって CMS などを使わない Web サイトの構築をすることになった。とはいえ、今さら html の直書きなんてやってられないということで、haml で記述する簡易システムを作った。

とりあえず、haml を使えるようにということで Gemfile を書く。

source 'http://rubygems.org'

gem 'haml'

hamlgem は bundler によって vendor/bundle にインストールする(gem install bundler でインストールされていることが前提)。

bundle install --path vendor/bundle

オリジナルの haml と自動生成される html はそれぞれ contents/haml, contents/html に書かれるものとし、事前にこれらのディレクトリを作っておく。

mkdir -p contents/h{a,t}ml

ほとんどのページはタイトル以外はヘッダ情報などは変わらないので、用意する haml は 1 行目にタイトルを記述し、2行目以降に内容を記載する。contents/haml/index.haml の例はこんな感じ。haml なので閉じタグか必要ないこと、Rubyプログラムが内部に書けること、テーブルクラスのデフォルト値などを予め変数で設定しておけることなどができ、html を直接書くよりも便利にサイト管理ができる。

小林研究室 (東京都立産業技術高等専門学校 ものづくり工学科 電気電子工学コース)
.jumbotron
  %h1 小林研究室
  %p
    これはテストページです
%table{ class: default_table_class }
  %thead
    %tr
      %th x
      %th x*x
  %tbody
    - (1..10).each do |i|
      %tr
        %td= i
        %td= i*i

この部分 haml を入力として、正式な haml ファイルを生成する makeHaml.rb という ruby スクリプトを書く。ヘッダには Bootswatch の CSSjQuery と Bootstrap の Javascript のための CDN が記載されている。body の頭に今後さまざまな定数を用意する予定である(今は default_table_class だけ定義している)。また、haml なので読み込んだ部分 haml のインデント調整だけはわすれないようにする。

#! /usr/bin/env ruby
if ARGV.length == 1
  open(ARGV.first) do |f|
    title = f.gets
    print <<"EOL"
!!!
%html
  %head
    %meta{charset: 'UTF-8'}
    %link{href: "https://maxcdn.bootstrapcdn.com/bootswatch/3.3.6/flatly/bootstrap.min.css", rel: "stylesheet", integrity: "sha384-XYCjB+hFAjSbgf9yKUgbysEjaVLOXhCgATTEBpCqT1R3jvG5LGRAK5ZIyRbH5vpX", crossorigin: "anonymous"}
    %script{src: "https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"}
    %script{src: "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js", integrity: "sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS", crossorigin:"anonymous"}
    %title #{ title }
  %body
    .container-fluid
      - default_table_class = "table table-bordered table-striped table-hover"
EOL
    for line in f
      print "      #{line}"
    end
  end
end

これらの部品ができてしまえば、該当する haml から html を自動生成するだけである。存在する haml ファイルの中から更新されたものだけを html に変換するように Rakefile を記載する。Makefile と違って動的に依存関係を作成できるので、こんな簡単に書けてしまう。

srcs = Dir::glob('contents/**/*.haml')
dst_hash = srcs.map { |f| [ f, f.gsub(/haml/, 'html') ] }.to_h

dst_hash.each do |k, v|
  desc v
  file v => k do |t|
    exec = "./makeHaml.rb #{k} | bundle exec haml -s #{ v }"
    sh exec
  end
end

task default: dst_hash.values

あとは、このディレクトリで以下のコマンドを実行するだけである。haml ディレクトリを操作し、前回から更新されたものだけが html に変換される。

rake

ローカルで変更を確認したら、git で親リポジトリに push する。親リポジトリでは .git/hooks/post-update を構成しておき、該当のサーバhtml ファイル群を rsync で同期する。

以上が今日 2 時間程度で作ったシステム。このシステムのおかげでコンテンツを作るのは楽になったが、中身を考えるのは時間がかかりそうだ。

トラックバック - http://d.hatena.ne.jp/hkob/20160427

2015-10-12

El Capitan へのアップデート

| 06:14

LaTeX 環境が整ったということで、El Capitan へのアップデートを行った。これまでは細かいインストール手順を書いてきたが、ansible で環境を整えるようになったため、全消し & 全インストールで十分であった。それでも何らかの参考になればとメモ書きを公開する。

  • /usr/local というフォルダは残しつつ、中身は基本的に消す。今入っているものは homebrew と MacTeX と homebrew-cask なので、これらを基本的にアンインストールする。以下はその記録
  • デフォルトで /usr/local/bin/fish を使っているので、cash -s /bin/zshzsh に戻す
  • brew cask を基本的に消す。消せないものは手動で
brew cask uninstall `brew cask list`
  • 次に brew を基本的に消す。依存関係で消せないものがあるので何度かやる
brew uninstall `brew list`
  • ここまでやって残っている /usr/local のファイルを確認し、基本的に必要なしと判断できたので全部消す
  • /usr/local が居ることを確認
ls -ld /usr/local
drwxr-xr-x  7 hkob  wheel  238 10 10 01:00 /usr/local
rm -rf /usr/local/.git
  • 今回は全部削除しているので、ansible は一括では実行せず task ごとに処理を分割する。
  • まず GUI の cask をまずインストールする
cd ansible_homebrew
ansible-playbook -i hosts site.yml -t cask
      - aquaterm          # gnuplot で使用
      - atom                  # Chromium ベースのテキストエディタ
      - cd-to                 # 現在のフォルダでターミナルを開く
      #- clamxav               # ウイルス対策ツール
      #- drobo-dashboard       # Drobo の管理ツール(研究室でのみインストール)
      - dropbox               # クラウドファイル共有ソフト
      - findings              # 研究用ノートツール
      - firefox               # Web ブラウザmai
      - github-desktop        # github 管理ツール
      - google-chrome         # Web ブラウザ
      - grandperspective      # ファイル容量の GUI での確認ツール
      - kobito                # Qiita 用 MarkDown エディタ
      - letterfix             # 文字化け送信を防ぐ
      - libreoffice           # Libre Office
      - logisim               # 論理回路シミュレータ
      - mactex                # Mac 上の TeX システム (TexLive ベース)
      - mailbox               # 受信メール 0 を目指す MUA
      - maczip4win            # 文字化けさせないメール
      - mysqlworkbench        # ER 図記述ツール
      - omnifocus             # GTD ツール (有料)
      - omnigraffle           # ベクトル系ドローツール (有料)
      - omniplan              # プロジェクトマネージメントツール (有料)
      - osxfuse               # NTFS にアクセスするための機能拡張
      - pandoc                # Markup 言語の相互変換ツール
      - papers                # 論文管理ツール
      - phantoms           # ヘッドレス web ブラウザ
      - qlstephen             # QuickLook text plugin
      - skim                  # PDF previewer
      - sourcetree            # Git 管理ツール
      - sshfs                 # ssh 接続先をファイルシステムで接続するツール(要 OSX FUSE)
      #- unity-web-player      # ブラウザ上で Unity を描画する機能拡張
      - vlc                   # 様々な動画を閲覧できるツール
      - vmware-fusion         # 仮想環境ツール (有料)
      - xmind                 # マインドマップツール
      - xquartz               # X11 環境
cd ../ansible_mactex
ansible-playbook -i hosts site.yml -K
cd ../ansible_homebrew
ansible-playbook -i hosts site.yml -t homebrew
      - { name: readline }    # ヒストリのライブラリ
      - { name: openssl }     # 暗号化ライブラリ
      - { name: jxrlib, install_options: 'HEAD' }      # JPEG XR ライブラリ
      - { name: libtiff }     # TIFF 画像ライブラリ
      - { name: libpng }      # PNG 画像ライブラリ
      - { name: fish }        # ユーザフレンドリなシェル
      - { name: fizsh }       # ユーザフレンドリなzsh
      - { name: rbenv }       # ホームディレクトリに様々な ruby 環境を構築
      - { name: ruby-build }  # rbenv 用の build tool
      - { name: gnuplot, install_options: 'with-aquaterm,latex,pdf' } # グラフ描画ソフトウェア(要 AquaTerm, MacTeX)
      # ↑ MacTeX インストール直後は path が通ってなくてコンパイルエラーとなる。ターミナルを閉じて再度 ansible するとインストールできる
      # ↑ libpng でエラーになる場合がある。この場合は一度止めて brew link libpng する
      - { name: ghostscript, install_options: 'with-djvu' }   # Postscript ビューア
      - { name: imagemagick, install_options: 'enable-hdri,with-jp2,with-openexr,with-liblqr,with-webp,with-libtiff' }  # 画像変換ツール
      - { name: graphviz }    # ブロック図描画ツール
      - { name: gettext }     # 国際化ライブラリ
      - { name: gibo }        # .gitignore 生成ツール
      - { name: nkf }         # 漢字コード変換ツール
      - { name: convmv }      # 文字化けしたファイル名を修正
      - { name: wget }        # http データ取得ツール
      - { name: jenkins }     # 継続的インテグレーションシステム
      - { name: qt4 }         # GUI ツールキット
      - { name: postgresql }  # データベースサーバ
      - { name: tmux }        # 接続を保持できるターミナル
      - { name: tmux-mem-cpu-load } # tmux 上のステータス表示
      - { name: git-flow }    # 開発用リポジトリ管理ツール
      - { name: node }        # node.js
      - { name: macvim, install_options: 'with-cscope,with-lua,override-system-vim' }      # システムの vim の差し替え
      - { name: gtk+, install_options: 'with-jasper,with-quartz-relocation' }        # GIMP Toolkit
      - { name: apple-gcc42 } # apple gcc42
  • 今回はまったのは phantoms。こちらは homebrew を諦めて cask にした。
  • 次に ruby を rbenv で実行する。最新版が 2.2.3 なので、roles/ruby-build/vars/mail.yml 内の install_version を 2.2.3 とする。
ansible-playbook -i hosts site.yml -t ruby
ansible-playbook -i hosts site.yml -t atom
トラックバック - http://d.hatena.ne.jp/hkob/20151012

2015-08-19

4. loop 関係のリファクタリングと画像処理関数の作成

| 23:20


reset の実装 (PixelIterator)

当初プログラムを作成している時には、MonoMatrix が主役の予定でいたが、いろいろと作っている内に PixelIterator の方が主役になってきた。画像を読み込んできたりしたときには MonoMatrix として読み込むが、基本的な処理は PixelIterator で作成していく。

昨日の段階で setPos() を何度か呼んでいたので、専用の reset() メソッドを作成した。せっかくなので method chain ができるように自分自身を返すようにした。

    public func reset() -> PixelIterator {
        setPos()
        return self
    }

loop 系の見直しについて

昨日の段階で作成した loop は IteratorSet を closure に渡していた。元々は IteratorSet 内で MonoMatrix から pixelIterator を作成するつもりでいたので、内部変数にアクセスする必要があると考えていたためである。先ほど記述したように pixelIterator をメインに使うようになると、IteratorSet は表にでる必要がないことに気がついた。このため、これまでのメソッドをすべて WithIs というメソッドに変えて、IteratorSet を渡さないものをこれまでのメソッド名にする。

testLoop (ImageLibraryTests)

loop のテストは以下のようになった。loopWithIs 版と同じテストを loop 版でも実行している。先ほど書いた reset() のおかげで loopWithIs 版も1行短くなっている。IteratorSet が使えないので、loop 版は零値のテストのみを実施している。パラメータが渡されないので、closure 外の self から pit1! をキャプチャしている。この時 self は強参照になってしまい、ARC がうまく働かずにメモリリークしてしまうので [ unowned self ] として ARC に所有しないことを宣言する必要がある。

    func testLoop() {
        IteratorSet(pits: [ pit1! ]).loopWithIs { ( set: IteratorSet ) -> () in
            set.setDoubleValue(0.0, at: 0)
        }
        XCTAssertEqual(mmat1!.doubleBuffer, [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ])
        //
        IteratorSet(pits: [ pit1!.reset() ]).loopWithIs {
            $0.setDoubleValue(Double($0.order), at: 0)
        }
        XCTAssertEqual(mmat1!.doubleBuffer, [ 0.0, 1.0, 2.0, 3.0, 4.0, 5.0 ])
        // without IteratorSet
        IteratorSet(pits: [ pit1!.reset() ]).loop { [ unowned self ] in self.pit1!.doubleValue = 0 }
        XCTAssertEqual(mmat1!.doubleBuffer, [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ])
    }

loopWithIs と loop の実装 (PixelIterator)

loopWithIs は前回のメソッド名を変更しただけである。loop の方は closure に IteratorSet を渡さなくしただけである。

    public func loopWithIs(closure: (IteratorSet) -> ()) {
        while !finished {
            closure(self)
            nextPos()
        }
    }
    
    public func loop(closure: () -> ()) {
        while !finished {
            closure()
            nextPos()
        }
    }

testLoopAndSet (ImageLibraryTests)

loopWithIsAndSet のテストは reset() が内部に入っただけで同じである。loopAndSet の方は同様に零値のセットのみしかしていないが、圧倒的に簡単に記述できているのがわかる。

    func testLoopAndSet() {
        IteratorSet(pits: [ pit1! ]).loopWithIsAndSet { set in 0.0 }
        XCTAssertEqual(mmat1!.doubleBuffer, [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ])
        IteratorSet(pits: [ pit1!.reset() ]).loopWithIsAndSet { Double($0.order) }
        XCTAssertEqual(mmat1!.doubleBuffer, [ 0.0, 1.0, 2.0, 3.0, 4.0, 5.0 ])
        // without IteratorSet
        IteratorSet(pits: [ pit1!.reset() ]).loopAndSet { 0.0 }
    }

loopWithIsAndSet と loopAndSet の実装 (PixelIterator)

こちらも loopWithIsAndSet は名前を変えただけ、loopAndSet は IteratorSet を渡さなくなっただけである。

    public func loopWithIsAndSet(closure: (IteratorSet) -> (Double)) {
        while !finished {
            setDoubleValue(closure(self), at: 0)
            nextPos()
        }
    }
    
    public func loopAndSet(closure: () -> (Double)) {
        while !finished {
            setDoubleValue(closure(), at: 0)
            nextPos()
        }
    }

testReduce (ImageLibraryTests)

reduceWithIs のテストも reset() が内部に入っただけで同じである。loopAndSet の方は closure には更新すべき値だけが渡されるので、[ unowned self ] を使って値の更新を行う。

    func testReduce() {
        let product = IteratorSet(pits: [ pit1! ]).reduceWithIs(value: 1.0) { $0 * $1.doubleValue(0)! }
        XCTAssertEqual(product, 540.0)
        let sum = IteratorSet(pits: [ pit1!.reset() ]).reduceWithIs { $0 + $1.doubleValue(0)! }
        XCTAssertEqual(sum, 23.0)
        // without IteratorSet
        let product2 = IteratorSet(pits: [ pit1!.reset() ]).reduce(value: 1.0) { [unowned self] in $0 * self.pit1!.doubleValue }
        XCTAssertEqual(product, 540.0)
        let sum2 = IteratorSet(pits: [ pit1!.reset() ]).reduce { [unowned self] in $0 * self.pit1!.doubleValue }
    }

reduceWithIs, reduce の実装 (PixelIterator)

こちらも reduceWithIs は名前を変えただけ、reduce は IteratorSet を渡さなくなっただけである。closure は更新すべき値だけを渡すようになった。

    public func reduceWithIs(value : Double = 0.0, closure: (Double, IteratorSet) -> (Double)) -> Double {
        var ans : Double = value
        while !finished {
            ans = closure(ans, self)
            nextPos()
        }
        return ans
    }

    public func reduce(value : Double = 0.0, closure: (Double) -> (Double)) -> Double {
        var ans : Double = value
        while !finished {
            ans = closure(ans)
            nextPos()
        }
        return ans
    }

testCheckSameSizePI (ImageLibraryTests)

以前、ImageMatrix プロトコルを受け付ける MonoMatrix と ColorMatrix に対して、大きさを比較する checkSameSize を実装していた。PixelIterator が主役になったのでこちらにも checkSameSize を実装する。テストは MonoMatrix のものを PixelIterator に差し替えただけである。

    func testCheckSameSizePI() {
        var mmat2 = MonoMatrix(rnum: 2, cnum: 3)
        var pcorrect1 = mmat2.pixelIterator()
        XCTAssertTrue(checkSameSize(pit1!, pcorrect1))
        var mmat3 = MonoMatrix(rnum: 8, cnum: 8)
        var pcorrect2 = mmat3.pixelIterator(wrnum: 2, wcnum: 3, offsetr: 4, offsetc: 5)
        XCTAssertTrue(checkSameSize(pit1!, pcorrect1, pcorrect2))
        var pdiffer = mmat3.pixelIterator()
        XCTAssertFalse(checkSameSize(pit1!, pdiffer))
        pdiffer = mmat3.pixelIterator(wrnum: 1, wcnum: 3, offsetr: 3, offsetc: 4)
        XCTAssertFalse(checkSameSize(pit1!, pdiffer))
        pdiffer = mmat2.pixelIterator(wrnum: 1, wcnum: 2)
        XCTAssertFalse(checkSameSize(pit1!, pdiffer))
        XCTAssertFalse(checkSameSize(pit1!, pcorrect1, pcorrect2, pdiffer))
    }

checkSameSizePI の実装 (PixelIterator)

実装は PixelIterator.swift に記述するが、MonoMatrix 版と同様グローバルスコープでの関数とした。Generics は PixelIterator 型で絞り込んでいるので、ImageMatrix Protocol のものとはかち合わない。内容は rnum → wrnum、cnum → wcnum になっているだけで同じである。

public func checkSameSize<T:PixelIterator>(pits: T ...) -> Bool {
    let r = pits[0].wrnum
    let c = pits[0].wcnum
    for p in pits {
        if r != p.wrnum || c != p.wcnum {
            return false
        }
    }
    return true
}

testEqual (ImageLibraryTests)

テストを書きやすくするために == で PixelIterator を比較できるようにしたい。こうなれば、XCTAssertEqual や XCTAssertNotEqual で直接比較が可能になる。この場合のテストは以下のようになる。

    func testEqual() {
        var mmat2 = MonoMatrix(doubleBuffer: [ 3.0, 1.0, 4.0, 1.0, 5.0, 9.0], rnum: 2, cnum: 3)
        var pit2 = mmat2.pixelIterator()
        XCTAssertEqual(pit1!, pit2)
        XCTAssertNotEqual(pit1!, mmat2.pixelIterator(wrnum: 2, wcnum: 2))
        pit2.reset().doubleValue = 4.0
        XCTAssertNotEqual(pit1!, pit2)
        pit2.reset().doubleValue = 3.0
        XCTAssertEqual(pit1!, pit2)
    }

Equatable プロトコルと == の実装 (PixelIterator)

== が使えるようにするには、まずクラスが Equatable プロトコルを受けなければならない。このためにはクラスの先頭を以下のように書き換える。

public class PixelIterator : Equatable {

== は先ほど修正した loop を使えば簡単にかける。 演算子の追加なのでPixelIterator.swift のグローバルスコープに記述する

public func == (lhs : PixelIterator, rhs: PixelIterator) -> Bool {
    var ans : Bool = false
    if checkSameSize(lhs, rhs) {
        ans = true
        IteratorSet(pits: [ lhs.reset(), rhs.reset() ]).loop { [unowned lhs, rhs ] in
            if ans && abs(lhs.doubleValue - rhs.doubleValue) > 1e-9 {
                ans = false
            }
        }
    }
    return ans
}

Equatable プロトコルと == の実装 (MonoMatrix)

ついでに MonoMatrix にも == を追加する。まず Equatable プロトコルを受け付ける。

public class MonoMatrix : ImageMatrix, Equatable {

実装は PixelIterator を作成してしまって、それを比較すればよい。これも MonoMatrix.swift のグローバルスコープに記述する。

public func == (lhs: MonoMatrix, rhs: MonoMatrix) -> Bool {
    return lhs.pixelIterator() == rhs.pixelIterator()
}

簡単な画像処理のテスト (ImageLibraryTests)

実際に loopAndSet を使って fill, addConst, mulconst, addSelf, subSelf, mulSelf, divSelf などを作ってみる。テストはまとめて記載する。

    func testFill() {
        pit1!.fill(255.0)
        XCTAssertEqual(mmat1!, MonoMatrix(doubleBuffer: [Double](count: 6, repeatedValue: 255.0), rnum: 2, cnum: 3))
        pit1!.reset().fill(0.0)
        XCTAssertEqual(mmat1!, MonoMatrix(rnum: 2, cnum: 3))
    }
    
    func testAddConstMulConst() {
        pit1!.addConst(5.0)
        XCTAssertEqual(mmat1!, MonoMatrix(doubleBuffer: [ 8.0, 6.0, 9.0, 6.0, 10.0, 14.0 ], rnum: 2, cnum: 3))
        pit1!.reset().mulConst(2.5)
        XCTAssertEqual(mmat1!, MonoMatrix(doubleBuffer: [ 20.0, 15.0, 22.5, 15.0, 25.0, 35.0 ], rnum: 2, cnum: 3))
    }
    
    func testAddSubMulDivSelf() {
        var mmat2 = MonoMatrix(doubleBuffer: [ 2.0, 9.0, 4.0, 7.0, 5.0, 3.0 ], rnum: 2, cnum: 3)
        var pit2 = mmat2.pixelIterator()
        pit1!.addSelf(pit2)
        XCTAssertEqual(mmat1!, MonoMatrix(doubleBuffer: [ 5.0, 10.0, 8.0, 8.0, 10.0, 12.0 ], rnum: 2, cnum: 3))
        pit1!.subSelf(pit2)
        XCTAssertEqual(mmat1!, MonoMatrix(doubleBuffer: [ 3.0, 1.0, 4.0, 1.0, 5.0, 9.0 ], rnum: 2, cnum: 3))
        pit1!.mulSelf(pit2)
        XCTAssertEqual(mmat1!, MonoMatrix(doubleBuffer: [ 6.0, 9.0, 16.0, 7.0, 25.0, 27.0 ], rnum: 2, cnum: 3))
        pit1!.divSelf(pit2)
        XCTAssertEqual(mmat1!, MonoMatrix(doubleBuffer: [ 3.0, 1.0, 4.0, 1.0, 5.0, 9.0 ], rnum: 2, cnum: 3))
    }

上記メソッドの実装 (PixelIterator)

基本的にピクセルごとの計算なので loopAndSet であっという間にかけてしまった。これらのメソッドは method chain されることも期待して self を返すようにしている。このとき呼び出し側でいちいち reset() するのは面倒なので、return するときに reset() するように決めた。引数で渡されるものがある場合にはそちらも reset() した。

    public func fill(value: Double) -> PixelIterator {
        IteratorSet(pits: [ self ]).loopAndSet { value }
        return self.reset()
    }

    public func addConst(value: Double) -> PixelIterator {
        IteratorSet(pits: [ self ]).loopAndSet { [ unowned self ] in self.doubleValue + value }
        return self.reset()
    }
    
    public func mulConst(value: Double) -> PixelIterator {
        IteratorSet(pits: [ self ]).loopAndSet { [ unowned self ] in self.doubleValue * value }
        return self.reset()
    }
    
    public func addSelf(other: PixelIterator) -> PixelIterator {
        IteratorSet(pits: [ self, other ]).loopAndSet { [ unowned self, other ] in self.doubleValue + other.doubleValue }
        other.reset()
        return self.reset()
    }
    
    public func subSelf(other: PixelIterator) -> PixelIterator {
        IteratorSet(pits: [ self, other ]).loopAndSet { [ unowned self, other ] in self.doubleValue - other.doubleValue }
        other.reset()
        return self.reset()
    }

    public func mulSelf(other: PixelIterator) -> PixelIterator {
        IteratorSet(pits: [ self, other ]).loopAndSet { [ unowned self, other ] in self.doubleValue * other.doubleValue }
        other.reset()
        return self.reset()
    }

    public func divSelf(other: PixelIterator) -> PixelIterator {
        IteratorSet(pits: [ self, other ]).loopAndSet { [ unowned self, other ] in self.doubleValue / other.doubleValue }
        other.reset()
        return self.reset()
    }

次はもう少し複雑なものを書いてみる予定。

トラックバック - http://d.hatena.ne.jp/hkob/20150819

2015-08-18

3. PlaneIterator と IteratorSet

| 17:28

testPlaneIterator (ImageLibaryTests)

これまでは MonoMatrix に対する Iterator だったが、ColorMatrix に対する PlaneIterator をテストする。これは ColorMatrix が所有する MonoMatrix の配列を一つずつスキャンするものである。特に新しい項目もないので、説明は省略

    func testPlaneIterator() {
        var mi1 = cmat1!.planeIterator()
        XCTAssertFalse(mi1.finished)
        XCTAssertEqual(mi1.nowp, 0)
        var m1 : MonoMatrix? = mi1.monoMatrix
        XCTAssertEqual(m1!.doubleBuffer, [ 3.0, 1.0, 4.0, 1.0, 5.0, 9.0 ])
        var mp1 = mi1.pixelIterator()
        XCTAssertEqual(mp1!.doubleValue, 3.0)
        mi1.nextPlane()
        XCTAssertFalse(mi1.finished)
        XCTAssertEqual(mi1.nowp, 1)
        m1 = mi1.monoMatrix
        XCTAssertEqual(m1!.doubleBuffer, [ 2.0, 6.0, 5.0, 3.0, 5.0, 8.0 ])
        mp1 = mi1.pixelIterator()
        XCTAssertEqual(mp1!.doubleValue, 2.0)
        mi1.nextPlane()
        XCTAssertFalse(mi1.finished)
        XCTAssertEqual(mi1.nowp, 2)
        m1 = mi1.monoMatrix
        XCTAssertEqual(m1!.doubleBuffer, [ 9.0, 7.0, 9.0, 3.0, 2.0, 3.0 ])
        mp1 = mi1.pixelIterator()
        XCTAssertEqual(mp1!.doubleValue, 9.0)
        mi1.nextPlane()
        XCTAssertTrue(mi1.finished)
        XCTAssertEqual(mi1.nowp, -1)
        XCTAssertTrue(mi1.pixelIterator() == nil)
        mi1.nextPlane()
        XCTAssertTrue(mi1.finished)
        XCTAssertEqual(mi1.nowp, -1)
        XCTAssertTrue(mi1.pixelIterator() == nil)
    }

planeIterator() の実装 (ColorMatrix)

ColorMatrix 内の planeIterator() の実装はこちら。特に引数はない。

    public func planeIterator() -> PlaneIterator {
        return PlaneIterator(matrix: self)
    }

PlaneIterator クラスの実装

こちらも新しい項目はないので、一括で掲示

public class PlaneIterator {
    private unowned var _matrix : ColorMatrix
    private var _nowp : Int
    
    public var nowp : Int { return _nowp }
    public var finished : Bool { return _nowp == -1 }
    public var monoMatrix : MonoMatrix? { return finished ? nil : _matrix.mmats[_nowp] }
    
    public init(matrix: ColorMatrix) {
        _matrix = matrix
        _nowp = 0
    }
    
    public func nextPlane() {
        if !finished {
            _nowp++
            if _nowp == _matrix.pnum {
                _nowp = -1
            }
        }
    }
    
    public func pixelIterator(wrnum: Int = -1, wcnum: Int = -1, offsetr: Int = -1, offsetc: Int = -1) -> PixelIterator? {
        if finished {
            return nil
        } else {
            return monoMatrix!.pixelIterator(wrnum: wrnum, wcnum: wcnum, offsetr: offsetr, offsetc: offsetc)
        }
    }
}

testIteratorSet (ImageLibraryTests)

ここまで各 Iterator を準備してきたが、画像処理ではこれらを同時に複数稼働することが多い。そこで、Iterator を一気に更新処理する IteratorSet を準備する。IteratorSet には PixelIterator, BlockIterator, PlaneIterator を 複数登録することができ、nextPos() でこれらの Iterator を一度に更新する。

IteratorSet に登録前に作られた iterator の場合には、それに直接アクセスしてもよい。ただし、今後 closure と組み合わせた時のことも考え、set からdoubleValue や set.pixelIteratorFromBlockIterator を直接呼ぶことができるようにしてみた。これらは IteratorSet に登録した順番で呼び出すものとし、範囲外の場合には nil を返す。Optional なので使用時には注意が必要である。

     func testIteratorSet() {
        var mmat2 = MonoMatrix(rnum: 8, cnum: 8)
        var pi1 = mmat1!.pixelIterator()
        var pi2 = mmat2.pixelIterator()
        var bi1 = mmat1!.blockIterator(brnum: 2, bcnum: 2, srnum: 1, scnum: 1, fixSize: true)
        var bi2 = mmat2.blockIterator(brnum: 3, bcnum: 5, srnum: 2, scnum: 4, fixSize: false)
        var mi1 = cmat1!.planeIterator()
        var set = IteratorSet(pits: [ pi1, pi2 ], bits: [ bi1, bi2 ], mits: [ mi1 ])
        XCTAssertFalse(set.finished)
        XCTAssertEqual(set.doubleValue(0)!, 3.0)
        XCTAssertEqual(set.doubleValue(1)!, 0.0)
        XCTAssertTrue(set.doubleValue(2) == nil)
        var bi1pi : PixelIterator? = set.pixelIteratorFromBlockIterator(0)
        XCTAssertEqual([ bi1pi!.wrnum, bi1pi!.wcnum, bi1pi!.nowp ], [ 2, 2, 0 ])
        var bi2pi : PixelIterator? = set.pixelIteratorFromBlockIterator(1)
        XCTAssertEqual([ bi2pi!.wrnum, bi2pi!.wcnum, bi2pi!.nowp ], [ 3, 5, 0 ])
        XCTAssertTrue(set.pixelIteratorFromBlockIterator(2) == nil)
        var mi1m1 : MonoMatrix? = set.monoMatrixFromPlaneIterator(0)
        XCTAssertEqual(mi1m1!.doubleBuffer, [ 3.0, 1.0, 4.0, 1.0, 5.0, 9.0 ])
        //
        set.nextPos()
        XCTAssertFalse(set.finished)
        XCTAssertEqual(set.doubleValue(0)!, 1.0)
        XCTAssertEqual(set.doubleValue(1)!, 0.0)
        XCTAssertTrue(set.doubleValue(2) == nil)
        bi1pi = set.pixelIteratorFromBlockIterator(0)
        XCTAssertEqual([ bi1pi!.wrnum, bi1pi!.wcnum, bi1pi!.nowp ], [ 2, 2, 1 ])
        bi2pi = set.pixelIteratorFromBlockIterator(1)
        XCTAssertEqual([ bi2pi!.wrnum, bi2pi!.wcnum, bi2pi!.nowp ], [ 3, 4, 4 ])
        XCTAssertTrue(set.pixelIteratorFromBlockIterator(2) == nil)
        mi1m1 = set.monoMatrixFromPlaneIterator(0)
        XCTAssertEqual(mi1m1!.doubleBuffer, [ 2.0, 6.0, 5.0, 3.0, 5.0, 8.0 ])
        //
        set.nextPos()
        XCTAssertTrue(set.finished)
        XCTAssertTrue(set.doubleValue(0) == nil)
        XCTAssertTrue(set.doubleValue(1) == nil)
        XCTAssertTrue(set.doubleValue(2) == nil)
        XCTAssertTrue(set.pixelIteratorFromBlockIterator(0) == nil)
        XCTAssertTrue(set.pixelIteratorFromBlockIterator(1) == nil)
        XCTAssertTrue(set.pixelIteratorFromBlockIterator(2) == nil)
        XCTAssertTrue(set.monoMatrixFromPlaneIterator(0) == nil)
    }

IteratorSet の実装

IteratorSet には各 Iterator を格納する配列を用意する。order は通番を保持する property、finished はどれか一つの iterator が終了したら true となる property である。nextPos() はすべての Iterator() を更新し、doubleValue(n) で n 番目の pixelIterator の値を取得する。setDoubleValue(value, at: n) で n 番目の pixelIterator に値をセットする。あとは、blockIterator から pixelIterator を取得するもの、planerIterator から monoMatrix を取得するものなどを用意している。実際に使う時にまたコメントする。

public class IteratorSet {
    private var _pixelIterators : [PixelIterator]
    private var _blockIterators : [BlockIterator]
    private var _planeIterators : [PlaneIterator]
    private var _order : Int
    public var pixelIterators : [PixelIterator] { return _pixelIterators }
    public var blockIterators : [BlockIterator] { return _blockIterators }
    public var planeIterators : [PlaneIterator] { return _planeIterators }
    public var order : Int { return _order }
    public var finished : Bool {
        for pit in _pixelIterators {
            if pit.finished {
                return true
            }
        }
        for bit in _blockIterators {
            if bit.finished {
                return true
            }
        }
        for mit in _planeIterators {
            if mit.finished {
                return true
            }
        }
        return false
    }
    
    public init(pits: [PixelIterator] = [], bits: [BlockIterator] = [], mits: [PlaneIterator] = []) {
        _pixelIterators = pits
        _blockIterators = bits
        _planeIterators = mits
        _order = 0
    }
    
    public func nextPos() {
        for pit in _pixelIterators {
            pit.nextPos()
        }
        for bit in _blockIterators {
            bit.nextPos()
        }
        for mit in _planeIterators {
            mit.nextPlane()
        }
        _order++
    }
    
    public func doubleValue(n: Int) -> Double? {
        return !finished && n < _pixelIterators.count ? _pixelIterators[n].doubleValue : nil
    }
    
    public func setDoubleValue(value: Double, at n: Int) {
        if !finished && n < _pixelIterators.count {
            _pixelIterators[n].doubleValue = value
        }
    }
    
    public func pixelIteratorFromBlockIterator(n: Int) -> PixelIterator? {
        return !finished && n < _blockIterators.count ? _blockIterators[n].pixelIterator() : nil
    }
    
    public func monoMatrixFromPlaneIterator(n: Int) -> MonoMatrix? {
        return !finished && n < _planeIterators.count ? _planeIterators[n].monoMatrix : nil
    }
}

testLoop (ImageLibraryTests)

役者がほぼ出揃ったので、もっともキモとなる IteratorSet の loop のテストを書く。closure を正式に書く方法と、省略記法の二つを試してみる。圧倒的に後者の方が楽なので今後は省略記法のみで記述する。ちなみに、前者はゼロ行列を作成するもの、後者は index を記述するものである。

    func testLoop() {
        IteratorSet(pits: [ mmat1!.pixelIterator() ]).loop { ( set: IteratorSet ) -> () in
            set.setDoubleValue(0.0, at: 0)
        }
        XCTAssertEqual(mmat1!.doubleBuffer, [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ])
        //
        IteratorSet(pits: [ mmat1!.pixelIterator() ]).loop {
            $0.setDoubleValue(Double($0.order), at: 0)
        }
        XCTAssertEqual(mmat1!.doubleBuffer, [ 0.0, 1.0, 2.0, 3.0, 4.0, 5.0 ])
    }

loop の実装 (IteratorSet)

ほぼお膳立てが終わったので、loop は非常に簡単。closure の書き方さえわかれば難しいところは何もない。

    public func loop(closure: (IteratorSet) -> ()) {
        while !finished {
            closure(self)
            nextPos()
        }
    }

testLoopAndSet (ImageLibraryTests)

画像処理では処理結果をあるピクセルごとに書き戻す作業も多い。pixelIterator の先頭を書き戻し専用とし、書き込み処理までをループないで処理する loopAndSet があると便利だと考えた。

実際に書いたのはこちら。先ほどと同じことを loopAndSet で書き換えてみた。pixelIterator は setPos することで何度も繰り返し使うことができるというのが新しいところか。二つ目の実装では $0 を使っているのでよいのだが、一つ目の実装は $0 に依存せず 0 を描くため、set in は省略できなかった。ただし、こういう実装はあまりないかと思う。

    func testLoopAndSet() {
        var pit = mmat1!.pixelIterator()
        IteratorSet(pits: [ pit ]).loopAndSet { set in 0.0 }
        XCTAssertEqual(mmat1!.doubleBuffer, [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ])
        pit.setPos()
        IteratorSet(pits: [ pit ]).loopAndSet { Double($0.order) }
        XCTAssertEqual(mmat1!.doubleBuffer, [ 0.0, 1.0, 2.0, 3.0, 4.0, 5.0 ])
    }

loopAndSet の実装 (IteratorSet)

loopAndSet は loop とほぼ一緒だが、closure の返り値が Double になっていて、その値を setDoubleValue で格納している。

    public func loopAndSet(closure: (IteratorSet) -> (Double)) {
        while !finished {
            setDoubleValue(closure(self), at: 0)
            nextPos()
        }
    }

testReduce (ImageLibraryTests)

loopAndSet ができるなら inject もできるんじゃないかと思ってテストを書いてみた。Ruby 使いだと inject という意識なのだが、最近は reduce の方が流行りらしい(Ruby でも 1.9 から reduce も使える)。Swift でも reduce という処理があるので、名前は合わせてみる。

reduce は関数型言語でよく使われる表現で、初期値に対して再帰的に要素を演算することで集計処理を簡単にする機能である。例えば、Ruby配列の合計値を計算する時だと、array.inject(&:+) のようにすればよい。

今回は画像処理が中心なので、AnyObject ではなく Double に特化した reduce とすることにした。テストは以下のようになった。reduce では初期値を value で設定できる。省略した場合には初期値は 0 が設定される。その後、要素ごとに closure が実行され、その返り値が reduce の内部の値になる。すべての要素に対して計算が終わった時の返り値が reduce 関数の返り値になる。ここで、最初の引数の $0 が前回までの計算値であり、$1 が IteratorSet である。このテストでは前者で要素すべての積、後者で要素すべての話を計算している。

    func testReduce() {
        var pit = mmat1!.pixelIterator()
        let product = IteratorSet(pits: [ pit ]).reduce(value: 1.0) { $0 * $1.doubleValue(0)! }
        XCTAssertEqual(product, 540.0)
        pit.setPos()
        let sum = IteratorSet(pits: [ pit ]).reduce { $0 + $1.doubleValue(0)! }
        XCTAssertEqual(sum, 23.0)
    }

reduce の実装 (IteratorSet)

reduce の実装も loopAndSet とあまり変わらない。初期値 value がデフォルト値 0 で用意されたこと、closure の引数が増えたこと、reduce 自体が返り値 Double を持つことくらいが大きな違い。

    public func reduce(value : Double = 0.0, closure: (Double, IteratorSet) -> (Double)) -> Double {
        var ans = value
        while !finished {
            ans = closure(ans, self)
            nextPos()
        }
        return ans
    }

当初 loop だけで記事を終わらせていたが、保存後に loopAndSet と reduce を思いついたので追記した。これだけあると実際の処理プログラムはかなり短く書けそうだ。今日はここまで。

トラックバック - http://d.hatena.ne.jp/hkob/20150818

2015-08-17

2. Iterator 群の作成

| 13:46

testCheckSameSize (ImageLibraryTests)

大きさの比較を行う checkSameSize をテストする。テストはこんな感じ。これまではあるオブジェクトに対して他と等しいかインスタンスメソッドで確認していたが、ジェネリックを使ってみたいので関数で書いてみる(二つのクラスに同じような内容を書きたくないし)。Objective-C の頃は二つのオブジェクトの比較だけをしていたが、可変長引数を使って複数個を一度に比較できるようにしてみた。すべての画像が同じ大きさの場合のみ true を返すことが確認できればよい。

    func testCheckSameSize() {
        var mcorrect1 = MonoMatrix(rnum: 2, cnum: 3)
        XCTAssertTrue(checkSameSize(mmat1!, mcorrect1))
        var mcorrect2 = MonoMatrix(rnum: 2, cnum: 3)
        XCTAssertTrue(checkSameSize(mmat1!, mcorrect1, mcorrect2))
        var mdiffer = MonoMatrix(rnum: 2, cnum: 2)
        XCTAssertFalse(checkSameSize(mmat1!, mdiffer))
        mdiffer = MonoMatrix(rnum: 1, cnum: 3)
        XCTAssertFalse(checkSameSize(mmat1!, mdiffer))
        mdiffer = MonoMatrix(rnum: 1, cnum: 2)
        XCTAssertFalse(checkSameSize(mmat1!, mdiffer))
        XCTAssertFalse(checkSameSize(mmat1!, mcorrect1, mcorrect2, mdiffer))
        
        var ccorrect1 = ColorMatrix(pnum: 3, rnum:2, cnum: 3, type: ColorType.RGB)
        XCTAssertTrue(checkSameSize(cmat1!, ccorrect1))
        var ccorrect2 = ColorMatrix(pnum: 3, rnum: 2, cnum: 3, type: ColorType.RGB)
        XCTAssertTrue(checkSameSize(cmat1!, ccorrect1, ccorrect2))
        var cdiffer = ColorMatrix(pnum: 3, rnum: 2, cnum: 2, type: ColorType.RGB)
        XCTAssertFalse(checkSameSize(cmat1!, cdiffer))
        cdiffer = ColorMatrix(pnum: 3, rnum: 1, cnum: 3, type: ColorType.RGB)
        XCTAssertFalse(checkSameSize(cmat1!, cdiffer))
        cdiffer = ColorMatrix(pnum: 3, rnum: 1, cnum: 2, type: ColorType.RGB)
        XCTAssertFalse(checkSameSize(cmat1!, cdiffer))
        XCTAssertFalse(checkSameSize(cmat1!, ccorrect1, ccorrect2, cdiffer))
    }

checkSameSize の実装 (ImageMatrix)

checkSameSize は引数に ImageMatrix を取るため、プロトコルを書いた ImageMatrix.m に記載した。
ImageMatrix は rnum, cnum に応答するので、関数内でキャストすることもなく使用可能である。初回だけ余計な比較があるが、余計なことをするとコードが汚くなるのでこのままにする。

public func checkSameSize<T:ImageMatrix>(mats: T ...) -> Bool {
    let r = mats[0].rnum
    let c = mats[0].cnum
    for m in mats {
        if r != m.rnum || c != m.cnum {
            return false
        }
    }
    return true
}

testDoubleValueAndSetDoubleValue (ImageLibraryTests)

MonoMatrix のみの機能として任意の位置の doubleValue を読み込むメソッドと、任意の位置に doubleValue を書き込むメソッドを追加する。テストは以下のように記述した。doubleValue, setDoubleValue 共に doubleValue であることがわかっているので、External Parameter Name は使わずに第一引数名は省略した。

    func testDoubleValueAndSetDoubleValue() {
        let data : [Double] = [ 3.0, 1.0, 4.0, 1.0, 5.0, 9.0 ]
        for i in 0 ..< 6 {
            XCTAssertEqual(mmat1!.doubleValue(i), data[i])
        }
        let data2 : [Double] = [ 2.0, 9.0, 4.0, 7.0, 5.0, 3.0 ]
        for i in 0 ..< 6 {
            mmat1!.setDoubleValue(data2[i], at: i)
        }
        XCTAssertEqual(mmat1!.doubleBuffer, data2)
    }

doubleValue と setDoubleValue の実装 (MonoMatrix)

MonoMatrix.swift に実装を記述する。setDoubleValue の方で変数名を揃えたかったので、at n のようにして引数名と変数名をわざと変えたことくらいが新しいところ。

    public func doubleValue(n: Int) -> Double {
        return _doubleBuffer[n]
    }
    
    public func setDoubleValue(value: Double, at n: Int) {
        _doubleBuffer[n] = value
    }

testPixelIterator (ImageLibraryTests)

画像処理では Pixel ごとの逐次処理や Block ごとの逐次処理が多く行われる。そこでこれらの Iterator 処理を行うクラスを設計する。PixelIterator では、MonoMatrix の全部もしくは一部を Pixel ごとに逐次処理する。PixelIterator オブジェクトは MonoMatrix に依存するため、直接コンストラクタは呼ばず、MonoMatrix のインスタンスメソッドで作成することにする。ここでは作成のみのテストを行う。

引数なしの場合には画像全体となり、逆に引数 wrnum, wcnum, offsetr, offsetc が設定された場合には画像の一部分を切り出す。これらの値は省略可能で省略時には画像のサイズや設定された値から計算して割り当てる。範囲が画像からはみ出ている場合には、はみ出ないギリギリのところの値を設定する。ここで、nowp は切り出した左上 Pixel の doubleBuffer 上の位置を示す。

    func testPixelIterator() {
        var pi1 : PixelIterator = mmat1!.pixelIterator() // 画像全体
        XCTAssertEqual([ pi1.wrnum, pi1.wcnum, pi1.nowp], [ 2, 3, 0 ])
        var pi2 : PixelIterator = mmat1!.pixelIterator(wrnum: 1, wcnum: 2) // 大きさのみ指定
        XCTAssertEqual([ pi2.wrnum, pi2.wcnum, pi2.nowp], [ 1, 2, 0 ])
        var pi3 : PixelIterator = mmat1!.pixelIterator(wrnum: 1, wcnum: 3, offsetr: 1) // 大きさと横オフセット
        XCTAssertEqual([ pi3.wrnum, pi3.wcnum, pi3.nowp], [ 1, 3, 3 ])
        var pi4 : PixelIterator = mmat1!.pixelIterator(wrnum: 1, wcnum: 2, offsetr: 1, offsetc: 1) // 大きさとオフセット
        XCTAssertEqual([ pi4.wrnum, pi4.wcnum, pi4.nowp], [ 1, 2, 4 ])
        var pi5 : PixelIterator = mmat1!.pixelIterator(offsetr: 1, offsetc: 2) // オフセットのみ
        XCTAssertEqual([ pi5.wrnum, pi5.wcnum, pi5.nowp], [ 1, 1, 5 ])
        var pi6 : PixelIterator = mmat1!.pixelIterator(offsetc: 2) // 横オフセットのみ
        XCTAssertEqual([ pi6.wrnum, pi6.wcnum, pi6.nowp], [ 2, 1, 2 ])
        var pi7 : PixelIterator = mmat1!.pixelIterator(wrnum: 5, wcnum: 5, offsetr: 5, offsetc: 5) // すべてはみ出る
        XCTAssertEqual([ pi7.wrnum, pi7.wcnum, pi7.nowp], [ 1, 1, 5 ]) // 右下 1 pixel だけ対象
    }

pixelIterator() の実装 (MonoMatrix)

pixelIterator() の実装については、PixelIterator のコンストラクタ呼び出しのみでよい。値が存在しなかった場合には -1 を渡し、初期化部で対応する。

    public func pixelIterator(wrnum: Int = -1, wcnum: Int = -1, offsetr: Int = -1, offsetc: Int = -1) -> PixelIterator {
        return PixelIterator(matrix: self, wrnum: wrnum, wcnum: wcnum, offsetr: offsetr, offsetc: offsetc)
    }

PixelIterator クラスの実装 (PixelIterator)

_wrnum, _wcnum, _offsetr, _offsetc は初期化時のみに値が設定されるので定数とする。_matrix は PixelIterator が生きている間は消えないので、unowned var とした。_nowr, _nowc は現在の Pixel 位置を保持する変数であるため、初期化時は 0 とする。

_offsetr, _offsetc については未指定時(負の値)の時には 0 とする。逆に画像サイズを超えている時には、画像の大きさ-1に設定する。_wrnum, _wcnum については未指定時(負の値)の時には画像サイズからオフセットを引いた値に設定する。値が指定されており画像サイズを越える時には超えない最大値を設定する。_nowr, _nowc は 0 とし、_nowp は _offsetr, _offsetc から計算する。

public class PixelIterator {
    private unowned var _matrix : MonoMatrix
    private let _wrnum, _wcnum : Int
    private let _offsetr, _offsetc : Int
    private var _nowr, _nowc, _nowp: Int
    
    public var wrnum :Int { return _wrnum }
    public var wcnum :Int { return _wcnum }
    public var nowr : Int { return _nowr }
    public var nowc : Int { return _nowc }
    public var nowp : Int { return _nowp }

    public init(matrix: MonoMatrix, wrnum: Int, wcnum: Int, offsetr: Int, offsetc: Int) {
        _matrix = matrix
        _offsetr = offsetr < 0 ? 0 : min(offsetr, _matrix.rnum - 1)
        _offsetc = offsetc < 0 ? 0 : min(offsetc, _matrix.cnum - 1)
        let tmpr = _matrix.rnum - _offsetr
        _wrnum = wrnum < 0 ? tmpr : min(wrnum, tmpr)
        let tmpc = _matrix.cnum - _offsetc
        _wcnum = wcnum < 0 ? tmpc : min(wcnum, tmpc)
        _nowr = 0
        _nowc = 0
        _nowp = _offsetr * _matrix.cnum + _offsetc
    }

testSetPos (ImageLibraryTests)

PixelIterator の setPos をテストする。これは、row, col で直接位置を指定するものである。指定により nowr, nowc, nowp の値が適切に設定されることを確認する。あまり使わない予定なので、性善説に立ち範囲外のチェックはしていない。引数なしで設定された場合には、(0, 0) に戻る。

    func testSetPos() {
        var pi1 : PixelIterator = mmat1!.pixelIterator()
        pi1.setPos(row: 0, col:1)
        XCTAssertEqual([ pi1.nowr, pi1.nowc, pi1.nowp ], [ 0, 1, 1 ])
        pi1.setPos(row: 1, col:2)
        XCTAssertEqual([ pi1.nowr, pi1.nowc, pi1.nowp ], [ 1, 2, 5 ])
        pi1.setPos()
        XCTAssertEqual([ pi1.nowr, pi1.nowc, pi1.nowp ], [ 0, 0, 0 ])
        
        var pi2 : PixelIterator = mmat1!.pixelIterator(wrnum: 1, wcnum: 2, offsetr: 1, offsetc: 1)
        pi2.setPos(row: 0, col:1)
        XCTAssertEqual([ pi2.nowr, pi2.nowc, pi2.nowp ], [ 0, 1, 5 ])
        pi2.setPos()
        XCTAssertEqual([ pi2.nowr, pi2.nowc, pi2.nowp ], [ 0, 0, 4 ])
    }

setPos の実装 (PixelIterator)

範囲チェックはしないので、そのまま実装する。

    public func setPos(row: Int = 0, col: Int = 0) {
        _nowr = row
        _nowc = col
        _nowp = (_nowr + _offsetr) * _matrix.cnum + (_nowc + _offsetc)
    }

testNextPI (ImageLibraryTests)

PixelIterator 用の nextPos のテストを行う。finished になっていなければ、次の値に移動する。finished かどうかは nowp の値が -1 になっているかどうかで判断する。画像全体の場合と、一部切り出しの両方で確認を行っている。

    func testNextPosPI() {
        var pi1 = mmat1!.pixelIterator()
        XCTAssertFalse(pi1.finished)
        pi1.nextPos()
        XCTAssertFalse(pi1.finished)
        XCTAssertEqual([ pi1.nowr, pi1.nowc, pi1.nowp ], [ 0, 1, 1 ])
        pi1.nextPos()
        XCTAssertFalse(pi1.finished)
        XCTAssertEqual([ pi1.nowr, pi1.nowc, pi1.nowp ], [ 0, 2, 2 ])
        pi1.nextPos()
        XCTAssertFalse(pi1.finished)
        XCTAssertEqual([ pi1.nowr, pi1.nowc, pi1.nowp ], [ 1, 0, 3 ])
        pi1.nextPos()
        XCTAssertFalse(pi1.finished)
        XCTAssertEqual([ pi1.nowr, pi1.nowc, pi1.nowp ], [ 1, 1, 4 ])
        pi1.nextPos()
        XCTAssertFalse(pi1.finished)
        XCTAssertEqual([ pi1.nowr, pi1.nowc, pi1.nowp ], [ 1, 2, 5 ])
        pi1.nextPos()
        XCTAssertTrue(pi1.finished)
        XCTAssertEqual([ pi1.nowr, pi1.nowc, pi1.nowp ], [ 0, 0, -1 ])
        pi1.nextPos()
        XCTAssertTrue(pi1.finished)
        XCTAssertEqual([ pi1.nowr, pi1.nowc, pi1.nowp ], [ 0, 0, -1 ])
        
        var pi2 = mmat1!.pixelIterator(wrnum: 1, wcnum: 2, offsetr: 1, offsetc: 1)
        XCTAssertFalse(pi2.finished)
        pi2.nextPos()
        XCTAssertFalse(pi2.finished)
        XCTAssertEqual([ pi2.nowr, pi2.nowc, pi2.nowp ], [ 0, 1, 5 ])
        pi2.nextPos()
        XCTAssertTrue(pi2.finished)
        XCTAssertEqual([ pi2.nowr, pi2.nowc, pi2.nowp ], [ 0, 0, -1 ])
        pi2.nextPos()
        XCTAssertTrue(pi2.finished)
        XCTAssertEqual([ pi2.nowr, pi2.nowc, pi2.nowp ], [ 0, 0, -1 ])
    }

nextPos の実装 (PixelIterator)

finished は computed property で _nowp が -1 かどうかを判断する。また、nextPos() は _nowc と _nowp を進め、右端まで行ったら _nowc を戻し、_nowr と _nowp を進める。下端まで行ったら _nowr も初期化し、_nowp を -1 とする。

    public var finished : Bool { return _nowp == -1 }

    public func nextPos() {
        if !finished {
            _nowc++
            _nowp++
            if _nowc == _wcnum {
                _nowc = 0
                _nowr++
                _nowp -= _wcnum
                _nowp += _matrix.cnum
                if _nowr == _wrnum {
                    _nowr = 0
                    _nowp = -1
                }
            }
        }
    }

testDoubleValuePI (ImageLibraryTests)

PixelIterator を経由して、元の MonoMatrix 画像の Pixel 値を取得・設定する。doubleValue で現在位置の pixel 値を読み込み、doubleValue = で pixel 値を書き込む。nextPos() と組み合わせることで二重ループを作ることなく、全画素にアクセスできている。

    func testDoubleValuePI() {
        let preData : [Double] = [ 3.0, 1.0, 4.0, 1.0, 5.0, 9.0 ]
        let postData : [Double] = [ 2.0, 9.0, 4.0, 7.0, 5.0, 3.0 ]
        var pi1 = mmat1!.pixelIterator()
        for i in 0 ..< 6 {
            XCTAssertEqual(pi1.doubleValue, preData[i])
            pi1.doubleValue = postData[i]
            pi1.nextPos()
        }
        XCTAssertEqual(mmat1!.doubleBuffer, postData)
    }

doubleValue の実装 (PixelIterator)

doubleValue は computed property で実現できる。ここで始めて setter と getter が出てきた(これまでのは getter のみだったので省略記述だけだった)。getter は get {} 内に記載し、setter は set {} 内に記載する。setter でセットする値は newValue という変数で与えられるので、これを MonoMatrix の setDoubleValue:at に渡せばよい。

    public var doubleValue : Double {
        get {
            return _matrix.doubleValue(_nowp)
        }
        set {
            _matrix.setDoubleValue(newValue, at: _nowp)
        }
    }

testBlockIterator (ImageLibraryTests)

PixelIterator は pixel 単位での Iterator だったが、BlockIterator はブロック単位の Iterator となる。ブロックサイズ(brnum, bcnum)と移動幅(srnum, scnum)は独立して設定できるようにする。例えばブロックごとの DCT 変換であれば brnum, bcnum, srnum, scnum はすべて 8 にするが、ブロックマッチングなどの場合だと brnum, bcnum は 8 に設定し、srnum, scnum を 1 に設定する。fixSize フラグはブロックサイズが固定かどうかを示すもので、true の場合には右端・下端のブロック幅に満たない pixel は無視される。逆に false の場合には、ブロックサイズを小さくして対応する。

BlockIterator に対して pixelIterator() を呼び出すと、対象としているブロックに対して PixelIterator を作成する。nextPos() は srnum, scnum, fixSize に従って次のブロックに進める作業をする。これらを一括でテストしたので、以下のようにかなり大きいテストになった。

    func testBlockIterator() {
        var mat2 = MonoMatrix(rnum: 4, cnum: 8);
        var bi1 : BlockIterator = mat2.blockIterator(brnum: 2, bcnum: 4, srnum: 2, scnum: 4, fixSize: true);
        XCTAssertFalse(bi1.finished)
        XCTAssertEqual([ bi1.nowr, bi1.nowc, bi1.nowp, bi1.brnum, bi1.bcnum ], [ 0, 0, 0, 2, 4 ])
        var pi1 : PixelIterator? = bi1.pixelIterator()
        XCTAssertEqual([ pi1!.nowr, pi1!.nowc, pi1!.nowp ], [ 0, 0, 0 ])
        //
        bi1.nextPos()
        XCTAssertFalse(bi1.finished)
        XCTAssertEqual([ bi1.nowr, bi1.nowc, bi1.nowp, bi1.brnum, bi1.bcnum ], [ 0, 4, 4, 2, 4 ])
        pi1 = bi1.pixelIterator()
        XCTAssertEqual([ pi1!.nowr, pi1!.nowc, pi1!.nowp ], [ 0, 0, 4 ])
        //
        bi1.nextPos()
        XCTAssertFalse(bi1.finished)
        XCTAssertEqual([ bi1.nowr, bi1.nowc, bi1.nowp, bi1.brnum, bi1.bcnum ], [ 2, 0, 16, 2, 4 ])
        pi1 = bi1.pixelIterator()
        XCTAssertEqual([ pi1!.nowr, pi1!.nowc, pi1!.nowp ], [ 0, 0, 16 ])
        //
        bi1.nextPos()
        XCTAssertFalse(bi1.finished)
        XCTAssertEqual([ bi1.nowr, bi1.nowc, bi1.nowp, bi1.brnum, bi1.bcnum ], [ 2, 4, 20, 2, 4 ])
        pi1 = bi1.pixelIterator()
        XCTAssertEqual([ pi1!.nowr, pi1!.nowc, pi1!.nowp ], [ 0, 0, 20 ])
        //
        bi1.nextPos()
        XCTAssertTrue(bi1.finished)
        XCTAssertEqual([ bi1.nowr, bi1.nowc, bi1.nowp, bi1.brnum, bi1.bcnum ], [ 0, 0, -1, 2, 4 ])
        XCTAssertTrue(bi1.pixelIterator() == nil)
        XCTAssertTrue(bi1.finished)
        XCTAssertEqual([ bi1.nowr, bi1.nowc, bi1.nowp, bi1.brnum, bi1.bcnum ], [ 0, 0, -1, 2, 4 ])
        XCTAssertTrue(bi1.pixelIterator() == nil)

        var bi2 : BlockIterator = mat2.blockIterator(brnum: 3, bcnum: 5, srnum: 2, scnum: 4, fixSize: false);
        XCTAssertFalse(bi2.finished)
        XCTAssertEqual([ bi2.nowr, bi2.nowc, bi2.nowp, bi2.brnum, bi2.bcnum ], [ 0, 0, 0, 3, 5 ])
        var pi2 : PixelIterator? = bi2.pixelIterator()
        XCTAssertEqual([ pi2!.nowr, pi2!.nowc, pi2!.nowp ], [ 0, 0, 0 ])
        //
        bi2.nextPos()
        XCTAssertFalse(bi2.finished)
        XCTAssertEqual([ bi2.nowr, bi2.nowc, bi2.nowp, bi2.brnum, bi2.bcnum ], [ 0, 4, 4, 3, 4 ])
        pi2 = bi2.pixelIterator()
        XCTAssertEqual([ pi2!.nowr, pi2!.nowc, pi2!.nowp ], [ 0, 0, 4 ])
        //
        bi2.nextPos()
        XCTAssertFalse(bi2.finished)
        XCTAssertEqual([ bi2.nowr, bi2.nowc, bi2.nowp, bi2.brnum, bi2.bcnum ], [ 2, 0, 16, 2, 5 ])
        pi2 = bi2.pixelIterator()
        XCTAssertEqual([ pi2!.nowr, pi2!.nowc, pi2!.nowp ], [ 0, 0, 16 ])
        //
        bi2.nextPos()
        XCTAssertFalse(bi2.finished)
        XCTAssertEqual([ bi2.nowr, bi2.nowc, bi2.nowp, bi2.brnum, bi2.bcnum ], [ 2, 4, 20, 2, 4 ])
        pi2 = bi2.pixelIterator()
        XCTAssertEqual([ pi2!.nowr, pi2!.nowc, pi2!.nowp ], [ 0, 0, 20 ])
        //
        bi2.nextPos()
        XCTAssertTrue(bi2.finished)
        XCTAssertEqual([ bi2.nowr, bi2.nowc, bi2.nowp, bi2.brnum, bi2.bcnum ], [ 0, 0, -1, 3, 5 ])
        XCTAssertTrue(bi2.pixelIterator() == nil)
        bi2.nextPos()
        XCTAssertTrue(bi2.finished)
        XCTAssertEqual([ bi2.nowr, bi2.nowc, bi2.nowp, bi2.brnum, bi2.bcnum ], [ 0, 0, -1, 3, 5 ])
        XCTAssertTrue(bi2.pixelIterator() == nil)
    }

blockIterator() の実装 (MonoMatrix)

MonoMatrix に対する blockIterator() の実装はほぼ pixelIterator() と同じ。こちらはデフォルト値がないので、最初の brnum という引数名は実際にはいらない。ただし、pixelIterator() との対称性が悪くなるので、こちらでは # をわざとつけて External Parameter Name にした。

    public func blockIterator(#brnum: Int, bcnum: Int, srnum: Int, scnum: Int, fixSize: Bool) -> BlockIterator {
        return BlockIterator(matrix: self, brnum: brnum, bcnum: bcnum, srnum: srnum, scnum: scnum, fixSize: fixSize);
    }

BlockIterator クラスの実装

BlockIterator はほぼ PixelIterator と同じ構造。_matrix を unowned var とし、_brnum, _bcnum, _srnum, _scnum は定数とした。_nowr, _nowc, _nowp はすべて初期値 0 の変数としている。ここで brnum, bcnum は実際のブロックの大きさを computed property によって返すようになっており、_fixSize でない場合には定数で保持されている _brnum, _bcnum よりも小さい値が計算されるようになっている。

public class BlockIterator {
    private unowned var _matrix : MonoMatrix
    private let _brnum, _bcnum, _srnum, _scnum: Int
    private let _fixSize: Bool
    private var _nowr, _nowc, _nowp : Int
    public var brnum : Int {
        return _fixSize ? _brnum : min(_brnum, _matrix.rnum - _nowr)
    }
    public var bcnum : Int {
        return _fixSize ? _bcnum : min(_bcnum, _matrix.cnum - _nowc)
    }
    public var srnum : Int { return _srnum }
    public var scnum : Int { return _scnum }
    public var fixSize : Bool { return _fixSize }
    public var finished : Bool { return _nowp == -1 }
    public var nowr : Int { return _nowr }
    public var nowc : Int { return _nowc }
    public var nowp : Int { return _nowp }
    
    public init(matrix: MonoMatrix, brnum: Int, bcnum: Int, srnum: Int, scnum: Int, fixSize: Bool) {
        _matrix = matrix
        _brnum = brnum
        _bcnum = bcnum
        _srnum = srnum
        _scnum = scnum
        _fixSize = fixSize
        _nowr = 0
        _nowc = 0
        _nowp = 0
    }

nextPos() の実装は以下のようになる。PixelIterator の nextPos() とほぼ同内容だが、_fixSize の値により右端・下端を使うかどうかが異なっている。

    public func nextPos() {
        if !finished {
            _nowc += _scnum
            _nowp += _scnum
            if (_fixSize ? _nowc + _bcnum : _nowc + 1) > _matrix.cnum {
                _nowp -= _nowc
                _nowc = 0
                _nowr += _srnum
                _nowp += _srnum * _matrix.cnum
                if (_fixSize ? _nowr + _brnum : _nowr + 1) > _matrix.rnum {
                    _nowc = 0
                    _nowr = 0
                    _nowp = -1
                }
            }
        }
    }

pixelIterator() の実装は、computed property の brnum, bcnum があるおかげで非常に簡単にかける。

    public func pixelIterator() -> PixelIterator? {
        if !finished {
            return _matrix.pixelIterator(wrnum: brnum, wcnum: bcnum, offsetr: _nowr, offsetc: _nowc)
        } else {
            return nil
        }
    }

長くなったので今日はここまで。

トラックバック - http://d.hatena.ne.jp/hkob/20150817
Connection: close