Ejemplo n.º 1
0
class EMClassPtclTool(QtGui.QWidget):
	"""This class is a tab widget for inspecting particles within class-averages"""

	def __init__(self,extrafiles=None):
		QtGui.QWidget.__init__(self)
		self.vbl = QtGui.QVBoxLayout(self)

		self.extrafiles=extrafiles

		# A listwidget for selecting which class-average file we're looking at
		self.wclassfilel=QtGui.QLabel("Class-average File:")
		self.vbl.addWidget(self.wclassfilel)

		self.wfilesel=QtGui.QListWidget()
		self.vbl.addWidget(self.wfilesel)
		self.vbl.addSpacing(5)

		# A widget containing the current particle filename, editable by the user
		# If edited it will also impact set generation !
		self.wptclfilel=QtGui.QLabel("Particle Data File:")
		self.vbl.addWidget(self.wptclfilel)

		self.wptclfile=QtGui.QComboBox(self)
		self.vbl.addWidget(self.wptclfile)
		self.vbl.addSpacing(5)

		# Selection tools
		self.wselectg=QtGui.QGroupBox("Class Selection",self)
		self.wselectg.setFlat(False)
		self.vbl.addWidget(self.wselectg)
		self.vbl.addSpacing(5)

		self.gbl0=QtGui.QGridLayout(self.wselectg)

		self.wselallb=QtGui.QPushButton("All")
		self.gbl0.addWidget(self.wselallb,0,0)

		self.wselnoneb=QtGui.QPushButton("Clear")
		self.gbl0.addWidget(self.wselnoneb,0,1)

		self.wselrangeb=QtGui.QPushButton("Range")
		self.gbl0.addWidget(self.wselrangeb,1,0)

		self.wselinvertb=QtGui.QPushButton("Invert")
		self.gbl0.addWidget(self.wselinvertb,0,2)

		self.wsel3db=QtGui.QPushButton("From 3D")
		self.gbl0.addWidget(self.wsel3db,1,2)

		self.wprocessg=QtGui.QGroupBox("Process results",self)
		self.wprocessg.setFlat(False)
		self.vbl.addWidget(self.wprocessg)

		self.vbl2=QtGui.QVBoxLayout(self.wprocessg)

		self.wselused=CheckBox(None,"Included Ptcls",1,100)
		self.vbl2.addWidget(self.wselused)

		self.wselunused=CheckBox(None,"Excluded Ptcls",1,100)
		self.vbl2.addWidget(self.wselunused)

		# Mark particles in selected classes as bad
		self.wmarkbut=QtGui.QPushButton("Mark as Bad")
		self.vbl2.addWidget(self.wmarkbut)

		# Mark particles in selected classes as good
		self.wmarkgoodbut=QtGui.QPushButton("Mark as Good")
		self.vbl2.addWidget(self.wmarkgoodbut)

		# Make a new set from selected classes
		self.wmakebut=QtGui.QPushButton("Make New Set")
		self.vbl2.addWidget(self.wmakebut)
#		self.wmakebut.setEnabled(False)

		# Save list
		self.wsavebut=QtGui.QPushButton("Save Particle List")
		self.vbl2.addWidget(self.wsavebut)

		# Save micrograph dereferenced lists
		self.wsaveorigbut=QtGui.QPushButton("Save CCD-based List")
		self.vbl2.addWidget(self.wsaveorigbut)


		QtCore.QObject.connect(self.wfilesel,QtCore.SIGNAL("itemSelectionChanged()"),self.fileUpdate)
		QtCore.QObject.connect(self.wptclfile,QtCore.SIGNAL("currentIndexChanged(int)"),self.ptclChange)
		QtCore.QObject.connect(self.wselallb,QtCore.SIGNAL("clicked(bool)"),self.selAllClasses)
		QtCore.QObject.connect(self.wselnoneb,QtCore.SIGNAL("clicked(bool)"),self.selNoClasses)
		QtCore.QObject.connect(self.wselrangeb,QtCore.SIGNAL("clicked(bool)"),self.selRangeClasses)
		QtCore.QObject.connect(self.wselinvertb,QtCore.SIGNAL("clicked(bool)"),self.selInvertClasses)
		QtCore.QObject.connect(self.wsel3db,QtCore.SIGNAL("clicked(bool)"),self.sel3DClasses)
		QtCore.QObject.connect(self.wmakebut,QtCore.SIGNAL("clicked(bool)"),self.makeNewSet)
		QtCore.QObject.connect(self.wmarkbut,QtCore.SIGNAL("clicked(bool)"),self.markBadPtcl)
		QtCore.QObject.connect(self.wmarkgoodbut,QtCore.SIGNAL("clicked(bool)"),self.markGoodPtcl)
		QtCore.QObject.connect(self.wsavebut,QtCore.SIGNAL("clicked(bool)"),self.savePtclNum)
		QtCore.QObject.connect(self.wsaveorigbut,QtCore.SIGNAL("clicked(bool)"),self.saveOrigPtclNum)

		# View windows, one for class-averages, one for good particles and one for bad particles
		self.vclasses=None
		self.vgoodptcl=None
		self.vbadptcl=None

		self.updateFiles()

	def makeNewSet(self,x):
		"Makes a new particle set based on the selected class-averages"
		setname=QtGui.QInputDialog.getText(None,"Set Name","Please specify the name for the set. If you specify an existing set, new particles will be added to the end")
		if setname[1]==False : return
		else: setname=setname[0]
		if setname[-4:]!=".lst" : setname=setname+".lst"
		if not "/" in setname : setname="sets/"+setname

		lst=LSXFile(self.curPtclFile())		# lst file for dereferenceing
		lstout=LSXFile(setname)
		include=[]
		# iterate over each particle from each marked class-average
		for n in self.curPtclIter(self.wselused.getValue(),self.wselunused.getValue()):
			try :
				orign,origfile,comment=lst.read(n)			# the original file/number dereferenced from the LST file
			except:
				QtGui.QMessageBox.warning(self,"Error !","The data_source '%s' does not follow EMAN2.1 project conventions. Cannot find raw particles for set."%srcfile)
				return

			include.append((origfile,orign,comment))		# build a list so we can sort by frame
		
		# write the new set
		for i in sorted(include) : lstout.write(-1,i[1],i[0],i[2])

	def markBadPtcl(self,x):
		"Mark particles from the selected class-averages as bad in the set interface"

		r=QtGui.QMessageBox.question(None,"Are you sure ?","WARNING: There is no undo for this operation. It will  mark all particles associated with the selected class-averages as bad. Are you sure you want to proceed ?",QtGui.QMessageBox.Yes|QtGui.QMessageBox.Cancel)
		if r==QtGui.QMessageBox.Cancel : return

		lst=LSXFile(self.curPtclFile())		# lst file for dereferenceing
		ptcls={}						# dictionary keyed by original frame filename with list of selected particle #s
		# iterate over each particle from each marked class-average
		for n in self.curPtclIter(self.wselused.getValue(),self.wselunused.getValue()):
			try :
				orign,origfile,comment=lst.read(n)
			except:
				QtGui.QMessageBox.warning(self,"Error !","The data_source '%s' does not follow EMAN2.1 project conventions. Cannot find raw particles for set."%srcfile)
				return

			try: ptcls[origfile].append(orign)		# try to add to a list for an existing filename
			except: ptcls[origfile]=[orign]			# creates the list for this filename if it's new

		#now mark the particles as bad
		newbad=0
		totbad=0
		for origfile in ptcls:
			js=js_open_dict(info_name(origfile))	# get the info dict for this file

			try: sets=js["sets"]
			except: sets={"bad_particles":[]}
			try: badset=set(sets["bad_particles"])
			except: badset=set()

			try:
				newset=list(set(ptcls[origfile])|badset)
				sets["bad_particles"]=newset	# update the set of bad particles for this image file
				js["sets"]=sets
				totbad+=len(badset)
				newbad+=len(newset)-len(badset)
			except:
				print "Error setting bad particles in ",origfile
				
		print newbad, " new particles marked as bad. Total of ",totbad," in affected micrographs"

	def markGoodPtcl(self,x):
		"Mark particles from the selected class-averages as good in the set interface"

		r=QtGui.QMessageBox.question(None,"Are you sure ?","WARNING: There is no undo for this operation. It will un-mark all particles associated with the selected class-averages as bad. Are you sure you want to proceed ?",QtGui.QMessageBox.Yes|QtGui.QMessageBox.Cancel)
		if r==QtGui.QMessageBox.Cancel : return

		lst=LSXFile(self.curPtclFile())		# lst file for dereferenceing
		ptcls={}						# dictionary keyed by original frame filename with list of selected particle #s
		# iterate over each particle from each marked class-average
		for n in self.curPtclIter(self.wselused.getValue(),self.wselunused.getValue()):
			try :
				orign,origfile,comment=lst.read(n)
			except:
				QtGui.QMessageBox.warning(self,"Error !","The data_source '%s' does not follow EMAN2.1 project conventions. Cannot find raw particles for set."%srcfile)
				return

			try: ptcls[origfile].append(orign)		# try to add to a list for an existing filename
			except: ptcls[origfile]=[orign]			# creates the list for this filename if it's new

		#now mark the particles as good
		badafter=0
		badbefore=0
		for origfile in ptcls:
			js=js_open_dict(info_name(origfile))	# get the info dict for this file
			try:
				badset=set(js["sets"]["bad_particles"])
				js["sets"]["bad_particles"]=list(badset-set(ptcls[origfile]))	# update the set of bad particles for this image file
			except:
				pass		# since marking as good is the same as removing from the bad list, if there is no bad list, there is nothing to do

			try: sets=js["sets"]
			except: continue	# if no current bad particles, nothing to mark good
			try: badset=sets["bad_particles"]
			except: continue

			try:
				newset=list(badset-set(ptcls[origfile]))
				sets["bad_particles"]=newset	# update the set of bad particles for this image file
				js["sets"]=sets
				badbefore+=len(badset)
				badafter+=len(newset)
			except:
				continue
		
		print badbefore," bad particles before processing, now ",badafter

	def savePtclNum(self,x):
		"Saves a list of particles from marked classes into a text file"

		filename=QtGui.QInputDialog.getText(None,"Filename","Please enter a filename for the particle list. The file will contain the particle number (within the particle file) for each particle associated with a selected class-average.")
		if filename[1]==False or filename[0]=="" : return

		out=file(filename[0],"w")
		for i in self.curPtclIter(self.wselused.getValue(),self.wselunused.getValue()): out.write("%d\n"%i)
		out.close()

	def saveOrigPtclNum(self,x):
		"Saves a file containing micrograph-dereferenced particles"
		filename=QtGui.QInputDialog.getText(None,"Filename","Please enter a filename for the particle list. The file will contain particle number and image file, one per line. Image files will be referenced back to the original per-CCD frame stacks.")
		if filename[1]==False or filename[0]=="" : return

		lst=LSXFile(self.curPtclFile())		# lst file for dereferenceing
		include=[]
		# iterate over each particle from each marked class-average
		for n in self.curPtclIter(self.wselused.getValue(),self.wselunused.getValue()):
			try :
				orign,origfile,comment=lst.read(n)			# the original file/number dereferenced from the LST file
			except:
				QtGui.QMessageBox.warning(self,"Error !","The data_source '%s' does not follow EMAN2.1 project conventions. Cannot find raw particles for set."%srcfile)
				return

			include.append((origfile,orign,comment))		# build a list so we can sort by frame
		
		# write the output file
		out=file(filename,"w")
		for i in sorted(include) : out.write("{}\t{}\n".format(i[1],i[0]))
		out=None

	def selAllClasses(self,x):
		"Mark all classes as selected"
		self.vclasses.all_set()

	def selNoClasses(self,x):
		"Clear selection"
		self.vclasses.clear_set()

	def selRangeClasses(self,x):
		"Select a range of images (ask the user for the range)"
		rng=QtGui.QInputDialog.getText(None,"Select Range","Enter the range of particle values as first-last (inclusive). Merges with existing selection.")
		if rng[1]==False : return

		try:
			x0,x1=rng[0].split("-")
			x0=int(x0)
			x1=int(x1)+1
		except:
			QtGui.QMessageBox.warning(self,"Error !","Invalid range specified. Use: min-max")
			return

		self.vclasses.subset_set(range(x0,x1))

	def selInvertClasses(self,x):
		"Inverts the current selection set"
		self.vclasses.invert_set()

	def sel3DClasses(self,x):
		"Select a range of images based on those used in a 3-D reconstruction associated with this classes file. Removes current selection first."

		f=self.curFile()
		if not '#classes_' in f :
			QtGui.QMessageBox.warning(self,"Error !","A classes_xx file from a refine_xx directory is not currently selected")
			return

		# construct the path to the threed_xx file
		num=f.split("_")[-1]
		pre=f.split("#")[0]
		d3path="%s#threed_%s"%(pre,num)
		try:
			a=EMData(d3path,0,True)
			goodptcl=a["threed_ptcl_idxs"]
		except:
			QtGui.QMessageBox.warning(self,"Error !","Cannot read classes from "+d3path)
			return

		self.vclasses.clear_set()
		self.vclasses.subset_set(goodptcl)

	def ptclChange(self,value):
		"Called when a change of particle data file occurs to zero out the display"
		try:
			self.vgoodptcl.set_data(None)
			self.vbadptcl.set_data(None)
		except:
			pass

	def updateFiles(self):
		"Updates the list of classes files"
		subdir=sorted([i for i in os.listdir(e2getcwd()) if "r2d_" in i or "relion2d_" in i or "refine_" in i or "multi_" in i])
		for d in subdir:
			try: dbs=os.listdir(d)
			except: continue
			dbs.sort()
			for db in dbs:
				if "classes_" in db or "allrefs_" in db :
					self.wfilesel.addItem("%s/%s"%(d,db))

		for f in self.extrafiles:
			self.wfilesel.addItem(f)

		dbs=os.listdir("sets")
		dbs.sort()
		for db in dbs:
			self.wptclfile.addItem("sets/"+db)

	def curPtclIter(self,included=True,excluded=True):
		"This is a generator function which yields n (in self.curPtclFile()) for all particles associated with selected classes"
		for ci in self.curSet():
			try :
				c=EMData(self.curFile(),ci,True)		# read header for current class average
				if included :
					incl=c["class_ptcl_idxs"]
					if isinstance(incl,int) : incl=[incl]		# This should not happen, but seems to sometimes for some reason
					for i in incl:
						yield(i)
				if excluded and c.has_attr("exc_class_ptcl_idxs"):
					excl=c["exc_class_ptcl_idxs"]
					if isinstance(excl,int) : excl=[excl]		# This should not happen, but seems to sometimes for some reason
					for i in excl:
						yield(i)
			except:
				print "Problem with class %d (%s). Skipping"%(ci,self.curFile())
				traceback.print_exc()
				continue

	def curFile(self):
		"return the currently selected file as a readable path"
		return str(self.wfilesel.item(self.wfilesel.currentRow()).text())		# text of the currently selected item

	def curSet(self):
		"return a set (integers) of the currently selected class-averages"
		
		return self.vclasses.get_set("evalptcl")

	def curPtclFile(self):
		"return the particle file associated with the currently selected classes file"
		return str(self.wptclfile.currentText())		# text of the currently selected item


	def fileUpdate(self):
		"Called when the user selects a file from the list or need to completely refresh display"

		QtGui.qApp.setOverrideCursor(Qt.BusyCursor)

		if self.vclasses==None :
			self.vclasses=EMImageMXWidget()
			self.vclasses.set_mouse_mode("App")
			QtCore.QObject.connect(self.vclasses,QtCore.SIGNAL("mx_image_selected"),self.classSelect)
			QtCore.QObject.connect(self.vclasses,QtCore.SIGNAL("mx_image_double"),self.classDouble)

		self.vclasses.set_title("Classes")


