Skip to content

Commit

Permalink
Refactor MRZScanner
Browse files Browse the repository at this point in the history
  • Loading branch information
romanmazeev committed Feb 2, 2025
1 parent 99ea337 commit 1d07870
Show file tree
Hide file tree
Showing 21 changed files with 641 additions and 503 deletions.
2 changes: 1 addition & 1 deletion .swiftpm/xcode/xcshareddata/xcschemes/MRZScanner.xcscheme
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1420"
LastUpgradeVersion = "1600"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1420"
LastUpgradeVersion = "1600"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
33 changes: 21 additions & 12 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -14,53 +14,62 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/romanmazeev/MRZParser.git",
"state" : {
"branch" : "master",
"revision" : "a39f93e35e4d8de2dceeb6103ca4089856e3c566"
"revision" : "f6a0b10395e7cac783189ef2a0c8e1a55296ff37",
"version" : "1.1.4"
}
},
{
"identity" : "swift-clocks",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-clocks",
"state" : {
"revision" : "d1fd837326aa719bee979bdde1f53cd5797443eb",
"version" : "1.0.0"
"revision" : "a8421d68068d8f45fbceb418fbf22c5dad4afd33",
"version" : "1.0.2"
}
},
{
"identity" : "swift-concurrency-extras",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-concurrency-extras.git",
"location" : "https://github.com/pointfreeco/swift-concurrency-extras",
"state" : {
"revision" : "bb5059bde9022d69ac516803f4f227d8ac967f71",
"version" : "1.1.0"
}
},
{
"identity" : "swift-custom-dump",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-custom-dump.git",
"state" : {
"revision" : "f01efb26f3a192a0e88dcdb7c3c391ec2fc25d9c",
"version" : "1.3.0"
}
},
{
"identity" : "swift-dependencies",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-dependencies",
"state" : {
"revision" : "9783b58167f7618cb86011156e741cbc6f4cc864",
"version" : "1.1.2"
"revision" : "00bc30ca03f98881329fab7f1bebef8eba472596",
"version" : "1.3.1"
}
},
{
"identity" : "swift-syntax",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-syntax",
"state" : {
"revision" : "6ad4ea24b01559dde0773e3d091f1b9e36175036",
"version" : "509.0.2"
"revision" : "303e5c5c36d6a558407d364878df131c3546fad8",
"version" : "510.0.2"
}
},
{
"identity" : "xctest-dynamic-overlay",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/xctest-dynamic-overlay.git",
"location" : "https://github.com/pointfreeco/xctest-dynamic-overlay",
"state" : {
"revision" : "23cbf2294e350076ea4dbd7d5d047c1e76b03631",
"version" : "1.0.2"
"revision" : "6f30bdba373bbd7fbfe241dddd732651f2fbd1e2",
"version" : "1.1.2"
}
}
],
Expand Down
20 changes: 16 additions & 4 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ let package = Package(
),
],
dependencies: [
.package(url: "https://github.com/romanmazeev/MRZParser.git", branch: "master"),
.package(url: "https://github.com/pointfreeco/swift-dependencies", from: "1.1.2")
.package(url: "https://github.com/romanmazeev/MRZParser.git", from: "1.1.4"),
.package(url: "https://github.com/pointfreeco/swift-dependencies", from: "1.3.1"),
.package(url: "https://github.com/pointfreeco/swift-custom-dump.git", from: "1.3.0")
],
targets: [
.target(
Expand All @@ -24,12 +25,23 @@ let package = Package(
.product(
name: "Dependencies",
package: "swift-dependencies"
),
.product(
name: "DependenciesMacros",
package: "swift-dependencies"
)
]
),
.testTarget(
name: "MRZScannerTests",
dependencies: ["MRZScanner"],
resources: [.process("Private/TextRecognizerTests/ImageTest.png")]),
dependencies: [
"MRZScanner",
.product(
name: "CustomDump",
package: "swift-custom-dump"
)
],
resources: [.process("TestImage.png")]
)
]
)
40 changes: 18 additions & 22 deletions Sources/MRZScanner/Private/BoundingRectConverter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,31 @@

import CoreImage
import Dependencies
import DependenciesMacros

@DependencyClient
struct BoundingRectConverter: Sendable {
let convert: @Sendable (_ results: [TextRecognizer.Result], _ validLines: [Validator.Result]) -> ScannedBoundingRects
var convert: @Sendable (_ results: [TextRecognizer.Result], _ validLines: [Validator.Result]) -> ScannedBoundingRects = { _, _ in .init(valid: [], invalid: []) }
}

