Forum Archive

Photos Module - Start Camera from Button

simple

Hi, I want to create a button that launches the camera (...and saves the photo in the next step). Somehow whenever I press the button with photos.capture_image() in the script below, Pythonsista freezes. I thought that I could get around this with masking it with @ui.in_background but that doesn't work. Some idea how to trigger the camera from the button? (Note: If I write the photos.capture_image() outside of the ui.View class it launches the camera)

import ui
import console
import clipboard
import photos

class Extracter(ui.View):
  def __init__(self):

  # Some UI Elements 
  (...)

  # Take Photo Button
  self.take_photo = ui.Button(flex = 'LR', title = 'Take Photo')
  self.take_photo.action = self.take_photo_action
  # Some Button Styles
  (...)

  # Take Photo Action
  @ui.in_background
  def take_photo_action(self, sender):
    image = photos.capture_image()

if __name__=='__main__':
  view = Extracter()
cvp

This code functions

import ui
import console
import clipboard
import photos

class Extracter(ui.View):
    def __init__(self):

        # Some UI Elements 
        #(...)

        # Take Photo Button
        self.take_photo = ui.Button(flex = 'LR', title = 'Take Photo')
        self.take_photo.action = self.take_photo_action
        # Some Button Styles
        #(...)
        self.add_subview(self.take_photo)

    # Take Photo Action
    @ui.in_background
    def take_photo_action(self, sender):
        image = photos.capture_image()
        photos.save_image(image)

if __name__=='__main__':
    view = Extracter()
    view.present('sheet')

I also got some problems after several tests and I have had to cancel the app before retrying.
Remove Pythonista app from tasks list (4/5 fingers up, slide app up) and restart Pythonista.

simple

On iPhone 6 Plus Pythonista stops responing, even with the subview added, which I intentionally had left out (I thought it would make the code here cleaner, but you are right to add it since it is needed). I've to remove it from the switch to reopen the application.

Sometimes it stops after the press, sometimes the 'camera sheet' slides up but is all black and in the upper right corner is the 'Take Photo' button. After that it stops responing too.

cvp

Strange, I don't have these problems on an IPad Mini 4.
I take the photo, then click "use photo", then it's saved in Photos.
Sorry, I can't help 🤕

Phuket2

@cvp , @simple , in Pythonista py3 at least photos.save_image(image) is deprecated.
The below is in the help file.
save_image(image) – Save a PIL image or ui.Image to the camera roll. Use create_image_asset() instead.

I couldn't get it working. But I am sure it's just me

cvp

Thanks for the info (just forgotten 🙄) but the problem does not come from the save action (I think but I'm never more sure of nothing) but from the capture action, unless a guru tells me the inverse!

simple

In the initial code sample I don't save the received image, and the application stops responding, so that shouldn't be the problem.

The save_image(image) should still work since it is available for backwards compatibility, but was replaced for create_image_asset().

But first I have to be able to take the image :)

(Edit: I've tried the same script on an iPhone 6 and it crashes the application too. I've also checked that Pythonista has access to the camera.)

cvp

Very strange.
Do you use Pythonista 3 with default Python 3? Just to be sure that you have the same environment as I've, because the script I posted is ok for me.

Phuket2

For me, I can take the image, it's the saving that fails. Does not crash, but generates errors when I click use photo. Just mention to give insight. I am on a iPad Pro 12 inch , latest iOS latest Pythonista beta 3

simple

I've Pythonista 3 installed with default interpreter 3.5 on latest iOS 9.3.3