#		self.classes=EMData.read_images(self.curFile())
		self.vclasses.set_data(self.curFile(),self.curFile())
#		self.vclasses.set_single_active_set("selected")		# This makes the 'set' representing the selected class-averages current
		self.vclasses.set_mouse_mode("App")
		self.vclasses.enable_set("evalptcl",[])

		# This makes sure the particle file is in the list of choices and is selected
		try:
			ptclfile=EMData(self.curFile(),0,True)["class_ptcl_src"]
			i=self.wptclfile.findText(ptclfile)
			if i==-1 :
				self.wptclfile.insertItem(0,ptclfile)
				self.wptclfile.setCurrentIndex(0)
			else:
				self.wptclfile.setCurrentIndex(i)
		except:
			QtGui.QMessageBox.warning(self,"Error !","This image does not appear to be a class average. (No class_ptcl_src, etc.)")
			ptclfile="None"


		# Make sure our display widgets exist
		if self.vgoodptcl==None :
			self.vgoodptcl=EMImageMXWidget()
		self.vgoodptcl.set_title("Included Particles")

		if self.vbadptcl==None :
			self.vbadptcl=EMImageMXWidget()
		self.vbadptcl.set_title("Excluded Particles")

		self.vclasses.show()
		self.vgoodptcl.show()
		self.vbadptcl.show()

		QtGui.qApp.setOverrideCursor(Qt.ArrowCursor)

	def classSelect(self,event,lc):
		"Single clicked class particle. lc=(img#,x,y,image_dict)"

		QtGui.qApp.setOverrideCursor(Qt.BusyCursor)
		ptclfile=self.curPtclFile()
		try:
			ptclgood=lc[3]["class_ptcl_idxs"]
			self.vgoodptcl.set_data(EMData.read_images(ptclfile,ptclgood))
		except:
			QtGui.QMessageBox.warning(self,"Error !","This image does not appear to be a class average. (No class_ptcl_src, etc.)")
			QtGui.qApp.setOverrideCursor(Qt.ArrowCursor)
			return
		try:
			ptclbad=lc[3]["exc_class_ptcl_idxs"]
			self.vbadptcl.set_data(EMData.read_images(ptclfile,ptclbad))
		except:
			ptclbad=[]
			self.vbadptcl.set_data(None)

		self.vgoodptcl.show()
		self.vbadptcl.show()
		QtGui.qApp.setOverrideCursor(Qt.ArrowCursor)

	def classDouble(self,event,lc):
		self.vclasses.image_set_associate(lc[0],update_gl=True)

	def closeEvent(self,event):
		try :
			self.vclasses.commit_sets()
			self.vclasses.close()
		except: pass
		try : self.vgoodptcl.close()
		except: pass
		try : self.vbadptcl.close()
		except: pass

		QtGui.QWidget.closeEvent(self, event)
