Using USB API (MTP) on MacOS

Has anyone had any luck using the USB api on mac? I have installed libmtp but the camera is not recognized. Is there a ptp lib that works? Ideally there would be a python lib I could use but looking for any working ‘hello world’ example at this point.

Alternatively, is it possible to use libusb rather than ptp? I do see the device using libusb.

Thanks!

Have you tried libmtp on Mac with Python bindings?

http://macappstore.org/libmtp/

I think I did get it working on Linux with the Python bindings. However, everyone seemed to be writing bash scripts, so I stopped. I think there were a few glitches with the Python bindings.

Does libptp not compile on Mac?

People have gotten it running in the past.

https://micro-manager.org/wiki/GPhoto

I did get libmtp and pymtp installed but it is not seeing the camera:

>>> import pymtp
>>> pymtp.MTP().detect_devices()
[] 

or

>>> mtp-detect
libmtp version: 1.1.16

Listing raw device(s)
  No raw devices found.

There are no Theta cameras listed as supported by libmtp.

libptp is proving difficult to build but I will try to get that working. Thanks!

1 Like

For prototyping, can you use Vagrant or straight VirtualBox and run Linux inside the VM?

That way, you can test out your app concepts quickly instead of diving into compiling the library.

Maybe you can even deploy with Linux in a VM?

gphoto2 works well on linux and it seems to be installable on MacOs.
I haven’t a Mac but on Linux Raspbian I need to uninstall sort of automount to make it work

Hugues

2 Likes

Additional tests with Automount

FWIW I have had the best luck with gphoto2. Older versions (2.5.4) will not set shutter speed but I am having luck with the latest (2.5.23).

List all shutter speeds:

gphoto2 --get-config=d00f

Set shutter speed iso and fstop:

gphoto2 --set-config-index=d00f=0 --set-config=500f=80 --set-config=5007=560
2 Likes

Thanks for the report. It’s surprising that there’s a difference between 2.5.4 and 2.5.23. This information should be helpful to other people. :slight_smile:

Here is some code using the gphoto2 python module…I have a UI that uses this to shoot HDRI exposures…an example in _unittest(). Not totally polished but maybe useful to others. Works with Z1:

"""
USB api for added performance over http

Theta api reference:
https://developers.theta360.com/en/docs/v2/usb_reference/

Unable to get mtp or ptp to connect to the camera; After some pain was able to get gphoto2 working
"""

import os
import time

import gphoto2 as gp

# Properties
SHUTTER_SPEED = 'd00f'
EXPOSURE_INDEX = '500f'
F_NUMBER = '5007'
AUDIO_VOLUME = '502c'
COLOR_TEMPERATURE = 'd813'
EXPOSURE_PROGRAM_MODE = '500e'

# milliseconds
TIMEOUT = 10
TIMEOUT_CAPTURE_DNG = 10000


def wait_for_event(camera, timeout=TIMEOUT, event_type=gp.GP_EVENT_TIMEOUT):
    """
    Wait for event_type to to be triggered.
    :param camera:
    :param timeout:
    :param event_type:
    :return: event_data
    """
    while True:
        _event_type, event_data = camera.wait_for_event(timeout)
        if _event_type == gp.GP_EVENT_TIMEOUT:
            return
        if _event_type == event_type:
            return event_data


def set_config_by_index(config, index):
    """Set config using choice index"""
    value = config.get_choice(index)
    config.set_value(value)

    return config


# def list_files(camera, path='/'):
#     result = []
#     # get files
#     for name, value in camera.folder_list_files(path):
#         result.append(os.path.join(path, name))
#     # read folders
#     folders = []
#     for name, value in camera.folder_list_folders(path):
#         folders.append(name)
#     # recurse over subfolders
#     for name in folders:
#         result.extend(list_files(camera, os.path.join(path, name)))
#     return result
#
#
# def get_file_info(camera, path):
#     folder, name = os.path.split(path)
#     return camera.file_get_info(folder, name)


class CameraUsb(object):
    """
    Define API for multiple exposure
    """
    def __init__(self, verbose=False):
        self.verbose = verbose

        self.camera = gp.Camera()

        self.camera_config = None
        self.status_config = None
        self.other_config = None
        self.shutter_speed_config = None
        self.shutter_speed_options = []

    def init(self):
        """
        Set manual exposure and other defaults
        :return: config
        """
        try:
            self.camera_config = self.camera.get_config()
        except gp.GPhoto2Error:
            raise RuntimeError("Unable to connect to Camera")

        self.other_config = self.camera_config.get_child_by_name('other')

        # Manual/f-stop/iso
        exposure_program_mode = self.other_config.get_child_by_name(EXPOSURE_PROGRAM_MODE)
        if not exposure_program_mode.get_value() == '1':
            print('Setting camera to Manual exposure program')
            exposure_program_mode.set_value('1')
            self.camera.set_config(self.camera_config)
            wait_for_event(self.camera)

            # When switching exposure program, we need to refresh the configs
            self.camera_config = self.camera.get_config()
            self.other_config = self.camera_config.get_child_by_name('other')

        self.status_config = self.camera_config.get_child_by_name('status')

        self.shutter_speed_config = self.other_config.get_child_by_name(SHUTTER_SPEED)
        self.shutter_speed_options = [str(x) for x in self.shutter_speed_config.get_choices()]
        if len(self.shutter_speed_options) != 61:
            raise RuntimeError('Unble to determine shutter speed options; restart app')

        fstop = self.other_config.get_child_by_name(F_NUMBER)
        fstop.set_value('560')

        iso = self.other_config.get_child_by_name(EXPOSURE_INDEX)
        iso.set_value('80')

        self.camera.set_config(self.camera_config)
        wait_for_event(self.camera)

    def get_info(self):
        """
        :return: Dict containing serialnumber, batterylevel, remainingpictures, etc
        """
        if not self.camera_config:
            self.init()

        battery_level = self.status_config.get_child_by_name('batterylevel').get_value()
        # Convert '67%' to int
        battery_level = int(''.join([x for x in battery_level if x.isdigit()]))

        info = {'serialnumber': self.status_config.get_child_by_name('serialnumber').get_value(),
                'cameramodel': self.status_config.get_child_by_name('cameramodel').get_value(),
                'deviceversion': self.status_config.get_child_by_name('deviceversion').get_value(),
                'batterylevel': battery_level,
                'remainingpictures': int(self.camera.get_storageinfo()[0].freeimages)}
        return info

    def take_picture(self, shutter_speed_index=None, color_temperature=None, volume=None):
        """
        Set camera options and take picture
        Blocking
        :param shutter_speed_index: int in range 0-60 (0 fastest shutter)
        :param color_temperature: in in range 2500-10000 by 100 increment
        :param volume: int in range 0-100
        :return: (jpg_path, dng_path)
        """
        t1 = time.time()
        if not self.camera_config:
            self.init()

        if shutter_speed_index is not None:
            self.shutter_speed_config.set_value(self.shutter_speed_options[shutter_speed_index])

        if color_temperature is not None:
            self.other_config.get_child_by_name(COLOR_TEMPERATURE).set_value(color_temperature)

        if volume is not None:
            self.other_config.get_child_by_name(AUDIO_VOLUME).set_value(str(volume))

        self.camera.set_config(self.camera_config)
        # We need this even though no event is triggered
        wait_for_event(self.camera)

        gp_jpg_path = self.camera.capture(gp.GP_CAPTURE_IMAGE)

        gp_dng_path = wait_for_event(self.camera, timeout=TIMEOUT_CAPTURE_DNG, event_type=gp.GP_EVENT_FILE_ADDED)
        if not gp_dng_path:
            raise RuntimeError('Unable to copy DNG')

        jpg_path = os.path.join(gp_jpg_path.folder, gp_jpg_path.name)
        dng_path = os.path.join(gp_dng_path.folder, gp_dng_path.name)

        print('Capture took %0.03f sec' % (time.time() - t1, ))
        return jpg_path, dng_path

    def download_file(self, src_path, dst_path, delete=True):
        """Copy the file from the camera src_path to local dst_path"""
        t1 = time.time()

        src_folder, src_name = os.path.split(src_path)
        src_file = self.camera.file_get(src_folder, src_name, gp.GP_FILE_TYPE_NORMAL)
        print('Download %s ->\n\t%s' % (src_path, dst_path))
        src_file.save(dst_path)
        wait_for_event(self.camera)
        print('Download took %0.03f sec' % (time.time() - t1, ))

        if delete:
            t1 = time.time()
            print('Delete %s' % src_path)
            self.camera.file_delete(src_folder, src_name)
            wait_for_event(self.camera)
            print('Delete took %0.03f sec' % (time.time() - t1, ))


def _unittest():
    """test a short exposure sequence"""
    # temporary directory
    dst_template = '/tmp/theta/capture.%04d.%s'

    t1 = time.time()
    camera = CameraUsb()

    camera.init()

    print(camera.get_info())

    frame = 1
    jpg_path, dng_path = camera.take_picture(0)
    print(jpg_path, dng_path)
    camera.download_file(dng_path, dst_template % (frame, 'dng'))
    frame += 1

    jpg_path, dng_path = camera.take_picture(24)
    print(jpg_path, dng_path)
    camera.download_file(dng_path, dst_template % (frame, 'dng'))
    frame += 1

    jpg_path, dng_path = camera.take_picture(42)
    print(jpg_path, dng_path)
    camera.download_file(dng_path, dst_template % (frame, 'dng'))
    frame += 1
    print('Done in %0.03f sec' % (time.time() - t1, ))


if __name__ == "__main__":

    _unittest()

2 Likes

Changelog in Gphoto2 indicates support of Theta ShutterSpeed in 2.5.10
But after some tests
Gphoto2 2.5.23 (fresh compilation) on raspbian + Theta V => shutterSpeed (on video mode, i.e. shutter is less than a second) is not working (no real change after a set-config)

Maybe it’s work on other cameras.For now, ptpcam still needed for shutterspeed configuration in USB

I found a way to change shutterspeed on the Theta V with gphoto2 (on linux raspbian but it shoud work on others platforms)
Since version 2.5.10, Ricoh Shutterspeed in implemented in libghoto2 but it was not working with the Theta V (at least for me).
After a few tries I found that the VendorID reported on the Theta V is 0x00000006, this number is MICROSOFT and not PENTAX (the ricoh shutterspeed is only for PENTAX VendorID)

I add this ligne in config.c (libgphoto2-2.5.23\camlibs\ptp2)

{ N_(“Shutter Speed”), “shutterspeed”, PTP_DPC_RICOH_ShutterSpeed, PTP_VENDOR_MICROSOFT, PTP_DTC_UINT64, _get_Ricoh_ShutterSpeed, _put_Ricoh_ShutterSpeed },

just after this one :

{ N_(“Shutter Speed”), “shutterspeed”, PTP_DPC_RICOH_ShutterSpeed, PTP_VENDOR_PENTAX, PTP_DTC_UINT64, _get_Ricoh_ShutterSpeed, _put_Ricoh_ShutterSpeed },

You need to compile libgphoto2 and now you can use
gphoto2 --get-config shutterspeed
gphoto2 --set-config-index shutterspeed=10

(with the property 0xD00F it’s not working)

If ExposureProgramMode=2 (Normal)
I get:

Label: Shutter Speed
Readonly: 0
Type: RADIO
Current: Auto
Choice: 0 Auto
END

if I switch to ExposureProgramMode=4 (Shutter priority program)
(gphoto2 --set-config=/main/other/500e=4 )
I get

Label: Shutter Speed
Readonly: 0
Type: RADIO
Current: 1/60
Choice: 0 1/25000
Choice: 1 1/20000
Choice: 2 1/16000
Choice: 3 1/12500
Choice: 4 1/10000
Choice: 5 1/8000
Choice: 6 1/6400
Choice: 7 1/5000
Choice: 8 1/4000
Choice: 9 1/3200
Choice: 10 1/2500
Choice: 11 1/2000
Choice: 12 1/1600
Choice: 13 1/1250
Choice: 14 1/1000
Choice: 15 1/800
Choice: 16 1/640
Choice: 17 1/500
Choice: 18 1/400
Choice: 19 1/320
Choice: 20 1/250
Choice: 21 1/200
Choice: 22 1/160
Choice: 23 1/125
Choice: 24 1/100
Choice: 25 1/80
Choice: 26 1/60
Choice: 27 1/50
Choice: 28 1/40
Choice: 29 1/30
END

Bye bye ptpcam (the main camera led was blinking during ptpcam shutterspeed modification - not very good - and sometimes the camera was disconnected from usb)

Hugues

FWIW I set ExposureProgramMode=1 (Manual) before setting shutter_speed