extension BoundingRectConverter: DependencyKey {
static var liveValue: Self {
.init(
convert: { results, validLines in
let allBoundingRects = results.map(\.boundingRect)
let validRectIndexes = Set(validLines.map(\.index))

var validScannedBoundingRects: [CGRect] = []
var invalidScannedBoundingRects: [CGRect] = []
allBoundingRects.enumerated().forEach {
if validRectIndexes.contains($0.offset) {
validScannedBoundingRects.append($0.element)
} else {
invalidScannedBoundingRects.append($0.element)
}
.init { results, validLines in
let allBoundingRects = results.map(\.boundingRect)
let validRectIndexes = Set(validLines.map(\.index))

var validScannedBoundingRects: [CGRect] = []
var invalidScannedBoundingRects: [CGRect] = []
allBoundingRects.enumerated().forEach {
if validRectIndexes.contains($0.offset) {
validScannedBoundingRects.append($0.element)
} else {
invalidScannedBoundingRects.append($0.element)
}

return .init(valid: validScannedBoundingRects, invalid: invalidScannedBoundingRects)
}
)

return .init(valid: validScannedBoundingRects, invalid: invalidScannedBoundingRects)
}
}
}

Expand All @@ -45,10 +45,6 @@ extension DependencyValues {

#if DEBUG
extension BoundingRectConverter: TestDependencyKey {
static var testValue: Self {
Self(
convert: unimplemented("BoundingRectConverter.convert")
)
}
static let testValue = Self()
}
#endif
20 changes: 9 additions & 11 deletions Sources/MRZScanner/Private/Parser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,21 @@
//

import Dependencies
import DependenciesMacros
import MRZParser

public typealias ParserResult = MRZResult

@DependencyClient
struct Parser: Sendable {
let parse: @Sendable (_ mrzLines: [String]) -> ParserResult?
var parse: @Sendable (_ mrzLines: [String]) -> ParserResult?
}

extension Parser: DependencyKey {
static var liveValue: Self {
.init(
parse: { mrzLines in
MRZParser(isOCRCorrectionEnabled: true).parse(mrzLines: mrzLines)
}
)
.init { mrzLines in
MRZParser(isOCRCorrectionEnabled: true).parse(mrzLines: mrzLines)
}
}
}

Expand All @@ -31,10 +33,6 @@ extension DependencyValues {

#if DEBUG
extension Parser: TestDependencyKey {
static var testValue: Self {
Self(
parse: unimplemented("Parser.parse")
)
}
static let testValue = Self()
}
#endif
54 changes: 25 additions & 29 deletions Sources/MRZScanner/Private/TextRecognizer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,45 +7,45 @@

import CoreImage
import Dependencies
import DependenciesMacros
import Vision

@DependencyClient
struct TextRecognizer: Sendable {
struct Result {
let results: [String]
let boundingRect: CGRect
}

let recognize: @Sendable (_ configuration: ScanningConfiguration, _ scanningImage: CIImage) async throws -> [Result]
var recognize: @Sendable (_ configuration: ScanningConfiguration, _ scanningImage: CIImage) async throws -> [Result]
}

extension TextRecognizer: DependencyKey {
static var liveValue: Self {
.init(
recognize: { request, scanningImage in
try await withCheckedThrowingContinuation { continuation in
let visionRequest = VNRecognizeTextRequest { request, _ in
guard let visionResults = request.results as? [VNRecognizedTextObservation] else {
return
}

continuation.resume(returning: visionResults.map {
Result(results: $0.topCandidates(10).map(\.string), boundingRect: $0.boundingBox)
})
}
visionRequest.regionOfInterest = request.regionOfInterest
visionRequest.minimumTextHeight = request.minimumTextHeight
visionRequest.recognitionLevel = request.recognitionLevel
visionRequest.usesLanguageCorrection = false

do {
try VNImageRequestHandler(ciImage: scanningImage, orientation: request.orientation)
.perform([visionRequest])
} catch {
continuation.resume(throwing: error)
.init { request, scanningImage in
try await withCheckedThrowingContinuation { continuation in
let visionRequest = VNRecognizeTextRequest { request, _ in
guard let visionResults = request.results as? [VNRecognizedTextObservation] else {
return
}

continuation.resume(returning: visionResults.map {
Result(results: $0.topCandidates(10).map(\.string), boundingRect: $0.boundingBox)
})
}
visionRequest.regionOfInterest = request.regionOfInterest
visionRequest.minimumTextHeight = request.minimumTextHeight
visionRequest.recognitionLevel = request.recognitionLevel
visionRequest.usesLanguageCorrection = false

do {
try VNImageRequestHandler(ciImage: scanningImage, orientation: request.orientation)
.perform([visionRequest])
} catch {
continuation.resume(throwing: error)
}
}
)
}
}
}

Expand All @@ -58,10 +58,6 @@ extension DependencyValues {

#if DEBUG
extension TextRecognizer: TestDependencyKey {
static var testValue: Self {
Self(
recognize: unimplemented("TextRecognizer.recognize")
)
}
static let testValue = Self()
}
#endif
29 changes: 14 additions & 15 deletions Sources/MRZScanner/Private/Tracker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,26 @@
//

import Dependencies
import DependenciesMacros

public typealias TrackerResult = [ParserResult: Int]

@DependencyClient
struct Tracker: Sendable {
let updateResults: @Sendable (_ results: TrackerResult, _ result: ParserResult) -> TrackerResult
var currentResults: @Sendable () -> TrackerResult = { [:] }
var track: @Sendable (_ result: ParserResult) -> Void
}

extension Tracker: DependencyKey {
static var liveValue: Self {
.init(
updateResults: { results, result in
var seenResults = results
guard let seenResultFrequency = seenResults[result] else {
seenResults[result] = 1
return seenResults
}
let seenResults: LockIsolated<TrackerResult> = .init([:])

seenResults[result] = seenResultFrequency + 1
return seenResults
return .init(
currentResults: { seenResults.value },
track: { result in
seenResults.withValue { seenResults in
seenResults[result, default: 0] += 1
}
}
)
}
Expand All @@ -37,10 +40,6 @@ extension DependencyValues {

#if DEBUG
extension Tracker: TestDependencyKey {
static var testValue: Self {
Self(
updateResults: unimplemented("Tracker.updateResults")
)
}
static let testValue = Self()
}
#endif
Loading

0 comments on commit 1d07870

Please sign in to comment.