Ejemplo n.º 2
0
class EMClassPtclTool(QtGui.QWidget):
    """This class is a tab widget for inspecting particles within class-averages"""
    def __init__(self, extrafiles=None):
        QtGui.QWidget.__init__(self)
        self.vbl = QtGui.QVBoxLayout(self)

        self.extrafiles = extrafiles

        # A listwidget for selecting which class-average file we're looking at
        self.wclassfilel = QtGui.QLabel("Class-average File:")
        self.vbl.addWidget(self.wclassfilel)

        self.wfilesel = QtGui.QListWidget()
        self.vbl.addWidget(self.wfilesel)
        self.vbl.addSpacing(5)

        # A widget containing the current particle filename, editable by the user
        # If edited it will also impact set generation !
        self.wptclfilel = QtGui.QLabel("Particle Data File:")
        self.vbl.addWidget(self.wptclfilel)

        self.wptclfile = QtGui.QComboBox(self)
        self.vbl.addWidget(self.wptclfile)
        self.vbl.addSpacing(5)

        # Selection tools
        self.wselectg = QtGui.QGroupBox("Class Selection", self)
        self.wselectg.setFlat(False)
        self.vbl.addWidget(self.wselectg)
        self.vbl.addSpacing(5)

        self.gbl0 = QtGui.QGridLayout(self.wselectg)

        self.wselallb = QtGui.QPushButton("All")
        self.gbl0.addWidget(self.wselallb, 0, 0)

        self.wselnoneb = QtGui.QPushButton("Clear")
        self.gbl0.addWidget(self.wselnoneb, 0, 1)

        self.wselrangeb = QtGui.QPushButton("Range")
        self.gbl0.addWidget(self.wselrangeb, 1, 0)

        self.wselinvertb = QtGui.QPushButton("Invert")
        self.gbl0.addWidget(self.wselinvertb, 0, 2)

        self.wsel3db = QtGui.QPushButton("From 3D")
        self.gbl0.addWidget(self.wsel3db, 1, 2)

        self.wprocessg = QtGui.QGroupBox("Process results", self)
        self.wprocessg.setFlat(False)
        self.vbl.addWidget(self.wprocessg)

        self.vbl2 = QtGui.QVBoxLayout(self.wprocessg)

        self.wselused = CheckBox(None, "Included Ptcls", 1, 100)
        self.vbl2.addWidget(self.wselused)

        self.wselunused = CheckBox(None, "Excluded Ptcls", 1, 100)
        self.vbl2.addWidget(self.wselunused)

        # Mark particles in selected classes as bad
        self.wmarkbut = QtGui.QPushButton("Mark as Bad")
        self.vbl2.addWidget(self.wmarkbut)

        # Mark particles in selected classes as good
        self.wmarkgoodbut = QtGui.QPushButton("Mark as Good")
        self.vbl2.addWidget(self.wmarkgoodbut)

        # Make a new set from selected classes
        self.wmakebut = QtGui.QPushButton("Make New Set")
        self.vbl2.addWidget(self.wmakebut)
        #		self.wmakebut.setEnabled(False)

        # Save list
        self.wsavebut = QtGui.QPushButton("Save Particle List")
        self.vbl2.addWidget(self.wsavebut)

        # Save micrograph dereferenced lists
        self.wsaveorigbut = QtGui.QPushButton("Save CCD-based List")
        self.vbl2.addWidget(self.wsaveorigbut)

        QtCore.QObject.connect(self.wfilesel,
                               QtCore.SIGNAL("itemSelectionChanged()"),
                               self.fileUpdate)
        QtCore.QObject.connect(self.wptclfile,
                               QtCore.SIGNAL("currentIndexChanged(int)"),
                               self.ptclChange)
        QtCore.QObject.connect(self.wselallb, QtCore.SIGNAL("clicked(bool)"),
                               self.selAllClasses)
        QtCore.QObject.connect(self.wselnoneb, QtCore.SIGNAL("clicked(bool)"),
                               self.selNoClasses)
        QtCore.QObject.connect(self.wselrangeb, QtCore.SIGNAL("clicked(bool)"),
                               self.selRangeClasses)
        QtCore.QObject.connect(self.wselinvertb,
                               QtCore.SIGNAL("clicked(bool)"),
                               self.selInvertClasses)
        QtCore.QObject.connect(self.wsel3db, QtCore.SIGNAL("clicked(bool)"),
                               self.sel3DClasses)
        QtCore.QObject.connect(self.wmakebut, QtCore.SIGNAL("clicked(bool)"),
                               self.makeNewSet)
        QtCore.QObject.connect(self.wmarkbut, QtCore.SIGNAL("clicked(bool)"),
                               self.markBadPtcl)
        QtCore.QObject.connect(self.wmarkgoodbut,
                               QtCore.SIGNAL("clicked(bool)"),
                               self.markGoodPtcl)
        QtCore.QObject.connect(self.wsavebut, QtCore.SIGNAL("clicked(bool)"),
                               self.savePtclNum)
        QtCore.QObject.connect(self.wsaveorigbut,
                               QtCore.SIGNAL("clicked(bool)"),
                               self.saveOrigPtclNum)

        # View windows, one for class-averages, one for good particles and one for bad particles
        self.vclasses = None
        self.vgoodptcl = None
        self.vbadptcl = None

        self.updateFiles()

    def makeNewSet(self, x):
        "Makes a new particle set based on the selected class-averages"
        setname = QtGui.QInputDialog.getText(
            None, "Set Name",
            "Please specify the name for the set. If you specify an existing set, new particles will be added to the end"
        )
        if setname[1] == False: return
        else: setname = setname[0]
        if setname[-4:] != ".lst": setname = setname + ".lst"
        if not "/" in setname: setname = "sets/" + setname

        lst = LSXFile(self.curPtclFile())  # lst file for dereferenceing
        lstout = LSXFile(setname)
        include = []
        # iterate over each particle from each marked class-average
        for n in self.curPtclIter(self.wselused.getValue(),
                                  self.wselunused.getValue()):
            try:
                orign, origfile, comment = lst.read(
                    n
                )  # the original file/number dereferenced from the LST file
            except:
                QtGui.QMessageBox.warning(
                    self, "Error !",
                    "The data_source '%s' does not follow EMAN2.1 project conventions. Cannot find raw particles for set."
                    % srcfile)
                return

            include.append((origfile, orign,
                            comment))  # build a list so we can sort by frame

        # write the new set
        for i in sorted(include):
            lstout.write(-1, i[1], i[0], i[2])

    def markBadPtcl(self, x):
        "Mark particles from the selected class-averages as bad in the set interface"

        r = QtGui.QMessageBox.question(
            None, "Are you sure ?",
            "WARNING: There is no undo for this operation. It will  mark all particles associated with the selected class-averages as bad. Are you sure you want to proceed ?",
            QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel)
        if r == QtGui.QMessageBox.Cancel: return

        lst = LSXFile(self.curPtclFile())  # lst file for dereferenceing
        ptcls = {
        }  # dictionary keyed by original frame filename with list of selected particle #s
        # iterate over each particle from each marked class-average
        for n in self.curPtclIter(self.wselused.getValue(),
                                  self.wselunused.getValue()):
            try:
                orign, origfile, comment = lst.read(n)
            except:
                QtGui.QMessageBox.warning(
                    self, "Error !",
                    "The data_source '%s' does not follow EMAN2.1 project conventions. Cannot find raw particles for set."
                    % srcfile)
                return

            try:
                ptcls[origfile].append(
                    orign)  # try to add to a list for an existing filename
            except:
                ptcls[origfile] = [
                    orign
                ]  # creates the list for this filename if it's new

        #now mark the particles as bad
        newbad = 0
        totbad = 0
        for origfile in ptcls:
            js = js_open_dict(
                info_name(origfile))  # get the info dict for this file

            try:
                sets = js["sets"]
            except:
                sets = {"bad_particles": []}
            try:
                badset = set(sets["bad_particles"])
            except:
                badset = set()

            try:
                newset = list(set(ptcls[origfile]) | badset)
                sets[
                    "bad_particles"] = newset  # update the set of bad particles for this image file
                js["sets"] = sets
                totbad += len(badset)
                newbad += len(newset) - len(badset)
            except:
                print "Error setting bad particles in ", origfile

            js_close_dict(info_name(origfile))
        print newbad, " new particles marked as bad. Total of ", totbad, " in affected micrographs"

    def markGoodPtcl(self, x):
        "Mark particles from the selected class-averages as good in the set interface"

        r = QtGui.QMessageBox.question(
            None, "Are you sure ?",
            "WARNING: There is no undo for this operation. It will un-mark all particles associated with the selected class-averages as bad. Are you sure you want to proceed ?",
            QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel)
        if r == QtGui.QMessageBox.Cancel: return

        lst = LSXFile(self.curPtclFile())  # lst file for dereferenceing
        ptcls = {
        }  # dictionary keyed by original frame filename with list of selected particle #s
        # iterate over each particle from each marked class-average
        for n in self.curPtclIter(self.wselused.getValue(),
                                  self.wselunused.getValue()):
            try:
                orign, origfile, comment = lst.read(n)
            except:
                QtGui.QMessageBox.warning(
                    self, "Error !",
                    "The data_source '%s' does not follow EMAN2.1 project conventions. Cannot find raw particles for set."
                    % srcfile)
                return

            try:
                ptcls[origfile].append(
                    orign)  # try to add to a list for an existing filename
            except:
                ptcls[origfile] = [
                    orign
                ]  # creates the list for this filename if it's new

        #now mark the particles as good
        badafter = 0
        badbefore = 0
        for origfile in ptcls:
            js = js_open_dict(
                info_name(origfile))  # get the info dict for this file
            try:
                badset = set(js["sets"]["bad_particles"])
                js["sets"]["bad_particles"] = list(
                    badset - set(ptcls[origfile])
                )  # update the set of bad particles for this image file
            except:
                pass  # since marking as good is the same as removing from the bad list, if there is no bad list, there is nothing to do

            try:
                sets = js["sets"]
            except:
                continue  # if no current bad particles, nothing to mark good
            try:
                badset = sets["bad_particles"]
            except:
                continue

            try:
                newset = list(badset - set(ptcls[origfile]))
                sets[
                    "bad_particles"] = newset  # update the set of bad particles for this image file
                js["sets"] = sets
                badbefore += len(badset)
                badafter += len(newset)
            except:
                continue

        print badbefore, " bad particles before processing, now ", badafter

    def savePtclNum(self, x):
        "Saves a list of particles from marked classes into a text file"

        filename = QtGui.QInputDialog.getText(
            None, "Filename",
            "Please enter a filename for the particle list. The file will contain the particle number (within the particle file) for each particle associated with a selected class-average."
        )
        if filename[1] == False or filename[0] == "": return

        out = file(filename[0], "w")
        for i in self.curPtclIter(self.wselused.getValue(),
                                  self.wselunused.getValue()):
            out.write("%d\n" % i)
        out.close()

    def saveOrigPtclNum(self, x):
        "Saves a file containing micrograph-dereferenced particles"
        filename = QtGui.QInputDialog.getText(
            None, "Filename",
            "Please enter a filename for the particle list. The file will contain particle number and image file, one per line. Image files will be referenced back to the original per-CCD frame stacks."
        )
        if filename[1] == False or filename[0] == "": return

        lst = LSXFile(self.curPtclFile())  # lst file for dereferenceing
        include = []
        # iterate over each particle from each marked class-average
        for n in self.curPtclIter(self.wselused.getValue(),
                                  self.wselunused.getValue()):
            try:
                orign, origfile, comment = lst.read(
                    n
                )  # the original file/number dereferenced from the LST file
            except:
                QtGui.QMessageBox.warning(
                    self, "Error !",
                    "The data_source '%s' does not follow EMAN2.1 project conventions. Cannot find raw particles for set."
                    % srcfile)
                return

            include.append((origfile, orign,
                            comment))  # build a list so we can sort by frame

        # write the output file
        out = file(filename, "w")
        for i in sorted(include):
            out.write("{}\t{}\n".format(i[1], i[0]))
        out = None

    def selAllClasses(self, x):
        "Mark all classes as selected"
        self.vclasses.all_set()

    def selNoClasses(self, x):
        "Clear selection"
        self.vclasses.clear_set()

    def selRangeClasses(self, x):
        "Select a range of images (ask the user for the range)"
        rng = QtGui.QInputDialog.getText(
            None, "Select Range",
            "Enter the range of particle values as first-last (inclusive). Merges with existing selection."
        )
        if rng[1] == False: return

        try:
            x0, x1 = rng[0].split("-")
            x0 = int(x0)
            x1 = int(x1) + 1
        except:
            QtGui.QMessageBox.warning(self, "Error !",
                                      "Invalid range specified. Use: min-max")
            return

        self.vclasses.subset_set(range(x0, x1))

    def selInvertClasses(self, x):
        "Inverts the current selection set"
        self.vclasses.invert_set()

    def sel3DClasses(self, x):
        "Select a range of images based on those used in a 3-D reconstruction associated with this classes file. Removes current selection first."

        f = self.curFile()
        if not '#classes_' in f:
            QtGui.QMessageBox.warning(
                self, "Error !",
                "A classes_xx file from a refine_xx directory is not currently selected"
            )
            return

        # construct the path to the threed_xx file
        num = f.split("_")[-1]
        pre = f.split("#")[0]
        d3path = "%s#threed_%s" % (pre, num)
        try:
            a = EMData(d3path, 0, True)
            goodptcl = a["threed_ptcl_idxs"]
        except:
            QtGui.QMessageBox.warning(self, "Error !",
                                      "Cannot read classes from " + d3path)
            return

        self.vclasses.clear_set()
        self.vclasses.subset_set(goodptcl)

    def ptclChange(self, value):
        "Called when a change of particle data file occurs to zero out the display"
        try:
            self.vgoodptcl.set_data(None)
            self.vbadptcl.set_data(None)
        except:
            pass

    def updateFiles(self):
        "Updates the list of classes files"
        subdir = sorted([
            i for i in os.listdir(e2getcwd())
            if "r2d_" in i or "relion2d_" in i or "refine_" in i
            or "multi_" in i or "multinoali_" in i
        ])
        for d in subdir:
            try:
                dbs = os.listdir(d)
            except:
                continue
            dbs.sort()
            for db in dbs:
                if "classes_" in db or "allrefs_" in db:
                    self.wfilesel.addItem("%s/%s" % (d, db))

        for f in self.extrafiles:
            self.wfilesel.addItem(f)

        dbs = os.listdir("sets")
        dbs.sort()
        for db in dbs:
            self.wptclfile.addItem("sets/" + db)

    def curPtclIter(self, included=True, excluded=True):
        "This is a generator function which yields n (in self.curPtclFile()) for all particles associated with selected classes"
        for ci in self.curSet():
            try:
                c = EMData(self.curFile(), ci,
                           True)  # read header for current class average
                if included:
                    incl = c["class_ptcl_idxs"]
                    if isinstance(incl, int):
                        incl = [
                            incl
                        ]  # This should not happen, but seems to sometimes for some reason
                    for i in incl:
                        yield (i)
                if excluded and c.has_attr("exc_class_ptcl_idxs"):
                    excl = c["exc_class_ptcl_idxs"]
                    if isinstance(excl, int):
                        excl = [
                            excl
                        ]  # This should not happen, but seems to sometimes for some reason
                    for i in excl:
                        yield (i)
            except:
                print "Problem with class %d (%s). Skipping" % (ci,
                                                                self.curFile())
                traceback.print_exc()
                continue

    def curFile(self):
        "return the currently selected file as a readable path"
        return str(self.wfilesel.item(self.wfilesel.currentRow()).text()
                   )  # text of the currently selected item

    def curSet(self):
        "return a set (integers) of the currently selected class-averages"

        return self.vclasses.get_set("evalptcl")

    def curPtclFile(self):
        "return the particle file associated with the currently selected classes file"
        return str(self.wptclfile.currentText()
                   )  # text of the currently selected item

    def fileUpdate(self):
        "Called when the user selects a file from the list or need to completely refresh display"

        QtGui.qApp.setOverrideCursor(Qt.BusyCursor)

        if self.vclasses == None:
            self.vclasses = EMImageMXWidget()
            self.vclasses.set_mouse_mode("App")
            QtCore.QObject.connect(self.vclasses,
                                   QtCore.SIGNAL("mx_image_selected"),
                                   self.classSelect)
            QtCore.QObject.connect(self.vclasses,
                                   QtCore.SIGNAL("mx_image_double"),
                                   self.classDouble)

        self.vclasses.set_title("Classes")

        #		self.classes=EMData.read_images(self.curFile())
        self.vclasses.set_data(self.curFile(), self.curFile())
        #		self.vclasses.set_single_active_set("selected")		# This makes the 'set' representing the selected class-averages current
        self.vclasses.set_mouse_mode("App")
        self.vclasses.enable_set("evalptcl", [])

        # This makes sure the particle file is in the list of choices and is selected
        try:
            ptclfile = EMData(self.curFile(), 0, True)["class_ptcl_src"]
            i = self.wptclfile.findText(ptclfile)
            if i == -1:
                self.wptclfile.insertItem(0, ptclfile)
                self.wptclfile.setCurrentIndex(0)
            else:
                self.wptclfile.setCurrentIndex(i)
        except:
            QtGui.QMessageBox.warning(
                self, "Error !",
                "This image does not appear to be a class average. (No class_ptcl_src, etc.)"
            )
            ptclfile = "None"

        # Make sure our display widgets exist
        if self.vgoodptcl == None:
            self.vgoodptcl = EMImageMXWidget()
        self.vgoodptcl.set_title("Included Particles")

        if self.vbadptcl == None:
            self.vbadptcl = EMImageMXWidget()
        self.vbadptcl.set_title("Excluded Particles")

        self.vclasses.show()
        self.vgoodptcl.show()
        self.vbadptcl.show()

        QtGui.qApp.setOverrideCursor(Qt.ArrowCursor)

    def classSelect(self, event, lc):
        "Single clicked class particle. lc=(img#,x,y,image_dict)"

        QtGui.qApp.setOverrideCursor(Qt.BusyCursor)
        ptclfile = self.curPtclFile()
        try:
            ptclgood = lc[3]["class_ptcl_idxs"]
            self.vgoodptcl.set_data(EMData.read_images(ptclfile, ptclgood))
        except:
            QtGui.QMessageBox.warning(
                self, "Error !",
                "This image does not appear to be a class average. (No class_ptcl_src, etc.)"
            )
            QtGui.qApp.setOverrideCursor(Qt.ArrowCursor)
            return
        try:
            ptclbad = lc[3]["exc_class_ptcl_idxs"]
            self.vbadptcl.set_data(EMData.read_images(ptclfile, ptclbad))
        except:
            ptclbad = []
            self.vbadptcl.set_data(None)

        self.vgoodptcl.show()
        self.vbadptcl.show()
        QtGui.qApp.setOverrideCursor(Qt.ArrowCursor)

    def classDouble(self, event, lc):
        self.vclasses.image_set_associate(lc[0], update_gl=True)

    def closeEvent(self, event):
        try:
            self.vclasses.commit_sets()
            self.vclasses.close()
        except:
            pass
        try:
            self.vgoodptcl.close()
        except:
            pass
        try:
            self.vbadptcl.close()
        except:
            pass

        QtGui.QWidget.closeEvent(self, event)
