def errorSurface(axes=[3, 0], v1=None, v2=None, bounds=None, noise=0.0, n=5000): ## compute sum of squares error between two templates over a range of differences in v ## the two templates are generated from the parameters in v1 and v2 ## the error surface is computed by varying v2[axis[n]] from bounds[axis[n]][0] to bounds[axis[n]][1] on ## each axis of the surface. ## displays and returns the error surface, ## also returns an array of all the v2 parameters used for each point in the surface. x = np.linspace(0, 0.5, 5000) v = [1.0, 0.05, 0.05, 0.1] ## defaults used if v1 / v2 are not given if v1 is None: v1 = v[:] if v2 is None: v2 = v1[:] if bounds is None: bounds = [(0.0, 2.0), (0.0, 0.1), (0.01, 0.1), (0.01, 0.5)] template1 = pspFunc(v1, x) + np.random.normal(size=len(x), scale=noise) ## number of iterations per axis n = int(n**(1.0 / len(axes))) axv = [] for ax in axes: axv.append(np.linspace(bounds[ax][0], bounds[ax][1], n)) err = np.empty((n, ) * len(axes)) vals = np.empty(err.shape, dtype=object) inds = np.indices(err.shape).reshape((len(axes), err.size)) for i in xrange(inds.shape[1]): ind = tuple(inds[:, i]) v2a = v2[:] for j in range(len(axes)): v2a[axes[j]] = axv[j][ind[j]] template2 = pspFunc(v2a, x) err[ind] = np.sum((template2 - template1)**2) vals[ind] = v2a if len(axes) == 2: p = pg.plot() img = pg.ImageItem(err) p.addItem(img) b1 = bounds[axes[0]] b2 = bounds[axes[1]] img.setRect(QtCore.QRectF(b1[0], b2[0], b1[1] - b1[0], b2[1] - b2[0])) elif len(axes) == 3: pg.image(err) return err, vals
def errorSurface(axes=[3, 0], v1=None, v2=None, bounds=None, noise=0.0, n=5000): ## compute sum of squares error between two templates over a range of differences in v ## the two templates are generated from the parameters in v1 and v2 ## the error surface is computed by varying v2[axis[n]] from bounds[axis[n]][0] to bounds[axis[n]][1] on ## each axis of the surface. ## displays and returns the error surface, ## also returns an array of all the v2 parameters used for each point in the surface. x = np.linspace(0, 0.5, 5000) v = [1.0, 0.05, 0.05, 0.1] ## defaults used if v1 / v2 are not given if v1 is None: v1 = v[:] if v2 is None: v2 = v1[:] if bounds is None: bounds = [(0.0, 2.0), (0.0, 0.1), (0.01, 0.1), (0.01, 0.5)] template1 = pspFunc(v1, x) + np.random.normal(size=len(x), scale=noise) ## number of iterations per axis n = int(n**(1.0/len(axes))) axv = [] for ax in axes: axv.append(np.linspace(bounds[ax][0], bounds[ax][1], n)) err = np.empty((n,)*len(axes)) vals = np.empty(err.shape, dtype=object) inds = np.indices(err.shape).reshape((len(axes), err.size)) for i in xrange(inds.shape[1]): ind = tuple(inds[:,i]) v2a = v2[:] for j in range(len(axes)): v2a[axes[j]] = axv[j][ind[j]] template2 = pspFunc(v2a, x) err[ind] = np.sum((template2-template1)**2) vals[ind] = v2a if len(axes) == 2: p = pg.plot() img = pg.ImageItem(err) p.addItem(img) b1 = bounds[axes[0]] b2 = bounds[axes[1]] img.setRect(QtCore.QRectF(b1[0], b2[0], b1[1]-b1[0], b2[1]-b2[0])) elif len(axes) == 3: pg.image(err) return err, vals
def _matchTemplateSingle(self, img, template, show=False, unsharp=3): import skimage.feature if img.shape[0] < template.shape[0] or img.shape[1] < template.shape[1]: raise ValueError("Image must be larger than template. %s %s" % (img.shape, template.shape)) cc = skimage.feature.match_template(img, template) # high-pass filter; we're looking for a fairly sharp peak. if unsharp is not False: cc_filt = cc - scipy.ndimage.gaussian_filter(cc, (unsharp, unsharp)) else: cc_filt = cc if show: pg.image(cc) ind = np.argmax(cc_filt) pos = np.unravel_index(ind, cc.shape) val = cc[pos[0], pos[1]] return pos, val
def showErrorAnalysis(self): if not hasattr(self, 'errorMap'): filename = self.dev.configFileName('error_map.np') self.errorMap = np.load(open(filename, 'rb'))[np.newaxis][0] err = self.errorMap imx = pg.image(err['err'][..., 0].transpose(1, 0, 2), title='X error') imy = pg.image(err['err'][..., 1], title='Y error') imz = pg.image(err['err'][..., 2], title='Z error') # get N,3 array of offset values used to randomize hysteresis off = np.vstack(err['offsets']) sh = err['err'].shape # Get N,3 array of measured position errors errf = err['err'].reshape(sh[0] * sh[1] * sh[2], 3)[err['order']] # Display histogram of errors win = pg.GraphicsWindow(title="%s error" % self.dev.name()) # subtract out slow drift normErr = errf - scipy.ndimage.gaussian_filter(errf, (20, 0)) # calculate magnitude of error absErr = (normErr**2).sum(axis=1)**0.5 # errPlot.plot(absErr) title = "Error Histogram (mean=%s)" % pg.siFormat(absErr.mean(), suffix='m') errPlot = win.addPlot(row=0, col=0, title=title, labels={'bottom': ('Position error', 'm')}) hist = np.histogram(absErr, bins=50) errPlot.plot(hist[1], hist[0], stepMode=True) # display drift and hysteresis plots driftPlot = win.addPlot(row=0, col=1, rowspan=1, colspan=2, title="Pipette Drift", labels={ 'left': ('Position error', 'm'), 'bottom': ('Time', 's') }) driftPlot.plot(np.linspace(0, err['time'], errf.shape[0]), errf[:, 0], pen='r') driftPlot.plot(np.linspace(0, err['time'], errf.shape[0]), errf[:, 1], pen='g') driftPlot.plot(np.linspace(0, err['time'], errf.shape[0]), errf[:, 2], pen='b') xhplot = win.addPlot(row=1, col=0, title='X Hysteresis', labels={ 'left': ('Position error', 'm'), 'bottom': ('Last pipette movement', 'm') }) xhplot.plot(-off[:, 0], errf[:, 0], pen=None, symbol='o') yhplot = win.addPlot(row=1, col=1, title='Y Hysteresis', labels={ 'left': ('Position error', 'm'), 'bottom': ('Last pipette movement', 'm') }) yhplot.plot(-off[:, 1], errf[:, 1], pen=None, symbol='o') zhplot = win.addPlot(row=1, col=2, title='Z Hysteresis', labels={ 'left': ('Position error', 'm'), 'bottom': ('Last pipette movement', 'm') }) zhplot.plot(-off[:, 2], errf[:, 2], pen=None, symbol='o') # Print best fit for manipulator axes expPos = err['inds'] * err['stepSize'] measPos = expPos + off guess = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0]], dtype='float') def errFn(v): return ((measPos - np.dot(expPos, v.reshape(3, 4))[:, :3])**2).sum() fit = scipy.optimize.minimize(errFn, guess) print("Pipette position transform:", fit) self.errorMapAnalysis = (imx, imy, imz, win)
def mapErrors(self, nSteps=(5, 5, 7), stepSize=(50e-6, 50e-6, 50e-6), padding=60e-6, threshold=0.4, speed='slow', show=False, intermediateDist=60e-6): """Move pipette tip randomly to locations in a grid and measure the position error at each location. All tip locations must be within the field of view. """ startTime = time.time() start = np.array(self.dev.globalPosition()) npts = nSteps[0] * nSteps[1] * nSteps[2] inds = np.mgrid[0:nSteps[0], 0:nSteps[1], 0:nSteps[2]].reshape( (3, npts)).transpose() order = np.arange(npts) np.random.shuffle(order) err = np.zeros(nSteps + (3, )) stepSize = np.array(stepSize) if show: imv = pg.image() mark1 = pg.QtGui.QGraphicsEllipseItem( pg.QtCore.QRectF(-5, -5, 10, 10)) mark1.setBrush(pg.mkBrush(255, 255, 0, 100)) mark1.setZValue(100) imv.addItem(mark1) mark2 = pg.QtGui.QGraphicsEllipseItem( pg.QtCore.QRectF(-5, -5, 10, 10)) mark2.setBrush(pg.mkBrush(255, 0, 0, 100)) mark2.setZValue(100) imv.addItem(mark2) # loop over all points in random order, and such that we do heavy computation while # pipette is moving. images = [] offsets = [] try: with pg.ProgressDialog("Acquiring error map...", 0, len(order)) as dlg: for i in range(len(order) + 1): if i > 0: lastPos = pos if i < len(order): ind = inds[order[i]] pos = start.copy() + (stepSize * ind) # Jump to position + a random 20um offset to avoid hysteresis offset = np.random.normal(size=3) offset *= intermediateDist / (offset**2).sum()**0.5 offsets.append(offset) mfut = self.dev._moveToGlobal(pos + offset, speed) ffut = self.dev.scopeDevice().setFocusDepth( pos[2], speed) if i > 0: ind = inds[order[i - 1]] print("Frame: %d %s" % (i - 1, lastPos)) err[tuple(ind)] = self.measureError( padding=padding, threshold=threshold, frame=frame, pos=lastPos) print(" error: %s" % err[tuple(ind)]) dlg += 1 if show: imv.setImage(frame.data()[0]) p1 = frame.globalTransform().inverted()[0].map( pg.Vector(lastPos)) p2 = frame.globalTransform().inverted()[0].map( pg.Vector(lastPos + err[tuple(ind)])) mark1.setPos(p1.x(), p1.y()) mark2.setPos(p2.x(), p2.y()) # wait for previous moves to complete mfut.wait(updates=True) ffut.wait(updates=True) # step back to actual target position self.dev._moveToGlobal(pos, speed).wait(updates=True) frame = self.takeFrame() if dlg.wasCanceled(): return None finally: self.dev._moveToGlobal(start, 'fast') self.dev.scopeDevice().setFocusDepth(start[2], 'fast') self.errorMap = { 'err': err, 'nSteps': nSteps, 'stepSize': stepSize, 'order': order, 'inds': inds, 'offsets': offsets, 'time': time.time() - startTime, } filename = self.dev.configFileName('error_map.np') np.save(open(filename, 'wb'), self.errorMap) return self.errorMap