Forum Archive

How to access VisionKit?

emkay_online

Hi,

I'm just dipping my toes into objc_util and have fallen at the first hurdle.

I want to write a small script that calls VisionKit to scan a document and return the result to Pythonista. API is here: https://developer.apple.com/documentation/visionkit

I've started with:

from objc_util import *
VNDocumentCameraViewController = ObjCClass('VNDocumentCameraViewController')

But Pythonista says that the class VNDocumentCameraViewController doesn't exist.

I'd love some help getting past this (and if anyone could take the example further, that would be amazing).

Thank you,

Martin

cvp

@emkay_online said:

VNDocumentCameraViewController = ObjCClass('VNDocumentCameraViewController')

from objc_util import *
load_framework('VisionKit')
VNDocumentCameraViewController = ObjCClass('VNDocumentCameraViewController') 
cvp

@emkay_online try this but not finished, only a first step to help you. No time more now

from objc_util import *
import ui
load_framework('VisionKit')

@on_main_thread 
def main():
    VNDocumentCameraViewController = ObjCClass('VNDocumentCameraViewController').alloc().init()
    #print(dir(VNDocumentCameraViewController))

    uiview = ui.View()
    uiview.frame = (0,0,500,500)
    uiview.present('sheet')

    objc_uiview = ObjCInstance(uiview)
    SUIViewController = ObjCClass('SUIViewController')
    vc = SUIViewController.viewControllerForView_(objc_uiview)  

    VNDocumentCameraViewController.setModalTransitionStyle_(3)  
    vc.presentViewController_animated_completion_(VNDocumentCameraViewController, True, None)

main() 
emkay_online

You are amazing - thank you. You have saved me so much frustration and guessing.

Thank you so much,

Martin

cvp

@emkay_online next step but documentCameraViewController_didFinishWith_ not called.
Either my bug either iOS bug (other people seem to meet the same problem)

from objc_util import *
import ui
load_framework('VisionKit')

#========= VNDocumentViewController 
def documentCameraViewController_didFinishWith_(_self, _cmd, _controller, _scan):
    print('documentCameraViewController_didFinishWith_')
    VNDocumentViewController = ObjCInstance(_controller)
    scan = ObjCInstance(_scan)  # VNDocumentCameraScan
    #print(scan)


methods = [documentCameraViewController_didFinishWith_,]
protocols = ['VNDocumentCameraViewControllerDelegate']
try:
        MyVNDocumentCameraViewControllerDelegate = ObjCClass('MyVNDocumentCameraViewControllerDelegate')
except:
    MyVNDocumentCameraViewControllerDelegate = create_objc_class('MyVNDocumentCameraViewControllerDelegate', methods=methods, protocols=protocols)

@on_main_thread 
def main():
    VNDocumentCameraViewController = ObjCClass('VNDocumentCameraViewController').alloc().init()
    #print(dir(VNDocumentCameraViewController))
    delegate = MyVNDocumentCameraViewControllerDelegate.alloc().init()
    VNDocumentCameraViewController.delegate = delegate      

    uiview = ui.View()
    uiview.frame = (0,0,500,500)
    uiview.present('sheet')

    objc_uiview = ObjCInstance(uiview)
    SUIViewController = ObjCClass('SUIViewController')
    vc = SUIViewController.viewControllerForView_(objc_uiview)  

    VNDocumentCameraViewController.setModalTransitionStyle_(4)  
    vc.presentViewController_animated_completion_(VNDocumentCameraViewController, True, None)

main() 
emkay_online

Thank you for your code. I'm as puzzled as you as to why it doesn't work. I added the other methods in case an error or a cancel was being returned, but nothing.

I'll keep playing and hope I stumble on the answer.

Thanks again.

Martin

cvp

@emkay_online I did it also and cancel reacts

def documentCameraViewController_didFailWithError_(_self, _cmd, _controller, _error):
    print('documentCameraViewController_didFailWithError_')

def documentCameraViewControllerDidCancel_(_self, _cmd, _controller):
    print('documentCameraViewControllerDidCancel_')

methods = [documentCameraViewController_didFinishWith_, documentCameraViewController_didFailWithError_, documentCameraViewControllerDidCancel_]
cvp

Cancel is called but you still need to tap save to terminate...

stephen

@cvp the recent iPadOS update seems to have effected accesing different areas of the sandbox. and unless someone finds the new access classes, Apple will have some upset developers on thier hands lol