Ejemplo n.º 3
0
class EMEulerExplorer(EM3DSymModel,Animator):

	def mousePressEvent(self,event):
		if self.events_mode == "inspect":
			self.current_hit = self.get_hit(event)
			if self.current_hit == None:
				EM3DSymModel.mousePressEvent(self,event)
		else:
			EM3DSymModel.mousePressEvent(self,event)

	def mouseReleaseEvent(self,event):
		if self.events_mode == "inspect":
			if self.current_hit != None:
				self.updateGL() # there needs to be a clear or something  in order for the picking to work. This is  bit of hack but our rendering function doesn't take long anyhow
				hit = self.get_hit(event)
				if hit == self.current_hit:
					self.emit(QtCore.SIGNAL("point_selected"),self.current_hit,event)
			else:
				#EM3DSymModel.mouseReleaseEvent(self,event)
				EM3DModel.mouseReleaseEvent(self, event) #behavior in EM3DSymModel is not what we want (needed in sibling classes?)

			self.current_hit = None
		else:
				#EM3DSymModel.mouseReleaseEvent(self,event)
				EM3DModel.mouseReleaseEvent(self, event) #behavior in EM3DSymModel is not what we want (needed in sibling classes?)


	def mouseMoveEvent(self,event):
		if self.events_mode == "inspect" and self.current_hit:
			pass
		else:
			EM3DSymModel.mouseMoveEvent(self,event)

	def get_hit(self,event):
		v = self.vdtools.wview.tolist()
		self.get_gl_widget().makeCurrent() # prevents a stack underflow
