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

Add auto rotate, auto scan, and Vision support #41

Merged
merged 83 commits into from Nov 12, 2018
Merged

Add auto rotate, auto scan, and Vision support #41

merged 83 commits into from Nov 12, 2018

Conversation

julianschiavo
Copy link
Contributor

@julianschiavo julianschiavo commented Jul 27, 2018

This pull request adds support for auto rotating images to Portrait on iOS 10 and above, and uses Vision instead of CoreImage to detect rectangles on iOS 11 and above. It also adds iPad support to the sample project.

Auto rotation, auto scanning, and Vision support have been fully tested by me, but I'd appreciate any feedback and improvements.

@julianschiavo
Copy link
Contributor Author

I've just pushed a commit to actually send the rotated image to didFinishScanningWithResults.

Copy link
Contributor

@AvdLee AvdLee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your contribution, really great!! 💪

I got a few remarks, let me know your thoughts on them.
Also, would it be possible to write some tests for the added code? I feel like some parts are at least easy to test.

Further, you mentioned Vision support may not work yet . Can you elaborate a bit more on that? Does that mean we're merging in non-working code here? I might lack a bit of knowledge here, but I'm happy to learn from you :)


} else { completion(nil) }
})
rectDetectRequest.minimumConfidence = 0.7 // Be confident.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Be confident. doesn't really add any value and could be removed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wasn't my contribution.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries, let's not blame anyone. Either way, would you mind removing it within these code changes? @Boris-Em what are your thoughts here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed it with the commit 6d5573f.


guard let rectangleFeatures = rectangleDetector?.features(in: image) as? [CIRectangleFeature] else {
return nil
static func rectangle(forImage image: CIImage, completion: @escaping ((Quadrilateral?) -> ())) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great if we can use the Result enum here for completion, including the Quadrilateral if it succeeded and the occurred error if it failed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's quite late here, I'll look at implementing that tomorrow.

let biggestRectangle = Quadrilateral(rectangleFeature: biggest)

completion(biggestRectangle)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We more or less have two detectors here, one using CoreImage and one using Vision. Can we make these two separated methods or even classes for readability, ending up with code like for example:

if #available(iOS 11.0, *) {
    VisionRectangleDetector.detectRectangle(in: image, completion: ..)
} else {
    CIRectangleDetector.detectRectangle(in: image, completion: ..)
}

Keeping the code separated also makes it easier for us to write tests 🎉

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's quite late here, I'll look at implementing that tomorrow.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've separated the detectors with commit e7b07ac.

import AVFoundation

var editImageOrientation: CGImagePropertyOrientation = .downMirrored
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For what reason are we defining this property as a global? Can't we solve this in a different way?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to rotate the image when it's being passed to the ReviewVC, so that it'll work if the person rotates their phone after scanning a picture.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand, but why do we define it as a global parameter and not just as a property within a class for example?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CaptureSessionManager class is initialized from the ScannerViewController, while the ReviewViewController is initialized from the EditScanViewController (so it's hard for them to access each other's properties). I think it would be best to keep this as a global property, but if anyone knows a better way of doing this feel free to suggest it and i'll push it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to dependency inject this value through the view controllers? We might even want to create a wrapper class CaptureSession or thinking about it, a singleton class for this case?

CaptureSession.current which always keeps the current capture session.

@Boris-Em can you think with us here, I feel like this is getting to be an important change for the library.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm in favor of using a Singleton in this case.
There can only be one orientation and one capture session at any given time, and they're relevant during the whole life of the scanner.
I would use CaptureSession.current.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has finally been implemented in 9083131 using CaptureSession.current.

@@ -90,7 +91,7 @@ final class RectangleFeaturesFunnel {
bestRectangle.rectangleFeature.isWithin(matchingThreshold, ofRectangleFeature: previousRectangle) {
} else if bestRectangle.matchingScore >= minNumberOfMatches {
completion(bestRectangle.rectangleFeature)
}
}*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't need this code anymore, can we get rid of it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which code?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AvdLee is referring to the dead code (code commented out).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I understand. Not using it seems to work on iOS 10, but again, I haven't had a chance to test on iOS 11. As soon as I do, i'll either leave it in/take it out.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update on this: the commented out code makes the rectangles very jerky on all versions of iOS. I'm looking at reimplementing this code soon.

Edit: Re-adding the commented out code makes the rectangles very inaccurate, but commenting it out makes it very jerky. I'm working on making it better but it might take some time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-added with some changes. See commit f1e01c1.