JonB

Have you checked supported?

cvp

@JonB I tried but does not exist in dir(VNDocumentCameraViewController).
And the process seems to be realized, you receive the camera, take a photo, system aligns a frame, you tap save ..... and the viewcontroller closes but does not call the didfinish

mikael

@cvp, @JonB, same behavior on iPhone.

isSupported returns True.

cvp

@mikael @JonB strange, on my iPad mini 4, three different prints and their results

    print('VNDocumentCameraViewController.isSupported')
    print(VNDocumentCameraViewController.isSupported)
    print()
    print('dir(VNDocumentCameraViewController.isSupported)')
    print(dir(VNDocumentCameraViewController.isSupported))
    print()
    print('VNDocumentCameraViewController.isSupported()')
    print(VNDocumentCameraViewController.isSupported())
'''
VNDocumentCameraViewController.isSupported
<objc_util.ObjCInstanceMethodProxy object at 0x113ca84e0>

dir(VNDocumentCameraViewController.isSupported)
['__call__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'method_cache', 'name', 'obj']

VNDocumentCameraViewController.isSupported()
Traceback (most recent call last):
  File "_ctypes/callbacks.c", line 234, in 'calling callback function'
  File "/var/containers/Bundle/Application/34BAEE1A-BC33-4D6F-A0C1-B733E4991F31/Pythonista3.app/Frameworks/Py3Kit.framework/pylib/site-packages/objc_util.py", line 1066, in OMMainThreadDispatcher_invoke_imp
    retval = func(*args, **kwargs)
  File "/private/var/mobile/Containers/Shared/AppGroup/668A7D98-7216-47ED-917D-AA0B6173167E/Pythonista3/Documents/VNDocumentCameraViewController.py", line 37, in main
    print(VNDocumentCameraViewController.isSupported())
  File "/var/containers/Bundle/Application/34BAEE1A-BC33-4D6F-A0C1-B733E4991F31/Pythonista3.app/Frameworks/Py3Kit.framework/pylib/site-packages/objc_util.py", line 798, in __call__
    method_name, kwarg_order = resolve_instance_method(obj, self.name, args, kwargs)
  File "/var/containers/Bundle/Application/34BAEE1A-BC33-4D6F-A0C1-B733E4991F31/Pythonista3.app/Frameworks/Py3Kit.framework/pylib/site-packages/objc_util.py", line 405, in resolve_instance_method
    raise AttributeError('No method found for %s' % (name,))
AttributeError: No method found for isSupported
''' 
cvp

@mikael @jonb class without alloc and init returns True

    VNDocumentCameraViewController = ObjCClass('VNDocumentCameraViewController')#.alloc().init()
    print('VNDocumentCameraViewController.isSupported()')
    print(VNDocumentCameraViewController.isSupported()) 
JonB

I bet the function name is wrong, or the arguments are wrong.

Shouldn't it be
documentCameraViewController_didFinishWithScan_?

You could also swizzle respondsToSelector on your delegate, and log what gets queried.
Have you checked what the protocol

cvp

@JonB doc gives this name and we are not alone with this problem, see here

cvp

@JonB said:

documentCameraViewController_didFinishWithScan_

You're right. I sincerely don't understand how I could do so big mistake.
Thanks, I'll try

Γ‰dit: save button does not close the view

cvp

The delegate is called but the view is not closed as I though thus I have proven by writing in a file. And restart Pythonista because I could not close the view.

def documentCameraViewController_didFinishWithScan_(_self, _cmd, _controller, _scan):
    with open('a.txt',mode='wt') as fil:
        fil.write('documentCameraViewController_didFinishWithScan_')
    print('documentCameraViewController_didFinishWithScan_')
    VNDocumentViewController = ObjCInstance(_controller)
    scan = ObjCInstance(_scan)  # VNDocumentCameraScan
    #print(scan) 
.
.
.

methods = [documentCameraViewController_didFinishWithScan_, documentCameraViewController_didFailWithError_, documentCameraViewControllerDidCancel_]

JonB

https://developer.apple.com/documentation/visionkit/vndocumentcameraviewcontrollerdelegate/3194635-documentcameraviewcontroller?language=objc

Has the Scan at the end.

