Skip to content

Commit

Permalink
Refactoring
Browse files Browse the repository at this point in the history
* Improved performance
* Minor API changes
* Test coverage increased to max
  • Loading branch information
romanmazeev committed Jan 8, 2024
1 parent b1847c3 commit 144de44
Show file tree
Hide file tree
Showing 33 changed files with 1,234 additions and 612 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/Build and test framework.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ on:
jobs:
build:

runs-on: macos-latest
runs-on: macos-13

steps:
- uses: actions/checkout@v2
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,6 @@ fastlane/test_output
# https://github.com/johnno1962/injectionforxcode

iOSInjectionProject/

# OS X Finder
.DS_Store
Original file line number Diff line number Diff line change
@@ -1,12 +1,66 @@
{
"pins" : [
{
"identity" : "combine-schedulers",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/combine-schedulers",
"state" : {
"revision" : "9dc9cbe4bc45c65164fa653a563d8d8db61b09bb",
"version" : "1.0.0"
}
},
{
"identity" : "mrzparser",
"kind" : "remoteSourceControl",
"location" : "https://github.com/romanmazeev/MRZParser.git",
"state" : {
"revision" : "2c1c4809379f081c01297f0ac92df2e94c6db54a",
"version" : "1.1.3"
"branch" : "master",
"revision" : "a39f93e35e4d8de2dceeb6103ca4089856e3c566"
}
},
{
"identity" : "swift-clocks",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-clocks",
"state" : {
"revision" : "d1fd837326aa719bee979bdde1f53cd5797443eb",
"version" : "1.0.0"
}
},
{
"identity" : "swift-concurrency-extras",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-concurrency-extras.git",
"state" : {
"revision" : "bb5059bde9022d69ac516803f4f227d8ac967f71",
"version" : "1.1.0"
}
},
{
"identity" : "swift-dependencies",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-dependencies",
"state" : {
"revision" : "9783b58167f7618cb86011156e741cbc6f4cc864",
"version" : "1.1.2"
}
},
{
"identity" : "swift-syntax",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-syntax",
"state" : {
"revision" : "6ad4ea24b01559dde0773e3d091f1b9e36175036",
"version" : "509.0.2"
}
},
{
"identity" : "xctest-dynamic-overlay",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/xctest-dynamic-overlay.git",
"state" : {
"revision" : "23cbf2294e350076ea4dbd7d5d047c1e76b03631",
"version" : "1.0.2"
}
}
],
Expand Down
9 changes: 6 additions & 3 deletions Example/MRZScannerExample/Camera.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,20 @@ import CoreImage
final class Camera: NSObject {
let captureSession = AVCaptureSession()

private(set) lazy var imageStream: AsyncStream<CIImage> = {
var imageStream: AsyncStream<CIImage> {
AsyncStream { continuation in
imageStreamCallback = { ciImage in
continuation.yield(ciImage)
}
}
}()
private var imageStreamCallback: ((CIImage) -> Void)?
}

private var imageStreamCallback: ((CIImage) -> Void)?
private let captureDevice = AVCaptureDevice.default(for: .video)

// TODO: Refactor to use Swift Concurrency
private let sessionQueue = DispatchQueue(label: "Session queue")

private var isCaptureSessionConfigured = false
private var deviceInput: AVCaptureDeviceInput?
private var videoOutput: AVCaptureVideoDataOutput?
Expand Down
31 changes: 16 additions & 15 deletions Example/MRZScannerExample/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
// Created by Roman Mazeev on 01/01/2023.
//

import MRZScanner
import SwiftUI
import MRZParser

struct ContentView: View {
private let dateFormatter: DateFormatter = {
Expand All @@ -17,35 +17,34 @@ struct ContentView: View {
}()

@StateObject private var viewModel = ViewModel()
@State private var cameraRect: CGRect?
@State private var mrzRect: CGRect?

var body: some View {
GeometryReader { proxy in
Group {
if let cameraRect = viewModel.cameraRect {
if let cameraRect {
CameraView(captureSession: viewModel.captureSession)
.frame(width: cameraRect.width, height: cameraRect.height)
}

ZStack {
Color.black.opacity(0.5)

if let mrzRect = viewModel.mrzRect {
if let mrzRect {
Rectangle()
.blendMode(.destinationOut)
.frame(width: mrzRect.size.width, height: mrzRect.size.height)
.position(mrzRect.origin)
.task {
do {
try await viewModel.startMRZScanning()
} catch {
print(error.localizedDescription)
}
guard let cameraRect else { return }

await viewModel.startMRZScanning(cameraRect: cameraRect, mrzRect: mrzRect)
}
}
}
.compositingGroup()


if let boundingRects = viewModel.boundingRects {
ForEach(boundingRects.valid, id: \.self) { boundingRect in
createBoundingRect(boundingRect, color: .green)
Expand All @@ -57,9 +56,9 @@ struct ContentView: View {
}
}
.onAppear {
viewModel.cameraRect = proxy.frame(in: .global)
viewModel.mrzRect = .init(origin: .init(x: proxy.size.width / 2, y: proxy.size.height / 2),
size: .init(width: proxy.size.width - 40, height: proxy.size.width / 5))
cameraRect = proxy.frame(in: .global)
mrzRect = .init(origin: .init(x: proxy.size.width / 2, y: proxy.size.height / 2),
size: .init(width: proxy.size.width - 40, height: proxy.size.width / 5))
}
}
.alert(isPresented: .init(get: { viewModel.mrzResult != nil }, set: { _ in viewModel.mrzResult = nil })) {
Expand All @@ -68,13 +67,15 @@ struct ContentView: View {
message: Text(createAlertMessage(mrzResult: viewModel.mrzResult!)),
dismissButton: .default(Text("Got it!")) {
Task {
try await viewModel.startMRZScanning()
guard let cameraRect, let mrzRect else { return }

await viewModel.startMRZScanning(cameraRect: cameraRect, mrzRect: mrzRect)
}
}
)
}
.task {
viewModel.startCamera()
await viewModel.startCamera()
}
.statusBarHidden()
.ignoresSafeArea()
Expand All @@ -87,7 +88,7 @@ struct ContentView: View {
.position(rect.origin)
}

private func createAlertMessage(mrzResult: MRZResult) -> String {
private func createAlertMessage(mrzResult: ParserResult) -> String {
var birthdateString: String?
var expiryDateString: String?

Expand Down
64 changes: 28 additions & 36 deletions Example/MRZScannerExample/ViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,54 +8,48 @@
import AVFoundation
import SwiftUI
import MRZScanner
import MRZParser
import Vision

@MainActor
final class ViewModel: ObservableObject {

// MARK: Camera
private let camera = Camera()
@Published var cameraRect: CGRect?
var captureSession: AVCaptureSession {
camera.captureSession
}

func startCamera() {
Task {
await camera.start()
}
func startCamera() async {
await camera.start()
}

// MARK: Scanning
@Published var boundingRects: ScanedBoundingRects?
@Published var mrzRect: CGRect?
@Published var mrzResult: MRZResult?

private var scanningTask: Task<(), Error>?

func startMRZScanning() async throws {
guard let cameraRect, let mrzRect else { return }

let correctedMRZRect = correctCoordinates(to: .leftTop, rect: mrzRect)
let roi = MRZScanner.convertRect(to: .normalizedRect, rect: correctedMRZRect, imageWidth: Int(cameraRect.width), imageHeight: Int(cameraRect.height))
let scanningStream = MRZScanner.scanLive(
imageStream: camera.imageStream,
configuration: .init(orientation: .up, regionOfInterest: roi, minimumTextHeight: 0.1, recognitionLevel: .fast)
)

scanningTask = Task {
for try await liveScanningResult in scanningStream {
Task { @MainActor in
switch liveScanningResult {
case .found(let scanningResult):
boundingRects = correctBoundingRects(to: .center, rects: scanningResult.boundingRects)
mrzResult = scanningResult.result
scanningTask?.cancel()
case .notFound(let boundingRects):
self.boundingRects = correctBoundingRects(to: .center, rects: boundingRects)
}
@Published var boundingRects: ScannedBoundingRects?
@Published var mrzResult: ParserResult?

func startMRZScanning(cameraRect: CGRect, mrzRect: CGRect) async {
do {
for try await scanningResult in camera.imageStream.scanForMRZCode(
configuration: .init(
orientation: .up,
regionOfInterest: VNNormalizedRectForImageRect(
correctCoordinates(to: .leftTop, rect: mrzRect),
Int(cameraRect.width),
Int(cameraRect.height)
),
minimumTextHeight: 0.1,
recognitionLevel: .fast
)
) {
boundingRects = correctBoundingRects(to: .center, rects: scanningResult.boundingRects, mrzRect: mrzRect)
if let bestResult = scanningResult.best(repetitions: 2) {
mrzResult = bestResult
boundingRects = nil
return
}
}
} catch {
print(error.localizedDescription)
}
}

Expand All @@ -66,9 +60,7 @@ final class ViewModel: ObservableObject {
case leftTop
}

private func correctBoundingRects(to type: CorrectionType, rects: ScanedBoundingRects) -> ScanedBoundingRects {
guard let mrzRect else { fatalError("Camera rect must be set") }

private func correctBoundingRects(to type: CorrectionType, rects: ScannedBoundingRects, mrzRect: CGRect) -> ScannedBoundingRects {
let convertedCoordinates = rects.convertedToImageRects(imageWidth: Int(mrzRect.width), imageHeight: Int(mrzRect.height))
let correctedMRZRect = correctCoordinates(to: .leftTop, rect: mrzRect)

Expand Down
58 changes: 56 additions & 2 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -1,12 +1,66 @@
{
"pins" : [
{
"identity" : "combine-schedulers",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/combine-schedulers",
"state" : {
"revision" : "9dc9cbe4bc45c65164fa653a563d8d8db61b09bb",
"version" : "1.0.0"
}
},
{
"identity" : "mrzparser",
"kind" : "remoteSourceControl",
"location" : "https://github.com/romanmazeev/MRZParser.git",
"state" : {
"revision" : "2c1c4809379f081c01297f0ac92df2e94c6db54a",
"version" : "1.1.3"
"branch" : "master",
"revision" : "a39f93e35e4d8de2dceeb6103ca4089856e3c566"
}
},
{
"identity" : "swift-clocks",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-clocks",
"state" : {
"revision" : "d1fd837326aa719bee979bdde1f53cd5797443eb",
"version" : "1.0.0"
}
},
{
"identity" : "swift-concurrency-extras",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-concurrency-extras.git",
"state" : {
"revision" : "bb5059bde9022d69ac516803f4f227d8ac967f71",
"version" : "1.1.0"
}
},
{
"identity" : "swift-dependencies",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-dependencies",
"state" : {
"revision" : "9783b58167f7618cb86011156e741cbc6f4cc864",
"version" : "1.1.2"
}
},
{
"identity" : "swift-syntax",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-syntax",
"state" : {
"revision" : "6ad4ea24b01559dde0773e3d091f1b9e36175036",
"version" : "509.0.2"
}
},
{
"identity" : "xctest-dynamic-overlay",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/xctest-dynamic-overlay.git",
"state" : {
"revision" : "23cbf2294e350076ea4dbd7d5d047c1e76b03631",
"version" : "1.0.2"
}
}
],
Expand Down
19 changes: 16 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,28 @@ let package = Package(
),
],
dependencies: [
.package(url: "https://github.com/romanmazeev/MRZParser.git", .upToNextMajor(from: "1.1.3"))
.package(url: "https://github.com/romanmazeev/MRZParser.git", branch: "master"),
.package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay.git", .upToNextMajor(from: "1.0.2")),
.package(url: "https://github.com/pointfreeco/swift-dependencies", from: "1.1.2")
],
targets: [
.target(
name: "MRZScanner",
dependencies: ["MRZParser"]
dependencies: [
"MRZParser",
.product(
name: "XCTestDynamicOverlay",
package: "xctest-dynamic-overlay"
),
.product(
name: "Dependencies",
package: "swift-dependencies"
)
]
),
.testTarget(
name: "MRZScannerTests",
dependencies: ["MRZScanner"]),
dependencies: ["MRZScanner"],
resources: [.process("Private/TextRecognizerTests/ImageTest.png")]),
]
)
Loading

0 comments on commit 144de44

Please sign in to comment.