@julianschiavo
Copy link
Contributor Author

Vision scanning may not work because I implemented it a few weeks ago and haven't had time to test it, while i've tested auto rotate code. I don't think this PR should be merged yet, it's here so people (and me) can test it and suggest things 😄

@jcampbell05
Copy link
Contributor

jcampbell05 commented Jul 27, 2018

Works better with some of the more complicated documents on our iOS 11 device which previously failed under the old code

The only piece of feedback we have is we have to be slow when scanning as the rectangle jumps around a bit. Not sure if that's a bug with this framework or the way this library processes the incoming information but we need to improve the snapping so that it's instant and consistent :)
2018-07-27 15.18.01.mp4.zip

I've attached a video for you, @Boris-Em and @AvdLee to see as an example

@julianschiavo
Copy link
Contributor Author

julianschiavo commented Jul 27, 2018

That's probably because of the commented out code (see AvdLee's requested changes), it happens on iOS 10 as well. I'll work on it tomorrow 😄

Also, to fix the rotation issue at the end of your video, hold the phone up more so it's obvious to the accelerometer that you're in portrait.

@jcampbell05
Copy link
Contributor

Okay what the rotation issue exactly?

@julianschiavo
Copy link
Contributor Author

I've fixed some of the issues raised in the review and am working on the others. Let me know if there's anything else I should change/add 👍 😄

@julianschiavo julianschiavo changed the title Add auto rotate and Vision support Add auto rotate, auto scan, and Vision support Jul 28, 2018
@julianschiavo
Copy link
Contributor Author

julianschiavo commented Jul 28, 2018

I've just pushed a big commit with auto scanning support (setting for it coming soon) as well as more accurate Vision scanning and iPad support on the sample project.

Edit: Just pushed a commit to add a setting for auto scan.

Copy link
Contributor

@AvdLee AvdLee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great to see the changes, the VisionRectangleDetector and CIRectangleDetector are exactly what I meant! Awesome :)

Also great to see you've pushed even more features, but I do want to ask you to create a separated PR next time to prevent big PRs.

Big PRs slows down reviews in general and makes it harder for the creator to keep push needed changes.

For this PR, would it be possible to write some tests? Quite some code is testable and should be tested for future maintenance.

Thanks again!

import AVFoundation

var editImageOrientation: CGImagePropertyOrientation = .downMirrored
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to dependency inject this value through the view controllers? We might even want to create a wrapper class CaptureSession or thinking about it, a singleton class for this case?

CaptureSession.current which always keeps the current capture session.

@Boris-Em can you think with us here, I feel like this is getting to be an important change for the library.

