Exemplo n.º 1
0
    def setGlobalPosition(self, pos, speed='fast'):
        """Move the microscope such that its center axis is at a specified global position.

        If *pos* is a 3-element vector, then this method will also attempt to set the focus depth
        accordingly.

        Return a MoveFuture instance.

        Note: If the xy positioning device is different from the z positioning
        device, then the MoveFuture returned only corresponds to the xy motion.
        """
        pd = self.positionDevice()
        fd = self.focusDevice()

        if len(pos) == 3 and fd is not pd:
            z = pos[2]
            self.setFocusDepth(z)
            pos = pos[:2]
        if len(pos) == 2:
            pos = list(pos) + [self.getFocusDepth()]

        # Determine how to move the xy(z) stage to react the new center position
        gpos = self.globalPosition()
        sgpos = pd.globalPosition()
        sgpos2 = pg.Vector(sgpos) + (pg.Vector(pos) - gpos)
        sgpos2 = [sgpos2.x(), sgpos2.y(), sgpos2.z()]
        return pd.moveToGlobal(sgpos2, speed)
Exemplo n.º 2
0
    def __init__(self, config, scope, key):
        #self.__sigProxy = Objective.SignalProxyObject()
        #self.sigTransformChanged = self.__sigProxy.sigTransformChanged
        #self._config = config
        self._config = config
        self._scope = scope
        self._key = key
        offset = config.get('offset', pg.Vector(0, 0, 0))
        scale = config.get('scale', pg.Vector(1, 1, 1))
        name = config['name']

        OptomechDevice.__init__(self, scope.dm, {}, name)

        if 'offset' in config:
            self.setOffset(config['offset'])
        if 'scale' in config:
            self.setScale(config['scale'])
Exemplo n.º 3
0
    def measureTipPosition(self, padding=50e-6, threshold=0.7, frame=None, pos=None, tipLength=None, show=False):
        """Find the pipette tip location by template matching within a region surrounding the
        expected tip position.

        Return `((x, y, z), corr)`, where *corr* is the normalized cross-correlation value of
        the best template match.

        If the strength of the match is less than *threshold*, then raise RuntimeError.
        """
        # Grab one frame (if it is not already supplied) and crop it to the region around the pipette tip.
        if frame is None:
            frame = self.takeFrame()

        # load up template images
        reference = self._getReference()

        if tipLength is None:
            # select a tip length similar to template images
            tipLength = reference['tipLength']

        minImgPos, maxImgPos, tipRelPos = self.getTipImageArea(frame, padding, pos=pos, tipLength=tipLength)
        img = frame.data()
        if img.ndim == 3:
            img = img[0]
        img = img[minImgPos[0]:maxImgPos[0], minImgPos[1]:maxImgPos[1]]
        img = self.filterImage(img)

        # resample acquired image to match template pixel size
        pxr = frame.info()['pixelSize'][0] / reference['pixelSize'][0]
        if pxr != 1.0:
            img = scipy.ndimage.zoom(img, pxr)

        # run template match against all template frames, find the frame with the strongest match
        match = [self.matchTemplate(img, t) for t in reference['frames']]

        if show:
            pg.plot([m[0][0] for m in match], title='x match vs z')
            pg.plot([m[0][1] for m in match], title='y match vs z')
            pg.plot([m[1] for m in match], title='match correlation vs z')

        maxInd = np.argmax([m[1] for m in match])
        if match[maxInd][1] < threshold:
            raise RuntimeError("Unable to locate pipette tip (correlation %0.2f < %0.2f)" % (match[maxInd][1], threshold))

        # measure z error
        zErr = (maxInd - reference['centerInd']) * reference['zStep']

        # measure xy position
        offset = match[maxInd][0]
        tipImgPos = (minImgPos[0] + (offset[0] + reference['centerPos'][0]) / pxr, 
                     minImgPos[1] + (offset[1] + reference['centerPos'][1]) / pxr)
        tipPos = frame.mapFromFrameToGlobal(pg.Vector(tipImgPos))
        return (tipPos.x(), tipPos.y(), tipPos.z() + zErr), match[maxInd][1]
Exemplo n.º 4
0
    def autoCalibrate(self, **kwds):
        """Automatically calibrate the pipette tip position using template matching on a single camera frame.

        Return the offset in pipette-local coordinates and the normalized cross-correlation value of the template match.

        All keyword arguments are passed to `measureTipPosition()`.
        """
        # If no image padding is given, then use the template tip length as a first guess
        if 'padding' not in kwds:
            ref = self._getReference()
            kwds['padding'] = ref['tipLength']

        try:
            tipPos, corr = self.measureTipPosition(**kwds)
        except RuntimeError:
            kwds['padding'] *= 2
            tipPos, corr = self.measureTipPosition(**kwds)

        localError = self.dev.mapFromGlobal(tipPos)
        tr = self.dev.deviceTransform()
        tr.translate(pg.Vector(localError))
        self.dev.setDeviceTransform(tr)
        return localError, corr
