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
Conversation
I've just pushed a commit to actually send the rotated image to didFinishScanningWithResults. |
There was a problem hiding this 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. |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?) -> ())) { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) | ||
|
There was a problem hiding this comment.
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 🎉
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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) | |||
} | |||
}*/ |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which code?
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
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 😄 |
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 :) I've attached a video for you, @Boris-Em and @AvdLee to see as an example |
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. |
Okay what the rotation issue exactly? |
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 👍 😄 |
… auto scanning (setting for it coming soon), and other changes
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. |
…anner vc) with a flash and auto-scan setting, to resemble Apple's Notes document scanner
There was a problem hiding this 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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
…ed code to clean up the project
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. |
There was a problem hiding this 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!
import AVFoundation | ||
|
||
var editImageOrientation: CGImagePropertyOrientation = .downMirrored |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep. Same thing here.
Yeah I guess it must be eliminating false negatives by resetting the rectangles. |
Yeah P.S. Interesting use of Apple event time 👀 |
Usually iit's at 6pm in the UK :) so I didn't realise |
@Boris-Em can you re-review ? :) |
Sorry guys for the delay. I'll have time to help and review the PRs tomorrow! |
There was a problem hiding this 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 =)
Woop :) 🎂Time to merge! |
@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 |
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. |
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?) { |
There was a problem hiding this comment.
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 |
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. |
Done. Ready to merge @jcampbell05 @AvdLee @Boris-Em |
🎉 🎊 We finally made it! It's taken ages but i'm pretty happy with the final result 😅 |
@justjs really great work :) |
Great work @justjs @jcampbell05, thanks for all the work and positive energy to work on our feedback. Highly appreciated! |
Thanks again everyone for the feedback + help with tests 😄 |
@justjs - You should be, this is really great! |
@justjs how can I add auto capture? It doesn't work to me right now |
It’s available on the latest develop branch (maybe master too). Make sure you’re on Auto not Manual and it only works if the rectangle is very accurate.
…Sent from my iPhone
On Dec 28, 2018, at 3:11 PM, adityativ-issart ***@***.***> wrote:
@justjs how can I add auto capture? It doesn't work to me right now
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.
|
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.