#		x = event.x()
#		y = v[-1]-event.y()
#		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT )
#		vals = self.render(color_picking=True)
#		glFlush()
#		vv = glReadPixels(x,y,1,1,GL_RGB,GL_FLOAT)
#		reslt = Vec3f(float(vv[0][0][0]),float(vv[0][0][1]),float(vv[0][0][2]))
#		for i,val in enumerate(vals):
##			print val,reslt,(reslt-val).length(),vv[0][0]
#			if (reslt-val).length() < 0.01:
#				print i
##				print (reslt-val).length()
#				return i
#		print vv
#
		# the problem with this approach is that depth testing is not part of picking
		sb = [0 for i in xrange(0,512)]
		glSelectBuffer(512)
		glRenderMode(GL_SELECT)
		glInitNames()
		glMatrixMode(GL_PROJECTION)
		glPushMatrix()
		glLoadIdentity()
		gluPickMatrix(event.x(),v[-1]-event.y(),5,5,v)
		self.get_gl_widget().load_perspective()
		glMatrixMode(GL_MODELVIEW)
		glInitNames()
		self.render()
		glMatrixMode(GL_PROJECTION)
		glPopMatrix()
		glMatrixMode(GL_MODELVIEW)
		glFlush()

		intersection = None
		hits = list(glRenderMode(GL_RENDER))
		for hit in hits:
			a,b,c=hit
			if len(c) > 0:
				intersection = c[0]-1
				break

		return intersection


	def keyPressEvent(self,event):

		if event.key() == Qt.Key_F1:
			self.display_web_help("http://blake.bcm.edu/emanwiki/EMAN2/Programs/e2eulerxplor")
		elif event.key() == Qt.Key_F :
			if self.flatten>0 : self.flatten=0.0
			else: self.flatten=1.0
			self.generate_current_display_list(True)
			self.updateGL()
		else:
			EM3DSymModel.keyPressEvent(self,event)

	def __init__(self, gl_widget=None, auto=True,sparse_mode=False, file_name = ""):
		self.current_hit = None
		self.events_mode_list = ["navigate", "inspect"]
		self.events_mode = self.events_mode_list[1]


		self.init_lock = True # a lock indicated that we are still in the __init__ function
		self.au_data = None # This will be a dictionary, keys will be refinement directories, values will be something like available iterations for visual study
		if auto: # this a flag that tells the eulerxplorer to search for refinement data and automatically add elements to the inspector, if so
			self.gen_refinement_data()

		EM3DSymModel.__init__(self, gl_widget, eulerfilename=file_name)
		Animator.__init__(self)
		self.height_scale = 8.0 # This is a value used in EM3DSymModel which scales the height of the displayed cylinders - I made it 8 because it seemed fine. The user can change it anyhow
		self.projection_file = None  # This is a string - the name of the projection images file
		self.average_file = None # This is a string - the name of the class averages file
		self.proj_class_viewer = None # This will be an EMImageMXWidget that shows the class and/or projection
		self.particle_viewer = None  # This will be an EMImageMXWidget that shows the particles in a class
		self.clsdb = None # I think this will become redundant - it used to be the old database that stores which particles are in a class, but now that's stored in the header
		self.particle_file = None # This will be a string - the name of the file that has the particle files in it. This might be made redundant with the new approach
		self.alignment_file = None # This will be a string - the name of the file containing the alignment parameters - this is essential if you we want to show the aligned particles
		self.refine_dir = None # This will be a string - the name of the current refinement directory that is being studied
		self.dx = None # This is an EMData object storing the x shifts of the alignments for all particles. Generated by e2classaverage
		self.dy = None # This is an EMData object storing the y shifts of the alignments for all particles. Generated by e2classaverage
		self.da = None# This is an EMData object storing the angle of the alignments for all particles. Generated by e2classaverage
		self.dflip = None # This is an EMData object storing whether or not tthe alignment involved a flip, for all particles. Generated by e2classaverage
		self.classes = None # This is an EMData object storing which class(es) a particle belongs to. Generated by e2classaverage
		self.inclusions = None # This is and EMDAta storing a boolean that indicates the particle was actually included in the final average. Generated by e2classaverage

		self.average = None # This the class average itself, an EMData object
		self.projection = None # This is the projection itelse, an EMData object
		self.class_idx = None # This is the idx of the current class being studied in the interface

		self.previous_len = -1 # To keep track of the number of class averages that were previously viewable. This helps to make sure we can switch to the same class average in the context of a different refinement iteration
		self.mirror_eulers = False
		if sparse_mode:
			self.mirror_eulers = True # If True the drawn Eulers are are also rendered on the opposite side of the sphere - see EM3DSymModel.make_sym_dl_lis

		# Grab the symmetry from the workflow database if possible
		sym = "c1"
		if js_check_dict("refine_01/0_refine_parms.json"):
			try: sym = str(js_open_dict("refine_01/0_refine_parms.json")["sym"])
			except: pass

		# Have to tell the EM3DSymModel that there is a new sym
		self.set_symmetry(sym)

		# this object will have
		if self.au_data != None:
			combo_entries = self.au_data.keys()
			combo_entries.sort()
			combo_entries.reverse()

			if len(combo_entries) > 0:
				au = combo_entries[0]
				cls = self.au_data[au][0][0]
				self.au_selected(au,cls)
				self.mirror_eulers = True

		self.init_lock = False
		self.force_update=True # Force a display udpdate in EMImage3DSymModule

		QtCore.QObject.connect(self, QtCore.SIGNAL("point_selected"), self.au_point_selected)

	def __del__(self):
		EM3DSymModel.__del__(self) # this is here for documentation purposes - beware that the del function is important

	def initializeGL(self):
		glEnable(GL_NORMALIZE)

	def generate_current_display_list(self,force=False):
		'''
		Redefinition of EMImage3DSymModule.generate_current_display_list

		'''
		if self.init_lock: return 0
		if self.au_data == None or len(self.au_data) == 0:
			EM3DSymModel.generate_current_display_list(self,force)

		self.init_basic_shapes()
		if self.nomirror == True : val = 0
		else: val = 1
		self.trace_great_arcs(self.sym_object.get_asym_unit_points(val))
		self.trace_great_triangles(val)

		self.eulers = self.specified_eulers
		if self.eulers == None:	return 0

#		if not self.colors_specified: self.point_colors = []
#		else: self.point_colors = self.specified_colors
#		self.points = []
#		for i in self.eulers:
#			p = i.transpose()*Vec3f(0,0,self.radius)
#			self.points.append(p)
#			if not self.colors_specified: self.point_colors.append((0.34615, 0.3143, 0.0903,1))

		self.make_sym_dl_list(self.eulers)
		return 1
	def get_data_dims(self):
		return (2*self.radius,2*self.radius,2*self.radius)

	def width(self): return 2*self.radius
	def height(self): return 2*self.radius
	def depth(self): return 2*self.radius

	def gen_refinement_data(self):
		dirs,files = get_files_and_directories()

		dirs.sort()
		for i in range(len(dirs)-1,-1,-1):
			if len(dirs[i]) != 9:
				dirs.pop(i)
			elif dirs[i][:7] != "refine_":
				dirs.pop(i)
			else:
				try: int(dirs[i][7:])
				except: dirs.pop(i)

		self.dirs = dirs
		print dirs

		self.au_data = {}
		for dir in self.dirs:
			d = self.check_refine_db_dir(dir)
			if len(d) != 0 and len(d[dir]) != 0: self.au_data.update(d)

	def check_refine_db_dir(self,dir,s1="classes",s2=None,s3="cls_result",s4="threed",s5="projections"):
		# s2 used to be class_indices
		names = [s1,s2,s3,s4,s5]
		data = {}
		data[dir] = []
		register_js_name = "{}/0_refine_parms.json".format(dir)

		files=os.listdir(dir)
		try:
			nums=[int(i[7:9]) for i in files if "threed" in i and "even" not in i and "odd" not in i]
			maxnum=max(nums)
		except :
			print "Nothing in ",dir
			return {}

		for i in xrange(1,maxnum+1):
			exte="_{:02d}_even.hdf".format(i)
			exto="_{:02d}_odd.hdf".format(i)
			data[dir].append([sadd(dir,s1,exte),sadd(dir,s2,exte),sadd(dir,s3,exte),sadd(dir,s4,exte),sadd(dir,s5,exte)])
			data[dir].append([sadd(dir,s1,exto),sadd(dir,s2,exto),sadd(dir,s3,exto),sadd(dir,s4,exto),sadd(dir,s5,exto)])

		return data

	def set_projection_file(self,projection_file): self.projection_file = projection_file
	def get_inspector(self):
		if not self.inspector :
			if (self.au_data == None or len(self.au_data) == 0) and self.mirror_eulers == False: #self.mirror_eulers thing is a little bit of a hack, it's tied to the sparse_mode flag in the init function, which is used by euler_display in EMAN2.py
				self.inspector=EMAsymmetricUnitInspector(self,True,True)
			else:
				self.inspector=EMAsymmetricUnitInspector(self)
			QtCore.QObject.connect(self.inspector,QtCore.SIGNAL("au_selected"),self.au_selected)
		return self.inspector


	def au_selected(self,refine_dir,cls):
		self.refine_dir = refine_dir
		get_application().setOverrideCursor(Qt.BusyCursor)
		data = []
		for d in self.au_data[refine_dir]:
			if d[0] == cls:
				data = d;
				break

		if len(data) == 0:
			error("error, no data for %s %s, returning" %(refine_dir,cls))
#			print "error, no data for",au,cls,"returning"
			self.events_handlers["inspect"].reset()
			get_application().setOverrideCursor(Qt.ArrowCursor)
			return

		try :
			self.particle_file=js_open_dict(refine_dir+"/0_refine_parms.json")["input"]
		except:
			error("No data in "+refine_dir )
			self.events_handlers["inspect"].reset()
			get_application().setOverrideCursor(Qt.ArrowCursor)
			return

		self.average_file = cls
		self.projection_file = data[4]
		self.alignment_file = data[2]
		self.clsdb = data[1]

		self.dx = None
		self.dy = None
		self.da = None
		self.dflip = None
		self.classes = None

		eulers = get_eulers_from(self.average_file)
		#s = Symmetries.get("d7")
		#eulers = s.gen_orientations("rand",{"n":EMUtil.get_image_count(self.average_file)})

		self.specify_eulers(eulers)
		#from emimagemx import EMDataListCache
		#a = EMData.read_images(self.average_file)
		#a = [test_image() for i in range(EMUtil.get_image_count(self.average_file))]
		#print len(a),len(eulers)
		#b = [a[i].set_attr("xform.projection",eulers[i]) for i in range(len(eulers))]
		#b = [a[i].set_attr("ptcl_repr",1) for i in range(len(eulers))]

		self.set_emdata_list_as_data(EMLightWeightParticleCache.from_file(self.average_file),"ptcl_repr")
		#self.set_emdata_list_as_data(EMDataListCache(self.average_file),"ptcl_repr")
#		self.set_emdata_list_as_data(a,"ptcl_repr")
		self.force_update = True
		self.au_point_selected(self.class_idx,None)
		# if we have the same number of Eulers we can update everything
#		if self.previous_len == len(eulers) : self.events_handlers["inspect"].repeat_event()
#		else:self.events_handlers["inspect"].reset()
		self.previous_len = len(eulers)
		if not self.init_lock:self.updateGL()
		get_application().setOverrideCursor(Qt.ArrowCursor)

	def __get_file_headers(self,filename):
		headers = []
		n = EMUtil.get_image_count(filename)
		for i in range(n):
			e = EMData()
			e.read_image(filename,i,True)
			headers.append(e)
		return headers

	def au_point_selected(self,i,event=None):
		if i == None:
			if event != None and event.modifiers()&Qt.ShiftModifier:
				if self.special_euler != None:
					self.special_euler = None
					if not self.init_lock:self.regen_dl()
			return
#		self.arc_anim_points = None
		self.projection = None
		if self.euler_data:
