class Stack: "A class to load and manipulate images generated during single molecule experiments" defaultROI = {} defaultDonorROI = 'donor' defaultAcceptorROI = 'acceptor' @classmethod def fromfile(cls, imgfile, camfile=''): img = fileIO.loadimg(imgfile) camfile = camfile or fileIO.to_cam_filename(imgfile) metadata = fileIO.loadcam(camfile) metadata['filename'] = imgfile stack = cls(img, metadata) # Keeping the .filename attribute for backward compatibility, but should move to # metadata lookup stack.filename = metadata['filename'] return stack # def __init__(self, filename, camFile='', deepcopy=False): def __init__(self, data, metadata={}, roi={}): self._img = copy.copy(data) self.metadata = dict(metadata) self._donorROIName = Stack.defaultDonorROI self._acceptorROIName = Stack.defaultAcceptorROI self._figure = Figure() self._roi = roi if not roi: self.addROI(*self.defaultROI.values()) self._frame_iter = cycle(range(self.frames)) if metadata: self.origin = (self.metadata['roileft'],self.metadata['roibottom']) if self._img.shape != (self.metadata['frames'],self.metadata['height'],self.metadata['width']): raise StackError, ".img file and .cam file dimensions do not agree" def copy(self, deep=True): newstack = copy.copy(self) if deep: newstack._img = copy.copy(self._img) return newstack @property def frames(self): return self._img.shape[0] def __len__(self): return self.frames @property def time(self): return np.arange(1,self.frames+1)*self.metadata['exposurems']/1000. @property def height(self): return self._img.shape[1] @property def width(self): return self._img.shape[2] @property def donor(self): if not self._roi.has_key(self._donorROIName): raise StackError, "ROI called %s hasn't been defined yet" % self._donorROIName return self.counts(self._roi[self._donorROIName]) @property def acceptor(self): if not self._roi.has_key(self._acceptorROIName): raise StackError, "ROI called %s hasn't been defined yet" % self._acceptorROIName return self.counts(self._roi[self._acceptorROIName]) @property def roi(self): return self._roi @classmethod def setDefaultROI(cls, *args): for _roi in args: cls.defaultROI[_roi.name]=_roi def toBackground(self,zfilter='median'): width, height = self.width, self.height if zfilter == 'median': self._img = np.median( self._img, axis=0, overwrite_input=True ).reshape((1,height,width)) elif zfilter == 'mean': self._img = np.mean( self._img, axis=0 ).reshape((1,height,width)) elif zfilter == 'min': self._img = np.min( self._img, axis=0 ).reshape((1,height,width)) else: raise ValueError, "Filter type can only be median, mean, or min" return self def addROI(self, *ROIs): for roi in ROIs: try: roi = ROI.copy(roi) key = roi.name roi = roi.toRelative(self.origin) if roi.right > self.width: raise StackError( "ROI 'right' {0} is outside right edge of image {1}: \n {2}".format(roi.right,self.width,roi) ) if roi.top > self.height: raise StackError, "ROI 'top' is outside top edge of image: {0}\n {1}".format(roi.top,roi) self._roi[key] = roi except AttributeError: raise TypeError, "Must use objects with ROI interface" def showROI(self,*args): for roi in args: self._roi[roi].draw() def show(self, frame=None, **kwargs): if not isinstance(frame, int) and frame is not None: raise ValueError('First argument frame must be an integer') self._figure = kwargs.pop('figure', self._figure) if frame is None: frame = next(self._frame_iter) else: self._frame_iter = dropwhile(lambda n: n<=frame, cycle(range(self.frames))) self._figure.show() self._figure.makeCurrent() plt.title('Frame %d' % frame) self[frame].show(**kwargs) return frame def setDonorROI(self, roi_name): if not self._roi.has_key(roi_name): raise KeyError, "Image.Stack does not have an ROI named %s" % roi_name self._donorROIName = roi_name return self def setAcceptorROI(self, roi_name): if not self._roi.has_key(roi_name): raise KeyError, "Image.Stack does not have an ROI named %s" % roi_name self._acceptorROIName = roi_name return self def counts(self, roi=None): if roi: if self._roi.has_key(str(roi)): roi = self._roi[roi] roi = roi.toRelative(self.origin) return self[:,roi.bottom:roi.top,roi.left:roi.right].counts() else: return np.sum( np.sum(self._img,axis=1), axis=1 ) def attime(self,time): if isinstance(time,slice): start,step = None,None exposurems = self.metadata['exposurems'] if time.start: start = time.start/exposurems if time.step: step = time.step/exposurems time = slice(start,time.stop/exposurems,step) return self[time/exposurems] def __getitem__(self,key): if isinstance(key,int): # Single frame return Frame(self._img[key], self._roi) else: # It's a slice temp = self.copy(deep=False) temp._img = temp._img[key] if isinstance(temp._img.shape,tuple) and len(temp._img.shape) > 2: temp.frames = temp._img.shape[0] else: temp.frames = 1 return temp raise IndexError, "Invalid index: %s" % str(key) def append(self, stack): temp = copy.copy(self) temp._img = np.append( temp._img, stack._img, axis=0 ) return temp def __sub__(self, stack): return self.__add__(stack.__neg__()) def __add__(self, stack): temp = self.copy() if hasattr(stack,'_img'): try: temp._img = temp._img + stack._img except ValueError: raise StackError("Couldn't add images: check sizes are the same") else: temp._img = temp._img + stack return temp def __neg__(self): temp = self.copy() temp._img = -temp._img return temp def __eq__(self, other): return np.all(self._img == other._img) def __ne__(self, other): return not self==other def __repr__(self): return "Stack %dx%dx%d" % (self.frames, self.height, self.width) def __iter__(self): for i in range(self.frames): yield self[i]