diff --git a/ImageCollection.py b/ImageCollection.py index a5dae7e..8405036 100644 --- a/ImageCollection.py +++ b/ImageCollection.py @@ -2,9 +2,11 @@ import matplotlib.pyplot as plt import os from PIL import Image +from libtiff import TIFFimage import time import json from Rectangle import Rectangle +import traceback,sys #from imageSourceMM import imageSource @@ -89,8 +91,11 @@ def file_has_metadata(self,path): return 'txt' in theext def saveData(self,data): - img = Image.fromarray(data) - img.save(self.imagePath) + #img = Image.fromarray(data) + #img.save(self.imagePath) + tiff = TIFFimage(data, description='') + tiff.write_file(self.imagePath, compression='none') + del tiff def contains_rect(self,box): return self.boundBox.contains_rect(box) @@ -100,6 +105,7 @@ def contains_point(self,x,y): def getData(self): img=Image.open(self.imagePath,mode='r') thedata=img.getdata() + (width,height)=img.size data=np.reshape(np.array(thedata,np.dtype('uint8')),(height,width)) return data @@ -120,10 +126,28 @@ def __init__(self,rootpath,imageClass=MyImage,imageSource=None,axis=None): self.matplot_images=[] self.minvalue=0 self.maxvalue=512 - + + def display8bit(self,image, display_min, display_max): + image = np.array(image, copy=True) + image.clip(display_min, display_max, out=image) + image -= display_min + image //= (display_max - display_min + 1) / 256. + return image.astype(np.uint8) + + def lut_convert16as8bit(self,image, display_min, display_max) : + lut = np.arange(2**16, dtype='uint16') + lut = self.display8bit(lut, display_min, display_max) + return np.take(lut, image) + + + def get_pixel_size(self): return self.imageSource.get_pixel_size() + def get_image_size_um(self): + (fw,fh)=self.imageSource.get_frame_size_um() + return (fw,fh) + def set_view_home(self,box=None): if box==None: self.axis.set_xlim(left=self.bigBox.left,right=self.bigBox.right) @@ -168,9 +192,15 @@ def add_image_at(self,x,y): #go get an image at x,y try: (thedata,bbox)=self.imageSource.take_image(x,y) + if thedata.dtype == np.uint16: + print "converting" + maxval=self.imageSource.get_max_pixel_value() + thedata=self.lut_convert16as8bit(thedata,0,maxval) + except: #todo handle this better print "ahh no! imageSource failed us" + traceback.print_exc(file=sys.stdout) print "hmm.. bbox is" bbox.printRect() print "x,y is",x,y @@ -263,6 +293,9 @@ def printBoundBoxes(self): def loadImageCollection(self): #list all metadata files in rootdir testimage=self.imageClass() + if not os.path.isdir(self.rootpath): + os.makedirs(self.rootpath) + metafiles=[os.path.join(self.rootpath,f) for f in os.listdir(self.rootpath) if f.endswith('.txt') ] print "loading metadata" diff --git a/MosaicImage.py b/MosaicImage.py index 950e841..535e927 100644 --- a/MosaicImage.py +++ b/MosaicImage.py @@ -132,7 +132,7 @@ def __init__(self,axis,one_axis,two_axis,corr_axis,imgSrc,rootPath): self.oneImage=None self.twoImage=None self.corrImage=None - + self.imgSrc = imgSrc self.imgCollection=ImageCollection(rootpath=rootPath,imageSource=imgSrc,axis=self.axis) (x,y)=imgSrc.get_xy() @@ -141,9 +141,8 @@ def __init__(self,axis,one_axis,two_axis,corr_axis,imgSrc,rootPath): self.imgCollection.loadImageCollection() - self.maxvalue=255 - imgSrc.set_channel('Violet') - imgSrc.set_exposure(250) + self.maxvalue=512 + self.axis.set_title('Mosaic Image') @@ -490,8 +489,11 @@ def align_by_sift(self,xy1,xy2,window=70): #one_cuta=np.minimum(one_cut*256.0/self.maxvalue,255.0).astype(np.uint8) #two_cuta=np.minimum(two_cut*256.0/self.maxvalue,255.0).astype(np.uint8) - one_cuta=cv2.equalizeHist(one_cut) - two_cuta=cv2.equalizeHist(two_cut) + one_cuta = np.copy(one_cut) + two_cuta = np.copy(two_cut) + + one_cuta=cv2.equalizeHist(one_cuta) + two_cuta=cv2.equalizeHist(two_cuta) sift = cv2.SIFT(nfeatures=500,contrastThreshold=.2) @@ -502,8 +504,8 @@ def align_by_sift(self,xy1,xy2,window=70): - img_one = cv2.drawKeypoints(one_cuta,kp1) - img_two = cv2.drawKeypoints(two_cuta,kp2) + #img_one = cv2.drawKeypoints(one_cut,kp1) + #img_two = cv2.drawKeypoints(two_cut,kp2) #image2=self.two_axis.imshow(img_two) @@ -597,13 +599,13 @@ def align_by_sift(self,xy1,xy2,window=70): print (dx_um,dy_um) - self.paintImageOne(img_one,xy=xy1) - self.paintImageTwo(img_two,xy=xy2,xyp=(x2-dx_um,y2-dy_um)) + self.paintImageOne(one_cut,xy=xy1) + self.paintImageTwo(two_cut,xy=xy2,xyp=(x2-dx_um,y2-dy_um)) return ((dx_um,dy_um),len(bestInlierIdx)) else: - self.paintImageOne(img_one,xy=xy1) - self.paintImageTwo(img_two,xy=xy2) + self.paintImageOne(one_cut,xy=xy1) + self.paintImageTwo(two_cut,xy=xy2) return ((0.0,0.0),0) diff --git a/MosaicPlanner.py b/MosaicPlanner.py index 663e76a..b65b0c2 100644 --- a/MosaicPlanner.py +++ b/MosaicPlanner.py @@ -23,7 +23,7 @@ from PIL import Image import wx.lib.intctrl import numpy as np -from Settings import MosaicSettings, CameraSettings, ChangeCameraSettings, ImageSettings, ChangeImageMetadata, SmartSEMSettings, ChangeSEMSettings +from Settings import MosaicSettings, CameraSettings, ChangeCameraSettings, ImageSettings, ChangeImageMetadata, SmartSEMSettings, ChangeSEMSettings, ChannelSettings, ChangeChannelSettings from PositionList import posList from MyLasso import MyLasso from MosaicImage import MosaicImage @@ -35,7 +35,12 @@ import xml.etree.ElementTree as ET import numpy from imageSourceMM import imageSource - +import LiveMode +from pyqtgraph.Qt import QtCore, QtGui +import sys, traceback +from libtiff import TIFFimage +import time + class MosaicToolbar(NavBarImproved): """A custom toolbar which adds buttons and to interact with a MosaicPanel @@ -80,9 +85,12 @@ class MosaicToolbar(NavBarImproved): ON_GRID = wx.NewId() ON_ROTATE = wx.NewId() ON_REDRAW = wx.NewId() + ON_LIVE_MODE = wx.NewId() MAGCHOICE = wx.NewId() SHOWMAG = wx.NewId() - + ON_ACQGRID = wx.NewId() + ON_RUN = wx.NewId() + def __init__(self, plotCanvas): """initializes this object @@ -109,19 +117,25 @@ def __init__(self, plotCanvas): smalltargetBmp = wx.Image('icons/small-target-icon.png', wx.BITMAP_TYPE_PNG).ConvertToBitmap() rotateBmp = wx.Image('icons/rotate-icon.png', wx.BITMAP_TYPE_PNG).ConvertToBitmap() gridBmp = wx.Image('icons/grid-icon.png', wx.BITMAP_TYPE_PNG).ConvertToBitmap() - + cameraBmp = wx.Image('icons/camera-icon.png',wx.BITMAP_TYPE_PNG).ConvertToBitmap() + mosaicBmp = wx.Image('icons/mosaic-icon.png',wx.BITMAP_TYPE_PNG).ConvertToBitmap() + carBmp = wx.Image('icons/car-icon.png',wx.BITMAP_TYPE_PNG).ConvertToBitmap() #add the mutually exclusive/toggleable tools to the toolbar, see superclass for details on how function works - self.selectNear=self.add_user_tool('selectnear',7,selectnearBmp,True,'Add Nearest Point to selection') - self.selectTool=self.add_user_tool('select', 8, selectBmp, True, 'Select Points') - self.addTool=self.add_user_tool('add', 9, addpointBmp, True, 'Add a Point') - self.oneTool = self.add_user_tool('selectone', 10, oneBmp, True, 'Choose pointLine2D 1') - self.twoTool = self.add_user_tool('selecttwo', 11, twoBmp, True, 'Choose pointLine2D 2') + + self.snapPictureTool = self.add_user_tool('snappic',7,mosaicBmp,True,'take 3x3 mosaic on click') + self.selectNear=self.add_user_tool('selectnear',8,selectnearBmp,True,'Add Nearest Point to selection') + self.selectTool=self.add_user_tool('select', 9, selectBmp, True, 'Select Points') + self.addTool=self.add_user_tool('add', 10, addpointBmp, True, 'Add a Point') + self.oneTool = self.add_user_tool('selectone', 11, oneBmp, True, 'Choose pointLine2D 1') + self.twoTool = self.add_user_tool('selecttwo', 12, twoBmp, True, 'Choose pointLine2D 2') + self.AddSeparator() self.AddSeparator() #add the simple button click tools - #self.leftcorrTool=self.AddSimpleTool(self.ON_CORR_LEFT,leftcorrBmp,'do something with correlation','correlation baby!') + #self.leftcorrTool=self.AddSimpleTool(self.ON_CORR_LEFT,leftcorrBmp,'do something with correlation','correlation baby!') + self.liveModeTool = self.AddSimpleTool(self.ON_LIVE_MODE,cameraBmp,'Enter Live Mode','liveMode') self.deleteTool=self.AddSimpleTool(self.ON_DELETE_SELECTED,trashBmp,'Delete selected points','delete points') self.corrTool=self.AddSimpleTool(self.ON_CORR,corrBmp,'Ajdust pointLine2D 2 with correlation','corrTool') self.stepTool=self.AddSimpleTool(self.ON_STEP,stepBmp,'Take one step using points 1+2','stepTool') @@ -134,12 +148,13 @@ def __init__(self, plotCanvas): #self.redrawTool=self.AddSimpleTool(self.ON_REDRAW,smalltargetBmp,'redraw canvas','redrawTool') self.rotateTool=self.AddCheckTool(self.ON_ROTATE,rotateBmp,wx.NullBitmap,'toggle rotate boxes') #self.AddSimpleTool(self.ON_ROTATE,rotateBmp,'toggle rotate mosaic boxes according to rotation','rotateTool') - + self.runAcqTool=self.AddSimpleTool(self.ON_RUN,carBmp,'Acquire AT Data','run_tool') + #setup the controls for the mosaic self.showmagCheck = wx.CheckBox(self) self.showmagCheck.SetValue(False) self.magChoiceCtrl = wx.lib.agw.floatspin.FloatSpin(self,size=(65, -1 ), - value=65.486, + value=self.canvas.posList.mosaic_settings.mag, min_val=0, increment=.1, digits=2, @@ -188,15 +203,18 @@ def __init__(self, plotCanvas): #self.Bind(wx.lib.intctrl.EVT_INT,self.updateSliderRange, self.sliderMinCtrl) self.Bind(wx.lib.intctrl.EVT_INT,self.updateSliderRange, self.sliderMaxCtrl) + wx.EVT_TOOL(self, self.ON_LIVE_MODE, self.canvas.OnLiveMode) wx.EVT_TOOL(self, self.ON_DELETE_SELECTED, self.canvas.OnDeletePoints) wx.EVT_TOOL(self, self.ON_CORR, self.canvas.OnCorrTool) - wx.EVT_TOOL(self, self.ON_STEP, self.canvas.OnStepTool) + wx.EVT_TOOL(self, self.ON_STEP, self.canvas.OnStepTool) + wx.EVT_TOOL(self, self.ON_RUN, self.canvas.OnRunAcq) wx.EVT_TOOL(self, self.ON_FF, self.canvas.OnFastForwardTool) wx.EVT_TOOL(self, self.ON_GRID, self.canvas.OnGridTool) #wx.EVT_TOOL(self, self.ON_FINETUNE, self.canvas.OnFineTuneTool) #wx.EVT_TOOL(self, self.ON_REDRAW, self.canvas.OnRedraw) wx.EVT_TOOL(self, self.ON_ROTATE, self.canvas.OnRotateTool) - + self.app = QtGui.QApplication([]) + self.Realize() def updateMosaicSettings(self,evt=""): @@ -259,13 +277,36 @@ def __init__(self, parent, config, **kwargs): #initialize the camera settings and mosaic settings self.cfg=config - camera_settings=CameraSettings() - camera_settings.load_settings(config) + self.camera_settings=CameraSettings() + self.camera_settings.load_settings(config) mosaic_settings=MosaicSettings() - mosaic_settings.load_settings(config) - + mosaic_settings.load_settings(config) + self.MM_config_file= str(self.cfg.Read('MM_config_file',"C:\Users\Smithlab\Documents\ASI_LUM_RETIGA_CRISP.cfg")) + print self.MM_config_file + + #setup the image source + self.imgSrc=None + while self.imgSrc is None: + try: + self.imgSrc=imageSource(self.MM_config_file) + except: + traceback.print_exc(file=sys.stdout) + dlg = wx.MessageBox("Error Loading Micromanager\n check scope and re-select config file","MM Error") + self.EditMMConfig() + + channels=self.imgSrc.get_channels() + self.channel_settings=ChannelSettings(self.imgSrc.get_channels()) + self.channel_settings.load_settings(config) + + map_chan=self.channel_settings.map_chan + if map_chan not in channels: #if the saved settings don't match, call up dialog + self.EditChannels() + map_chan=self.channel_settings.map_chan + self.imgSrc.set_channel(map_chan) + self.imgSrc.set_exposure(self.channel_settings.exposure_times[map_chan]) + #setup a blank position list - self.posList=posList(self.subplot,mosaic_settings,camera_settings) + self.posList=posList(self.subplot,mosaic_settings,self.camera_settings) #start with no MosaicImage self.mosaicImage=None #start with relative_motion on, so that keypress calls shift_selected_curved() of posList @@ -285,13 +326,164 @@ def __init__(self, parent, config, **kwargs): self.canvas.mpl_connect('button_release_event', self.on_release) self.canvas.mpl_connect('key_press_event', self.on_key) - def OnLoad(self,rootPath,configfile="C:\Users\Smithlab\Documents\ASI_LUM_RETIGA_CRISP.cfg"): - #configfile="C:\Users\Smithlab\Documents\ASI_LUM_RETIGA_CRISP.cfg" - imgSrc=imageSource(configfile) + def OnLoad(self,rootPath): + self.rootPath=rootPath - self.mosaicImage=MosaicImage(self.subplot,self.posone_plot,self.postwo_plot,self.corrplot,imgSrc,rootPath) + self.mosaicImage=MosaicImage(self.subplot,self.posone_plot,self.postwo_plot,self.corrplot,self.imgSrc,rootPath) self.draw() + def write_slice_metadata(self,filename,ch,xpos,ypos,zpos): + + f = open(filename, 'w') + channelname=self.channel_settings.prot_names[ch] + (height,width)=self.imgSrc.get_sensor_size() + ScaleFactorX=self.imgSrc.get_pixel_size() + ScaleFactorY=self.imgSrc.get_pixel_size() + exp_time=self.channel_settings.exposure_times[ch] + + f.write("Channel\tWidth\tHeight\tMosaicX\tMosaicY\tScaleX\tScaleY\tExposureTime\n") + f.write("%s\t%d\t%d\t%d\t%d\t%f\t%f\t%f\n" % \ + (channelname, width, height, 1, 1, ScaleFactorX, ScaleFactorY, exp_time)) + + f.write("XPositions\tYPositions\tFocusPositions\n") + f.write("%s\t%s\t%s\n" %(xpos, ypos, zpos)) + + def write_session_metadata(self,outdir): + filename=os.path.join(outdir,'session_metadata.txt') + f = open(filename, 'w') + + (height,width)=self.imgSrc.get_sensor_size() + Nch=0 + for k,ch in enumerate(self.channel_settings.channels): + if self.channel_settings.usechannels[ch]: + Nch+=1 + + f.write("Width\tHeight\t#chan\tMosaicX\tMosaicY\tScaleX\tScaleY\n") + f.write("%d\t%d\t%d\t%d\t%d\t%f\t%f\n" % (width,height, Nch,self.posList.mosaic_settings.mx, self.posList.mosaic_settings.mx, self.imgSrc.get_pixel_size(), self.imgSrc.get_pixel_size())) + f.write("Channel\tExposure Times (msec)\tRLPosition\n") + for k,ch in enumerate(self.channel_settings.channels): + if self.channel_settings.usechannels[ch]: + f.write(self.channel_settings.prot_names[ch] + "\t" + "%f\t%s\n" % (self.channel_settings.exposure_times[ch],ch)) + + + + def MultiDAcq(self,outdir,x,y,slice_index,frame_index=0): + + self.imgSrc.set_hardware_autofocus_state(True) + self.imgSrc.move_stage(x,y) + attempts=0 + + #wait till autofocus settles + while not self.imgSrc.is_hardware_autofocus_done(): + time.sleep(.1) + attempts+=1 + if attempts>100: + print "not auto-focusing correctly.. giving up after 10 seconds" + break + time.sleep(.1) #wait an extra 100 ms for settle + + self.imgSrc.set_hardware_autofocus_state(False) #turn off autofocus + currZ=self.imgSrc.get_z() + + for k,ch in enumerate(self.channel_settings.channels): + prot_name=self.channel_settings.prot_names[ch] + path=os.path.join(outdir,prot_name) + if self.channel_settings.usechannels[ch]: + z=currZ+self.channel_settings.zoffsets[ch] + self.imgSrc.set_z(z) + self.imgSrc.set_exposure(self.channel_settings.exposure_times[ch]) + self.imgSrc.set_channel(ch) + data=self.imgSrc.snap_image() + + tif_filepath=os.path.join(path,prot_name+"_S%04d_F%04d.tif"%(slice_index,frame_index)) + metadata_filepath=os.path.join(path,prot_name+"_S%04d_F%04d_metadata.txt"%(slice_index,frame_index)) + + tiff = TIFFimage(data, description='') + tiff.write_file(tif_filepath, compression='none') + del tiff + + self.write_slice_metadata(metadata_filepath,ch,x,y,z) + + + + def OnRunAcq(self,event="none"): + print "running" + #self.channel_settings + #self.pos_list + #self.imgSrc + + #get an output directory + dlg=wx.DirDialog(self,message="Pick output directory",defaultPath= os.path.split(self.rootPath)[0]) + dlg.ShowModal() + outdir=dlg.GetPath() + dlg.Destroy() + #setup output directories + for k,ch in enumerate(self.channel_settings.channels): + if self.channel_settings.usechannels[ch]: + thedir=os.path.join(outdir,self.channel_settings.prot_names[ch]) + if not os.path.isdir(thedir): + os.makedirs(thedir) + + self.write_session_metadata(outdir) + + #step the stage back to the first position, position by position + #so as to not lose the immersion oil + (x,y)=self.imgSrc.get_xy() + currpos=self.posList.get_position_nearest(x,y) + while currpos is not None: + #turn on autofocus + self.imgSrc.set_hardware_autofocus_state(True) + self.imgSrc.move_stage(currpos.x,currpos.y) + currpos=self.posList.get_prev_pos(currpos) + + #loop over positions + for i,pos in enumerate(self.posList.slicePositions): + #turn on autofocus + if pos.frameList is None: + self.MultiDAcq(outdir,pos.x,pos.y,i) + else: + for j,fpos in enumerate(pos.frameList.slicePositions): + self.MultiDAcq(outdir,fpos.x,fpos.y,i,j) + + + + def EditChannels(self,event = "none"): + dlg = ChangeChannelSettings(None, -1, title = "Channel Settings", settings = self.channel_settings,style=wx.OK) + ret=dlg.ShowModal() + if ret == wx.ID_OK: + self.channel_settings=dlg.GetSettings() + self.channel_settings.save_settings(self.cfg) + map_chan=self.channel_settings.map_chan + self.imgSrc.set_channel(map_chan) + self.imgSrc.set_exposure(self.channel_settings.exposure_times[map_chan]) + print "should be changed" + + dlg.Destroy() + + def OnLiveMode(self,evt="none"): + expTimes=LiveMode.launchLive(self.imgSrc,exposure_times=self.channel_settings.exposure_times) + self.channel_settings.exposure_times=expTimes + self.channel_settings.save_settings(self.cfg) + #reset the current channel to the mapping channel, and it's exposure + map_chan=self.channel_settings.map_chan + self.imgSrc.set_channel(map_chan) + self.imgSrc.set_exposure(self.channel_settings.exposure_times[map_chan]) + + + def EditMMConfig(self, event = "none"): + + fullpath=self.MM_config_file + if fullpath is None: + fullpath = "" + + (dir,file)=os.path.split(fullpath) + dlg = wx.FileDialog(self,"select configuration file",dir,file,"*.cfg") + + dlg.ShowModal() + self.MM_config_file = str(dlg.GetPath()) + self.cfg.Write('MM_config_file',self.MM_config_file) + + dlg.Destroy() def repaint_image(self,evt): """event handler used when the slider bar changes and you want to repaint the MosaicImage with a different color scale""" @@ -364,6 +556,12 @@ def on_press(self, evt): self.lasso = MyLasso(evt.inaxes, (evt.xdata, evt.ydata), self.lasso_callback,linecolor='white') self.lassoLock=True self.canvas.widgetlock(self.lasso) + elif (mode == 'snappic' ): + (fw,fh)=self.mosaicImage.imgCollection.get_image_size_um() + for i in range(-1,2): + for j in range(-1,2): + self.mosaicImage.imgCollection.add_covered_point(evt.xdata+(j*fw),evt.ydata+(i*fh)) + self.draw() def on_release(self, evt): @@ -399,6 +597,8 @@ def OnGridTool(self,evt): #make the frames grid visible/invisible accordingly self.posList.set_frames_visible(visible) self.draw() + + def OnDeletePoints(self,event="none"): """handlier for handling the Delete tool press""" @@ -606,6 +806,9 @@ class ZVISelectFrame(wx.Frame): ID_FLIPVERT = wx.NewId() ID_FULLRES = wx.NewId() ID_SAVE_SETTINGS = wx.NewId() + ID_EDIT_CHANNELS = wx.NewId() + ID_EDIT_MM_CONFIG = wx.NewId() + def __init__(self, parent, title): """default init function for a wx.Frame @@ -619,16 +822,23 @@ def __init__(self, parent, title): #default_meta="" #default_image="" + + #recursively call old init function - wx.Frame.__init__(self, parent, title=title, size=(1400,885),pos=(5,5)) + wx.Frame.__init__(self, parent, title=title, size=(1550,885),pos=(5,5)) self.cfg = wx.Config('settings') + #setup a mosaic panel + self.mosaicCanvas=MosaicPanel(self,config=self.cfg) + #setup menu menubar = wx.MenuBar() options = wx.Menu() transformMenu = wx.Menu() - SmartSEM_Menu = wx.Menu() + Platform_Menu = wx.Menu() + Channels_Menu = wx.Menu() - #setup the menu options + + #OPTIONS MENU self.relative_motion = options.Append(self.ID_RELATIVEMOTION, 'Relative motion?', 'Move points in the ribbon relative to the apparent curvature, else in absolution coordinates',kind=wx.ITEM_CHECK) self.sort_points = options.Append(self.ID_SORTPOINTS,'Sort positions?','Should the program automatically sort the positions by their X coordinate from right to left?',kind=wx.ITEM_CHECK) self.show_numbers = options.Append(self.ID_SHOWNUMBERS,'Show numbers?','Display a number next to each position to show the ordering',kind=wx.ITEM_CHECK) @@ -636,6 +846,7 @@ def __init__(self, parent, title): self.fullResOpt = options.Append(self.ID_FULLRES,'Load full resolution (speed vs memory)','Rather than loading a 10x downsampled ',kind=wx.ITEM_CHECK) self.saveSettings = options.Append(self.ID_SAVE_SETTINGS,'Save Settings','Saves current configuration settings to config file that will be loaded automatically',kind=wx.ITEM_NORMAL) + #SET THE INTIAL SETTINGS options.Check(self.ID_RELATIVEMOTION,self.cfg.ReadBool('relativemotion',True)) options.Check(self.ID_SORTPOINTS,True) options.Check(self.ID_SHOWNUMBERS,False) @@ -645,12 +856,14 @@ def __init__(self, parent, title): self.edit_transform = options.Append(self.ID_EDIT_CAMERA_SETTINGS,'Edit Camera Properties...','Edit the size of the camera chip and the pixel size',kind=wx.ITEM_NORMAL) + #SETUP THE CALLBACKS self.Bind(wx.EVT_MENU, self.SaveSettings, id=self.ID_SAVE_SETTINGS) self.Bind(wx.EVT_MENU, self.ToggleRelativeMotion, id=self.ID_RELATIVEMOTION) self.Bind(wx.EVT_MENU, self.ToggleSortOption, id=self.ID_SORTPOINTS) self.Bind(wx.EVT_MENU, self.ToggleShowNumbers,id=self.ID_SHOWNUMBERS) self.Bind(wx.EVT_MENU, self.EditCameraSettings, id=self.ID_EDIT_CAMERA_SETTINGS) + #TRANSFORM MENU self.save_transformed = transformMenu.Append(self.ID_SAVETRANSFORM,'Save Transformed?',\ 'Rather than save the coordinates in the original space, save a transformed set of coordinates according to transform configured in set_transform...',kind=wx.ITEM_CHECK) transformMenu.Check(self.ID_SAVETRANSFORM,self.cfg.ReadBool('savetransform',False)) @@ -662,18 +875,25 @@ def __init__(self, parent, title): self.Transform = Transform() self.Transform.load_settings(self.cfg) - - self.edit_smartsem_settings = SmartSEM_Menu.Append(self.ID_EDIT_SMARTSEM_SETTINGS,'Edit SmartSEMSettings',\ + #PLATFORM MENU + self.edit_smartsem_settings = Platform_Menu.Append(self.ID_EDIT_SMARTSEM_SETTINGS,'Edit SmartSEMSettings',\ 'Edit the settings used to set the magnification, rotation,tilt, Z position, and working distance of SEM software in position list',kind=wx.ITEM_NORMAL) self.Bind(wx.EVT_MENU, self.EditSmartSEMSettings, id=self.ID_EDIT_SMARTSEM_SETTINGS) + #CHANNELS MENU + self.edit_micromanager_config = Channels_Menu.Append(self.ID_EDIT_MM_CONFIG,'Set MicroManager Configuration',kind=wx.ITEM_NORMAL) + self.edit_channels = Channels_Menu.Append(self.ID_EDIT_CHANNELS,'Edit Channels',kind=wx.ITEM_NORMAL) + self.Bind(wx.EVT_MENU, self.mosaicCanvas.EditMMConfig, id = self.ID_EDIT_MM_CONFIG) + self.Bind(wx.EVT_MENU, self.mosaicCanvas.EditChannels, id = self.ID_EDIT_CHANNELS) + + menubar.Append(options, '&Options') menubar.Append(transformMenu,'&Transform') - menubar.Append(SmartSEM_Menu,'&Platform Options') + menubar.Append(Platform_Menu,'&Platform Options') + menubar.Append(Channels_Menu,'&Microscope Settings') self.SetMenuBar(menubar) - #setup a mosaic panel - self.mosaicCanvas=MosaicPanel(self,config=self.cfg) + #setup a file picker for the metadata selector #self.meta_label=wx.StaticText(self,id=wx.ID_ANY,label="metadata file") @@ -708,7 +928,7 @@ def __init__(self, parent, title): self.array_filepicker.SetPath(self.cfg.Read('default_arraypath',"")) self.array_load_button=wx.Button(self,id=wx.ID_ANY,label="Load",name="load button") - self.array_formatBox=wx.ComboBox(self,id=wx.ID_ANY,value='AxioVision',\ + self.array_formatBox=wx.ComboBox(self,id=wx.ID_ANY,value='uManager',\ size=wx.DefaultSize,choices=['uManager','AxioVision','SmartSEM','OMX','ZEN'], name='File Format For Position List') self.array_formatBox.SetEditable(False) self.array_save_button=wx.Button(self,id=wx.ID_ANY,label="Save",name="save button") @@ -769,6 +989,8 @@ def __init__(self, parent, title): #self.OnArrayLoad() #self.mosaicCanvas.draw() + + def SaveSettings(self,event="none"): #save the transform parameters self.Transform.save_settings(self.cfg) @@ -780,7 +1002,7 @@ def SaveSettings(self,event="none"): self.cfg.WriteBool('savetransform',self.save_transformed.IsChecked()) #save the camera settings - #self.mosaicCanvas.posList.camera_settings.save_settings(self.cfg) + self.mosaicCanvas.posList.camera_settings.save_settings(self.cfg) #save the mosaic options self.mosaicCanvas.posList.mosaic_settings.save_settings(self.cfg) @@ -887,15 +1109,21 @@ def ToggleShowNumbers(self,event): else: self.mosaicCanvas.posList.setNumberVisibility(False) self.mosaicCanvas.draw() - + + + + + + def EditCameraSettings(self,event): """event handler for clicking the camera setting menu button""" dlg = ChangeCameraSettings(None, -1, title="Camera Settings", settings=self.mosaicCanvas.camera_settings) dlg.ShowModal() - del self.posList.camera_settings + #del self.posList.camera_settings #passes the settings to the position list + self.mosaicCanvas.camera_settings=dlg.GetSettings() self.mosaicCanvas.posList.set_camera_settings(dlg.GetSettings()) dlg.Destroy() @@ -932,3 +1160,4 @@ def EditTransform(self,event): frame = ZVISelectFrame(None,"Mosaic Planner") # A Frame is a top-level window. app.MainLoop() +QtGui.QApplication.quit() \ No newline at end of file diff --git a/PositionList.py b/PositionList.py index 55ab9f9..c861eee 100644 --- a/PositionList.py +++ b/PositionList.py @@ -64,7 +64,7 @@ def get_next_pos(self,pos): return self.slicePositions[myindex+1] def get_prev_pos(self,pos): - self.__sort_points() + #self.__sort_points() myindex=self.slicePositions.index(pos) if (myindex)==0: return None diff --git a/Settings.py b/Settings.py index 3978070..cfd330a 100644 --- a/Settings.py +++ b/Settings.py @@ -37,7 +37,140 @@ def load_settings(self,cfg): self.sensor_width=cfg.ReadInt('sensor_width',1388) self.pix_width=cfg.ReadFloat('pix_width',6.5) self.pix_height=cfg.ReadFloat('pix_height',6.5) + +class ChannelSettings(): + """simple struct for containing the parameters for the microscope""" + def __init__(self,channels,exposure_times=dict([]),zoffsets=dict([]),usechannels=dict([]),prot_names=dict([]),map_chan=None,def_exposure=100,def_offset=0.0): + #def_exposure is default exposure time in msec + + + self.channels= channels + self.def_exposure=def_exposure + self.def_offset=0.0 + + self.exposure_times=exposure_times + self.zoffsets=zoffsets + self.usechannels=usechannels + self.prot_names=prot_names + + if map_chan is None: + for ch in self.channels: + if 'dapi' in ch.lower(): + map_chan = ch + if map_chan is None: + map_chan = channels[0] + + self.map_chan = map_chan + + def save_settings(self,cfg): + + cfg.Write('map_chan',self.map_chan) + for ch in self.channels: + cfg.WriteInt('Exposures/'+ch,self.exposure_times[ch]) + cfg.WriteFloat('ZOffsets/'+ch,self.zoffsets[ch]) + cfg.WriteBool('UseChannel/'+ch,self.usechannels[ch]) + cfg.Write('ProteinNames/'+ch,self.prot_names[ch]) + + def load_settings(self,cfg): + for ch in self.channels: + self.exposure_times[ch]=cfg.ReadInt('Exposures/'+ch, self.def_exposure) + self.zoffsets[ch]=cfg.ReadFloat('ZOffsets/'+ch,self.def_offset) + self.usechannels[ch]=cfg.ReadBool('UseChannel/'+ch,True) + self.prot_names[ch]=cfg.Read('ProteinNames/'+ch,ch) + self.map_chan=str(cfg.Read('map_chan','DAPI')) + +class ChangeChannelSettings(wx.Dialog): + """simple dialog for changing the channel settings""" + def __init__(self, parent, id, title, settings,style): + wx.Dialog.__init__(self, parent, id, title,style=wx.DEFAULT_DIALOG_STYLE, size=(420, -1)) + + self.settings=settings + vbox = wx.BoxSizer(wx.VERTICAL) + Nch=len(settings.channels) + print Nch + + gridSizer=wx.FlexGridSizer(rows=Nch+1,cols=6,vgap=5,hgap=5) + + + gridSizer.Add(wx.StaticText(self,id=wx.ID_ANY,label="chan"),border=5) + gridSizer.Add(wx.StaticText(self,id=wx.ID_ANY,label="protein"),border=5) + gridSizer.Add(wx.StaticText(self,id=wx.ID_ANY,label="use?"),border=5) + gridSizer.Add(wx.StaticText(self,id=wx.ID_ANY,label="exposure"),border=5) + gridSizer.Add(wx.StaticText(self,id=wx.ID_ANY,label="map?"),border=5) + gridSizer.Add(wx.StaticText(self,id=wx.ID_ANY,label="zoffset "),border=5) + + + self.ProtNameCtrls=[] + self.UseCtrls=[] + self.ExposureCtrls=[] + self.MapRadCtrls=[] + self.ZOffCtrls=[] + + for ch in settings.channels: + hbox =wx.BoxSizer(wx.HORIZONTAL) + Txt=wx.StaticText(self,label=ch) + ProtText=wx.TextCtrl(self,value=settings.prot_names[ch]) + ChBox = wx.CheckBox(self) + ChBox.SetValue(settings.usechannels[ch]) + IntCtrl=wx.lib.intctrl.IntCtrl( self, value=settings.exposure_times[ch],size=(50,-1)) + FloatCtrl=wx.lib.agw.floatspin.FloatSpin(self, + value=settings.zoffsets[ch], + min_val=-3.0, + max_val=3.0, + increment=.1, + digits=2, + name='', + size=(95,-1)) + + if ch is settings.channels[0]: + RadBut = wx.RadioButton(self,-1,'',style=wx.RB_GROUP) + else: + RadBut = wx.RadioButton(self,-1,'') + if ch == settings.map_chan: + RadBut.SetValue(True) + + gridSizer.Add(Txt,0,flag=wx.ALL|wx.EXPAND,border=5) + gridSizer.Add(ProtText,1,flag=wx.ALL|wx.EXPAND,border=5) + gridSizer.Add(ChBox,0,flag=wx.ALL|wx.EXPAND,border=5) + gridSizer.Add(IntCtrl,0,border=5) + gridSizer.Add(RadBut,0,flag=wx.ALL|wx.EXPAND,border=5) + gridSizer.Add(FloatCtrl,0,flag=wx.ALL|wx.EXPAND,border=5) + + self.ProtNameCtrls.append(ProtText) + self.UseCtrls.append(ChBox) + self.ExposureCtrls.append(IntCtrl) + self.MapRadCtrls.append(RadBut) + self.ZOffCtrls.append(FloatCtrl) + + + hbox = wx.BoxSizer(wx.HORIZONTAL) + ok_button = wx.Button(self,wx.ID_OK,'OK') + cancel_button = wx.Button(self,wx.ID_CANCEL,'Cancel') + hbox.Add(ok_button) + hbox.Add(cancel_button) + + vbox.Add(gridSizer) + vbox.Add(hbox) + + self.SetSizer(vbox) + + + def GetSettings(self): + prot_names=dict([]) + usechannels=dict([]) + exposure_times=dict([]) + zoffsets=dict([]) + + for i,ch in enumerate(self.settings.channels): + prot_names[ch]=self.ProtNameCtrls[i].GetValue() + usechannels[ch]=self.UseCtrls[i].GetValue() + exposure_times[ch]=self.ExposureCtrls[i].GetValue() + if self.MapRadCtrls[i].GetValue(): + map_chan=ch + zoffsets[ch]=self.ZOffCtrls[i].GetValue() + return ChannelSettings(self.settings.channels,exposure_times=exposure_times,zoffsets=zoffsets,usechannels=usechannels,prot_names=prot_names,map_chan=map_chan) + class MosaicSettings: def __init__(self,mag=65.486,mx=1,my=1,overlap=10,show_box=False,show_frames=False): """a simple struct class for encoding settings about mosaics diff --git a/bioformats_package.jar b/bioformats_package.jar deleted file mode 100644 index 194c5ac..0000000 Binary files a/bioformats_package.jar and /dev/null differ diff --git a/imageSourceMM.py b/imageSourceMM.py index 1a86b76..63403e7 100644 --- a/imageSourceMM.py +++ b/imageSourceMM.py @@ -8,22 +8,42 @@ class imageSource(): def __init__(self,configFile,channelGroupName='Channels'): #NEED TO IMPLEMENT IF NOT MICROMANAGER + self.configFile=configFile self.mmc = MMCorePy.CMMCore() self.mmc.loadSystemConfiguration(self.configFile) + self.channelGroupName=channelGroupName + #set the exposure to use + + def get_max_pixel_value(self): + bit_depth=self.mmc.getImageBitDepth() + return np.power(2,bit_depth) def set_exposure(self,exp_msec): #NEED TO IMPLEMENT IF NOT MICROMANAGER self.mmc.setExposure(exp_msec) + def reset_focus_offset(self): + if self.has_hardware_autofocus(): + focusDevice=self.mmc.getAutoFocusDevice() + self.mmc.setProperty(focusDevice,"CRISP State","Reset Focus Offset") + + def get_hardware_autofocus_state(self): + if self.has_hardware_autofocus(): + return self.mmc.isContinuousFocusLocked() + + def set_hardware_autofocus_state(self,state): + if self.has_hardware_autofocus(): + self.mmc.enableContinuousFocus(state) + def has_hardware_autofocus(self): #NEED TO IMPLEMENT IF NOT MICROMANAGER - print "need to implement automatic detection of hardware autofocus" - + #print "need to implement automatic detection of hardware autofocus" return True + def is_hardware_autofocus_done(self): #NEED TO IMPLEMENT IF NOT MICROMANAGER #hardware autofocus assumes the focus score is <1 when focused @@ -100,26 +120,37 @@ def get_xy(self): y=self.mmc.getYPosition(xystg) return (-x,y) - + def get_z(self): + focus_stage=self.mmc.getFocusDevice() + return self.mmc.getPosition(focus_stage) + def set_z(self,z): + focus_stage=self.mmc.getFocusDevice() + self.mmc.setPosition (focus_stage,z) + self.mmc.waitForDevice(focus_stage) def get_pixel_size(self): #NEED TO IMPLEMENT IF NOT MICROMANAGER return self.mmc.getPixelSizeUm() + + def get_frame_size_um(self): + (sensor_width,sensor_height)=self.get_sensor_size() + pixsize = self.get_pixel_size() + return (sensor_width*pixsize,sensor_height*pixsize) + def calc_bbox(self,x,y): #do not need to implement - (sensor_width,sensor_height)=self.get_sensor_size() - pixsize = self.get_pixel_size() + (fw,fh)=self.get_frame_size_um() #we are going to follow the convention of upper left being 0,0 #and lower right being X,X where X is positive - left = x - pixsize*sensor_width/2; - right = x + pixsize*sensor_width/2; + left = x - fw/2; + right = x + fw/2; - top = y - pixsize*sensor_height/2; - bottom = y + pixsize*sensor_height/2; + top = y - fh/2; + bottom = y + fh/2; - print (x,y,pixsize,sensor_height) + return Rectangle(left,right,top,bottom) diff --git a/live_video.py b/live_video.py index 789b2a2..b7244f8 100644 --- a/live_video.py +++ b/live_video.py @@ -39,8 +39,8 @@ def main(): # mmc.setCircularBufferMemoryFootprint(100) cam=mmc.getCameraDevice() - mmc.setConfig('Channels','Violet') - mmc.waitForConfig('Channels','Violet') + mmc.setConfig('Channels','DAPI') + mmc.waitForConfig('Channels','DAPI') #self.mmc.setShutterOpen(False) #cv2.namedWindow('MM controls') diff --git a/live_video_pyqt.py b/live_video_pyqt.py index 912c290..a10f670 100644 --- a/live_video_pyqt.py +++ b/live_video_pyqt.py @@ -13,6 +13,7 @@ import MMCorePy import pyqtgraph as pg from pyqtgraph.Qt import QtGui, QtCore +import time WIDTH, HEIGHT = 320, 240 DEVICE = ['Camera', 'DemoCamera', 'DCam'] @@ -45,7 +46,7 @@ def set_mmc_resolution(mmc, width, height): mmc.enableDebugLog(False) # mmc.setCircularBufferMemoryFootprint(100) cam=mmc.getCameraDevice() - +mmc.setExposure(50) mmc.setConfig('Channels','Violet') mmc.waitForConfig('Channels','Violet') #self.mmc.setShutterOpen(False) @@ -63,53 +64,62 @@ def set_mmc_resolution(mmc, width, height): # int(float(mmc.getProperty(cam, 'Exposure'))), # 100, # int(mmc.getPropertyUpperLimit(DEVICE[0], 'Exposure')), # lambda value: mmc.setProperty(cam, 'Exposure', int(value))) - +def myExitHandler(): + global mmc + mmc.stopSequenceAcquisition() + mmc.reset() + set_mmc_resolution(mmc, WIDTH, HEIGHT) -mmc.setProperty(cam, 'Gain', 20) +mmc.setProperty(cam, 'Gain', 1) mmc.snapImage() # Baumer workaround data=mmc.getImage() gray = data.view(dtype=np.uint8) - +mmc.startContinuousSequenceAcquisition(50) #QtGui.QApplication.setGraphicsSystem('raster') app = QtGui.QApplication([]) +app.aboutToQuit.connect(myExitHandler) #mw = QtGui.QMainWindow() #mw.resize(800,800) win = QtGui.QMainWindow() win.setWindowTitle('pyqtgraph example: VideoSpeedTest') win.resize(800,800) -ui = Ui_MainWindow() -win.show() +gv = pg.GraphicsView() +win.setCentralWidget(gv) vb = pg.ViewBox() -ui.graphicsView.setCentralItem(vb) +gv.setCentralItem(vb) vb.setAspectLocked() img = pg.ImageItem() +img.setImage(gray) vb.addItem(img) vb.setRange(QtCore.QRectF(0, 0, 512, 512)) +win.show() + + + def update(): - global ui,mmc + global img,mmc remcount = mmc.getRemainingImageCount() - print('Images in circular buffer: %s') % remcount + #print remcount + + #print('Images in circular buffer: %s') % remcount + if remcount > 0: - # rgb32 = mmc.popNextImage() - rgb32 = mmc.getLastImage() + rgb32 = mmc.popNextImage() + #rgb32 = mmc.getLastImage() gray = rgb32.view(dtype=np.uint8) gray=cv2.equalizeHist(gray) - ui.rawGLImg.setImage(gray) + img.setImage(gray) #cv2.imshow('Video', gray) - else: - print('No frame') - - -mmc.stopSequenceAcquisition() -mmc.reset() - -## Start Qt event loop unless running in interactive mode or using pyside. -if __name__ == '__main__': - import sys - if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): - QtGui.QApplication.instance().exec_() \ No newline at end of file + #else: + # print('No frame') + +viewtimer = QtCore.QTimer() +viewtimer.timeout.connect(update) +viewtimer.start(5) + +