#			db = db_open_dict(self.average_file)
#			a = db.get(i)
#			print a["nx"]
#			print self.average_file,i
#			self.average = EMData(self.average_file,i)
#			self.average["nx"]
			self.average = self.euler_data[i]#
			self.projection = EMData(self.projection_file,self.average.get_attr("projection_image_idx"))
			self.average.process_inplace("normalize.toimage",{"to":self.projection})
			try:
				self.class_idx = self.average.get_attr("projection_image_idx")
				print "%d (%d)"%(self.class_idx,self.average["ptcl_repr"])
			except:
				self.class_idx = -1
		else: return

		#if self.projection  == None and self.average == None: return
		first = False
		if self.proj_class_viewer == None:
			first = True
			self.proj_class_viewer = EMImageMXWidget(data=None,application=get_application())
#			self.proj_class_viewer = EMImage2DWidget(image=None,application=get_application())
			QtCore.QObject.connect(self.proj_class_viewer,QtCore.SIGNAL("module_closed"),self.on_mx_view_closed)
#			self.proj_class_viewer.set_mouse_mode("App" )
			QtCore.QObject.connect(self.proj_class_viewer,QtCore.SIGNAL("mx_image_selected"), self.mx_image_selected)
			get_application().show_specific(self.proj_class_viewer)

			self.proj_class_single = EMImage2DWidget(image=None,application=get_application())
			QtCore.QObject.connect(self.proj_class_single,QtCore.SIGNAL("module_closed"),self.on_mx_view_closed)
#			QtCore.QObject.connect(self.proj_class_single,QtCore.SIGNAL("mx_image_selected"), self.mx_image_selected)
			get_application().show_specific(self.proj_class_single)

		disp = []
		if self.projection != None: disp.append(self.projection)
		if self.average != None and self.projection!=None:
			# ok, this really should be put into its own processor
			#dataf = self.projection.do_fft()
			#apix=self.projection["apix_x"]
			#curve = dataf.calc_radial_dist(dataf["ny"], 0, 0.5,True)
			#curve=[i/(dataf["nx"]*dataf["ny"])**2 for i in curve]
			#xcurve=[i/(apix*2.0*dataf["ny"]) for i in range(len(curve))]
			#xyd=XYData()
			#xyd.set_xy_list(xcurve,curve)
			#filt=self.average.process("filter.setstrucfac",{"apix":apix,"strucfac":xyd})
			#filt.process_inplace("normalize.toimage",{"to":self.average})
			self.projection["apix_x"]=self.average["apix_x"]
			self.projection["apix_y"]=self.average["apix_y"]
			self.projection["apix_z"]=self.average["apix_z"]
			filt=self.projection.process("threshold.notzero")
			filt.mult(self.average)
			filt.process_inplace("filter.matchto",{"to":self.projection})

			disp.append(filt)

		if self.average!=None:
			disp.append(self.average)

		self.proj_class_viewer.set_data(disp)
		self.proj_class_single.set_data(disp)

		self.proj_class_viewer.updateGL()
		self.proj_class_single.updateGL()
		if self.particle_viewer != None:
			self.mx_image_selected(None,None)
		if first: self.proj_class_viewer.optimally_resize()

		if i != self.special_euler:
			self.special_euler = i
			self.force_update = True

		if not self.init_lock: self.updateGL()


	def on_mx_view_closed(self):
		self.proj_class_viewer = None
		self.proj_class_single = None

	def on_particle_mx_view_closed(self):
		self.particle_viewer = None

	def animation_done_event(self,animation):
		pass

	def alignment_time_animation(self,transforms):
		if len(transforms) < 2: return
		animation = OrientationListAnimation(self,transforms,self.radius)
		self.register_animatable(animation)

	def particle_selected(self,event,lc):
		if lc != None:
			d = lc[3]
			ptcl_idx = d["Img #"]
			data = self.au_data[self.refine_dir]
			prj = []
			cls_result = []
			for l in data:
				for s in l:
					stag = base_name(s)

					if len(stag) > 11 and stag[:11] == "projections":
						prj.append(s)
					elif len(stag) > 10 and stag[:10] == "cls_result":
						cls_result.append(s)

			transforms = []
			if len(prj) != len(cls_result): RunTimeError("The number of cls_result files does not match the number of projection files?")

			e = EMData()
			for i,cr in enumerate(cls_result):
				r = Region(0,ptcl_idx,1,1)
				e.read_image(cr,0,False,r)
				p = int(e.get(0))
				e.read_image(prj[i],p,True)
				transforms.append(e["xform.projection"])

			self.alignment_time_animation(transforms)

	def mx_image_selected(self,event,lc):
#		self.arc_anim_points = None
		get_application().setOverrideCursor(Qt.BusyCursor)
		if lc != None: self.sel = lc[0]

		if self.average != None:
			included = []
			if self.average.has_attr("class_ptcl_idxs"):
				included = self.average["class_ptcl_idxs"]
			excluded = []
			if self.average.has_attr("exc_class_ptcl_idxs"):
				excluded = self.average["exc_class_ptcl_idxs"]

			all = included + excluded
			#all.sort()

			bdata = []
			data = []
			idx_included = []
			running_idx = 0
			from emimagemx import ApplyAttribute
			for val in included:
				bdata.append([self.particle_file,val,[ApplyAttribute("Img #",val)]])
				idx_included.append(running_idx)
				running_idx += 1

			idx_excluded = []
			for val in excluded:
				bdata.append([self.particle_file,val,[ApplyAttribute("Img #",val)]])
				idx_excluded.append(running_idx)
				running_idx += 1

			data = EMLightWeightParticleCache(bdata)

			first = False
			if self.particle_viewer == None:
				first = True
				self.particle_viewer = EMImageMXWidget(data=None,application=get_application())
				self.particle_viewer.set_mouse_mode("App" )
				QtCore.QObject.connect(self.particle_viewer,QtCore.SIGNAL("module_closed"),self.on_particle_mx_view_closed)
				QtCore.QObject.connect(self.particle_viewer,QtCore.SIGNAL("mx_image_selected"), self.particle_selected)
				get_application().show_specific(self.particle_viewer)


			self.check_images_in_memory()

			if self.sel== 0 or self.alignment_file == None:
				self.particle_viewer.set_data(data)
			else:

				for i,[name,idx,f] in enumerate(bdata):
					index = -1
					if self.classes.get_xsize() == 1:
						index = 0 # just assume it's the first one - this is potentially fatal assumption, but in obscure situations only
					else:
						for j in range(self.classes.get_xsize()):
							if int(self.classes.get(j,idx)) == self.class_idx:
								index = j
								break
					if index == -1:
						print "couldn't find"
						get_application().setOverrideCursor(Qt.ArrowCursor)
						return

					x = self.dx.get(index,idx)
					y = self.dy.get(index,idx)
					a = self.da.get(index,idx)
					m = self.dflip.get(index,idx)

					t = Transform({"type":"2d","alpha":a,"mirror":int(m)})
					t.set_trans(x,y)
					from emimagemx import ApplyTransform
					f.append(ApplyTransform(t))
					#data[i].transform(t)
				self.particle_viewer.set_data(data)


			if first:
				self.particle_viewer.updateGL()
				self.particle_viewer.optimally_resize()

			self.particle_viewer.clear_sets(False)
			self.particle_viewer.enable_set("Excluded",idx_excluded,True,False)
			self.particle_viewer.enable_set("Included",idx_included,False,False)
			self.particle_viewer.updateGL()

			get_application().setOverrideCursor(Qt.ArrowCursor)

			self.updateGL()

	def check_images_in_memory(self):
		if self.alignment_file != None:
			if self.dx == None:
				self.dx = EMData(self.alignment_file,2)
			if self.dy == None:
				self.dy = EMData(self.alignment_file,3)
			if self.da == None:
				self.da = EMData(self.alignment_file,4)
			if self.dflip == None:
				self.dflip  = EMData(self.alignment_file,5)
			if self.classes == None:
				self.classes  = EMData(self.alignment_file,0)
			if self.inclusions == None:
				self.inclusions  = EMData(self.alignment_file,1)


	def set_events_mode(self,mode):
		if not mode in self.events_mode_list:
			print "error, unknown events mode", mode
			return

		else:
			self.events_mode = mode

	def closeEvent(self,event):
		if self.inspector !=None: self.inspector.close()
		if self.proj_class_viewer !=None: self.proj_class_viewer.close()
		if self.proj_class_single !=None: self.proj_class_single.close()
		if self.particle_viewer != None: self.particle_viewer.close()
		get_application().close_specific(self)
		self.emit(QtCore.SIGNAL("module_closed")) # this signal is
Ejemplo n.º 4
0
class EMEulerExplorer(EM3DSymModel,Animator):

	def mousePressEvent(self,event):
		if self.events_mode == "inspect":
			self.current_hit = self.get_hit(event)
			if self.current_hit == None:
				EM3DSymModel.mousePressEvent(self,event)
		else:
			EM3DSymModel.mousePressEvent(self,event)

	def mouseReleaseEvent(self,event):
		if self.events_mode == "inspect":
			if self.current_hit != None:
				self.updateGL() # there needs to be a clear or something  in order for the picking to work. This is  bit of hack but our rendering function doesn't take long anyhow
				hit = self.get_hit(event)
				if hit == self.current_hit:
					self.emit(QtCore.SIGNAL("point_selected"),self.current_hit,event)
			else:
				#EM3DSymModel.mouseReleaseEvent(self,event)
				EM3DModel.mouseReleaseEvent(self, event) #behavior in EM3DSymModel is not what we want (needed in sibling classes?)

			self.current_hit = None
		else:
				#EM3DSymModel.mouseReleaseEvent(self,event)
				EM3DModel.mouseReleaseEvent(self, event) #behavior in EM3DSymModel is not what we want (needed in sibling classes?)


	def mouseMoveEvent(self,event):
		if self.events_mode == "inspect" and self.current_hit:
			pass
		else:
			EM3DSymModel.mouseMoveEvent(self,event)

	def get_hit(self,event):
		v = self.vdtools.wview.tolist()
		self.get_gl_widget().makeCurrent() # prevents a stack underflow
