3. PlaneIterator と IteratorSet

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 を思いついたので追記した。これだけあると実際の処理プログラムはかなり短く書けそうだ。今日はここまで。