@cvp When you launch the script, do you have to press the button to activate the camera? If I launch it, the camera activates first (I don't see the sheet with the button).

cvp

I launch, I see the camera screen, I press the camera button, I hear a click, I press the "use photo" and the script saves the photo

simple

But that's not how the code should work, or did I understand that wrong (explained it wrong)?

It should first show the ui.view sheet with a button called 'Take Photo'. Then you press that button and the camera launches and then you take the photo.

JonB

The posted code worked for me, but one thing you could try in cases like this is to add a ui.delay with a long enough delay to let the camera view fully dismiss.

   # Take Photo Action
    @ui.in_background
    def take_photo_action(self, sender):
        image = photos.capture_image()
        def a():
           photos.save_image(image)
           self.close()
        ui.delay(a,.5)
cvp

@simple sorry, I forgot to say that I see first the ui with the "take photo" button and of course I press it.

cvp

To see the ui sheof t with your "take photo" button, you have first to present the view.

simple

If you first see the UI button the code works as it should.

On my iPhones if I launch the script the camera opens immediately and I don't see the ui.view sheet.

(Edit: I've been mistaken, I do see the ui.view sheet with the button.. I thought that the black ui.view was the camera and not the ui.view. Still, if I press the button the application crashes.)

cvp

When you say crash, you mind terminate?
If I go in the settings set that Pythonista can't use camera, I also have this abnormal end.
If I reset on in the settings, I need to remove the app from active tasks to let the script function.

simple

Ok, I got it working.

I've reduced the code to a minimum and checked what happens. With this, Pythonista freezes, I have to remove it from the iOS switch and restart the application.

import ui
import photos

class Extracter(ui.View):
    def __init__(self):
        print('Executed')
        photos.capture_image()

if __name__=='__main__':
    view = Extracter()

After that I tried to put the photo.capture_image() out of the class, that worked before and still does but is not what I want.

import ui
import photos

photos.capture_image()

class Extracter(ui.View):
    def __init__(self):
        print('Executed')

if __name__=='__main__':
    view = Extracter()

Now after your post @cvp I've double checked that Pythonista has camera permissions. I've disabled it and run the first script, and the camera launched (!) but without a picture. I went back to settings and enabled it again, and now it works.

I've tested that on the second iPhone. If you run the script it freezes as before, disable and enable camera permissions again, and it works.

cvp

Eureka!!!

simple

I've now tested a few code snippets and there still seems to be a problem.

The same script launched several times sometimes activates the camera and sometimes not. If I got back to the button activated camera it doesn't work no matter what I do.

cvp

Sorry, I don't think I could help more.

cvp

You could try this code, using objective c to start the camera but it is not yet finished.
The code to get the captured picture needs to use delegate methods and is not yet written.
It's a little bit too complex for me and I would need some time...
But try it, only to see if executing the script several times also generate a crash or not.

import ui
import console
import clipboard
from objc_util import *
import photos

SUIViewController = ObjCClass('SUIViewController')

class Extracter(ui.View):
    def __init__(self):

        # Some UI Elements 
        #(...)

        # Take Photo Button
        self.take_photo = ui.Button(flex = 'LR', title = 'Take Photo')
        self.take_photo.action = self.take_photo_action
        # Some Button Styles
        #(...)
        self.add_subview(self.take_photo)

    # Take Photo Action
    @ui.in_background
    def take_photo_action(self, sender):

        # Show camera
        picker = ObjCClass('UIImagePickerController').alloc().init()
        picker.mame = 'picker'
        picker.delegate = self
        picker.allowsEditing = False
        picker.sourceType = 1 # UIImagePickerControllerSourceTypeCamera
        super_view = sender.superview
        super_view_pntr = ObjCInstance(super_view)
        vc = SUIViewController.viewControllerForView_(super_view_pntr)
        vc.presentModalViewController_animated_(picker, True)

        # To get the captured photo, we need to work with delegates
        # not yet developped


if __name__=='__main__':
    view = Extracter()
    view.present('sheet')

cvp

One step more: delegate written but I'm still lost in converting the UIImage into an image...
The photo is taken, the photo is "used" and sent to the script via the delegate function, see the console printing, ...but img not yet available for Python code, I've not yet found why.

import ui
import console
import clipboard
from objc_util import *


def imagePickerController_didFinishPickingMediaWithInfo_(self,cmd,picker,info):

    pick = ObjCInstance(picker)
    # Set delegate to nil, and release its memory:
    pick.setDelegate_(None)
    ObjCInstance(self).release()
    # Dismiss the sheet:
    pick.dismissViewControllerAnimated_completion_(True, None)

    infos = ObjCInstance(info)
    print(infos)
    img = infos['UIImagePickerControllerEditedImage']   # UIImage 
    #img = infos['UIImagePickerControllerOriginalImage']
    print(img)

    #img.show()
    #photos.save_image(img)

SUIViewController = ObjCClass('SUIViewController')

MyPickerDelegate = create_objc_class('MyPickerDelegate',
methods=[imagePickerController_didFinishPickingMediaWithInfo_], protocols=['UIImagePickerControllerDelegate'])

class Extracter(ui.View):
    def __init__(self):

        # Take Photo Button
        self.take_photo = ui.Button(flex = 'LR', title = 'Take Photo')
        self.take_photo.action = self.take_photo_action
        self.add_subview(self.take_photo)

    # Take Photo Action
    #@ui.in_background
    @on_main_thread
    def take_photo_action(self, sender):

        # Show camera
        picker = ObjCClass('UIImagePickerController').alloc().init()

        delegate = MyPickerDelegate.alloc().init()
        picker.setDelegate_(delegate)

        picker.allowsEditing = True
        picker.sourceType = 1 # UIImagePickerControllerSourceTypeCamera
        super_view = sender.superview
        super_view_pntr = ObjCInstance(super_view)
        vc = SUIViewController.viewControllerForView_(super_view_pntr)
        vc.presentModalViewController_animated_(picker, True)

# Protect against import    
if __name__ == '__main__':
    view = Extracter()
    view.present('sheet')
simple

Thanks for all the help, I am glad there are people like you out there! I will see if your code works, but I assume there is a problem with Pythonista on iOS on certain devices. I've send a friend of mine the file and he was able to run it on his iPad, but on his iPhone (iPhone 6 on iOS 9.3.3) it crashed the application.

I will backup all Pythonista modules and scripts installed, remove and reinstall the application fresh, to see if something else causes the crash.

cvp

Ok, good luck

cvp

Last try
I have not been able to convert an Objective-C UIImage into a Pythonista ui.Image/PIL image, thus I did find this way through a lot of searches, not very nice but it functions and will allow you to test this "start camera - take photo - get image - display image" without using photos module, this perhaps could function on your iPhone 6 and, in this case, it could perhaps help @omz to debug the photos.capture which crashes on your device.
Anyway, I did learn a lot on objective-c, thus I didn't loose my time (I'm retired, thus I've a lot of free time) and I definitely love this app!

import ui
import console
import clipboard
from objc_util import *
import ctypes
from PIL import Image


def imagePickerController_didFinishPickingMediaWithInfo_(self,cmd,picker,info):

    pick = ObjCInstance(picker)
    # Set delegate to nil, and release its memory:
    pick.setDelegate_(None)
    ObjCInstance(self).release()
    # Dismiss the sheet:
    pick.dismissViewControllerAnimated_completion_(True, None)

    # Get UIImage
    infos = ObjCInstance(info)
    print(infos)
    img = infos['UIImagePickerControllerEditedImage']   # UIImage 
    # or
    #img = infos['UIImagePickerControllerOriginalImage']
    print(img)
    print(img.ptr)

    # I can't convert UIImage into ui.Image 🤕

    # 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(img.ptr, 1.0))
    x.writeToFile_atomically_('test.jpg', True)

    # Display jpg file
    image = Image.open("test.jpg")
    image.show()

SUIViewController = ObjCClass('SUIViewController')

MyPickerDelegate = create_objc_class('MyPickerDelegate',
methods=[imagePickerController_didFinishPickingMediaWithInfo_], protocols=['UIImagePickerControllerDelegate'])

class Extracter(ui.View):
    def __init__(self):

        # Take Photo Button
        self.take_photo = ui.Button(flex = 'LR', title = 'Take Photo')
        self.take_photo.action = self.take_photo_action
        self.add_subview(self.take_photo)

    # Take Photo Action
    #@ui.in_background
    @on_main_thread
    def take_photo_action(self, sender):

        # Show camera
        picker = ObjCClass('UIImagePickerController').alloc().init()

        delegate = MyPickerDelegate.alloc().init()
        picker.setDelegate_(delegate)

        picker.allowsEditing = True
        picker.sourceType = 1 # UIImagePickerControllerSourceTypeCamera
        super_view = sender.superview
        super_view_pntr = ObjCInstance(super_view)
        vc = SUIViewController.viewControllerForView_(super_view_pntr)
        vc.presentModalViewController_animated_(picker, True)

# Protect against import    
if __name__ == '__main__':
    view = Extracter()
    view.present('sheet')