Exemplo n.º 5
0
    def getBackground(self):
        if self.background is None:
            w, h = self.params['sensorSize']
            tr = self.globalTransform()

            if isinstance(self.bgData, dict):
                # select data based on objective
                obj = self.getObjective()
                data = self.bgData[obj]
                info = self.bgInfo[obj]
                px = info['pixelSize']
                pz = info['depths'][1] - info['depths'][0]

                m = pg.QtGui.QMatrix4x4()
                pos = info['transform']['pos']
                m.scale(1 / px[0], 1 / px[1], 1 / pz)
                m.translate(-pos[0], -pos[1], -info['depths'][0])

                tr2 = m * tr
                origin = tr2.map(pg.Vector(0, 0, 0))
                #print(origin)
                origin = [int(origin.x()), int(origin.y()), origin.z()]

                ## slice data
                camRect = QtCore.QRect(origin[0], origin[1], w, h)
                dataRect = QtCore.QRect(0, 0, data.shape[1], data.shape[2])
                overlap = camRect.intersected(dataRect)
                tl = overlap.topLeft() - camRect.topLeft()

                z = origin[2]
                z1 = np.floor(z)
                z2 = np.ceil(z)
                s = (z - z1) / (z2 - z1)
                z1 = int(np.clip(z1, 0, data.shape[0] - 1))
                z2 = int(np.clip(z2, 0, data.shape[0] - 1))
                src1 = data[z1,
                            overlap.left():overlap.left() + overlap.width(),
                            overlap.top():overlap.top() + overlap.height()]
                src2 = data[z2,
                            overlap.left():overlap.left() + overlap.width(),
                            overlap.top():overlap.top() + overlap.height()]
                src = src1 * (1 - s) + src2 * s

                bg = np.empty((w, h), dtype=data.dtype)
                bg[:] = 100
                bg[tl.x():tl.x() + overlap.width(),
                   tl.y():tl.y() + overlap.height()] = src
                self.background = bg
                #vectors = ([1, 0, 0], [0, 1, 0])
                #self.background = pg.affineSlice(data, (w,h), origin, vectors, (1, 2, 0), order=1)
            else:
                tr = pg.SRTTransform(tr)
                m = QtGui.QTransform()

                m.scale(3e6, 3e6)
                m.translate(0.0005, 0.0005)
                tr = tr * m

                origin = tr.map(pg.Point(0, 0))
                x = (tr.map(pg.Point(1, 0)) - origin)
                y = (tr.map(pg.Point(0, 1)) - origin)
                origin = np.array([origin.x(), origin.y()])
                x = np.array([x.x(), x.y()])
                y = np.array([y.x(), y.y()])

                ## slice fractal from pre-rendered data
                vectors = (x, y)
                self.background = pg.affineSlice(self.bgData, (w, h),
                                                 origin,
                                                 vectors, (0, 1),
                                                 order=1)

        return self.background
Exemplo n.º 6
0
    def getTipImageArea(self, frame, padding, pos=None, tipLength=None):
        """Generate coordinates needed to clip a camera frame to include just the
        tip of the pipette and some padding.

        By default, images will include the tip of the pipette to a length of 100 pixels.

        Return a tuple (minImgPos, maxImgPos, tipRelPos), where the first two
        items are (x,y) coordinate pairs giving the corners of the image region to 
        be extracted, and tipRelPos is the subpixel location of the pipette tip
        within this region.
        """
        img = frame.data()
        if img.ndim == 3:
            img = img[0]

        if tipLength is None:
            tipLength = self.suggestTipLength(frame)

        # determine bounding rectangle that we would like to acquire from the tip
        if pos is not None:
            tipPos = pos
        else:
            tipPos = self.dev.globalPosition()
        tipPos = np.array([tipPos[0], tipPos[1]])
        angle = self.dev.getYawAngle() * np.pi / 180.
        da = 10 * np.pi / 180  # half-angle of the tip
        pxw = frame.info()['pixelSize'][0]
        # compute back points of a triangle that circumscribes the tip
        backPos1 = np.array(
            [-tipLength * np.cos(angle + da), -tipLength * np.sin(angle + da)])
        backPos2 = np.array(
            [-tipLength * np.cos(angle - da), -tipLength * np.sin(angle - da)])

        # convert to image coordinates
        tr = frame.globalTransform().inverted()[0]
        originImgPos = tr.map(pg.Vector([0, 0]))
        backImgPos1 = tr.map(pg.Vector(backPos1)) - originImgPos
        backImgPos2 = tr.map(pg.Vector(backPos2)) - originImgPos
        backImgPos1 = np.array([backImgPos1.x(), backImgPos1.y()])
        backImgPos2 = np.array([backImgPos2.x(), backImgPos2.y()])

        # Pixel positions of bounding corners in the image relative to tip, including padding.
        # Note this is all calculated without actual tip position; this ensures the image
        # size is constant even as the tip moves.
        allPos = np.vstack([[0, 0], backImgPos1, backImgPos2]).astype('int')
        padding = int(padding / pxw)
        minRelPos = allPos.min(axis=0) - padding
        maxRelPos = allPos.max(axis=0) + padding

        # Get absolute pixel position of tip within image
        tipImgPos = tr.map(pg.Vector(tipPos))
        tipImgPos = np.array([tipImgPos.x(), tipImgPos.y()])
        tipImgPx = tipImgPos.astype('int')

        # clip bounding coordinates
        minRelPos = [
            np.clip(minRelPos[0], -tipImgPx[0],
                    img.shape[0] - 1 - tipImgPx[0]),
            np.clip(minRelPos[1], -tipImgPx[1], img.shape[1] - 1 - tipImgPx[1])
        ]
        maxRelPos = [
            np.clip(maxRelPos[0], -tipImgPx[0],
                    img.shape[0] - 1 - tipImgPx[0]),
            np.clip(maxRelPos[1], -tipImgPx[1], img.shape[1] - 1 - tipImgPx[1])
        ]

        # absolute image coordinates of bounding rect
        minImgPos = tipImgPx + minRelPos
        maxImgPos = tipImgPx + maxRelPos

        if np.any(maxImgPos - minImgPos < 1):
            raise RuntimeError("No part of tip overlaps with camera frame.")

        # subpixel location of tip within image
        tipRelPos = tipImgPos - tipImgPx - minRelPos

        return minImgPos, maxImgPos, tipRelPos
Exemplo n.º 7
0
    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