I think in Swift they auto complete the nouns (similar to how obj_util let's you omit the nouns if you name them in the kwargs)

JonB

@cvp perhaps you need to dismiss the _controller?

cvp

@JonB I was busy to post this

def documentCameraViewController_didFinishWithScan_(_self, _cmd, _controller, _scan):
    VNDocumentViewController = ObjCInstance(_controller)
    scan = ObjCInstance(_scan)  # VNDocumentCameraScan
    #print(scan)
    VNDocumentViewController.dismissViewControllerAnimated_completion_(True, None)
JonB

@cvp re: getting the name wrong, in Swift, they omit the "nouns", i think. So the docs show the selectors without nouns. In objc_util it lets you do this too, if you use kwargs.

Re: dismissing... So did that work? If not, maybe need to on_main_thread it?

cvp

scan returns an UIImage

    print(scan.imageOfPageAtIndex_(0)) 
cvp

@JonB said:

documentCameraViewController_didFinishWithScan_

One more time, thanks for your help.

I really become too old for this stuff. Shame on me, I'm so sad to have not seen that myself.
Sorry to all people who tried to help us.😒πŸ˜ͺ

cvp

@emkay_online quick an dirty to see the UIImage

    import ctypes
    from PIL import Image
    UIImage = scan.imageOfPageAtIndex_(0)   
    # Save UIImage to jpg file
    func = c.UIImageJPEGRepresentation
    func.argtypes = [ctypes.c_void_p, ctypes.c_float]
    func.restype = ctypes.c_void_p
    x = ObjCInstance(func(UIImage.ptr, 1.0))
    x.writeToFile_atomically_('test.jpg', True)
    # Display jpg file
    image = Image.open("test.jpg")
    image.show()
cvp

@emkay_online operational

from objc_util import *
import ui
load_framework('VisionKit')

def documentCameraViewController_didFinishWithScan_(_self, _cmd, _controller, _scan):
    VNDocumentViewController = ObjCInstance(_controller)
    scan = ObjCInstance(_scan)  # VNDocumentCameraSca

    UIImage = scan.imageOfPageAtIndex_(0)   
    ObjCInstance(VNDocumentViewController.ui_view['iv']).setImage_(UIImage)

    VNDocumentViewController.dismissViewControllerAnimated_completion_(True, None)

def documentCameraViewController_didFailWithError_(_self, _cmd, _controller, _error):
    print('documentCameraViewController_didFailWithError_')

def documentCameraViewControllerDidCancel_(_self, _cmd, _controller):
    print('documentCameraViewControllerDidCancel_')

methods = [documentCameraViewController_didFinishWithScan_, documentCameraViewController_didFailWithError_, documentCameraViewControllerDidCancel_]
protocols = ['VNDocumentCameraViewControllerDelegate']
try:
        MyVNDocumentCameraViewControllerDelegate = ObjCClass('MyVNDocumentCameraViewControllerDelegate')
except:
    MyVNDocumentCameraViewControllerDelegate = create_objc_class('MyVNDocumentCameraViewControllerDelegate', methods=methods, protocols=protocols)

@on_main_thread 
def main():
    VNDocumentCameraViewController = ObjCClass('VNDocumentCameraViewController').alloc().init()
    delegate = MyVNDocumentCameraViewControllerDelegate.alloc().init()
    VNDocumentCameraViewController.delegate = delegate  

    uiview = ui.View()
    iv = ui.ImageView(name='iv')
    iv.frame = uiview.bounds
    iv.flex = 'wh'
    uiview.add_subview(iv)
    uiview.present('fullscreen')
    VNDocumentCameraViewController.ui_view = uiview # to pass to delegate

    objc_uiview = ObjCInstance(uiview)
    SUIViewController = ObjCClass('SUIViewController')
    vc = SUIViewController.viewControllerForView_(objc_uiview)  
    vc.presentViewController_animated_completion_(VNDocumentCameraViewController, True, None)

main() 
mikael

@cvp, good stuff. It gives the straightened-out picture, right? Can I get a multi-page PDF, or is the framework for single pictures only?

cvp

@mikael no,no, the framework is for multiple scans but my little script not.
I think that if you don't dismiss the camera view controller, you could continue to scan multiple images, but I did not test it.

JonB

i think the document object guves you UIImages only, but you could create a pdf using some UIKit functions:

https://developer.apple.com/documentation/uikit/images_and_pdf?language=objc

(you need to acces the functions using c.UIGraphicsBeginPDFContextToFile etc and set argtypes and restype)

I think the basic flow is, you have to create a pdf context to a file, begn a pdf page, draw your content (UiImage's drawAtPoint ), begn a new page, etc, then finally end the context.

cvp

Do you know that PIL save has an option to save as pdf?

pil_image.save('File.pdf',"PDF",resolution=100.0) 
stephen

@cvp this would of been wonderfull information about 3 months go lol

JonB

@cvp can PIL do multipage PDF? I guess you still would need to do the ui2pil type business.

mikael

@JonB said:

@cvp can PIL do multipage PDF? I guess you still would need to do the ui2pil type business.

Which I guess would make it slow in a way that this type of use case does not reslly tolerate.

Playing with the idea of a truly personalized doc scanner that would put the scan whete it needs to go. Also, combining this with the text recognition stuff would be good (as soon as they get international characters right).

JonB

But the built in objc methods for writing to PDF (see link above) ought to be reasonably fast. I haven't tried.

cvp

@JonB said:

can PIL do multipage PDF? I guess you still would need to do the ui2pil type business.

I don't think it does multi pages, but easy to do it yourself via PdfFileMerger.
Yes for ui2pil

cvp

@JonB said:

But the built in objc methods for writing to PDF (see link above) ought to be reasonably fast. I haven't tried

I have already done the inverse (PDF -> image) via Objectivec context.. quick πŸ™„

emkay_online

@cvp Thank you so much for this code. It works a treat and really gives me a solid foundation to build on.

Also thank you for giving me a good grounding in the objc_util module - it's certainly a complex one, especially for someone with no iOS coding experience.

Thank you.

cvp

@emkay_online you should dismiss the viewcontroller in the didcancel delegate, not in the didfinish like I did for testing only. This if you want to process multiple scans

cvp

@JonB said:

But the built in objc methods for writing to PDF (see link above) ought to be reasonably fast. I haven't tried.

Please applaus. ok, I exaggerate πŸ˜‚, but was not so easy (for me)

from objc_util import *
import ui

UIGraphicsGetCurrentContext = c.UIGraphicsGetCurrentContext
UIGraphicsGetCurrentContext.restype = c_void_p
UIGraphicsGetCurrentContext.argtypes = []

UIGraphicsBeginPDFContextToData = c.UIGraphicsBeginPDFContextToData
UIGraphicsBeginPDFContextToData.restype = None
UIGraphicsBeginPDFContextToData.argtypes = [c_void_p, CGRect, c_void_p]

UIGraphicsBeginPDFPage = c.UIGraphicsBeginPDFPage
UIGraphicsBeginPDFPage.restype = None
UIGraphicsBeginPDFPage.argtypes = []

UIGraphicsEndPDFContext = c.UIGraphicsEndPDFContext
UIGraphicsEndPDFContext.restype = None
UIGraphicsEndPDFContext.argtypes = []

pdfdata = NSMutableData.alloc()#.init()
ui_image = ui.Image.named('test:Lenna')
uiimage = ObjCInstance(ui_image)
w,h = ui_image.size
frame = CGRect(CGPoint(0, 0), CGSize(w, h)) 
UIGraphicsBeginPDFContextToData(pdfdata, frame, None)
UIGraphicsBeginPDFPage()
pdfContext = UIGraphicsGetCurrentContext()
uiimage.drawInRect_(frame)
UIGraphicsEndPDFContext()
# just to be sure that pdfdata is ok
with open('a.pdf',mode='wb') as fil:
    b = nsdata_to_bytes(pdfdata)
    fil.write(b) 
JonB

πŸ‘πŸ‘πŸ‘

If you are going straight to file, you might consider using

https://developer.apple.com/documentation/uikit/1623927-uigraphicsbeginpdfcontexttofile?language=objc

instead of creating the context as nsdata. Might save a few lines.

Also, you can use drawAtPoint instead of drawInRect, so that you don't need to specify w,h.

I think if you omit the frame when you create the context, it also create an 8.5x11.

cvp

@JonB said:

so that you don't need to specify w,h.

Yes but UIGraphicsBeginPDFContextToData needs it also

Γ‰dit: sorry, written before reading your last line

cvp

I don't say it is the best way, it was only an exercice for me.

cvp

@JonB said:

instead of creating the context as nsdata.

I added the file lines only to test /check pdfdata.