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() }
次はもう少し複雑なものを書いてみる予定。