#		x = event.x()
#		y = v[-1]-event.y()
#		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT )
#		vals = self.render(color_picking=True)
#		glFlush()
#		vv = glReadPixels(x,y,1,1,GL_RGB,GL_FLOAT)
#		reslt = Vec3f(float(vv[0][0][0]),float(vv[0][0][1]),float(vv[0][0][2]))
#		for i,val in enumerate(vals):
##			print val,reslt,(reslt-val).length(),vv[0][0]
#			if (reslt-val).length() < 0.01:
#				print i
##				print (reslt-val).length()
#				return i
#		print vv
#
		# the problem with this approach is that depth testing is not part of picking
		sb = [0 for i in xrange(0,512)]
		glSelectBuffer(512)
		glRenderMode(GL_SELECT)
		glInitNames()
		glMatrixMode(GL_PROJECTION)
		glPushMatrix()
		glLoadIdentity()
		gluPickMatrix(event.x(),v[-1]-event.y(),5,5,v)
		self.get_gl_widget().load_perspective()
		glMatrixMode(GL_MODELVIEW)
		glInitNames()
		self.render()
		glMatrixMode(GL_PROJECTION)
		glPopMatrix()
		glMatrixMode(GL_MODELVIEW)
		glFlush()

		intersection = None
		hits = list(glRenderMode(GL_RENDER))
		for hit in hits:
			a,b,c=hit
			if len(c) > 0:
				intersection = c[0]-1
				break

		return intersection


	def keyPressEvent(self,event):

		if event.key() == Qt.Key_F1:
			self.display_web_help("http://blake.bcm.edu/emanwiki/EMAN2/Programs/e2eulerxplor")
		elif event.key() == Qt.Key_F :
			if self.flatten>0 : self.flatten=0.0
			else: self.flatten=1.0
			self.generate_current_display_list(True)
			self.updateGL()
		else:
			EM3DSymModel.keyPressEvent(self,event)

	def __init__(self, gl_widget=None, auto=True,sparse_mode=False, file_name = "", read_from=None):
		self.current_hit = None
		self.events_mode_list = ["navigate", "inspect"]
		self.events_mode = self.events_mode_list[1]

		self.init_lock = True # a lock indicated that we are still in the __init__ function
		self.au_data = None # This will be a dictionary, keys will be refinement directories, values will be something like available iterations for visual study
		if len(read_from)==0:
			read_from=None
		self.readfrom=read_from
		if self.readfrom:
			
			file_name=""
			auto=False
			datalst=[]
			for rr in self.readfrom:
				datalst.append([rr,None, None, None,rr])
			self.au_data={"data":datalst}

		if auto: # this a flag that tells the eulerxplorer to search for refinement data and automatically add elements to the inspector, if so
			self.gen_refinement_data()
		
		EM3DSymModel.__init__(self, gl_widget, eulerfilename=file_name)
		Animator.__init__(self)
		self.height_scale = 8.0 # This is a value used in EM3DSymModel which scales the height of the displayed cylinders - I made it 8 because it seemed fine. The user can change it anyhow
		self.projection_file = None  # This is a string - the name of the projection images file
		self.average_file = None # This is a string - the name of the class averages file
		self.proj_class_viewer = None # This will be an EMImageMXWidget that shows the class and/or projection
		self.particle_viewer = None  # This will be an EMImageMXWidget that shows the particles in a class
		self.clsdb = None # I think this will become redundant - it used to be the old database that stores which particles are in a class, but now that's stored in the header
		self.particle_file = None # This will be a string - the name of the file that has the particle files in it. This might be made redundant with the new approach
		self.alignment_file = None # This will be a string - the name of the file containing the alignment parameters - this is essential if you we want to show the aligned particles
		self.refine_dir = None # This will be a string - the name of the current refinement directory that is being studied
		self.dx = None # This is an EMData object storing the x shifts of the alignments for all particles. Generated by e2classaverage
		self.dy = None # This is an EMData object storing the y shifts of the alignments for all particles. Generated by e2classaverage
		self.da = None# This is an EMData object storing the angle of the alignments for all particles. Generated by e2classaverage
		self.dflip = None # This is an EMData object storing whether or not tthe alignment involved a flip, for all particles. Generated by e2classaverage
		self.classes = None # This is an EMData object storing which class(es) a particle belongs to. Generated by e2classaverage
		self.inclusions = None # This is and EMDAta storing a boolean that indicates the particle was actually included in the final average. Generated by e2classaverage

		self.average = None # This the class average itself, an EMData object
		self.projection = None # This is the projection itelse, an EMData object
		self.class_idx = None # This is the idx of the current class being studied in the interface

		self.previous_len = -1 # To keep track of the number of class averages that were previously viewable. This helps to make sure we can switch to the same class average in the context of a different refinement iteration
		self.mirror_eulers = False
		if sparse_mode:
			self.mirror_eulers = True # If True the drawn Eulers are are also rendered on the opposite side of the sphere - see EM3DSymModel.make_sym_dl_lis

		# Grab the symmetry from the workflow database if possible
		sym = "c1"
		if js_check_dict("refine_01/0_refine_parms.json"):
			try: sym = str(js_open_dict("refine_01/0_refine_parms.json")["sym"])
			except: pass

		# Have to tell the EM3DSymModel that there is a new sym
		self.set_symmetry(sym)

		# this object will have
		if self.au_data != None:
			combo_entries = self.au_data.keys()
			combo_entries.sort()
			combo_entries.reverse()

			if len(combo_entries) > 0:
				au = combo_entries[0]
				cls = self.au_data[au][0][0]
				self.au_selected(au,cls)
				self.mirror_eulers = True

		self.init_lock = False
		self.force_update=True # Force a display udpdate in EMImage3DSymModule

		QtCore.QObject.connect(self, QtCore.SIGNAL("point_selected"), self.au_point_selected)

	def __del__(self):
		EM3DSymModel.__del__(self) # this is here for documentation purposes - beware that the del function is important

	def initializeGL(self):
		glEnable(GL_NORMALIZE)

	def generate_current_display_list(self,force=False):
		'''
		Redefinition of EMImage3DSymModule.generate_current_display_list

		'''
		if self.init_lock: return 0
		if self.au_data == None or len(self.au_data) == 0:
			EM3DSymModel.generate_current_display_list(self,force)

		self.init_basic_shapes()
		if self.nomirror == True : val = 0
		else: val = 1
		self.trace_great_arcs(self.sym_object.get_asym_unit_points(val))
		self.trace_great_triangles(val)

		self.eulers = self.specified_eulers
		if self.eulers == None:	return 0

#		if not self.colors_specified: self.point_colors = []
#		else: self.point_colors = self.specified_colors
#		self.points = []
#		for i in self.eulers:
#			p = i.transpose()*Vec3f(0,0,self.radius)
#			self.points.append(p)
#			if not self.colors_specified: self.point_colors.append((0.34615, 0.3143, 0.0903,1))

		self.make_sym_dl_list(self.eulers)
		return 1
	def get_data_dims(self):
		return (2*self.radius,2*self.radius,2*self.radius)

	def width(self): return 2*self.radius
	def height(self): return 2*self.radius
	def depth(self): return 2*self.radius

	def gen_refinement_data(self):
		dirs,files = get_files_and_directories()

		dirs.sort()
		for i in range(len(dirs)-1,-1,-1):
			if dirs[i][:7] != "refine_" and dirs[i][:6]!="multi_" and dirs[i][:11]!="multinoali_":
				dirs.pop(i)
			else:
				try: int(dirs[i][7:])
				except: dirs.pop(i)

		self.dirs = dirs
		print(dirs)

		self.au_data = {}
		for dir in self.dirs:
			d = self.check_refine_db_dir(dir)
			if len(d) != 0 and len(d[dir]) != 0: self.au_data.update(d)

	def check_refine_db_dir(self,dir,s1="classes",s2=None,s3="cls_result",s4="threed",s5="projections"):
		# s2 used to be class_indices
		names = [s1,s2,s3,s4,s5]
		data = {}
		data[dir] = []
		register_js_name = "{}/0_refine_parms.json".format(dir)

		files=os.listdir(dir)
		
		try:
			nums=[int(i[7:9]) for i in files if "threed" in i and "even" not in i and "odd" not in i]
			maxnum=max(nums)
		except :
			print("Nothing in ",dir)
			return {}

		for i in xrange(1,maxnum+1):
			exte="_{:02d}_even.hdf".format(i)
			exto="_{:02d}_odd.hdf".format(i)
			data[dir].append([sadd(dir,s1,exte),sadd(dir,s2,exte),sadd(dir,s3,exte),sadd(dir,s4,exte),sadd(dir,s5,exte)])
			data[dir].append([sadd(dir,s1,exto),sadd(dir,s2,exto),sadd(dir,s3,exto),sadd(dir,s4,exto),sadd(dir,s5,exto)])

		return data

	def set_projection_file(self,projection_file): self.projection_file = projection_file
	def get_inspector(self):
		if not self.inspector :
			if (self.au_data == None or len(self.au_data) == 0) and self.mirror_eulers == False: #self.mirror_eulers thing is a little bit of a hack, it's tied to the sparse_mode flag in the init function, which is used by euler_display in EMAN2.py
				self.inspector=EMAsymmetricUnitInspector(self,True,True)
			else:
				self.inspector=EMAsymmetricUnitInspector(self)
			QtCore.QObject.connect(self.inspector,QtCore.SIGNAL("au_selected"),self.au_selected)
		return self.inspector


	def au_selected(self,refine_dir,cls):
		self.refine_dir = refine_dir
		get_application().setOverrideCursor(Qt.BusyCursor)
		data = []
		for d in self.au_data[refine_dir]:
			if d[0] == cls:
				data = d;
				break

		if len(data) == 0:
			error("error, no data for %s %s, returning" %(refine_dir,cls))
#			print "error, no data for",au,cls,"returning"
			self.events_handlers["inspect"].reset()
			get_application().setOverrideCursor(Qt.ArrowCursor)
			return
		
		if not self.readfrom:
			try :
				self.particle_file=js_open_dict(refine_dir+"/0_refine_parms.json")["input"]
			except:
				error("No data in "+refine_dir )
				self.events_handlers["inspect"].reset()
				get_application().setOverrideCursor(Qt.ArrowCursor)
				return

		self.average_file = cls
		self.projection_file = data[4]
		self.alignment_file = data[2]
		self.clsdb = data[1]

		self.dx = None
		self.dy = None
		self.da = None
		self.dflip = None
		self.classes = None

		eulers = get_eulers_from(self.average_file)
		#s = Symmetries.get("d7")
		#eulers = s.gen_orientations("rand",{"n":EMUtil.get_image_count(self.average_file)})

		self.specify_eulers(eulers)
		#from emimagemx import EMDataListCache
		#a = EMData.read_images(self.average_file)
		#a = [test_image() for i in range(EMUtil.get_image_count(self.average_file))]
		#print len(a),len(eulers)
		#b = [a[i].set_attr("xform.projection",eulers[i]) for i in range(len(eulers))]
		#b = [a[i].set_attr("ptcl_repr",1) for i in range(len(eulers))]

		self.set_emdata_list_as_data(EMLightWeightParticleCache.from_file(self.average_file),"ptcl_repr")
		#self.set_emdata_list_as_data(EMDataListCache(self.average_file),"ptcl_repr")
#		self.set_emdata_list_as_data(a,"ptcl_repr")
		self.force_update = True
		self.au_point_selected(self.class_idx,None)
		# if we have the same number of Eulers we can update everything
#		if self.previous_len == len(eulers) : self.events_handlers["inspect"].repeat_event()
#		else:self.events_handlers["inspect"].reset()
		self.previous_len = len(eulers)
		if not self.init_lock:self.updateGL()
		get_application().setOverrideCursor(Qt.ArrowCursor)

	def __get_file_headers(self,filename):
		headers = []
		n = EMUtil.get_image_count(filename)
		for i in range(n):
			e = EMData()
			e.read_image(filename,i,True)
			headers.append(e)
		return headers

	def au_point_selected(self,i,event=None):
		if self.readfrom:
			return
		if i == None:
			if event != None and event.modifiers()&Qt.ShiftModifier:
				if self.special_euler != None:
					self.special_euler = None
					if not self.init_lock:self.regen_dl()
			return
#		self.arc_anim_points = None
		self.projection = None
		if self.euler_data:
#			db = db_open_dict(self.average_file)
#			a = db.get(i)
#			print a["nx"]
#			print self.average_file,i
#			self.average = EMData(self.average_file,i)
#			self.average["nx"]
			self.average = self.euler_data[i]#
			self.projection = EMData(self.projection_file,self.average.get_attr("projection_image_idx"))
			self.average.process_inplace("normalize.toimage",{"to":self.projection})
			try:
				self.class_idx = self.average.get_attr("projection_image_idx")
				print("%d (%d)"%(self.class_idx,self.average["ptcl_repr"]))
			except:
				self.class_idx = -1
		else: return

		#if self.projection  == None and self.average == None: return
		first = False
		if self.proj_class_viewer == None:
			first = True
			self.proj_class_viewer = EMImageMXWidget(data=None,application=get_application())
#			self.proj_class_viewer = EMImage2DWidget(image=None,application=get_application())
			QtCore.QObject.connect(self.proj_class_viewer,QtCore.SIGNAL("module_closed"),self.on_mx_view_closed)
#			self.proj_class_viewer.set_mouse_mode("App" )
			QtCore.QObject.connect(self.proj_class_viewer,QtCore.SIGNAL("mx_image_selected"), self.mx_image_selected)
			get_application().show_specific(self.proj_class_viewer)

			self.proj_class_single = EMImage2DWidget(image=None,application=get_application())
			QtCore.QObject.connect(self.proj_class_single,QtCore.SIGNAL("module_closed"),self.on_mx_view_closed)
#			QtCore.QObject.connect(self.proj_class_single,QtCore.SIGNAL("mx_image_selected"), self.mx_image_selected)
			get_application().show_specific(self.proj_class_single)

		disp = []
		if self.projection != None: disp.append(self.projection)
		if self.average != None and self.projection!=None:
			# ok, this really should be put into its own processor
			#dataf = self.projection.do_fft()
			#apix=self.projection["apix_x"]
			#curve = dataf.calc_radial_dist(dataf["ny"], 0, 0.5,True)
			#curve=[i/(dataf["nx"]*dataf["ny"])**2 for i in curve]
			#xcurve=[i/(apix*2.0*dataf["ny"]) for i in range(len(curve))]
			#xyd=XYData()
			#xyd.set_xy_list(xcurve,curve)
			#filt=self.average.process("filter.setstrucfac",{"apix":apix,"strucfac":xyd})
			#filt.process_inplace("normalize.toimage",{"to":self.average})
			self.projection["apix_x"]=self.average["apix_x"]
			self.projection["apix_y"]=self.average["apix_y"]
			self.projection["apix_z"]=self.average["apix_z"]
			filt=self.projection.process("threshold.notzero")
			filt.mult(self.average)
			filt.process_inplace("filter.matchto",{"to":self.projection})

			disp.append(filt)

		if self.average!=None:
			disp.append(self.average)

		self.proj_class_viewer.set_data(disp)
		self.proj_class_single.set_data(disp)

		self.proj_class_viewer.updateGL()
		self.proj_class_single.updateGL()
		if self.particle_viewer != None:
			self.mx_image_selected(None,None)
		if first: self.proj_class_viewer.optimally_resize()

		if i != self.special_euler:
			self.special_euler = i
			self.force_update = True

		if not self.init_lock: self.updateGL()


	def on_mx_view_closed(self):
		self.proj_class_viewer = None
		self.proj_class_single = None

	def on_particle_mx_view_closed(self):
		self.particle_viewer = None

	def animation_done_event(self,animation):
		pass

	def alignment_time_animation(self,transforms):
		if len(transforms) < 2: return
		animation = OrientationListAnimation(self,transforms,self.radius)
		self.register_animatable(animation)

	def particle_selected(self,event,lc):
		if lc != None:
			d = lc[3]
			ptcl_idx = d["Img #"]
			data = self.au_data[self.refine_dir]
			prj = []
			cls_result = []
			for l in data:
				for s in l:
					stag = base_name(s)

					if len(stag) > 11 and stag[:11] == "projections":
						prj.append(s)
					elif len(stag) > 10 and stag[:10] == "cls_result":
						cls_result.append(s)

			transforms = []
			if len(prj) != len(cls_result): RunTimeError("The number of cls_result files does not match the number of projection files?")

			e = EMData()
			for i,cr in enumerate(cls_result):
				r = Region(0,ptcl_idx,1,1)
				e.read_image(cr,0,False,r)
				p = int(e.get(0))
				e.read_image(prj[i],p,True)
				transforms.append(e["xform.projection"])

			self.alignment_time_animation(transforms)

	def mx_image_selected(self,event,lc):
#		self.arc_anim_points = None
		get_application().setOverrideCursor(Qt.BusyCursor)
		if lc != None: self.sel = lc[0]

		if self.average != None:
			included = []
			if self.average.has_attr("class_ptcl_idxs"):
				included = self.average["class_ptcl_idxs"]
			excluded = []
			if self.average.has_attr("exc_class_ptcl_idxs"):
				excluded = self.average["exc_class_ptcl_idxs"]

			all = included + excluded
			#all.sort()

			bdata = []
			data = []
			idx_included = []
			running_idx = 0
			from emimagemx import ApplyAttribute
			for val in included:
				bdata.append([self.particle_file,val,[ApplyAttribute("Img #",val)]])
				idx_included.append(running_idx)
				running_idx += 1

			idx_excluded = []
			for val in excluded:
				bdata.append([self.particle_file,val,[ApplyAttribute("Img #",val)]])
				idx_excluded.append(running_idx)
				running_idx += 1

			data = EMLightWeightParticleCache(bdata)

			first = False
			if self.particle_viewer == None:
				first = True
				self.particle_viewer = EMImageMXWidget(data=None,application=get_application())
				self.particle_viewer.set_mouse_mode("App" )
				QtCore.QObject.connect(self.particle_viewer,QtCore.SIGNAL("module_closed"),self.on_particle_mx_view_closed)
				QtCore.QObject.connect(self.particle_viewer,QtCore.SIGNAL("mx_image_selected"), self.particle_selected)
				get_application().show_specific(self.particle_viewer)


			self.check_images_in_memory()

			if self.sel== 0 or self.alignment_file == None:
				self.particle_viewer.set_data(data)
			else:

				for i,[name,idx,f] in enumerate(bdata):
					index = -1
					if self.classes.get_xsize() == 1:
						index = 0 # just assume it's the first one - this is potentially fatal assumption, but in obscure situations only
					else:
						for j in range(self.classes.get_xsize()):
							if int(self.classes.get(j,idx)) == self.class_idx:
								index = j
								break
					if index == -1:
						print("couldn't find")
						get_application().setOverrideCursor(Qt.ArrowCursor)
						return

					x = self.dx.get(index,idx)
					y = self.dy.get(index,idx)
					a = self.da.get(index,idx)
					m = self.dflip.get(index,idx)

					t = Transform({"type":"2d","alpha":a,"mirror":int(m)})
					t.set_trans(x,y)
					from emimagemx import ApplyTransform
					f.append(ApplyTransform(t))
					#data[i].transform(t)
				self.particle_viewer.set_data(data)


			if first:
				self.particle_viewer.updateGL()
				self.particle_viewer.optimally_resize()

			self.particle_viewer.clear_sets(False)
			self.particle_viewer.enable_set("Excluded",idx_excluded,True,False)
			self.particle_viewer.enable_set("Included",idx_included,False,False)
			self.particle_viewer.updateGL()

			get_application().setOverrideCursor(Qt.ArrowCursor)

			self.updateGL()

	def check_images_in_memory(self):
		if self.alignment_file != None:
			if self.dx == None:
				self.dx = EMData(self.alignment_file,2)
			if self.dy == None:
				self.dy = EMData(self.alignment_file,3)
			if self.da == None:
				self.da = EMData(self.alignment_file,4)
			if self.dflip == None:
				self.dflip  = EMData(self.alignment_file,5)
			if self.classes == None:
				self.classes  = EMData(self.alignment_file,0)
			if self.inclusions == None:
				self.inclusions  = EMData(self.alignment_file,1)


	def set_events_mode(self,mode):
		if not mode in self.events_mode_list:
			print("error, unknown events mode", mode)
			return

		else:
			self.events_mode = mode

	def closeEvent(self,event):
		if self.inspector !=None: self.inspector.close()
		if self.proj_class_viewer !=None: self.proj_class_viewer.close()
		if self.proj_class_single !=None: self.proj_class_single.close()
		if self.particle_viewer != None: self.particle_viewer.close()
		get_application().close_specific(self)
		self.emit(QtCore.SIGNAL("module_closed")) # this signal is