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)
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)
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
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