# # COPYRIGHT: # The Leginon software is Copyright 2003 # The Scripps Research Institute, La Jolla, CA # For terms of the license agreement # see http://ami.scripps.edu/software/leginon-license # # target intensity: 140410.1 import ccdcamera import sys import time import gatansocket import numpy import itertools import os from pyscope import moduleconfig simulation = False if simulation: print 'USING SIMULATION SETTINGS' # only one connection will be shared among all classes def connect(): if not hasattr(gatansocket, 'myGS'): gatansocket.myGS = gatansocket.GatanSocket() return gatansocket.myGS configs = moduleconfig.getConfigured('dmsem.cfg') class DMSEM(ccdcamera.CCDCamera): ed_mode = None filter_method_names = [ 'getEnergyFilter', 'setEnergyFilter', 'getEnergyFilterWidth', 'setEnergyFilterWidth', 'alignEnergyFilterZeroLossPeak', ] def __init__(self): self.unsupported = [] self.camera = connect() self.idcounter = itertools.cycle(range(100)) ccdcamera.CCDCamera.__init__(self) if not self.getEnergyFiltered(): self.unsupported.extend(self.filter_method_names) self.bblankerid = 0 self.binning = {'x': 1, 'y': 1} self.offset = {'x': 0, 'y': 0} self.tempoffset = dict(self.offset) self.camsize = self.getCameraSize() self.dimension = {'x': self.camsize['x'], 'y': self.camsize['y']} self.exposuretype = 'normal' self.user_exposure_ms = 100 self.float_scale = 1000.0 # what to do in digital micrograph before handing back the image # unprocessed, dark subtracted, gain normalized #self.dm_processing = 'gain normalized' self.dm_processing = 'unprocessed' self.save_frames = False self.frames_name = None #self.frame_rate = 4.0 self.dosefrac_frame_time = 0.200 self.record_precision = 0.100 self.readout_delay_ms = 0 self.align_frames = False self.align_filter = 'None' def __getattribute__(self, attr_name): if attr_name in object.__getattribute__(self, 'unsupported'): raise AttributeError('attribute not supported') return object.__getattribute__(self, attr_name) def getDmsemConfig(self,optionname,itemname=None): if itemname is None: return configs[optionname] else: return configs[optionname][itemname] def getOffset(self): return dict(self.offset) def setOffset(self, value): # Work around self.offset = dict(value) self.tempoffset = {'x':0,'y':0} def getDimension(self): return dict(self.dimension) def setDimension(self, value): # Work around self.dimension = dict(value) def getBinning(self): return dict(self.binning) def setBinning(self, value): if value['x'] != value['y']: raise ValueError('multiple binning dimesions not supported') self.binning = dict(value) def getRealExposureTime(self): return self.getExposureTime() / 1000.0 def getExposureTime(self): return self.user_exposure_ms def setExposureTime(self, value): self.user_exposure_ms = value def getExposureTypes(self): return ['normal', 'dark'] def getExposureType(self): return self.exposuretype def setExposureType(self, value): if value not in ['normal', 'dark']: raise ValueError('invalid exposure type') self.exposuretype = value def isDM230orUp(self): version_id,version_string = self.getDMVersion() if version_id and version_id >= 40300: return True return False def isDM231orUp(self): version_id,version_string = self.getDMVersion() if version_id and version_id >= 40301: return True return False def needConfigDimensionFlip(self,height,width): # DM 2.3.0 and up needs camera dimension input in its original # orientation regardless of rotation when dose fractionation is used. if self.isDM230orUp() and (self.save_frames or self.align_frames): if height > width: return True return False def calculateAcquireParams(self): exptype = self.getExposureType() if exptype == 'dark': processing = 'dark' else: processing = self.dm_processing # I think it's negative... shutter_delay = -self.readout_delay_ms / 1000.0 physical_binning = self.binning['x'] if self.ed_mode != 'super resolution': binscale = 1 else: binscale = 2 if self.binning['x'] > 1: # physical binning is half super resolution binning except when the latter is 1 physical_binning /= binscale height = self.offset['y']+self.dimension['y'] width = self.offset['x']+self.dimension['x'] if self.needConfigDimensionFlip(height,width): tmpheight = height height = width width = tmpheight acqparams = { 'processing': processing, 'height': height, 'width': width, 'binning': physical_binning, 'top': self.tempoffset['y'] / binscale, 'left': self.tempoffset['x'] / binscale, 'bottom': height / binscale, 'right': width / binscale, 'exposure': self.getRealExposureTime(), 'shutterDelay': shutter_delay, } print 'DM acqire shape (%d, %d)' % (height,width) return acqparams def custom_setup(self): # required for non-K2 cameras self.camera.SetReadMode(-1) def _getImage(self): self.camera.SelectCamera(self.cameraid) self.custom_setup() acqparams = self.calculateAcquireParams() t0 = time.time() image = self.camera.GetImage(**acqparams) t1 = time.time() self.exposure_timestamp = (t1 + t0) / 2.0 if self.getExposureType() == 'dark': self.modifyDarkImage(image) # workaround dose fractionation image rotate-flip not applied problem print 'received shape',image.shape if self.save_frames or self.align_frames: if not self.isDM231orUp(): k2_rotate = self.getDmsemConfig('k2','rotate') k2_flip = self.getDmsemConfig('k2','flip') if k2_rotate: image = numpy.rot90(image,4-k2_rotate) if k2_flip: image = numpy.fliplr(image) # workaround to offset image problem startx = self.getOffset()['x'] starty = self.getOffset()['y'] if startx != 0 or starty != 0: endx = self.dimension['x'] + startx endy = self.dimension['y'] + starty image = image[starty:endy,startx:endx] print 'modified shape',image.shape if self.dm_processing == 'gain normalized' and self.ed_mode in ('counting','super resolution'): image = numpy.asarray(image, dtype=numpy.float32) image /= self.float_scale return image def modifyDarkImage(self,image): ''' in-place modification of image array ''' return def getPixelSize(self): ## TODO: move to config file: # pixel size on Gatan K2 return {'x': 5e-6, 'y': 5e-6} def getRetractable(self): return True def setInserted(self, value): inserted = self.getInserted() if not inserted and value: self.camera.InsertCamera(self.cameraid, value) elif inserted and not value: self.camera.InsertCamera(self.cameraid, value) else: return ## TODO: determine necessary settling time: time.sleep(5) def getInserted(self): return self.camera.IsCameraInserted(self.cameraid) def setReadoutDelay(self, ms): if not ms: ms = 0 self.readout_delay_ms = ms def getReadoutDelay(self): return self.readout_delay_ms def getDMVersion(self): ''' version: version_long, major.minor.sub ''' dm_version = self.getDmsemConfig('dm','dm_version') if dm_version: bits = map((lambda x:int(x)),dm_version.split('.')) version_long = (bits[0]+2)*10000 + (bits[1] //10)*100 + bits[1] % 10 else: version_long = self.camera.GetDMVersion() if version_long < 40000: major = 1 minor = None sub = None if version_long >=31100: # 2.3.0 gives an odd number of 31100 major = 2 minor = 3 sub = 0 version_long = 40300 elif version_long == 40000: # minor version can be 0 or 1 in this case # but likely 1 since we have not used this module until k2 is around major = 2 minor = 1 sub = 0 else: major = version_long // 10000 - 2 remainder = version_long - (major+2) * 10000 minor = remainder // 100 sub = remainder % 100 return (version_long,'%d.%d.%d' % (major,minor,sub)) def hasScriptFunction(self, name): return self.camera.hasScriptFunction(name) def getEnergyFiltered(self): ''' Return True if energy filter is available through this DM ''' for method_name in self.filter_method_names: method_name = method_name[0].upper() + method_name[1:] if method_name not in self.camera.filter_functions.keys(): return False return True def getEnergyFilter(self): ''' Return True if post column energy filter is enabled with slit in ''' return self.camera.GetEnergyFilter() > 0.0 def setEnergyFilter(self, value): ''' Enable/Disable post column energy filter by retracting the slit ''' if value: i = 1 else: i = 0 result = self.camera.SetEnergyFilter(i) if result < 0.0: raise RuntimeError('unable to set energy filter slit position') def getEnergyFilterWidth(self): return self.camera.GetEnergyFilterWidth() def setEnergyFilterWidth(self, value): result = self.camera.SetEnergyFilterWidth(value) if result < 0.0: raise RuntimeError('unable to set energy filter width') def alignEnergyFilterZeroLossPeak(self): result = self.camera.AlignEnergyFilterZeroLossPeak() if result < 0.0: raise RuntimeError('unable to align energy filter zero loss peak') class GatanOrius(DMSEM): name = 'GatanOrius' try: cameraid = configs['orius']['camera_id'] except: pass binning_limits = [1,2,4] binmethod = 'exact' class GatanUltraScan(DMSEM): name = 'GatanUltraScan' try: cameraid = configs['ultrascan']['camera_id'] except: pass binning_limits = [1,2,4,8] binmethod = 'exact' class GatanK2Base(DMSEM): name = 'GatanK2Base' try: cameraid = configs['k2']['camera_id'] except: pass # our name mapped to SerialEM plugin value readmodes = {'linear': 0, 'counting': 1, 'super resolution': 2} ed_mode = 'base' hw_proc = 'none' binning_limits = [1,2,4,8] binmethod = 'floor' filePerImage = False def custom_setup(self): #self.camera.SetShutterNormallyClosed(self.cameraid,self.bblankerid) if self.ed_mode != 'base': k2params = self.calculateK2Params() self.camera.SetK2Parameters(**k2params) fileparams = self.calculateFileSavingParams() if fileparams['rootname'] != 'dummy': print 'FILESAVING', fileparams['dirname'],fileparams['rootname'] self.camera.SetupFileSaving(**fileparams) def getFrameTime(self): ms = self.dosefrac_frame_time * 1000.0 return ms def setFrameTime(self,ms): seconds = ms / 1000.0 self.dosefrac_frame_time = seconds def getExposurePrecision(self): if self.isDoseFracOn(): frame_time = self.dosefrac_frame_time else: frame_time = self.record_precision return frame_time def getRealExposureTime(self): ''' The real exposure time is rounded to the nearest "exposure precision unit" in seconds, but not less than one "unit" ''' precision = self.getExposurePrecision() user_time = self.user_exposure_ms / 1000.0 if user_time < precision: real_time = precision else: real_time = round(user_time / precision) * precision return real_time def getExposureTime(self): real_time = self.getRealExposureTime() real_time_ms = int(round(real_time * 1000)) return real_time_ms # our name mapped to SerialEM plugin value hardwareProc = {'none': 0, 'dark': 2, 'gain': 4, 'dark+gain': 6} def isDoseFracOn(self): return self.save_frames or self.align_frames def calculateK2Params(self): frame_time = self.dosefrac_frame_time params = { 'readMode': self.readmodes[self.ed_mode], #'scaling': self.float_scale, 'scaling': 1.0, 'hardwareProc': self.hardwareProc[self.hw_proc], 'doseFrac': self.isDoseFracOn(), 'frameTime': frame_time, 'alignFrames': self.align_frames, 'saveFrames': self.save_frames, 'filt': self.align_filter, } return params def calculateFileSavingParams(self): ''' Creates raw frame file saving parameters independent of the integrated image returned to Leginon ''' if self.isDoseFracOn(): frames_name = time.strftime('%Y%m%d_%H%M%S', time.localtime()) self.frames_name = frames_name + '%02d' % (self.idcounter.next(),) else: self.frames_name = 'dummy' raw_frame_dir = self.getDmsemConfig('k2','raw_frame_dir') if self.filePerImage: path = raw_frame_dir + self.frames_name fileroot = 'frame' else: path = raw_frame_dir fileroot = self.frames_name # 0 means takes what DM gives rot_flip = 0 if not self.isDM231orUp(): # Backward compatibility flip = int(not self.getDmsemConfig('k2','flip')) # 0=none, 4=flip columns before rot, 8=flip after rot_flip = self.getDmsemConfig('k2','rotate') + flip params = { 'rotationFlip': rot_flip, 'dirname': path, 'rootname': fileroot, 'filePerImage': self.filePerImage, 'earlyReturnFrameCount': self.getEarlyReturnFrameCount(), } return params def getEarlyReturnFrameCount(self): #return self.getDmsemConfig('k2','early_return_frame_count') return self.early_return def setEarlyReturnFrameCount(self, grab_nframes, sum_nframes): self.early_return = 65536*grab_nframes + sum_nframes print self.early_return def setAlignFrames(self, value): self.align_frames = bool(value) def getAlignFrames(self): return self.align_frames def setAlignFilter(self, value): self.align_filter = str(value) def getAlignFilter(self): return self.align_filter def getSaveRawFrames(self): return self.save_frames def setSaveRawFrames(self, value): self.save_frames = bool(value) def getPreviousRawFramesName(self): return self.frames_name def getNumberOfFrames(self): frame_time = self.dosefrac_frame_time real_time = self.getRealExposureTime() nframes = int(round(real_time / frame_time)) return nframes def getNumberOfFramesSaved(self): if self.save_frames: return self.getNumberOfFrames() else: return 0 def setUseFrames(self, frames): pass def getUseFrames(self): nframes = self.getNumberOfFrames() return tuple(range(nframes)) def getFrameFlip(self): ''' Frame Flip is defined as up-down flip ''' return self.isDM231orUp() def getFrameRotate(self): ''' Frame Rotate direction is defined as x to -y rotation applied after up-down flip ''' return 0 class GatanK2Linear(GatanK2Base): name = 'GatanK2Linear' ed_mode = 'linear' hw_proc = 'none' class GatanK2Counting(GatanK2Base): logged_methods_on = True name = 'GatanK2Counting' ed_mode = 'counting' if simulation: hw_proc = 'none' else: hw_proc = 'dark+gain' def modifyDarkImage(self,image): if self.isDM231orUp(): image[:,:] = 0 class GatanK2Super(GatanK2Base): name = 'GatanK2Super' ed_mode = 'super resolution' binning_limits = [1] binmethod = 'floor' if simulation: hw_proc = 'none' else: hw_proc = 'dark+gain' def modifyDarkImage(self,image): if self.isDM231orUp(): image[:,:] = 0 def getPixelSize(self): ## TODO: move to config file: # pixel size on Gatan K2 return {'x': 2.5e-6, 'y': 2.5e-6}