• @sodoku Shortest code I can give, without searching first the rectangles containing text, but assuming the image is a square grid with only one digit per cell.
    You have to set (or ask, up to you) the number of cells per row/column, here 9, and the percentage (here 15) of the cell dimensions for the grid-lines. Even if the image seems to be entirely displayed, it is not fully because it is overriden by buttons where background_image is the cropped part which will be transmitted to the VNCoreMLRequest for trying to recognize the digit.
    And, as you can see in my example, it does not work correctly on my ipad mini 4 under ios 13 and latest Pythonista beta. You could try on your idevice but I can't help more, sorry. Hoping it will be better for you 😢

    import os import io import photos import dialogs from PIL import Image from objc_util import ObjCClass, nsurl, ns import ui MODEL_FILENAME = 'MNIST.mlmodel' #MODEL_FILENAME = 'MNISTClassifier.mlmodel' #MODEL_FILENAME = 'OCR.mlmodel' #MODEL_FILENAME = 'Alphanum_28x28.mlmodel' # Use a local path for caching the model file MODEL_PATH = os.path.join(os.path.expanduser('~/Documents/'), MODEL_FILENAME) # Declare/import ObjC classes: MLModel = ObjCClass('MLModel') VNCoreMLModel = ObjCClass('VNCoreMLModel') VNCoreMLRequest = ObjCClass('VNCoreMLRequest') VNImageRequestHandler = ObjCClass('VNImageRequestHandler') def pil2ui(imgIn): with io.BytesIO() as bIO: imgIn.save(bIO, 'PNG') imgOut = ui.Image.from_data(bIO.getvalue()) del bIO return imgOut def load_model(): global vn_model ml_model_url = nsurl(MODEL_PATH) # Compile the model: c_model_url = MLModel.compileModelAtURL_error_(ml_model_url, None) # Load model from the compiled model file: ml_model = MLModel.modelWithContentsOfURL_error_(c_model_url, None) # Create a VNCoreMLModel from the MLModel for use with the Vision framework: vn_model = VNCoreMLModel.modelForMLModel_error_(ml_model, None) return vn_model def _classify_img_data(img_data): global vn_model # Create and perform the recognition request: req = VNCoreMLRequest.alloc().initWithModel_(vn_model).autorelease() handler = VNImageRequestHandler.alloc().initWithData_options_(img_data, None).autorelease() success = handler.performRequests_error_([req], None) if success: best_result = req.results()[0] label = str(best_result.identifier()) confidence = best_result.confidence() return {'label': label, 'confidence': confidence} else: return None def classify_image(img): buffer = io.BytesIO() img.save(buffer, 'JPEG') img_data = ns(buffer.getvalue()) return _classify_img_data(img_data) def classify_asset(asset): mv = ui.View() mv.background_color = 'white' im = ui.ImageView() pil_image = asset.get_image() print(pil_image.size) ui_image = asset.get_ui_image() n_squares = 9 d_grid = 15 # % around the digit wim,him = pil_image.size ws,hs = ui.get_screen_size() if (ws/hs) < (wim/him): h = ws*him/wim im.frame = (0,(hs-h)/2,ws,h) else: w = hs*wim/him im.frame = ((ws-w)/2,0,w,hs) print(wim,him,ws,hs) mv.add_subview(im) wi = im.width hi = im.height im.image = ui_image im.content_mode = 1 #1 mv.frame = (0,0,ws,hs) mv.present('fullscreen') dx = wim/n_squares dy = him/n_squares d = dx*d_grid/100 dl = int((wi/n_squares)*d_grid/100) for ix in range(n_squares): x = ix*dx for iy in range(n_squares): y = iy*dy pil_char = pil_image.crop((int(x+d),int(y+d),int(x+dx-d),int(y+dy-d))) l = ui.Button() l.frame = (int(ix*wi/n_squares)+dl, int(iy*hi/n_squares)+dl, int(wi/n_squares)-2*dl, int(hi/n_squares)-2*dl) l.border_width = 1 l.border_color = 'red' l.tint_color = 'red' ObjCInstance(l).button().contentHorizontalAlignment= 1 # left l.background_image = pil2ui(pil_char) im.add_subview(l) l.title = classify_image(pil_char)['label'] def main(): global vn_model vn_model = load_model() all_assets = photos.get_assets() asset = photos.pick_asset(assets=all_assets) if asset is None: return classify_asset(asset) if __name__ == '__main__': main()

  • Well 💩.

    Thanks for the info.

  • @ccc, I don't want to come off as rude, but the question was a legitimate one and your reply states the obvious. Do you work in some means on this product to give a more specific answer than "it will be released when it's ready to be released? I would certainly hope it will not be released if it's not ready so this begs the question:
    When will Pythonista 3.3 be ready? Please don't answer "it will be ready when it's ready" lol.

  • @tomomo, I suggest you check your understanding of which sshtunnel address is which. Is your jump server address really load_url should use, i.e. the local endpoint of the tunnel - you can use the local_bind_address to specify the port.

    Also, would recommend the with ... as tunnel: format to manage the opening and closing of the tunnel.

    (ScrollView is not needed, WebView can scroll on its own.)

  • def pil2ui(pil_image): buffer = io.BytesIO() pil_image.save(buffer, format='PNG') return ui.Image.from_data(buffer.getvalue())

    is memory leaking buffer which has been proven to crash Pythonista when multiple images are processed. A better approach is to use a context manager to force the .close().

    def pil2ui(pil_image): with io.BytesIO() as buffer: pil_image.save(buffer, format='PNG') return ui.Image.from_data(buffer.getvalue())
  • @mikael in my use case, I'm actually making a Gestures instance per-custom-widget. Since each of my widgets can have 2 or 3 gestures, it is simpler to just let each one have its own Gestures instance and install the various recognizers on each one. Gestures isn't particularly heavyweight, especially without the internal ui.Button instance, and then I don't need any global tracking of who has gestures installed, since when the widget goes away, it takes the gestures instance with it.

    Creating a little handler method in the gesture delegate class, which can be attached to a gesture recognizer is as simple as:

    ...existing gesture delegate definition... def handleGesture_(_self, _cmd, recognizer): import objc_util delegate = objc_util.ObjCInstance(_self) if not delegate: return recognizer = objc_util.ObjCInstance(recognizer) if not recognizer: return gestures = delegate._gestures() if not gestures: return gestures._handleGestureRecognizer(recognizer) methods = [... handleGesture_]

    Then, where you make the recognizers, instead of the whole button action thing, with all the associated bookkeeping:

    recognizer = \ objc_util.ObjCClass(recognizer_type).alloc() recognizer.initWithTarget_action_(self._delegate, objc_util.sel("handleGesture:")).autorelease() view.objc_instance.addGestureRecognizer_(recognizer)
  • With a fresh install on 13.2.3 iPhone 11 Pro, I get a permissions error reading the default iCloud path in the app. I can create files but not read them.

    With the os.rmdir suggestion, I was able to delete the folder (Verified in Files app), but os.mkdir failed with “ FileExistsError: [Errno 17] File exists:”

  • Thanks for all the remarks.

    I want to disable WIFI during a gig because some synchonisation services (I use iCloud and Google Drive to synchronize my settings, apple update) use WIFI and might cause CPU spikes and so audio spikes.
    Perhaps it might not be a problem when this is an ad-hoc connection .

    Mulitpeer works great in Pythonista, but no library for regular python...
    Searched for hours.

    I might try UDP broadcast just to get the few bytes over to python on my MacBook

  • Indentation is super important in Python. Remove all indentation from your last two lines.

  • @cvp Thank you for the several amendments.
    I will try my best to change the contents of the alert dynamically.

  • @brianolive, seems that these days mypy depends on a typed-ast package, which uses C and cannot be installed, according to this earlier thread.

  • @cvp
    Thank you very much.
    Worked great.

  • @mikael
    You could try installing an old version of black from before regex was used, i.e. prior to 13 October 2019, I think... https://github.com/psf/black/pull/1047

  • I tried all possible combination, no midi is recieved by any clients. I spent way too much time on this....
    Would be nice if Pythonista had a coreMidi module installed...

    Thanks for all the help!

Internal error.

Oops! Looks like something went wrong!