Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1.1.0 Refactoring #4

Merged
merged 1 commit into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading