class MicroscopeServer(xmlrpc.XMLRPC): """ Implements an XML-RPC server allowing remote control of the OpenLabTools microscope over a network Exposes all Arduino commands, plus photo and video recording commands """ def __init__(self, serial_port, allowNone=False, useDateTime=False): """Initializes the class with serial port name of Arduino""" xmlrpc.XMLRPC.__init__(self, allowNone, useDateTime) self.microscope = Microscope(serial_port) def xmlrpc_calibrate(self): self.microscope.calibrate() def xmlrpc_is_calibrated(self): calibrated = self.microscope.is_calibrated() return calibrated def xmlrpc_get_length(self, axis): axis = self.microscope.get_length(axis) return axis def xmlrpc_get_position(self, axis): position = self.microscope.get_position(axis) return position def xmlrpc_get_distance_to_go(self, axis): axis = self.microscope.get_distance_to_go(axis) return axis def xmlrpc_move(self, axis, position): self.microscope.move(axis, position) def xmlrpc_move_to(self, axis, position): self.microscope.move_to(self, axis, position) def xmlrpc_set_ring_colour(self, colour): self.microscope.set_ring_colour(colour) def xmlrpc_set_ring_brightness(self, brightness): self.microscope.set_ring_brightness(brightness) def xmlrpc_set_stage_led_brightness(self, brightness): self.microscope.set_stage_led_brightness(brightness) def xmlrpc_take_picture(self): """Takes a photograph with datetime string for filename""" #Not hugely portable, definitely unstable, need better method now = datetime.now() filename = ("%s_%s_%s__%s_%s_%s.jpg" % (now.year, now.month, now.day, now.hour, now.minute, now.second)) os.system(("raspistill -n -t 0 -o %s &" % filename)) def xmlrpc_take_video(self, duration): """Records a video of specified duration with datetime string for filename""" now = datetime.now() filename = ("%s_%s_%s__%s_%s_%s.h264" % (now.year, now.month, now.day, now.hour, now.minute, now.second)) os.system(("raspivid -n -t %s -o %s" %(duration, filename)))
class MicroscopeServer(xmlrpc.XMLRPC): """ Implements an XML-RPC server allowing remote control of the OpenLabTools microscope over a network Exposes all Arduino commands, plus photo and video recording commands """ def __init__(self, serial_port, allowNone=False, useDateTime=False): """Initializes the class with serial port name of Arduino""" xmlrpc.XMLRPC.__init__(self, allowNone, useDateTime) self.microscope = Microscope(serial_port) def xmlrpc_calibrate(self): self.microscope.calibrate() def xmlrpc_is_calibrated(self): calibrated = self.microscope.is_calibrated() return calibrated def xmlrpc_get_length(self, axis): axis = self.microscope.get_length(axis) return axis def xmlrpc_get_position(self, axis): position = self.microscope.get_position(axis) return position def xmlrpc_get_distance_to_go(self, axis): axis = self.microscope.get_distance_to_go(axis) return axis def xmlrpc_move(self, axis, position): self.microscope.move(axis, position) def xmlrpc_move_to(self, axis, position): self.microscope.move_to(self, axis, position) def xmlrpc_set_ring_colour(self, colour): self.microscope.set_ring_colour(colour) def xmlrpc_set_ring_brightness(self, brightness): self.microscope.set_ring_brightness(brightness) def xmlrpc_set_stage_led_brightness(self, brightness): self.microscope.set_stage_led_brightness(brightness) def xmlrpc_take_picture(self): """Takes a photograph with datetime string for filename""" #Not hugely portable, definitely unstable, need better method now = datetime.now() filename = ( "%s_%s_%s__%s_%s_%s.jpg" % (now.year, now.month, now.day, now.hour, now.minute, now.second)) os.system(("raspistill -n -t 0 -o %s &" % filename)) def xmlrpc_take_video(self, duration): """Records a video of specified duration with datetime string for filename""" now = datetime.now() filename = ( "%s_%s_%s__%s_%s_%s.h264" % (now.year, now.month, now.day, now.hour, now.minute, now.second)) os.system(("raspivid -n -t %s -o %s" % (duration, filename)))
class WormTracker(): """Class for worm tracking algorithm""" def nothing(x, y): #Placeholder as trackbar requires callback pass def __init__(self, serial_port, camera): """Initializes the class with serial interface/camera names, algorithm control parameters. Arguments: serial_port -- String of name for the serial interface for the microscope Arduino - e.g. 'dev/tty.usb' camera -- Integer for the V4L2 video device name e.g. 0 from '/dev/video0' """ self.create_gui() #Setup camera and video recording self.camera = cv2.VideoCapture(camera) self.width = int(self.camera.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH)) self.height = int(self.camera.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT)) self.camera.set(cv2.cv.CV_CAP_PROP_FRAME_WIDTH, self.width) self.camera.set(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT, self.height) #Kernel for morphological opening/closing operation self.kernel = np.ones((3, 3), np.uint8) #Variables for controlling stage self.microscope = Microscope(serial_port) self.microscope.set_ring_colour('FF0000') self.last_step_time = datetime.now() #Use for FPS calculation self.last_frame = cv2.getTickCount() #Skeltonisation self.worm_spline = np.zeros((1001, 1, 2), dtype=np.int) self.tail = np.zeros((2, ), dtype=np.int) self.head = np.zeros((2, ), dtype=np.int) def change_kernel(self, size): '''Generate new kernel patch for morphological opening/closing''' size = size * 2 + 1 self.kernel = np.ones((size, size), np.uint8) def create_gui(self): """"Defines the HighGUI elements """ cv2.namedWindow('Preview') cv2.createTrackbar('Tracking Off/On', 'Preview', 0, 1, self.nothing) cv2.createTrackbar('Threshold', 'Preview', 0, 1, self.nothing) cv2.createTrackbar('Threshold Value', 'Preview', 0, 255, self.nothing) cv2.createTrackbar('Step Interval', 'Preview', 0, 1000, self.nothing) cv2.createTrackbar('Margin', 'Preview', 0, 200, self.nothing) cv2.createTrackbar('Step Size', 'Preview', 0, 20, self.nothing) cv2.createTrackbar('Contour', 'Preview', 0, 1, self.nothing) cv2.createTrackbar('Adaptive', 'Preview', 0, 1, self.nothing) cv2.createTrackbar('Adaptive Value', 'Preview', 0, 40, self.nothing) cv2.createTrackbar('Adaptive Size', 'Preview', 1, 40, self.nothing) cv2.createTrackbar('Gaussian', 'Preview', 0, 1, self.nothing) cv2.createTrackbar('Kernel Size', 'Preview', 1, 20, self.change_kernel) cv2.createTrackbar('Opening', 'Preview', 0, 20, self.nothing) cv2.createTrackbar('Closing', 'Preview', 0, 20, self.nothing) cv2.createTrackbar('Skeleton', 'Preview', 0, 1, self.nothing) cv2.createTrackbar('Smoothing', 'Preview', 0, 1000, self.nothing) #Set default values cv2.setTrackbarPos('Threshold Value', 'Preview', 100) cv2.setTrackbarPos('Step Interval', 'Preview', 500) cv2.setTrackbarPos('Margin', 'Preview', 100) cv2.setTrackbarPos('Step Size', 'Preview', 2) def read_trackbars(self): """Read trackbar values""" self.tracking = bool(cv2.getTrackbarPos('Tracking Off/On', 'Preview')) self.show_threshold = bool(cv2.getTrackbarPos('Threshold', 'Preview')) self.threshold = cv2.getTrackbarPos('Threshold Value', 'Preview') self.step_interval = cv2.getTrackbarPos('Step Interval', 'Preview') self.margin = cv2.getTrackbarPos('Margin', 'Preview') self.step_size = cv2.getTrackbarPos('Step Size', 'Preview') self.draw_contour = bool(cv2.getTrackbarPos('Contour', 'Preview')) self.precision = cv2.getTrackbarPos('Precision', 'Preview') self.adaptive = bool(cv2.getTrackbarPos('Adaptive', 'Preview')) self.adaptive_value = cv2.getTrackbarPos('Adaptive Value', 'Preview') self.adaptive_size = cv2.getTrackbarPos('Adaptive Size', 'Preview') self.adaptive_gauss = bool(cv2.getTrackbarPos('Gaussian', 'Preview')) self.opening = cv2.getTrackbarPos('Opening', 'Preview') self.closing = cv2.getTrackbarPos('Closing', 'Preview') self.skeleton = bool(cv2.getTrackbarPos('Skeleton', 'Preview')) self.smoothing = cv2.getTrackbarPos('Smoothing', 'Preview') def find_worm(self): """Threshold and contouring algorithm to find centroid of worm""" self.img_gray = cv2.cvtColor(self.img, cv2.COLOR_BGR2GRAY) if self.adaptive: #Perform adaptive thresholding size = self.adaptive_size * 2 + 1 if self.adaptive_gauss: mode = cv2.ADAPTIVE_THRESH_MEAN_C else: mode = cv2.ADAPTIVE_THRESH_GAUSSIAN_C self.img_thresh = cv2.adaptiveThreshold(self.img_gray, 255, mode, cv2.THRESH_BINARY_INV, size, self.adaptive_value) else: #Simple Threshold ret, self.img_thresh = cv2.threshold(self.img_gray, self.threshold, 255, cv2.THRESH_BINARY_INV) if self.opening != 0: #Morphological Opening self.img_thresh = cv2.morphologyEx(self.img_thresh, cv2.MORPH_OPEN, self.kernel, iterations=self.opening) if self.closing != 0: #Morphological Closing self.img_thresh = cv2.morphologyEx(self.img_thresh, cv2.MORPH_CLOSE, self.kernel, iterations=self.closing) #Copy image to allow displaying later img_contour = self.img_thresh.copy() contours, hierarchy = cv2.findContours(img_contour, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE) #Find the biggest contour worm_area = 0 for contour in contours: area = cv2.contourArea(contour) if area > worm_area: self.worm = contour worm_area = area #Compute the centroid of the worm contour moments = cv2.moments(self.worm) self.x = int(moments['m10'] / moments['m00']) self.y = int(moments['m01'] / moments['m00']) def skeletonise(self): '''Perform skeletonisation and determine location of head and tail''' x_in = self.worm[:, 0, 0] y_in = self.worm[:, 0, 1] tck, u = interpolate.splprep([x_in, y_in], per=True, s=self.smoothing, quiet=1) points = np.arange(0, 1.001, 0.001) spline = interpolate.splev(points, tck, der=0) x = spline[0] y = spline[1] self.worm_spline[:, 0, 0] = x self.worm_spline[:, 0, 1] = y d = interpolate.splev(points, tck, der=1) dx = d[0] dy = d[1] dd = interpolate.splev(points, tck, der=2) ddx = dd[0] ddy = dd[1] k = dx * ddy - dy * ddx k = np.absolute(k) k = k / ((dx**2 + dy**2)**1.5) tail_n = np.argmax(k) k[tail_n - 125:tail_n + 125] = 0 head_n = np.argmax(k) self.tail[0] = int(x[tail_n]) self.tail[1] = int(y[tail_n]) self.head[0] = int(x[head_n]) self.head[1] = int(y[head_n]) def move_stage(self): '''Move the stage if the worm gets near the edge of image''' now = datetime.now() elapsed_time = ((now - self.last_step_time).microseconds) / 1000 if self.tracking and elapsed_time > self.step_interval: self.last_step_time = now if self.x < self.margin: self.microscope.move('x', -1 * self.step_size) print 'Moving Left' if self.x > (self.width - self.margin): self.microscope.move('x', self.step_size) print 'Moving Right' if self.y < self.margin: self.microscope.move('y', -1 * self.step_size) print 'Moving Up' if self.y > (self.height - self.margin): self.microscope.move('y', self.step_size) print 'Moving Down' def update_gui(self): '''Update GUI with image and draw feature markers''' if self.show_threshold: self.img = cv2.cvtColor(self.img_thresh, cv2.COLOR_GRAY2BGR) if self.draw_contour: if self.skeleton: cv2.drawContours(self.img, [self.worm_spline], -1, (255, 0, 0), 2) else: cv2.drawContours(self.img, [self.worm], -1, (255, 0, 0), 2) #Draw markers for centroid and boundry cv2.circle(self.img, (self.x, self.y), 5, (0, 0, 255), -1) cv2.rectangle(self.img, (self.margin, self.margin), (self.width - self.margin, self.height - self.margin), (0, 255, 0), 2) #If skeletonise draw markers for head and tail if self.skeleton: cv2.circle(self.img, (self.tail[0], self.tail[1]), 5, (0, 255, 255), -1) cv2.circle(self.img, (self.head[0], self.head[1]), 5, (255, 255, 0), -1) #Show image cv2.imshow('Preview', self.img) cv2.waitKey(1) def run(self): """Runs the worm tracking algorithm indefinitely""" for i in range(100): #Spool off 100 images to allow camera to auto-adjust self.img = self.camera.read() while True: self.read_trackbars() ret, self.img = self.camera.read() self.find_worm() if self.skeleton: self.skeletonise() self.update_gui() self.move_stage() #FPS calculations now = cv2.getTickCount() fps = cv2.getTickFrequency() / (now - self.last_frame) self.last_frame = now print fps
class WormTracker(): """Class for worm tracking algorithm""" def nothing(x, y): #Placeholder as trackbar requires callback pass def __init__(self, serial_port, camera): """Initializes the class with serial interface/camera names, algorithm control parameters. Arguments: serial_port -- String of name for the serial interface for the microscope Arduino - e.g. 'dev/tty.usb' camera -- Integer for the V4L2 video device name e.g. 0 from '/dev/video0' """ self.create_gui() #Setup camera and video recording self.camera = cv2.VideoCapture(camera) self.width = int(self.camera.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH)) self.height = int(self.camera.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT)) self.camera.set(cv2.cv.CV_CAP_PROP_FRAME_WIDTH, self.width) self.camera.set(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT, self.height) #Kernel for morphological opening/closing operation self.kernel = np.ones((3, 3), np.uint8) #Variables for controlling stage self.microscope = Microscope(serial_port) self.microscope.set_ring_colour('FF0000') self.last_step_time = datetime.now() #Use for FPS calculation self.last_frame = cv2.getTickCount() #Skeltonisation self.worm_spline = np.zeros((1001, 1, 2), dtype=np.int) self.tail = np.zeros((2,), dtype=np.int) self.head = np.zeros((2,), dtype=np.int) def change_kernel(self, size): '''Generate new kernel patch for morphological opening/closing''' size = size*2 + 1 self.kernel = np.ones((size, size), np.uint8) def create_gui(self): """"Defines the HighGUI elements """ cv2.namedWindow('Preview') cv2.createTrackbar('Tracking Off/On', 'Preview', 0, 1, self.nothing) cv2.createTrackbar('Threshold', 'Preview', 0, 1, self.nothing) cv2.createTrackbar('Threshold Value', 'Preview', 0, 255, self.nothing) cv2.createTrackbar('Step Interval', 'Preview', 0, 1000, self.nothing) cv2.createTrackbar('Margin', 'Preview', 0, 200, self.nothing) cv2.createTrackbar('Step Size', 'Preview', 0, 20, self.nothing) cv2.createTrackbar('Contour', 'Preview', 0, 1, self.nothing) cv2.createTrackbar('Adaptive', 'Preview', 0, 1, self.nothing) cv2.createTrackbar('Adaptive Value', 'Preview', 0, 40, self.nothing) cv2.createTrackbar('Adaptive Size', 'Preview', 1, 40, self.nothing) cv2.createTrackbar('Gaussian', 'Preview', 0, 1, self.nothing) cv2.createTrackbar('Kernel Size', 'Preview', 1, 20, self.change_kernel) cv2.createTrackbar('Opening', 'Preview', 0, 20, self.nothing) cv2.createTrackbar('Closing', 'Preview', 0, 20, self.nothing) cv2.createTrackbar('Skeleton', 'Preview', 0, 1, self.nothing) cv2.createTrackbar('Smoothing', 'Preview', 0, 1000, self.nothing) #Set default values cv2.setTrackbarPos('Threshold Value', 'Preview', 100) cv2.setTrackbarPos('Step Interval', 'Preview', 500) cv2.setTrackbarPos('Margin', 'Preview', 100) cv2.setTrackbarPos('Step Size', 'Preview', 2) def read_trackbars(self): """Read trackbar values""" self.tracking = bool(cv2.getTrackbarPos('Tracking Off/On', 'Preview')) self.show_threshold = bool(cv2.getTrackbarPos('Threshold', 'Preview')) self.threshold = cv2.getTrackbarPos('Threshold Value', 'Preview') self.step_interval = cv2.getTrackbarPos('Step Interval', 'Preview') self.margin = cv2.getTrackbarPos('Margin', 'Preview') self.step_size = cv2.getTrackbarPos('Step Size', 'Preview') self.draw_contour = bool(cv2.getTrackbarPos('Contour', 'Preview')) self.precision = cv2.getTrackbarPos('Precision', 'Preview') self.adaptive = bool(cv2.getTrackbarPos('Adaptive', 'Preview')) self.adaptive_value = cv2.getTrackbarPos('Adaptive Value', 'Preview') self.adaptive_size = cv2.getTrackbarPos('Adaptive Size', 'Preview') self.adaptive_gauss = bool(cv2.getTrackbarPos('Gaussian', 'Preview')) self.opening = cv2.getTrackbarPos('Opening', 'Preview') self.closing = cv2.getTrackbarPos('Closing', 'Preview') self.skeleton = bool(cv2.getTrackbarPos('Skeleton', 'Preview')) self.smoothing = cv2.getTrackbarPos('Smoothing', 'Preview') def find_worm(self): """Threshold and contouring algorithm to find centroid of worm""" self.img_gray = cv2.cvtColor(self.img, cv2.COLOR_BGR2GRAY) if self.adaptive: #Perform adaptive thresholding size = self.adaptive_size*2 + 1 if self.adaptive_gauss: mode = cv2.ADAPTIVE_THRESH_MEAN_C else: mode = cv2.ADAPTIVE_THRESH_GAUSSIAN_C self.img_thresh = cv2.adaptiveThreshold(self.img_gray, 255, mode, cv2.THRESH_BINARY_INV, size, self.adaptive_value) else: #Simple Threshold ret, self.img_thresh = cv2.threshold(self.img_gray, self.threshold, 255, cv2.THRESH_BINARY_INV) if self.opening != 0: #Morphological Opening self.img_thresh = cv2.morphologyEx(self.img_thresh, cv2.MORPH_OPEN, self.kernel, iterations=self.opening) if self.closing != 0: #Morphological Closing self.img_thresh = cv2.morphologyEx(self.img_thresh, cv2.MORPH_CLOSE, self.kernel, iterations=self.closing) #Copy image to allow displaying later img_contour = self.img_thresh.copy() contours, hierarchy = cv2.findContours(img_contour, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE) #Find the biggest contour worm_area = 0 for contour in contours: area = cv2.contourArea(contour) if area > worm_area: self.worm = contour worm_area = area #Compute the centroid of the worm contour moments = cv2.moments(self.worm) self.x = int(moments['m10']/moments['m00']) self.y = int(moments['m01']/moments['m00']) def skeletonise(self): '''Perform skeletonisation and determine location of head and tail''' x_in = self.worm[:, 0, 0] y_in = self.worm[:, 0, 1] tck, u = interpolate.splprep([x_in, y_in], per=True, s=self.smoothing, quiet=1) points = np.arange(0, 1.001, 0.001) spline = interpolate.splev(points, tck, der=0) x = spline[0] y = spline[1] self.worm_spline[:, 0, 0] = x self.worm_spline[:, 0, 1] = y d = interpolate.splev(points, tck, der=1) dx = d[0] dy = d[1] dd = interpolate.splev(points, tck, der=2) ddx = dd[0] ddy = dd[1] k = dx*ddy - dy*ddx k = np.absolute(k) k = k/((dx**2 + dy**2)**1.5) tail_n = np.argmax(k) k[tail_n-125:tail_n+125] = 0 head_n = np.argmax(k) self.tail[0] = int(x[tail_n]) self.tail[1] = int(y[tail_n]) self.head[0] = int(x[head_n]) self.head[1] = int(y[head_n]) def move_stage(self): '''Move the stage if the worm gets near the edge of image''' now = datetime.now() elapsed_time = ((now - self.last_step_time).microseconds)/1000 if self.tracking and elapsed_time > self.step_interval: self.last_step_time = now if self.x < self.margin: self.microscope.move('x', -1*self.step_size) print 'Moving Left' if self.x > (self.width - self.margin): self.microscope.move('x', self.step_size) print 'Moving Right' if self.y < self.margin: self.microscope.move('y', -1*self.step_size) print 'Moving Up' if self.y > (self.height - self.margin): self.microscope.move('y', self.step_size) print 'Moving Down' def update_gui(self): '''Update GUI with image and draw feature markers''' if self.show_threshold: self.img = cv2.cvtColor(self.img_thresh, cv2.COLOR_GRAY2BGR) if self.draw_contour: if self.skeleton: cv2.drawContours(self.img, [self.worm_spline], -1, (255, 0, 0), 2) else: cv2.drawContours(self.img, [self.worm], -1, (255, 0, 0), 2) #Draw markers for centroid and boundry cv2.circle(self.img, (self.x, self.y), 5, (0, 0, 255), -1) cv2.rectangle(self.img, (self.margin, self.margin), (self.width-self.margin, self.height-self.margin), (0, 255, 0), 2) #If skeletonise draw markers for head and tail if self.skeleton: cv2.circle(self.img, (self.tail[0], self.tail[1]), 5, (0, 255, 255), -1) cv2.circle(self.img, (self.head[0], self.head[1]), 5, (255, 255, 0), -1) #Show image cv2.imshow('Preview', self.img) cv2.waitKey(1) def run(self): """Runs the worm tracking algorithm indefinitely""" for i in range(100): #Spool off 100 images to allow camera to auto-adjust self.img = self.camera.read() while True: self.read_trackbars() ret, self.img = self.camera.read() self.find_worm() if self.skeleton: self.skeletonise() self.update_gui() self.move_stage() #FPS calculations now = cv2.getTickCount() fps = cv2.getTickFrequency()/(now - self.last_frame) self.last_frame = now print fps