if let rectangle = rectangle {

self.noRectangleCount = 0
self.rectangleFunnel.add(rectangle, currentlyDisplayedRectangle: self.displayedRectangleResult?.rectangle) { (rectangle, shouldAutoScan) in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this method is asynchronous, we should capture self weak here. It might be good to in general capture self weakly to prevent us from unwanted retains.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

if thresholdPassed > 30 {
thresholdPassed = 0
completion(bestRectangle.rectangleFeature, true)
// Scan the rectangle automatically by passing True to the completion
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the fact that we need this documentation here implies a bad code pattern. Code should try to be self explaining as much as possible.

Can we replace the completion callback with something more describing letting the code explain that we're scanning the rectangle automatically or not?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: I replaced the Bool with a new enum, AddResult, that describes whether we're auto scanning or only showing the rectangle.

@@ -9,6 +9,9 @@
import UIKit
import AVFoundation

/// Whether auto scan is enabled or not (has to be public to be seen by CaptureSessionManager)
var autoScanEnabled = true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Boris-Em this would also be something for the currentSession I mentioned before.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. Same thing here.

func toggleTorch(shouldTurnOn: Bool) {
guard let device = AVCaptureDevice.default(for: AVMediaType.video) else { return }

if device.hasTorch {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be part of the guard

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

device.torchMode = shouldTurnOn ? .on : .off
device.unlockForConfiguration()
} catch {
return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't use the catch as we do here, it might be better to use try? instead and get rid of the do catch

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@julianschiavo
Copy link
Contributor Author

julianschiavo commented Jul 30, 2018

Sorry for the big PR! I've resolved all the issues except for the global variable, where I'm waiting for @Boris-Em's input.

Edit: As for the tests, I only started learning Swift 4-5 months ago and have no experience with testing. If anyone would like to contribute tests, they can, but I'm not able to.

Copy link
Contributor

@Boris-Em Boris-Em left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for the contribution!

WeScan/Common/Quadrilateral.swift Show resolved Hide resolved
import AVFoundation

var editImageOrientation: CGImagePropertyOrientation = .downMirrored
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm in favor of using a Singleton in this case.
There can only be one orientation and one capture session at any given time, and they're relevant during the whole life of the scanner.
I would use CaptureSession.current.

var motion: CMMotionManager!
motion = CMMotionManager()
motion.accelerometerUpdateInterval = 0.2
motion.startAccelerometerUpdates(to: OperationQueue()) { p, _ in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be in favor of using descriptive variable names here instead of a and p.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done (bb2db9d)

}
}

func processRectangle(rectangle: Quadrilateral?, imageSize: CGSize) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't this function be private?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done (bb2db9d)

@@ -9,6 +9,9 @@
import UIKit
import AVFoundation

/// Whether auto scan is enabled or not (has to be public to be seen by CaptureSessionManager)
var autoScanEnabled = true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. Same thing here.

@jcampbell05
Copy link
Contributor

Unfortunately, I found it incredibly hard to reproduce as it happened randomly. I think we could just add a comment explaining the fix?

Yeah I guess it must be eliminating false negatives by resetting the rectangles.

@julianschiavo
Copy link
Contributor Author

Yeah

P.S. Interesting use of Apple event time 👀

@jcampbell05
Copy link
Contributor

P.S. Interesting use of Apple event time 👀

Usually iit's at 6pm in the UK :) so I didn't realise

@jcampbell05
Copy link
Contributor

@Boris-Em can you re-review ? :)

@Boris-Em
Copy link
Contributor

Boris-Em commented Nov 1, 2018

Sorry guys for the delay. I'll have time to help and review the PRs tomorrow!

Copy link
Contributor

@Boris-Em Boris-Em left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! Thanks for all of the changes =)

@jcampbell05
Copy link
Contributor

Woop :) 🎂Time to merge!

@AvdLee
Copy link
Contributor

AvdLee commented Nov 6, 2018

@justjs if you can merge in develop for the latest changes, CI will probably succeed. After that, you can merge the PR in. Let me know if you have any questions

@julianschiavo
Copy link
Contributor Author

Uhm I’m kinda in Japan atm (no computer). If someOne makes a PR I’ll merge it, otherwise I’ll get back in a week or so.

@jcampbell05
Copy link
Contributor

I'll see what I can do :)

}

extension CaptureSessionManager: AVCapturePhotoCaptureDelegate {

func photoOutput(_ captureOutput: AVCapturePhotoOutput, didFinishProcessingPhoto photoSampleBuffer: CMSampleBuffer?, previewPhoto previewPhotoSampleBuffer: CMSampleBuffer?, resolvedSettings: AVCaptureResolvedPhotoSettings, bracketSettings: AVCaptureBracketedStillImageSettings?, error: Error?) {
Copy link

@ghost ghost Nov 10, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Function should have 5 parameters or less: it currently has 6

@julianschiavo
Copy link
Contributor Author

This should be good to go! I just have one last commit to push to update the tests for iOS 12.1, which will silence the bot.

@julianschiavo
Copy link
Contributor Author

Done. Ready to merge @jcampbell05 @AvdLee @Boris-Em

@jcampbell05
Copy link
Contributor

All green :) @Boris-Em and @AvdLee

@Boris-Em Boris-Em merged commit ea0a658 into WeTransfer:develop Nov 12, 2018
@julianschiavo
Copy link
Contributor Author

🎉 🎊 We finally made it! It's taken ages but i'm pretty happy with the final result 😅

@jcampbell05
Copy link
Contributor

@justjs really great work :)

@AvdLee
Copy link
Contributor

AvdLee commented Nov 12, 2018

Great work @justjs @jcampbell05, thanks for all the work and positive energy to work on our feedback. Highly appreciated!

@julianschiavo
Copy link
Contributor Author

Thanks again everyone for the feedback + help with tests 😄

@Boris-Em
Copy link
Contributor

@justjs - You should be, this is really great!

@adityativ-issart
Copy link

@justjs how can I add auto capture? It doesn't work to me right now

@julianschiavo
Copy link
Contributor Author

julianschiavo commented Dec 28, 2018 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants