From 59d233285ffa17fa1d8c49cbc181a7082594d081 Mon Sep 17 00:00:00 2001 From: Tom O'Connell Date: Wed, 1 Aug 2018 19:05:52 -0700 Subject: [PATCH 1/7] Changed png extension to jpg in pyicic calls to reflect actual output type. --- remoteOperation.py | 8 ++++---- robotutil.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/remoteOperation.py b/remoteOperation.py index 1e795d5..47b9d97 100644 --- a/remoteOperation.py +++ b/remoteOperation.py @@ -41,7 +41,7 @@ def notifyUserFail(robot, arenanum, mailfrom, attPic=0, qualPic=25, attImg=1, de msg = MIMEMultipart() for imgnum in range(len(arenanum)): curarenanum = str(arenanum[imgnum]) - imgname = curarenanum + 'errImage.png' + imgname = curarenanum + 'errImage.jpg' with open(imgname, 'rb') as fp: img = MIMEImage(fp.read()) msg.attach(img) @@ -57,19 +57,19 @@ def notifyUserFail(robot, arenanum, mailfrom, attPic=0, qualPic=25, attImg=1, de time.sleep(0.2) robot.cam.start_live() robot.cam.snap_image() - robot.cam.save_image(arenanum + 'errImage.png', 1, jpeq_quality=qualPic) + robot.cam.save_image(arenanum + 'errImage.jpg', 1, jpeq_quality=qualPic) robot.cam.stop_live() robot.dwell(50) robot.light(False) msg['Subject'] = 'Failure: Arena ' + arenanum + ' Withdraw' msg = MIMEMultipart() msg.preamble = 'Arena ' + arenanum + ' failed to unload.' - fp = open(arenanum + 'errImage.png', 'rb') + fp = open(arenanum + 'errImage.jpg', 'rb') img = MIMEImage(fp.read()) fp.close() msg.attach(img) if delFiles == 1: - os.remove(arenanum + 'errImage.png') + os.remove(arenanum + 'errImage.jpg') if attPic == 0 and attImg == 0: arenanum = str(arenanum) msg['Subject'] = 'Failure: Arena ' + arenanum + ' Withdraw' diff --git a/robotutil.py b/robotutil.py index 351c6d1..b5f9f1f 100644 --- a/robotutil.py +++ b/robotutil.py @@ -463,7 +463,7 @@ def smallPartManipVac(self, onOff = False): # rerouted to fly vacuum return # Captures arena picture at location (Images named consecutively if multiple coordinates specified) - def SavePicAt(self, Xcoords, Ycoords, IndVect, qualPic=25, Zcam=40, ImgName='errImage.png'): + def SavePicAt(self, Xcoords, Ycoords, IndVect, qualPic=25, Zcam=40, ImgName='errImage.jpg'): self.light(True) self.cam.start_live() for ImgNum in range(len(Xcoords)): @@ -471,7 +471,7 @@ def SavePicAt(self, Xcoords, Ycoords, IndVect, qualPic=25, Zcam=40, ImgName='err self.dwell(50) # Put higher to reduce effect of motion-caused rig trembling on picture self.cam.snap_image() curInd = str(IndVect[ImgNum]) - self.cam.save_image(curInd + 'errImage.png', 1, jpeq_quality=qualPic) + self.cam.save_image(curInd + 'errImage.jpg', 1, jpeq_quality=qualPic) self.dwell(10) self.cam.stop_live() self.light(False) From ea3d314d8692f48134d0798e57b9147566b6bbae Mon Sep 17 00:00:00 2001 From: Tom O'Connell Date: Thu, 2 Aug 2018 23:23:32 -0700 Subject: [PATCH 2/7] Draft of camera abstraction. --- cameras.py | 155 +++++++++++++++++++++++++++++++++++++++++++++ remoteOperation.py | 5 +- robotutil.py | 60 ++++++++++-------- 3 files changed, 191 insertions(+), 29 deletions(-) create mode 100644 cameras.py diff --git a/cameras.py b/cameras.py new file mode 100644 index 0000000..2bafdb0 --- /dev/null +++ b/cameras.py @@ -0,0 +1,155 @@ + +""" +Abstraction layer for other camera interfaces (pyicic, opencv) +""" + +import abc +from abc import abstractmethod +import warnings + +import numpy as np +import cv2 + +# TODO module (file) level list of supported cam properties? +# then take dict of those @ constructors, via base class? + +class CameraNotFoundError(IOError): pass + +# Should work across Python 2 and 3. +# see: https://gist.github.com/alanjcastonguay/25e4db0edd3534ab732d6ff615ca9fc1 +ABC = abc.ABCMeta('ABC', (object,), {}) + +class Camera(ABC): + # TODO context manager fns for starting resources / freeing them? do any of + # those functions actually need to be called for the kind of operation we + # will use the cameras for? + + # TODO type hint on return type + comment on dims, colorspace, etc + @abstractmethod + def get_frame(self): + pass + + # TODO provide instance variable for backing camera object + + def write_png(self, filename): + """ + Args: + filename (str): Path to save to. + """ + # TODO make sure this uses the subclass get_frame + frame = self.get_frame() + cv2.imwrite(filename, frame) + + + def write_jpg(self, filename, quality=50): + """ + Args: + filename (str): Path to save to. + quality (int): (optional) 0-100 jpg quality (check bounds. 99?) + """ + frame = self.get_frame() + raise NotImplementedError + # TODO add jpeg quality option + cv2.imwrite(filename, frame) + + +class PyICIC_Camera(Camera): + def __init__(self): + import pyicic.IC_ImagingControl + + ic_ic = pyicic.IC_ImagingControl.IC_ImagingControl() + ic_ic.init_library() + cam_names = ic_ic.get_unique_device_names() + + if len(cam_names) == 0: + raise CameraNotFoundError + + cam = ic_ic.get_device(cam_names[0]) + cam.open() + + cam.gain.value = 10 + cam.exposure.auto = False + # TODO does a negative exposure make sense? + cam.exposure.value = -10 + cam.set_video_format('BY8 (2592x1944)') + cam.set_frame_rate(4.00) + + cam.prepare_live() + + self.cam = cam + + + def get_frame(self): + """Returns a current frame from the camera. + """ + # TODO TODO normalized format. numpy array? colorspace? how to check? + self.cam.start_live() + self.cam.snap_image() + imgdata, w, h, d = self.cam.get_image_data() + self.cam.stop_live() + return np.ndarray(buffer=imgdata, dtype=np.uint8, shape=(h, w, d)) + + + def write_jpg(self, filename, quality=50): + """Writes a current image to filename in jpg format. + """ + robot.cam.start_live() + robot.cam.snap_image() + # the 1 is for JPG, 0 is for BMP + robot.cam.save_image(filename, 1, jpeq_quality=qualPic) + robot.cam.stop_live() + + +class OpenCVCamera(Camera): + def __init__(self): + if hasattr(cv2, 'cv'): + cv2_v3 = True + else: + cv2_v3 = False + + def cap_prop_id(name): + """Returns cv2 integer code for property with name.""" + # TODO also handle case where property doesn't exist + return getattr(cv2 if cv2_v3 else cv2.cv, + ('' if cv2_v3 else 'CV_') + 'CAP_PROP_' + name) + + def set_property(vc, name, value): + """Sets cv2.VideoCapture property. Warns if can not.""" + property_code = cap_prop_id(name) + v = vc.get(property_code) + if v == -1: + warnings.warn('Could not set property {}'.format(name)) + else: + vc.set(property_code, value) + + cam = VideoCapture(0) + if not cam.isOpened(): + return None + + # these don't need to happen before resource is open, do they? + set_property(cam, 'GAIN', 10) + # TODO false? 0? + # seems 0.25 might actually be value to turn off auto exposure? + # https://github.com/opencv/opencv/issues/9738 + set_property(cam, 'AUTO_EXPOSURE', 0) + set_property(cam, 'EXPOSURE', -10) + # TODO this closest to by8 above? necessary? + #set_property(cam, 'FORMAT', ) + # ? + #set_property(cam, 'FRAME_WIDTH', ) + #set_property(cam, 'FRAME_HEIGHT', ) + set_property(cam, 'FPS', 4.00) + + # TODO provide unified camera cleanup call + # and put cam.release() there in this case + + self.cam = cam + + + def get_frame(): + """Returns a current frame from the camera. + """ + # TODO TODO normalized format. numpy array? colorspace? how to check? + return self.cam.read() + #return np.ndarray(buffer=imgdata, dtype=np.uint8, shape=(h, w, d)) + diff --git a/remoteOperation.py b/remoteOperation.py index 47b9d97..5ffa83d 100644 --- a/remoteOperation.py +++ b/remoteOperation.py @@ -55,10 +55,7 @@ def notifyUserFail(robot, arenanum, mailfrom, attPic=0, qualPic=25, attImg=1, de arenanum = str(arenanum) robot.light(True) time.sleep(0.2) - robot.cam.start_live() - robot.cam.snap_image() - robot.cam.save_image(arenanum + 'errImage.jpg', 1, jpeq_quality=qualPic) - robot.cam.stop_live() + robot.write_jpg(arenanum + 'errImage.jpg', 1, quality=qualPic) robot.dwell(50) robot.light(False) msg['Subject'] = 'Failure: Arena ' + arenanum + ' Withdraw' diff --git a/robotutil.py b/robotutil.py index b5f9f1f..13e1a39 100644 --- a/robotutil.py +++ b/robotutil.py @@ -11,14 +11,16 @@ ## Dependencies import os import math -import cv2 -import numpy as np import time import ConfigParser import urllib2 -import random as rand -import pyicic.IC_ImagingControl +import importlib + +import numpy as np +import cv2 + import flysorterSerial +import cameras class MAPLE: """Class for fly manipulation robot.""" @@ -57,10 +59,11 @@ class MAPLE: 'Z2OffsetZ': '8', 'HFOV': '14.5', 'VFOV': '11.25', - 'StatusURL': '' + 'StatusURL': '', + 'camera_class': 'cameras.PyICIC_Camera' } - def __init__(self, robotConfigFile,initDisp=1): + def __init__(self, robotConfigFile, cam_class=None): print "Reading config file...", self.config = ConfigParser.RawConfigParser(self.configDefaults) self.readConfig(robotConfigFile) @@ -89,7 +92,29 @@ def __init__(self, robotConfigFile,initDisp=1): return print "Initializing camera...", - self.cam = cameraInit() + if cam_class is None: + parts = self.full_cam_class_name.rsplit('.', 1) + try: + cam_module = importlib.import_module(parts[0]) + except ImportError as e: + # TODO maybe print this if there are any problems starting cam? + import pyclbr + cams = pyclbr.readmodule('cameras') + print(cams) + cams.remove('Camera') + print "Available cameras:" + for c in cams: + print c + print "Set camera_class to one of them in your MAPLE.cfg file." + raise + + cam_class = getattr(cam_module, parts[1]) + + if not issubclass(cam_class, cameras.Camera): + raise ValueError('Camera class must subclass cameras.Camera') + + self.cam = cam_class() + if self.cam == None: print "Camera init fail." self.smoothie.close() @@ -119,7 +144,9 @@ def readConfig(self, configFile): self.StatusURL = self.config.get('DEFAULT', 'StatusURL') if ( self.StatusURL != ''): urllib2.urlopen(StatusURL + "&" + "st=1") - return + + self.full_cam_class_name = self.config.get('DEFAULT', 'camera_class') + def release(self): self.light(False) @@ -651,20 +678,3 @@ def findDegs(self, slowmode=True, precision=4, MAX_SIZE=74, MIN_SIZE=63, startp1 tempdeg = self.getDegs(circles) return tempdeg - -# Set up close-up camera -def cameraInit(): - global ic_ic - print "Opening camera interface." - ic_ic = pyicic.IC_ImagingControl.IC_ImagingControl() - ic_ic.init_library() - cam_names = ic_ic.get_unique_device_names() - cam = ic_ic.get_device(cam_names[0]) - cam.open() - cam.gain.value = 10 - cam.exposure.auto = False - cam.exposure.value = -10 - cam.set_video_format('BY8 (2592x1944)') - cam.set_frame_rate(4.00) - cam.prepare_live() - return cam From b4a8ed9f06a57331a465dcaf95d2f7bbd3daf816 Mon Sep 17 00:00:00 2001 From: Tom O'Connell Date: Thu, 2 Aug 2018 23:24:50 -0700 Subject: [PATCH 3/7] IOErrors for camera / smoothie not found. --- robotutil.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/robotutil.py b/robotutil.py index 13e1a39..9b395c2 100644 --- a/robotutil.py +++ b/robotutil.py @@ -86,10 +86,8 @@ def __init__(self, robotConfigFile, cam_class=None): tempPort.close() if self.smoothie is None: - print "Serial initialization failed." - if self.smoothie is None: - print "Smoothie board not found." - return + raise IOError( + "Serial initialization failed. Smoothie board not found.") print "Initializing camera...", if cam_class is None: @@ -118,7 +116,7 @@ def __init__(self, robotConfigFile, cam_class=None): if self.cam == None: print "Camera init fail." self.smoothie.close() - return + raise IOError("Camera init fail.") print "done." From 759f0b6a1985374f7deeb4c2cae950dce653d652 Mon Sep 17 00:00:00 2001 From: Tom O'Connell Date: Sat, 4 Aug 2018 18:47:01 -0700 Subject: [PATCH 4/7] cameras interface seems to work with OpenCVCamera. Don't have pyicic hardware to test myself. --- cameras.py | 168 ++++++++++++++++++++++++++++++++------------------- robotutil.py | 69 +++++++++++---------- 2 files changed, 143 insertions(+), 94 deletions(-) diff --git a/cameras.py b/cameras.py index 2bafdb0..96a0212 100644 --- a/cameras.py +++ b/cameras.py @@ -1,6 +1,13 @@ +## +## This copyrighted software is distributed under the GPL v2.0 license. +## See the LICENSE file for more details. +## """ -Abstraction layer for other camera interfaces (pyicic, opencv) +Abstraction layer for other camera interfaces (pyicic, opencv, etc) + +To implement a custom camera, subclass cameras.Camera, and implement the two +methods with the @abstractmethod decorator (get_frame and close) """ import abc @@ -10,47 +17,83 @@ import numpy as np import cv2 -# TODO module (file) level list of supported cam properties? -# then take dict of those @ constructors, via base class? class CameraNotFoundError(IOError): pass +class NoFrameError(IOError): pass # Should work across Python 2 and 3. # see: https://gist.github.com/alanjcastonguay/25e4db0edd3534ab732d6ff615ca9fc1 ABC = abc.ABCMeta('ABC', (object,), {}) class Camera(ABC): - # TODO context manager fns for starting resources / freeing them? do any of - # those functions actually need to be called for the kind of operation we - # will use the cameras for? + @abstractmethod + def __init__(self): + """ + Camera needs an __init__ with no arguments, so it can be instantiated + in a uniform way inside of the MAPLE __init__. + + Your __init__ implementation will likely create some other Python object + to manage interactions with the camera through whatever other library. + If this object is called `backing_camera_object`, the last line of your + __init__ function should be: + ``` + self.cam = backing_camera_object + ``` + + Then, to change other properties of the camera after __init__, you can + access the camera of the MAPLE robot like so: + ``` + robot = robotutil.MAPLE('MAPLE.cfg') + camera_wrapper = robot.cam + backing_camera_object = camera_wrapper.cam + ``` + """ + pass + - # TODO type hint on return type + comment on dims, colorspace, etc @abstractmethod def get_frame(self): + """Returns a current frame from the camera. + + Returned frame must be a numpy.ndarray of dimensions + (height, width, color), where the colorspace is BGR. + + Raises NoFrameError if could not get a frame from the camera. + """ pass - # TODO provide instance variable for backing camera object + @abstractmethod + def close(self): + """Releases resources associated with connection to this camera. + """ + pass def write_png(self, filename): - """ + """Write current frame to filename in PNG format. + Args: filename (str): Path to save to. + + Raises NoFrameError if could not get a frame from the camera. """ - # TODO make sure this uses the subclass get_frame frame = self.get_frame() cv2.imwrite(filename, frame) + def write_jpg(self, filename, quality=95): + """Write current frame to filename in JPG format, with optional quality. - def write_jpg(self, filename, quality=50): - """ Args: filename (str): Path to save to. - quality (int): (optional) 0-100 jpg quality (check bounds. 99?) + quality (int): (optional) 0-100 jpg quality (100=highest quality) + + Raises NoFrameError if could not get a frame from the camera. """ frame = self.get_frame() - raise NotImplementedError - # TODO add jpeg quality option - cv2.imwrite(filename, frame) + IMWRITE_JPEG_QUALITY = 1 + cv2.imwrite(filename, frame, [IMWRITE_JPEG_QUALITY, quality]) + + # TODO context manager fns for starting resources / freeing them? + # TODO provide instance variable for backing camera object (as property?) class PyICIC_Camera(Camera): @@ -62,7 +105,7 @@ def __init__(self): cam_names = ic_ic.get_unique_device_names() if len(cam_names) == 0: - raise CameraNotFoundError + raise CameraNotFoundError('pyicic camera not found.') cam = ic_ic.get_device(cam_names[0]) cam.open() @@ -78,18 +121,17 @@ def __init__(self): self.cam = cam - def get_frame(self): """Returns a current frame from the camera. """ - # TODO TODO normalized format. numpy array? colorspace? how to check? + # TODO is there much overhead w/ start_live / stop_live calls? + # mechanism to keep live? self.cam.start_live() self.cam.snap_image() imgdata, w, h, d = self.cam.get_image_data() self.cam.stop_live() return np.ndarray(buffer=imgdata, dtype=np.uint8, shape=(h, w, d)) - def write_jpg(self, filename, quality=50): """Writes a current image to filename in jpg format. """ @@ -99,57 +141,59 @@ def write_jpg(self, filename, quality=50): robot.cam.save_image(filename, 1, jpeq_quality=qualPic) robot.cam.stop_live() + def close(self): + """Releases resources associated with connection to this camera. + """ + self.cam.close() + class OpenCVCamera(Camera): def __init__(self): if hasattr(cv2, 'cv'): - cv2_v3 = True + self.cv2_v3 = False else: - cv2_v3 = False - - def cap_prop_id(name): - """Returns cv2 integer code for property with name.""" - # TODO also handle case where property doesn't exist - return getattr(cv2 if cv2_v3 else cv2.cv, - ('' if cv2_v3 else 'CV_') + 'CAP_PROP_' + name) - - def set_property(vc, name, value): - """Sets cv2.VideoCapture property. Warns if can not.""" - property_code = cap_prop_id(name) - v = vc.get(property_code) - if v == -1: - warnings.warn('Could not set property {}'.format(name)) - else: - vc.set(property_code, value) - - cam = VideoCapture(0) + self.cv2_v3 = True + + cam = cv2.VideoCapture(0) if not cam.isOpened(): - return None - - # these don't need to happen before resource is open, do they? - set_property(cam, 'GAIN', 10) - # TODO false? 0? - # seems 0.25 might actually be value to turn off auto exposure? - # https://github.com/opencv/opencv/issues/9738 - set_property(cam, 'AUTO_EXPOSURE', 0) - set_property(cam, 'EXPOSURE', -10) - # TODO this closest to by8 above? necessary? - #set_property(cam, 'FORMAT', ) - # ? - #set_property(cam, 'FRAME_WIDTH', ) - #set_property(cam, 'FRAME_HEIGHT', ) - set_property(cam, 'FPS', 4.00) - - # TODO provide unified camera cleanup call - # and put cam.release() there in this case + raise CameraNotFoundError('OpenCV compatible camera not found.') self.cam = cam - - def get_frame(): + def get_frame(self): """Returns a current frame from the camera. + + Raises NoFrameError if could not get a frame from the camera. + """ + # TODO detect and give meaningful error message for that select timeout + # err? (having trouble reproducing now... camera wasn't closed + # properly?) + success, data = self.cam.read() + if not success: + raise NoFrameError('OpenCV VideoCapture.read() failed.') + else: + return data + + def close(self): + """Releases resources associated with connection to this camera. """ - # TODO TODO normalized format. numpy array? colorspace? how to check? - return self.cam.read() - #return np.ndarray(buffer=imgdata, dtype=np.uint8, shape=(h, w, d)) + self.cam.release() + + def cap_prop_id(self, name): + """Returns cv2 integer code for property with name.""" + # TODO also handle case where property doesn't exist + return getattr(cv2 if self.cv2_v3 else cv2.cv, + ('' if self.cv2_v3 else 'CV_') + 'CAP_PROP_' + name) + + def set_property(self, vc, name, value): + """Sets cv2.VideoCapture property. Warns if can not.""" + print 'trying to set {} to {}'.format(name, value) + property_code = self.cap_prop_id(name) + v = vc.get(property_code) + if v == -1: + warnings.warn('Could not set property {}'.format(name)) + else: + vc.set(property_code, value) + + # TODO provide method to list properties camera *does* seem to support diff --git a/robotutil.py b/robotutil.py index 9b395c2..be622b6 100644 --- a/robotutil.py +++ b/robotutil.py @@ -15,6 +15,7 @@ import ConfigParser import urllib2 import importlib +import pyclbr import numpy as np import cv2 @@ -87,36 +88,50 @@ def __init__(self, robotConfigFile, cam_class=None): if self.smoothie is None: raise IOError( - "Serial initialization failed. Smoothie board not found.") + 'Serial initialization failed. Smoothie board not found.') + + def print_builtin_cameras(): + cams = pyclbr.readmodule('cameras') + cams.pop('Camera') + cams.pop('CameraNotFoundError') + cams.pop('NoFrameError') + print '\n\nAvailable cameras:' + for c in cams: + print 'cameras.{}'.format(c) + + def class2str(cls): + return '{}.{}'.format(cls.__module__, cls.__name__) print "Initializing camera...", if cam_class is None: parts = self.full_cam_class_name.rsplit('.', 1) try: cam_module = importlib.import_module(parts[0]) - except ImportError as e: - # TODO maybe print this if there are any problems starting cam? - import pyclbr - cams = pyclbr.readmodule('cameras') - print(cams) - cams.remove('Camera') - print "Available cameras:" - for c in cams: - print c - print "Set camera_class to one of them in your MAPLE.cfg file." - raise + cam_class = getattr(cam_module, parts[1]) - cam_class = getattr(cam_module, parts[1]) + except (ImportError, AttributeError) as e: + print_builtin_cameras() + print ('\nSet camera_class to one of them in your ' + + 'MAPLE.cfg file.\n') + self.smoothie.close() + raise if not issubclass(cam_class, cameras.Camera): - raise ValueError('Camera class must subclass cameras.Camera') - - self.cam = cam_class() - - if self.cam == None: - print "Camera init fail." + print_builtin_cameras() + self.smoothie.close() + raise ValueError('camera_class must subclass cameras.Camera' + + ', but class {} does not.'.format(class2str(cam_class))) + + try: + self.cam = cam_class() + except ImportError as e: + print_builtin_cameras() + print ('\nSet camera_class to one of them in your ' + + 'MAPLE.cfg file.\n') + print 'Missing dependencies for camera_class={}'.format( + class2str(cam_class)) self.smoothie.close() - raise IOError("Camera init fail.") + raise print "done." @@ -172,14 +187,7 @@ def homeZ0(self): # Captures current camera image; returns as numpy array def captureImage(self): - self.cam.start_live() - self.cam.snap_image() - (imgdata, w, h, d) = self.cam.get_image_data() - self.cam.stop_live() - img = np.ndarray(buffer = imgdata, - dtype = np.uint8, - shape = (h, w, d)) - return img + return self.cam.get_frame() # Returns current position as array def getCurrentPosition(self): @@ -490,15 +498,12 @@ def smallPartManipVac(self, onOff = False): # rerouted to fly vacuum # Captures arena picture at location (Images named consecutively if multiple coordinates specified) def SavePicAt(self, Xcoords, Ycoords, IndVect, qualPic=25, Zcam=40, ImgName='errImage.jpg'): self.light(True) - self.cam.start_live() for ImgNum in range(len(Xcoords)): self.moveToSpd(pt=[float(Xcoords[ImgNum]), float(Ycoords[ImgNum]), 0, Zcam, 10, 5000]) self.dwell(50) # Put higher to reduce effect of motion-caused rig trembling on picture - self.cam.snap_image() curInd = str(IndVect[ImgNum]) - self.cam.save_image(curInd + 'errImage.jpg', 1, jpeq_quality=qualPic) + self.cam.write_jpg(curInd + 'errImage.jpg', quality=qualPic) self.dwell(10) - self.cam.stop_live() self.light(False) # Finds immobile fly on white surface (CO2 board) From f570d070c92ce5ca8cec7d0dd9916e2e4862137f Mon Sep 17 00:00:00 2001 From: Tom O'Connell Date: Mon, 6 Aug 2018 18:49:19 -0700 Subject: [PATCH 5/7] Commented example line in MAPLE.cfg for OpenCV cam. --- MAPLE.cfg | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/MAPLE.cfg b/MAPLE.cfg index 92f2aba..6c29c9f 100644 --- a/MAPLE.cfg +++ b/MAPLE.cfg @@ -21,3 +21,8 @@ Z2OffsetY = 7 Z2OffsetZ = 10.7 +# To implement support for your own camera, follow examples in cameras.py. + +# Uncomment to use OpenCV supported camera. +# camera_class = cameras.OpenCVCamera + From 0e0e70693081d97da828d7285d8584a4e7fe481c Mon Sep 17 00:00:00 2001 From: Tom O'Connell Date: Mon, 6 Aug 2018 19:27:29 -0700 Subject: [PATCH 6/7] Updated docstring and explained where to import dependencies. --- cameras.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cameras.py b/cameras.py index 96a0212..67e2614 100644 --- a/cameras.py +++ b/cameras.py @@ -6,8 +6,8 @@ """ Abstraction layer for other camera interfaces (pyicic, opencv, etc) -To implement a custom camera, subclass cameras.Camera, and implement the two -methods with the @abstractmethod decorator (get_frame and close) +To implement a custom camera, subclass cameras.Camera, and implement the +methods with the @abstractmethod decorator. """ import abc @@ -32,6 +32,9 @@ def __init__(self): Camera needs an __init__ with no arguments, so it can be instantiated in a uniform way inside of the MAPLE __init__. + Any dependencies specific to your camera should be imported as the first + lines inside this function. + Your __init__ implementation will likely create some other Python object to manage interactions with the camera through whatever other library. If this object is called `backing_camera_object`, the last line of your @@ -50,7 +53,6 @@ def __init__(self): """ pass - @abstractmethod def get_frame(self): """Returns a current frame from the camera. From d5c1b669dd0d367b6633af9a6a2a77d8d6406dd2 Mon Sep 17 00:00:00 2001 From: Tom O'Connell Date: Mon, 13 Aug 2018 21:44:24 -0700 Subject: [PATCH 7/7] Removed less important camera TODOs. --- cameras.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/cameras.py b/cameras.py index 67e2614..88ceeef 100644 --- a/cameras.py +++ b/cameras.py @@ -94,9 +94,6 @@ def write_jpg(self, filename, quality=95): IMWRITE_JPEG_QUALITY = 1 cv2.imwrite(filename, frame, [IMWRITE_JPEG_QUALITY, quality]) - # TODO context manager fns for starting resources / freeing them? - # TODO provide instance variable for backing camera object (as property?) - class PyICIC_Camera(Camera): def __init__(self):