class Data_Reducer(QWidget): """ This widget is developed to reduce on the fly 2D SAXS data to azimuthally averaged 1D SAXS data """ def __init__(self,poniFile=None,dataFile=None, darkFile=None, maskFile=None,extractedFolder='/tmp', npt=1000, transmission_corr=True, azimuthRange=(-180.0,180.0), parent=None): """ poniFile is the calibration file obtained after Q-calibration """ QWidget.__init__(self,parent) self.layout=QGridLayout(self) self.setup_dict=json.load(open('./SetupData/reducer_setup.txt','r')) if poniFile is not None: self.poniFile=poniFile else: self.poniFile=self.setup_dict['poniFile'] if maskFile is not None: self.maskFile=maskFile else: self.maskFile=self.setup_dict['maskFile'] self.dataFile=dataFile if darkFile is None: self.dark_corrected=False self.darkFile='' else: self.darkFile=darkFile self.dark_corrected=True self.curDir=os.getcwd() self.extractedFolder=extractedFolder self.npt=npt self.set_externally=False self.transmission_corr=transmission_corr #ai=AIWidget() #self.layout.addWidget(ai) self.azimuthRange=azimuthRange self.create_UI() if os.path.exists(self.poniFile): self.openPoniFile(file=self.poniFile) if os.path.exists(self.maskFile): self.openMaskFile(file=self.maskFile) def create_UI(self): """ Creates the widget user interface """ row=0 col=0 dataFileLabel=QLabel('Data file') self.layout.addWidget(dataFileLabel,row,col) col+=1 self.dataFileLineEdit=QLineEdit(self.dataFile) self.layout.addWidget(self.dataFileLineEdit,row,col,1,2) col+=2 self.openDataPushButton=QPushButton('Select') self.openDataPushButton.clicked.connect(self.openDataFiles) self.layout.addWidget(self.openDataPushButton,row,col) col+=1 self.reducePushButton=QPushButton('Reduce data') self.reducePushButton.clicked.connect(self.reduce_multiple) self.layout.addWidget(self.reducePushButton,row,col,1,1) row+=1 col=0 darkFileLabel=QLabel('Dark file') self.layout.addWidget(darkFileLabel,row,col) col+=1 self.darkFileLineEdit=QLineEdit(self.darkFile) self.layout.addWidget(self.darkFileLineEdit,row,col,1,2) col+=2 self.openDarkPushButton=QPushButton('Select') self.openDarkPushButton.clicked.connect(self.openDarkFile) self.layout.addWidget(self.openDarkPushButton,row,col) col+=1 self.darkCheckBox=QCheckBox('Dark Correction') self.layout.addWidget(self.darkCheckBox,row,col) row+=1 col=0 poniFileLabel=QLabel('Calibration file') self.layout.addWidget(poniFileLabel,row,col) col+=1 self.poniFileLineEdit=QLineEdit(self.poniFile) self.layout.addWidget(self.poniFileLineEdit,row,col,1,2) col+=2 self.openPoniPushButton=QPushButton('Select') self.openPoniPushButton.clicked.connect(lambda x: self.openPoniFile(file=None)) self.layout.addWidget(self.openPoniPushButton,row,col) col+=1 self.calibratePushButton=QPushButton('Calibrate') self.calibratePushButton.clicked.connect(self.calibrate) self.layout.addWidget(self.calibratePushButton,row,col) row+=1 col=0 maskFileLabel=QLabel('Mask file') self.layout.addWidget(maskFileLabel,row,col) col+=1 self.maskFileLineEdit=QLineEdit(self.maskFile) self.maskFileLineEdit.returnPressed.connect(self.maskFileChanged) self.layout.addWidget(self.maskFileLineEdit,row,col,1,2) col+=2 self.openMaskPushButton=QPushButton('Select') self.openMaskPushButton.clicked.connect(lambda x: self.openMaskFile(file=None)) self.layout.addWidget(self.openMaskPushButton,row,col) col+=1 self.createMaskPushButton=QPushButton('Create mask') self.createMaskPushButton.clicked.connect(self.createMask) self.layout.addWidget(self.createMaskPushButton,row,col) row+=1 col=0 extractedFolderLabel=QLabel('Extracted folder') self.layout.addWidget(extractedFolderLabel,row,col) col+=1 self.extractedFolderLineEdit=QLineEdit() self.layout.addWidget(self.extractedFolderLineEdit,row,col,1,2) col+=2 self.extractedFolderPushButton=QPushButton('Select') self.extractedFolderPushButton.clicked.connect(self.openFolder) self.layout.addWidget(self.extractedFolderPushButton,row,col) row+=1 col=0 radialPointsLabel=QLabel('Radial Points') self.layout.addWidget(radialPointsLabel,row,col) col+=1 self.radialPointsLineEdit=QLineEdit(str(self.npt)) self.radialPointsLineEdit.returnPressed.connect(self.nptChanged) self.layout.addWidget(self.radialPointsLineEdit,row,col) col+=1 azimuthRangeLabel=QLabel('Azimuthal Range (min:max)') self.layout.addWidget(azimuthRangeLabel,row,col) col+=1 self.azimuthRangeLineEdit=QLineEdit('%.2f:%.2f'%(self.azimuthRange[0],self.azimuthRange[1])) self.azimuthRangeLineEdit.returnPressed.connect(self.azimuthRangeChanged) self.layout.addWidget(self.azimuthRangeLineEdit,row,col) col+=1 self.transCorrCheckBox=QCheckBox('Transmission Correction') self.transCorrCheckBox.setTristate(False) self.transCorrCheckBox.setChecked(self.transmission_corr) self.layout.addWidget(self.transCorrCheckBox) row+=1 col=0 progressLabel=QLabel('Status') self.layout.addWidget(progressLabel,row,col,1,1) col+=1 self.progressBar=QProgressBar() self.layout.addWidget(self.progressBar,row,col,1,1) col+=1 self.statusLabel=QLabel('Idle') self.layout.addWidget(self.statusLabel,row,col,1,1) col+=1 normLabel=QLabel('Normalized by') self.normComboBox=QComboBox() self.normComboBox.addItems(['BSDiode','Image sum','Monitor']) self.layout.addWidget(normLabel,row,col,1,1) col+=1 self.layout.addWidget(self.normComboBox,row,col,1,1) row+=1 col=0 self.tabWidget=QTabWidget(self) self.layout.addWidget(self.tabWidget,row,col,20,5) self.imageWidget=Image_Widget(zeros((100,100))) imgNumberLabel=QLabel('Image number') self.imgNumberSpinBox=QSpinBox() self.imgNumberSpinBox.setSingleStep(1) self.imageWidget.imageLayout.addWidget(imgNumberLabel,row=2,col=1) self.imageWidget.imageLayout.addWidget(self.imgNumberSpinBox,row=2,col=2) self.imageView=self.imageWidget.imageView.getView() self.plotWidget=PlotWidget() self.plotWidget.setXLabel('Q, Å<sup>-1</sup>',fontsize=5) self.plotWidget.setYLabel('Intensity',fontsize=5) self.tabWidget.addTab(self.plotWidget,'Reduced 1D-data') self.tabWidget.addTab(self.imageWidget,'Masked 2D-data') def createMask(self): """ Opens a mask-widget to create mask file """ fname=str(QFileDialog.getOpenFileName(self,'Select an image file', directory=self.curDir,filter='Image file (*.edf *.tif)')[0]) if fname is not None or fname!='': img=fb.open(fname).data self.maskWidget=MaskWidget(img) self.maskWidget.saveMaskPushButton.clicked.disconnect() self.maskWidget.saveMaskPushButton.clicked.connect(self.save_mask) self.maskWidget.show() else: QMessageBox.warning(self,'File error','Please import a data file first for creating the mask',QMessageBox.Ok) def maskFileChanged(self): """ Changes the mask file """ maskFile=str(self.maskFileLineEdit.text()) if str(maskFile)=='': self.maskFile=None elif os.path.exists(maskFile): self.maskFile=maskFile else: self.maskFile=None def save_mask(self): """ Saves the entire mask combining all the shape ROIs """ fname=str(QFileDialog.getSaveFileName(filter='Mask Files (*.msk)')[0]) name,extn=os.path.splitext(fname) if extn=='': fname=name+'.msk' elif extn!='.msk': QMessageBox.warning(self,'File extension error','Please donot provide file extension other than ".msk". Thank you!') return else: tmpfile=fb.edfimage.EdfImage(data=self.maskWidget.full_mask_data.T,header=None) tmpfile.save(fname) self.maskFile=fname self.maskFileLineEdit.setText(self.maskFile) def calibrate(self): """ Opens a calibartion widget to create calibration file """ fname=str(QFileDialog.getOpenFileName(self,'Select calibration image',directory=self.curDir, filter='Calibration image (*.edf *.tif)')[0]) if fname is not None: img=fb.open(fname).data pixel1=79.0 pixel2=79.0 self.calWidget=CalibrationWidget(img,pixel1,pixel2) self.calWidget.saveCalibrationPushButton.clicked.disconnect() self.calWidget.saveCalibrationPushButton.clicked.connect(self.save_calibration) self.calWidget.show() else: QMessageBox.warning(self,'File error','Please import a data file first for creating the calibration file',QMessageBox.Ok) def save_calibration(self): fname=str(QFileDialog.getSaveFileName(self,'Calibration file',directory=self.curDir,filter='Clibration files (*.poni)')[0]) tfname=os.path.splitext(fname)[0]+'.poni' self.calWidget.applyPyFAI() self.calWidget.geo.save(tfname) self.poniFile=tfname self.poniFileLineEdit.setText(self.poniFile) def openPoniFile(self,file=None): """ Select and imports the calibration file """ if file is None: self.poniFile=QFileDialog.getOpenFileName(self,'Select calibration file',directory=self.curDir,filter='Calibration file (*.poni)')[0] self.poniFileLineEdit.setText(self.poniFile) else: self.poniFile=file if os.path.exists(self.poniFile): self.setup_dict['poniFile']=self.poniFile json.dump(self.setup_dict,open('./SetupData/reducer_setup.txt','w')) fh=open(self.poniFile,'r') lines=fh.readlines() self.calib_data={} for line in lines: if line[0]!='#': key,val=line.split(': ') self.calib_data[key]=float(val) self.dist=self.calib_data['Distance'] self.pixel1=self.calib_data['PixelSize1'] self.pixel2=self.calib_data['PixelSize2'] self.poni1=self.calib_data['Poni1'] self.poni2=self.calib_data['Poni2'] self.rot1=self.calib_data['Rot1'] self.rot2=self.calib_data['Rot2'] self.rot3=self.calib_data['Rot3'] self.wavelength=self.calib_data['Wavelength'] self.ai=AzimuthalIntegrator(dist=self.dist,poni1=self.poni1,poni2=self.poni2,pixel1=self.pixel1,pixel2=self.pixel2,rot1=self.rot1,rot2=self.rot2,rot3=self.rot3,wavelength=self.wavelength) #pos=[self.poni2/self.pixel2,self.poni1/self.pixel1] #self.roi=cake(pos,movable=False) #self.roi.sigRegionChangeStarted.connect(self.endAngleChanged) #self.imageView.addItem(self.roi) else: QMessageBox.warning(self,'File error','The calibration file '+self.poniFile+' doesnot exists.',QMessageBox.Ok) def endAngleChanged(self,evt): print(evt.pos()) def nptChanged(self): """ Changes the number of radial points """ try: self.npt=int(self.radialPointsLineEdit.text()) except: QMessageBox.warning(self,'Value error', 'Please input positive integers only.',QMessageBox.Ok) def azimuthRangeChanged(self): """ Changes the azimuth angular range """ try: self.azimuthRange=tuple(map(float, self.azimuthRangeLineEdit.text().split(':'))) except: QMessageBox.warning(self,'Value error','Please input min:max angles in floating point numbers',QMessageBox.Ok) def openDataFile(self): """ Select and imports one data file """ dataFile=QFileDialog.getOpenFileName(self,'Select data file',directory=self.curDir,filter='Data file (*.edf *.tif)')[0] if dataFile!='': self.dataFile=dataFile self.curDir=os.path.dirname(self.dataFile) self.dataFileLineEdit.setText(self.dataFile) self.data2d=fb.open(self.dataFile).data if self.darkFile is not None: self.applyDark() if self.maskFile is not None: self.applyMask() self.imageWidget.setImage(self.data2d,transpose=True) self.tabWidget.setCurrentWidget(self.imageWidget) if not self.set_externally: self.extractedFolder=os.path.join(self.curDir,'extracted_pyFAI') if not os.path.exists(self.extractedFolder): os.makedirs(self.extractedFolder) def openDataFiles(self): """ Selects and imports multiple data files """ self.dataFiles=QFileDialog.getOpenFileNames(self,'Select data files', directory=self.curDir,filter='Data files (*.edf *.tif)')[0] if len(self.dataFiles)!=0: self.imgNumberSpinBox.valueChanged.connect(self.imageChanged) self.imgNumberSpinBox.setMinimum(0) self.imgNumberSpinBox.setMaximum(len(self.dataFiles)-1) self.dataFileLineEdit.setText(str(self.dataFiles)) self.curDir=os.path.dirname(self.dataFiles[0]) self.extractedFolder=os.path.join(self.curDir,'extracted_pyFAI') if not os.path.exists(self.extractedFolder): os.makedirs(self.extractedFolder) self.extractedFolderLineEdit.setText(self.extractedFolder) self.imgNumberSpinBox.setValue(0) self.imageChanged() def imageChanged(self): self.data2d=fb.open(self.dataFiles[self.imgNumberSpinBox.value()]).data if self.darkFile is not None: self.applyDark() if self.maskFile is not None: self.applyMask() self.imageWidget.setImage(self.data2d,transpose=True) def applyDark(self): if not self.dark_corrected and self.darkFile!='': self.dark2d=fb.open(self.darkFile).data self.data2d=self.data2d-self.dark2d self.dark_corrected=True def applyMask(self): self.mask2d=fb.open(self.maskFile).data self.data2d=self.data2d*(1+self.mask2d)/2.0 self.mask_applied=True def openDarkFile(self): """ Select and imports the dark file """ self.darkFile=QFileDialog.getOpenFileName(self,'Select dark file',directory=self.curDir,filter='Dark file (*.edf)')[0] if self.darkFile!='': self.dark_corrected=False self.darkFileLineEdit.setText(self.darkFile) if self.dataFile is not None: self.data2d=fb.open(self.dataFile).data self.applyDark() def openMaskFile(self,file=None): """ Select and imports the Mask file """ if file is None: self.maskFile=QFileDialog.getOpenFileName(self,'Select mask file',directory=self.curDir,filter='Mask file (*.msk)')[0] else: self.maskFile=file if self.maskFile!='': self.mask_applied=False if os.path.exists(self.maskFile): self.curDir=os.path.dirname(self.maskFile) self.maskFileLineEdit.setText(self.maskFile) self.setup_dict['maskFile']=self.maskFile self.setup_dict['poniFile']=self.poniFile json.dump(self.setup_dict,open('./SetupData/reducer_setup.txt','w')) else: self.openMaskFile(file=None) if self.dataFile is not None: self.applyMask() else: self.maskFile=None self.maskFileLineEdit.clear() def openFolder(self): """ Select the folder to save the reduce data """ self.extractedFolder=QFileDialog.getExistingDirectory(self,'Select extracted directory',directory=self.curDir) if self.extractedFolder!='': self.extractedFolderLineEdit.setText(self.extractedFolder) self.set_externally=True def reduceData(self): """ Reduces the 2d data to 1d data """ print('Iloveu', self.darkFile) if (self.dataFile is not None) and (os.path.exists(self.dataFile)): if (self.poniFile is not None) and (os.path.exists(self.poniFile)): # self.statusLabel.setText('Busy') # self.progressBar.setRange(0, 0) imageData=fb.open(self.dataFile) #self.data2d=imageData.data #if self.maskFile is not None: # self.applyMask() #self.imageWidget.setImage(self.data2d,transpose=True) #self.tabWidget.setCurrentWidget(self.imageWidget) self.header=imageData.header try: self.ai.set_wavelength(float(self.header['Wavelength'])*1e-10) except: self.ai.set_wavelength(self.wavelength) print('Iloveu',self.darkFile) if os.path.exists(self.dataFile.split('.')[0]+'_dark.edf') and self.darkCheckBox.isChecked(): self.darkFile=self.dataFile.split('.')[0]+'_dark.edf' dark=fb.open(self.darkFile) self.darkFileLineEdit.setText(self.darkFile) imageDark=dark.data self.header['BSDiode_corr']=max([1.0,(float(imageData.header['BSDiode'])-float(dark.header['BSDiode']))]) self.header['Monitor_corr']=max([1.0,(float(imageData.header['Monitor'])-float(dark.header['Monitor']))]) print("Dark File read from existing dark files") elif self.darkFile is not None and self.darkFile!='' and self.darkCheckBox.isChecked(): dark=fb.open(self.darkFile) imageDark=dark.data self.header['BSDiode_corr']=max([1.0,(float(imageData.header['BSDiode'])-float(dark.header['BSDiode']))]) self.header['Monitor_corr']=max([1.0,(float(imageData.header['Monitor'])-float(dark.header['Monitor']))]) print("Dark File from memory subtracted") else: imageDark=None try: self.header['BSDiode_corr']=float(imageData.header['BSDiode']) self.header['Monitor_corr']=float(imageData.header['Monitor']) except: self.transCorrCheckBox.setCheckState(Qt.Unchecked) print("No dark correction done") if self.transCorrCheckBox.isChecked(): if str(self.normComboBox.currentText())=='BSDiode': norm_factor=self.header['BSDiode_corr']#/float(self.header['count_time']) elif str(self.normComboBox.currentText())=='Monitor': norm_factor=self.header['Monitor_corr'] else: norm_factor=sum(imageData.data) else: norm_factor=1.0 if self.maskFile is not None: imageMask=fb.open(self.maskFile).data else: imageMask=None print(self.maskFile) # QApplication.processEvents() #print(self.azimuthRange) self.q,self.I,self.Ierr=self.ai.integrate1d(imageData.data,self.npt,error_model='poisson',mask=imageMask,dark=imageDark,unit='q_A^-1',normalization_factor=norm_factor,azimuth_range=self.azimuthRange) self.plotWidget.add_data(self.q,self.I,yerr=self.Ierr,name='Reduced data') self.plotWidget.setTitle(self.dataFile,fontsize=2) # self.progressBar.setRange(0,100) # self.progressBar.setValue(100) # self.statusLabel.setText('Idle') # QApplication.processEvents() self.saveData() self.tabWidget.setCurrentWidget(self.plotWidget) else: QMessageBox.warning(self,'Calibration File Error','Data reduction failed because either no calibration file provided or the provided file or path do not exists',QMessageBox.Ok) else: QMessageBox.warning(self,'Data File Error','No data file provided', QMessageBox.Ok) def reduce_multiple(self): """ Reduce multiple files """ #try: i=0 self.progressBar.setRange(0,len(self.dataFiles)) self.progressBar.setValue(i) self.statusLabel.setText('Busy') for file in self.dataFiles: self.dataFile=file QApplication.processEvents() self.reduceData() i=i+1 self.progressBar.setValue(i) QApplication.processEvents() self.statusLabel.setText('Idle') #except: # QMessageBox.warning(self,'File error','No data files to reduce',QMessageBox.Ok) def saveData(self): """ saves the extracted data into a file """ if not os.path.exists(self.extractedFolder): os.makedirs(self.extractedFolder) filename=os.path.join(self.extractedFolder,os.path.splitext(os.path.basename(self.dataFile))[0]+'.txt') headers='File extracted on '+time.asctime()+'\n' headers='Files used for extraction are:\n' headers+='Data file: '+self.dataFile+'\n' if self.darkFile is not None: headers+='Dark file: '+self.darkFile+'\n' else: headers+='Dark file: None\n' headers+='Poni file: '+self.poniFile+'\n' if self.maskFile is not None: headers+='mask file: '+self.maskFile+'\n' else: headers+='mask file: None\n' for key in self.header.keys(): headers+=key+'='+str(self.header[key])+'\n' headers+='Q (A^-1)\t\tIntensity\t\tIntensity_err' data=vstack((self.q,self.I,self.Ierr)).T savetxt(filename,data,header=headers,comments='#')
class Data_Dialog(QDialog): def __init__(self, fname=None, data=None, comment='#', skiprows=0, delimiter=' ', expressions={}, autoupdate=False, parent=None, matplotlib=False, plotIndex=None, colors=None): QDialog.__init__(self, parent=parent) loadUi('UI_Forms/Data_Dialog.ui', self) self.colcycler = cycle(['r', 'g', 'b', 'c', 'm', 'y', 'w']) self.plotWidget = PlotWidget(parent=self, matplotlib=matplotlib) self.plotTab = self.tabWidget.addTab(self.plotWidget, 'Plots') self.tabWidget.setCurrentIndex(0) self.show() self.fileWatcher = QFileSystemWatcher() self.fileWatcher.fileChanged.connect(self.fileUpdated) self.cwd = None self.plotNum = 0 self.xlabel = [] self.ylabel = [] self.oldPlotIndex = {} self.oldColors = {} self.dataAltered = False self.expressions = expressions if data is not None: self.data = data self.autoUpdateCheckBox.setEnabled(False) elif fname is not None: self.data = self.readData(fname, comment=comment, skiprows=skiprows, delimiter=delimiter) else: self.data = None self.autoUpdateCheckBox.setEnabled(False) self.saveDataPushButton.setEnabled(False) self.addRowPushButton.setEnabled(False) self.removeRowsPushButton.setEnabled(False) self.removeColumnPushButton.setEnabled(False) if self.data is not None: self.setMeta2Table() self.setData2Table() if plotIndex is None: self.addPlots(color=None) else: self.addMultiPlots(plotIndex=plotIndex, colors=colors) self.init_signals() self.okPushButton.setAutoDefault(False) self.make_default() self.setWindowTitle('Data Dialog') self.acceptData = True #self.setWindowSize((600,400)) # if self.parentWidget() is not None: # self.addPlotPushButton.setEnabled(False) # self.removePlotPushButton.setEnabled(False) def make_default(self): self.okPushButton.setAutoDefault(False) self.closePushButton.setAutoDefault(False) self.openDataFilePushButton.setAutoDefault(False) self.saveDataPushButton.setAutoDefault(False) self.okPushButton.setDefault(False) self.closePushButton.setDefault(False) self.openDataFilePushButton.setDefault(False) self.saveDataPushButton.setDefault(False) def init_signals(self): self.closePushButton.clicked.connect(self.closeWidget) self.okPushButton.clicked.connect(self.acceptWidget) self.openDataFilePushButton.clicked.connect(self.openFile) self.autoUpdateCheckBox.stateChanged.connect(self.autoUpdate_ON_OFF) self.saveDataPushButton.clicked.connect(self.saveData) self.addPlotPushButton.clicked.connect( lambda x: self.addPlots(plotIndex=None)) self.plotSetupTableWidget.cellChanged.connect(self.updatePlotData) self.removePlotPushButton.clicked.connect(self.removePlots) self.addMetaDataPushButton.clicked.connect(self.addMetaData) self.metaDataTableWidget.itemChanged.connect(self.metaDataChanged) self.metaDataTableWidget.itemClicked.connect(self.metaDataClicked) self.metaDataTableWidget.itemSelectionChanged.connect( self.metaDataSelectionChanged) self.removeMetaDataPushButton.clicked.connect(self.removeMetaData) self.dataTableWidget.itemChanged.connect(self.dataChanged) self.editColumnPushButton.clicked.connect(self.editDataColumn) self.addColumnPushButton.clicked.connect( lambda x: self.addDataColumn(colName='Col_X')) self.removeColumnPushButton.clicked.connect(self.removeDataColumn) self.removeRowsPushButton.clicked.connect(self.removeDataRows) self.dataTableWidget.setSelection self.dataTableWidget.horizontalHeader().sortIndicatorChanged.connect( self.dataSorted) self.addRowPushButton.clicked.connect(self.addDataRow) def closeWidget(self): self.acceptData = False self.reject() def acceptWidget(self): self.acceptData = True self.accept() def addMetaData(self): """ Opens a MetaData Dialog and by accepting the dialog inputs the data to the MetaDataTable """ self.metaDialog = MetaData_Dialog() if self.metaDialog.exec_(): name, value = self.metaDialog.parNameLineEdit.text( ), self.metaDialog.parValueLineEdit.text() if name not in self.data['meta'].keys(): row = self.metaDataTableWidget.rowCount() self.metaDataTableWidget.insertRow(row) self.metaDataTableWidget.setItem(row, 0, QTableWidgetItem(name)) self.metaDataTableWidget.setItem(row, 1, QTableWidgetItem(value)) try: self.data['meta'][name] = eval(value) except: self.data['meta'][name] = value else: QMessageBox.warning( self, "Parameter Exists", "The parameter %s already exists in meta data. Please provide a different parameter name" % name, QMessageBox.Ok) self.addMetaData() def removeMetaData(self): """ Removes the selected Metadata from the table """ self.metaDataTableWidget.itemSelectionChanged.disconnect() rows = list( set([ item.row() for item in self.metaDataTableWidget.selectedItems() ])) for row in rows: key = self.metaDataTableWidget.item(row, 0).text() if key != 'col_names': del self.data['meta'][key] self.metaDataTableWidget.removeRow(row) else: QMessageBox.warning(self, 'Restricted Parameter', 'You cannot delete the parameter %s' % key, QMessageBox.Ok) self.metaDataTableWidget.itemSelectionChanged.connect( self.metaDataSelectionChanged) def metaDataChanged(self, item): """ Updates the value metadata as per the changes in the metaDataTableWidget """ row = item.row() col = item.column() key = self.metaDataTableWidget.item(row, 0).text() if col != 0: try: self.data['meta'][key] = eval(item.text()) except: self.data['meta'][key] = item.text() if self.metaDataTableWidget.item( row, 0).text() == 'col_names' and len( self.data['meta'][key]) != len( self.data['data'].columns): QMessageBox.warning( self, 'Restricted Parameter', 'Please provide same length of col_names as the number of the column of the data' ) self.data['meta'][key] = eval(self.oldMetaText) item.setText(self.oldMetaText) elif self.metaDataTableWidget.item( row, 0).text() == 'col_names' and len( self.data['meta'][key]) == len( self.data['data'].columns): self.data['data'].columns = self.data['meta'][key] self.dataTableWidget.setHorizontalHeaderLabels( self.data['meta'][key]) self.dataAltered = True self.resetPlotSetup() self.dataAltered = False else: if self.oldMetaText == 'col_names': QMessageBox.warning( self, 'Restricted Parameter', 'col_names is a restricted parameter the name of which cannot be changed', QMessageBox.Ok) item.setText(self.oldMetaText) elif item.text() not in self.data['meta'].keys(): self.data['meta'][key] = self.data['meta'][self.oldMetaText] del self.data['meta'][self.oldMetaText] else: self.metaDataTableWidget.itemChanged.disconnect() QMessageBox.warning( self, "Parameter Exists", "The parameter %s already exists in meta data. Please provide a different parameter name" % item.text(), QMessageBox.Ok) item.setText(self.oldMetaText) self.metaDataTableWidget.itemChanged.connect( self.metaDataChanged) self.oldMetaText = item.text() def metaDataClicked(self, item): self.oldMetaText = item.text() def metaDataSelectionChanged(self): self.oldMetaText = self.metaDataTableWidget.selectedItems()[0].text() def dataChanged(self, item): row, col = item.row(), item.column() key = self.dataTableWidget.horizontalHeaderItem(col).text() self.data['data'][key][row] = eval(item.text()) self.dataAltered = True self.resetPlotSetup() self.dataAltered = False def dataSorted(self): """ Updates the data after sorting the DataTableWidget """ self.getDataFromTable() self.dataAltered = True self.resetPlotSetup() self.dataAltered = False def addDataRow(self): try: self.dataTableWidget.itemChanged.disconnect() except: pass row = self.dataTableWidget.currentRow() self.dataTableWidget.insertRow(row + 1) for col in range(self.dataTableWidget.columnCount()): self.dataTableWidget.setItem( row + 1, col, QCustomTableWidgetItem( float(self.dataTableWidget.item(row, col).text()))) self.getDataFromTable() self.dataAltered = True self.resetPlotSetup() self.dataAltered = False self.dataTableWidget.itemChanged.connect(self.dataChanged) def editDataColumn(self): if self.data is not None: items = self.dataTableWidget.selectedItems() selCols = list([item.column() for item in items]) if len(selCols) == 1: colName = self.dataTableWidget.horizontalHeaderItem( selCols[0]).text() self.addDataColumn(colName=colName, expr=self.expressions[colName], new=False) else: QMessageBox.warning( self, 'Column Selection Error', 'Please select only elements of a single column.', QMessageBox.Ok) else: QMessageBox.warning(self, 'Data error', 'There is no data', QMessageBox.Ok) def addDataColumn(self, colName='Col_X', expr=None, new=True): if self.data is not None: row, col = self.data['data'].shape self.insertColDialog = InsertCol_Dialog(colName=colName, minCounter=1, maxCounter=row, expr=expr) if self.insertColDialog.exec_(): imin = eval(self.insertColDialog.minCounterLineEdit.text()) imax = eval(self.insertColDialog.maxCounterLineEdit.text()) i = arange(imin, imax + 1) colname = self.insertColDialog.colNameLineEdit.text() data = copy.copy(self.data) if new: if colname not in self.data['data'].columns: try: self.data['data'][colname] = eval(expr) except: try: expr = self.insertColDialog.colExprTextEdit.toPlainText( ) cexpr = expr.replace('col', "self.data['data']") self.data['data'][colname] = eval(cexpr) self.data['meta']['col_names'].append(colname) except: QMessageBox.warning( self, 'Column Error', 'Please check the expression.\n The expression should be in this format:\n col[column_name]*5', QMessageBox.Ok) self.addDataColumn(colName='Col_X', expr=expr) self.expressions[colname] = expr self.setData2Table() self.setMeta2Table() self.dataAltered = True self.resetPlotSetup() self.dataAltered = False else: QMessageBox.warning( self, 'Column Name Error', 'Please choose different column name than the exisiting ones', QMessageBox.Ok) self.addDataColumn(colName='Col_X', expr=expr) else: try: self.data['data'][colname] = eval(expr) except: try: expr = self.insertColDialog.colExprTextEdit.toPlainText( ) cexpr = expr.replace('col', "self.data['data\']") self.data['data'][colname] = eval(cexpr) except: QMessageBox.warning( self, 'Column Error', 'Please check the expression.\n The expression should be in this format:\n col[column_name]*5', QMessageBox.Ok) self.addDataColumn(colName='Col_X', expr=expr) self.expressions[colname] = expr self.setData2Table() self.setMeta2Table() self.dataAltered = True self.resetPlotSetup() self.dataAltered = False else: self.data = {} self.insertColDialog = InsertCol_Dialog(colName=colName, minCounter=1, maxCounter=100, expr=expr) if self.insertColDialog.exec_(): imin = eval(self.insertColDialog.minCounterLineEdit.text()) imax = eval(self.insertColDialog.maxCounterLineEdit.text()) i = arange(imin, imax + 1) colname = self.insertColDialog.colNameLineEdit.text() expr = self.insertColDialog.colExprTextEdit.toPlainText() expr = expr.replace('col.', "self.data['data']") try: self.data['data'] = pd.DataFrame(eval(expr), columns=[colname]) self.data['meta'] = {} self.data['meta']['col_names'] = [colname] self.setData2Table() self.setMeta2Table() self.dataAltered = True self.resetPlotSetup() self.dataAltered = False self.saveDataPushButton.setEnabled(True) self.addRowPushButton.setEnabled(True) self.removeRowsPushButton.setEnabled(True) self.removeColumnPushButton.setEnabled(True) self.expressions[colname] = expr except: QMessageBox.warning( self, 'Column Error', 'Please check the expression.\n The expression should be in this format:\n col.column_name*5', QMessageBox.Ok) self.data = None self.addDataColumn(colName='Col_X', expr=expr) def removeDataColumn(self): """ Removes selected columns from dataTableWidget """ colIndexes = [ index.column() for index in self.dataTableWidget.selectionModel().selectedColumns() ] colIndexes.sort(reverse=True) if self.dataTableWidget.columnCount() - len( colIndexes) >= 2 or self.plotSetupTableWidget.rowCount() == 0: for index in colIndexes: colname = self.data['meta']['col_names'][index] self.data['meta']['col_names'].pop(index) del self.expressions[colname] self.dataTableWidget.removeColumn(index) if self.dataTableWidget.columnCount() != 0: self.getDataFromTable() self.setMeta2Table() self.dataAltered = True self.resetPlotSetup() self.dataAltered = False else: self.data['data'] = None self.dataTableWidget.clear() #self.metaDataTableWidget.clear() self.autoUpdateCheckBox.setEnabled(False) self.saveDataPushButton.setEnabled(False) self.addRowPushButton.setEnabled(False) self.removeRowsPushButton.setEnabled(False) self.removeColumnPushButton.setEnabled(False) else: QMessageBox.warning( self, 'Remove Error', 'Cannot remove these many columns because Data Dialog needs to have atleast two columns', QMessageBox.Ok) def removeDataRows(self): rowIndexes = [ index.row() for index in self.dataTableWidget.selectionModel().selectedRows() ] rowIndexes.sort(reverse=True) if len(rowIndexes) > 0: ans = QMessageBox.question( self, 'Confirmation', 'Are you sure of removing the selected rows?', QMessageBox.Yes, QMessageBox.No) if ans == QMessageBox.Yes: for i in rowIndexes: self.dataTableWidget.removeRow(i) self.getDataFromTable() self.dataAltered = True self.resetPlotSetup() self.dataAltered = False def setMeta2Table(self): """ Populates the metaDataTable widget with metadata available from the data """ try: self.metaDataTableWidget.itemChanged.disconnect() self.metaDataTableWidget.itemSelectionChanged.disconnect() except: pass self.metaDataTableWidget.clear() self.metaDataTableWidget.setColumnCount(2) self.metaDataTableWidget.setRowCount(len(self.data['meta'].keys())) for num, key in enumerate(self.data['meta'].keys()): self.metaDataTableWidget.setItem(num, 0, QTableWidgetItem(key)) self.metaDataTableWidget.setItem( num, 1, QTableWidgetItem(str(self.data['meta'][key]))) if 'col_names' not in self.data['meta'].keys(): self.data['meta']['col_names'] = self.data['data'].columns.tolist() self.metaDataTableWidget.insertRow( self.metaDataTableWidget.rowCount()) self.metaDataTableWidget.setItem(num + 1, 0, QTableWidgetItem('col_names')) self.metaDataTableWidget.setItem( num + 1, 1, QTableWidgetItem(str(self.data['meta']['col_names']))) self.metaDataTableWidget.setHorizontalHeaderLabels( ['Parameter', 'Value']) self.metaDataTableWidget.itemChanged.connect(self.metaDataChanged) self.metaDataTableWidget.itemSelectionChanged.connect( self.metaDataSelectionChanged) def getMetaFromTable(self): self.data['meta'] = {} for i in range(self.metaDataTableWidget.rowCount()): try: self.data['meta'][self.metaDataTableWidget.item( i, 0).text()] = eval( self.metaDataTableWidget.item(i, 1).text()) except: self.data['meta'][self.metaDataTableWidget.item( i, 0).text()] = self.metaDataTableWidget.item(i, 1).text() def setData2Table(self): """ Populates the dataTableWidget with data available from data """ try: self.dataTableWidget.itemChanged.disconnect() except: pass self.dataTableWidget.clear() self.dataTableWidget.setColumnCount(len(self.data['data'].columns)) self.dataTableWidget.setRowCount(len(self.data['data'].index)) for j, colname in enumerate(self.data['data'].columns): if colname not in self.expressions.keys(): self.expressions[colname] = "col['%s']" % colname for i in range(len(self.data['data'].index)): #self.dataTableWidget.setItem(i,j,QTableWidgetItem(str(self.data['data'][colname][i]))) self.dataTableWidget.setItem( i, j, QCustomTableWidgetItem(self.data['data'][colname][i])) self.dataTableWidget.setHorizontalHeaderLabels( self.data['data'].columns.values.tolist()) self.dataTableWidget.itemChanged.connect(self.dataChanged) def getDataFromTable(self): self.data['data'] = pd.DataFrame() for col in range(self.dataTableWidget.columnCount()): label = self.dataTableWidget.horizontalHeaderItem(col).text() self.data['data'][label] = array([ float(self.dataTableWidget.item(i, col).text()) for i in range(self.dataTableWidget.rowCount()) ]) def readData(self, fname, skiprows=0, comment='#', delimiter=' '): """ Read data from a file and put it in dictionary structure with keys 'meta' and 'data' and the data would look like the following data={'meta':meta_dictionary,'data'=pandas_dataframe} """ if os.path.exists(os.path.abspath(fname)): self.data = {} self.fname = fname self.dataFileLineEdit.setText(self.fname) self.cwd = os.path.dirname(self.fname) fh = open(os.path.abspath(self.fname), 'r') lines = fh.readlines() fh.close() self.data['meta'] = {} for line in lines[skiprows:]: if line[0] == comment: try: key, value = line[1:].strip().split('=') try: self.data['meta'][key] = eval( value ) # When the value is either valid number, lists, arrays, dictionaries except: self.data['meta'][ key] = value # When the value is just a string except: pass else: if '\t' in line: delimiter = '\t' elif ',' in line: delimiter = ',' elif ' ' in line: delimiter = ' ' break if 'col_names' in self.data['meta'].keys(): self.data['data'] = pd.read_csv( self.fname, comment=comment, names=self.data['meta']['col_names'], header=None, sep=delimiter) if not all(self.data['data'].isnull().values): self.data['data'] = pd.DataFrame( loadtxt(self.fname, skiprows=skiprows), columns=self.data['meta']['col_names']) else: self.data['data'] = pd.read_csv(self.fname, comment=comment, header=None, sep=delimiter) if not all(self.data['data'].isnull()): self.data['data'] = pd.DataFrame( loadtxt(self.fname, skiprows=skiprows)) self.data['data'].columns = [ 'Col_%d' % i for i in self.data['data'].columns.values.tolist() ] self.data['meta']['col_names'] = self.data[ 'data'].columns.values.tolist() self.autoUpdate_ON_OFF() self.autoUpdateCheckBox.setEnabled(True) self.saveDataPushButton.setEnabled(True) self.addRowPushButton.setEnabled(True) self.removeRowsPushButton.setEnabled(True) self.removeColumnPushButton.setEnabled(True) return self.data else: QMessageBox.warning(self, 'File Error', 'The file doesnot exists!') return None def fileUpdated(self, fname): QTest.qWait(1000) self.readData(fname=fname) if self.data is not None: self.setMeta2Table() self.setData2Table() self.dataAltered = True self.resetPlotSetup() self.dataAltered = False def autoUpdate_ON_OFF(self): files = self.fileWatcher.files() if len(files) != 0: self.fileWatcher.removePaths(files) if self.autoUpdateCheckBox.isChecked(): self.fileWatcher.addPath(self.fname) def saveData(self): """ Save data to a file """ fname = QFileDialog.getSaveFileName(self, 'Save file as', self.cwd, filter='*.*')[0] if fname != '': ext = os.path.splitext(fname)[1] if ext == '': ext = '.txt' fname = fname + ext header = 'File saved on %s\n' % time.asctime() for key in self.data['meta'].keys(): header = header + '%s=%s\n' % (key, str( self.data['meta'][key])) if 'col_names' not in self.data['meta'].keys(): header = header + 'col_names=%s\n' % str( self.data['data'].columns.tolist()) savetxt(fname, self.data['data'].values, header=header, comments='#') def openFile(self): """ Opens a openFileDialog to open a data file """ if self.cwd is not None: fname = QFileDialog.getOpenFileName(self, 'Select a data file to open', directory=self.cwd, filter='*.*')[0] else: fname = QFileDialog.getOpenFileName(self, 'Select a data file to open', directory='', filter='*.*')[0] if fname != '': self.data = self.readData(fname=fname) if self.data is not None: self.setMeta2Table() self.setData2Table() self.dataAltered = True self.resetPlotSetup() self.dataAltered = False def resetPlotSetup(self): try: self.plotSetupTableWidget.cellChanged.disconnect() except: pass columns = self.data['data'].columns.tolist() self.xlabel = [] self.ylabel = [] for row in range(self.plotSetupTableWidget.rowCount()): for i in range(1, 3): self.plotSetupTableWidget.cellWidget( row, i).currentIndexChanged.disconnect() self.plotSetupTableWidget.cellWidget(row, i).clear() self.plotSetupTableWidget.cellWidget(row, i).addItems(columns) self.plotSetupTableWidget.cellWidget(row, i).setCurrentIndex(i - 1) self.plotSetupTableWidget.cellWidget( row, i).currentIndexChanged.connect(self.updateCellData) self.xlabel.append( '[%s]' % self.plotSetupTableWidget.cellWidget(row, 1).currentText()) self.ylabel.append( '[%s]' % self.plotSetupTableWidget.cellWidget(row, 2).currentText()) self.plotSetupTableWidget.cellWidget( row, 3).currentIndexChanged.disconnect() self.plotSetupTableWidget.cellWidget(row, 3).clear() self.plotSetupTableWidget.cellWidget(row, 3).addItems(['None'] + columns) self.plotSetupTableWidget.cellWidget(row, 3).setCurrentIndex(0) self.plotSetupTableWidget.cellWidget( row, 3).currentIndexChanged.connect(self.updateCellData) self.plotSetupTableWidget.setCurrentCell(row, 3) color = self.plotSetupTableWidget.cellWidget(row, 4).color() self.plotSetupTableWidget.setCellWidget( row, 4, pg.ColorButton(color=color)) self.plotSetupTableWidget.cellWidget( row, 4).sigColorChanging.connect(self.updateCellData) self.plotSetupTableWidget.cellWidget( row, 4).sigColorChanged.connect(self.updateCellData) self.updatePlotData(row, i) self.plotSetupTableWidget.cellChanged.connect(self.updatePlotData) def addMultiPlots(self, plotIndex=None, colors=None): for key in plotIndex.keys(): pi = plotIndex[key] if colors is None: color = next(self.colcycler ) #array([random.randint(200, high=255),0,0]) print(color) else: color = colors[key] self.addPlots(plotIndex=pi, color=color) def addPlots(self, plotIndex=None, color=None): #self.plotSetupTableWidget.clear() # if self.parentWidget() is None or self.plotSetupTableWidget.rowCount()==0: try: self.plotSetupTableWidget.cellChanged.disconnect() except: pass columns = self.data['data'].columns.tolist() if len(columns) >= 2: self.plotSetupTableWidget.insertRow( self.plotSetupTableWidget.rowCount()) row = self.plotSetupTableWidget.rowCount() - 1 self.plotSetupTableWidget.setItem( row, 0, QTableWidgetItem('Data_%d' % self.plotNum)) for i in range(1, 3): self.plotSetupTableWidget.setCellWidget(row, i, QComboBox()) self.plotSetupTableWidget.cellWidget(row, i).addItems(columns) if plotIndex is not None: self.plotSetupTableWidget.cellWidget( row, i).setCurrentIndex(plotIndex[i - 1]) else: self.plotSetupTableWidget.cellWidget( row, i).setCurrentIndex(i - 1) self.plotSetupTableWidget.cellWidget( row, i).currentIndexChanged.connect(self.updateCellData) self.xlabel.append( '[%s]' % self.plotSetupTableWidget.cellWidget(row, 1).currentText()) self.ylabel.append( '[%s]' % self.plotSetupTableWidget.cellWidget(row, 2).currentText()) self.plotSetupTableWidget.setCellWidget(row, 3, QComboBox()) self.plotSetupTableWidget.cellWidget(row, 3).addItems(['None'] + columns) if color is None: color = next(self.colcycler ) #array([random.randint(200, high=255),0,0]) self.plotSetupTableWidget.setCellWidget( row, 4, pg.ColorButton(color=color)) self.plotSetupTableWidget.cellWidget( row, 4).sigColorChanging.connect(self.updateCellData) self.plotSetupTableWidget.cellWidget( row, 4).sigColorChanged.connect(self.updateCellData) if plotIndex is not None: self.plotSetupTableWidget.cellWidget(row, 3).setCurrentIndex( plotIndex[-1]) else: # try: # self.plotSetupTableWidget.cellWidget(row,3).setCurrentIndex(2) # except: # self.plotSetupTableWidget.cellWidget(row, 3).setCurrentIndex(0) self.plotSetupTableWidget.cellWidget( row, 3).currentIndexChanged.connect(self.updateCellData) self.plotSetupTableWidget.setCurrentCell(row, 3) self.updatePlotData(row, 3) self.plotNum += 1 else: QMessageBox.warning( self, 'Data file error', 'The data file do not have two or more columns to be plotted.', QMessageBox.Ok) self.plotSetupTableWidget.cellChanged.connect(self.updatePlotData) # else: # QMessageBox.warning(self,'Warning','As the Data Dialog is used within another widget you cannot add more plots',QMessageBox.Ok) def removePlots(self): """ Removes data for PlotSetup """ try: self.plotSetupTableWidget.cellChanged.disconnect() except: pass rowIndexes = self.plotSetupTableWidget.selectionModel().selectedRows() selectedRows = [index.row() for index in rowIndexes] selectedRows.sort(reverse=True) if self.parentWidget() is None: for row in selectedRows: name = self.plotSetupTableWidget.item(row, 0).text() self.plotWidget.remove_data([name]) self.plotSetupTableWidget.removeRow(row) else: if self.plotSetupTableWidget.rowCount() - len(rowIndexes) >= 1: for row in selectedRows: name = self.plotSetupTableWidget.item(row, 0).text() self.plotWidget.remove_data([name]) self.plotSetupTableWidget.removeRow(row) else: QMessageBox.warning( self, 'Warning', 'Cannot remove single plots from Data Dialog because the Data Dialog is used within another widget', QMessageBox.Ok) self.updatePlot() self.plotSetupTableWidget.cellChanged.connect(self.updatePlotData) def updatePlotData(self, row, col): #row=self.plotSetupTableWidget.currentRow() name = self.plotSetupTableWidget.item(row, 0).text() if self.dataAltered: for i in range(1, 4): try: self.plotSetupTableWidget.cellWidget( row, i).setCurrentIndex(self.oldPlotIndex[name][i - 1]) except: pass xcol, ycol, yerrcol = [ self.plotSetupTableWidget.cellWidget(row, i).currentText() for i in range(1, 4) ] #ycol=self.plotSetupTableWidget.cellWidget(row,2).currentText() #yerrcol=self.plotSetupTableWidget.cellWidget(row,3).currentText() if yerrcol != 'None': if ycol == 'fit': self.plotWidget.add_data( self.data['data'][xcol].values, self.data['data'][ycol].values, yerr=self.data['data'][yerrcol].values, name=name, fit=True, color=self.plotSetupTableWidget.cellWidget(row, 4).color()) else: self.plotWidget.add_data( self.data['data'][xcol].values, self.data['data'][ycol].values, yerr=self.data['data'][yerrcol].values, name=name, fit=False, color=self.plotSetupTableWidget.cellWidget(row, 4).color()) else: if ycol == 'fit': self.plotWidget.add_data( self.data['data'][xcol].values, self.data['data'][ycol].values, name=name, fit=True, color=self.plotSetupTableWidget.cellWidget(row, 4).color()) else: self.plotWidget.add_data( self.data['data'][xcol].values, self.data['data'][ycol].values, name=name, fit=False, color=self.plotSetupTableWidget.cellWidget(row, 4).color()) self.xlabel[row] = '[%s]' % self.plotSetupTableWidget.cellWidget( row, 1).currentText() self.ylabel[row] = '[%s]' % self.plotSetupTableWidget.cellWidget( row, 2).currentText() self.updatePlot() self.oldPlotIndex[name] = [ self.plotSetupTableWidget.cellWidget(row, i).currentIndex() for i in range(1, 4) ] def updateCellData(self, index): row = self.plotSetupTableWidget.indexAt(self.sender().pos()).row() self.updatePlotData(row, index) def updatePlot(self): self.make_default() names = [ self.plotSetupTableWidget.item(i, 0).text() for i in range(self.plotSetupTableWidget.rowCount()) ] #self.plotColIndex=[self.plotSetupTableWidget.cellWidget(0,i).currentIndex() for i in range(1,4)] self.plotColIndex = {} self.externalData = {} self.plotColors = {} for i in range(self.plotSetupTableWidget.rowCount()): key = self.plotSetupTableWidget.cellWidget(i, 2).currentText() self.plotColIndex[key] = [ self.plotSetupTableWidget.cellWidget(i, j).currentIndex() for j in range(1, 4) ] self.plotColors[key] = self.plotSetupTableWidget.cellWidget( i, 4).color() self.externalData[key] = copy.copy(self.data['meta']) self.externalData[key]['x'] = copy.copy( self.data['data'][self.plotSetupTableWidget.cellWidget( i, 1).currentText()].values) self.externalData[key]['y'] = copy.copy( self.data['data'][self.plotSetupTableWidget.cellWidget( i, 2).currentText()].values) if self.plotSetupTableWidget.cellWidget(i, 3).currentText() == 'None': self.externalData[key]['yerr'] = ones_like( self.externalData[key]['x']) else: self.externalData[key]['yerr'] = copy.copy( self.data['data'][self.plotSetupTableWidget.cellWidget( i, 3).currentText()].values) self.externalData[key][ 'color'] = self.plotSetupTableWidget.cellWidget(i, 4).color() self.plotWidget.Plot(names) self.plotWidget.setXLabel(' '.join(self.xlabel)) self.plotWidget.setYLabel(' '.join(self.ylabel))
class XAnoS_Reducer(QWidget): """ This widget is developed to reduce on the fly 2D SAXS data to azimuthally averaged 1D SAXS data """ def __init__(self,poniFile=None,dataFile=None, darkFile=None, maskFile=None,extractedFolder='/tmp', npt=1000, azimuthalRange=(-180.0,180.0), parent=None): """ poniFile is the calibration file obtained after Q-calibration """ QWidget.__init__(self,parent) self.setup_dict=json.load(open('./SetupData/reducer_setup.txt','r')) if poniFile is not None: self.poniFile=poniFile else: self.poniFile=self.setup_dict['poniFile'] if maskFile is not None: self.maskFile=maskFile else: self.maskFile=self.setup_dict['maskFile'] self.dataFile=dataFile if darkFile is None: self.dark_corrected=False self.darkFile='' else: self.darkFile=darkFile self.dark_corrected=True self.curDir=os.getcwd() self.extractedBaseFolder=extractedFolder self.npt=npt self.set_externally=False #ai=AIWidget() #self.layout.addWidget(ai) self.azimuthalRange=azimuthalRange self.create_UI() if os.path.exists(self.poniFile): self.openPoniFile(file=self.poniFile) if os.path.exists(self.maskFile): self.openMaskFile(file=self.maskFile) self.clientRunning=False def create_UI(self): """ Creates the widget user interface """ loadUi('UI_Forms/Data_Reduction_Client.ui',self) self.poniFileLineEdit.setText(str(self.poniFile)) self.maskFileLineEdit.setText(str(self.maskFile)) self.darkFileLineEdit.setText(str(self.darkFile)) self.extractedBaseFolderLineEdit.setText(self.extractedBaseFolder) self.radialPointsLineEdit.setText(str(self.npt)) self.openDataPushButton.clicked.connect(self.openDataFiles) self.reducePushButton.clicked.connect(self.reduce_multiple) self.openDarkPushButton.clicked.connect(self.openDarkFile) self.openPoniPushButton.clicked.connect(lambda x: self.openPoniFile(file=None)) self.calibratePushButton.clicked.connect(self.calibrate) self.maskFileLineEdit.returnPressed.connect(self.maskFileChanged) self.openMaskPushButton.clicked.connect(lambda x: self.openMaskFile(file=None)) self.createMaskPushButton.clicked.connect(self.createMask) self.extractedFolderPushButton.clicked.connect(self.openFolder) self.extractedFolderLineEdit.textChanged.connect(self.extractedFolderChanged) self.polCorrComboBox.currentIndexChanged.connect(self.polarizationChanged) self.polarizationChanged() self.radialPointsLineEdit.returnPressed.connect(self.nptChanged) self.azimuthalRangeLineEdit.returnPressed.connect(self.azimuthalRangeChanged) self.azimuthalRangeChanged() #self.statusLabel.setStyleSheet("color:rgba(0,1,0,0)") self.imageWidget=Image_Widget(zeros((100,100))) self.cakedImageWidget=Image_Widget(zeros((100,100))) imgNumberLabel=QLabel('Image number') self.imgNumberSpinBox=QSpinBox() self.imgNumberSpinBox.setSingleStep(1) self.imageWidget.imageLayout.addWidget(imgNumberLabel,row=2,col=1) self.imageWidget.imageLayout.addWidget(self.imgNumberSpinBox,row=2,col=2) self.imageView=self.imageWidget.imageView.getView() self.plotWidget=PlotWidget() self.plotWidget.setXLabel('Q, Å<sup>-1</sup>',fontsize=5) self.plotWidget.setYLabel('Intensity',fontsize=5) self.tabWidget.addTab(self.plotWidget,'Reduced 1D-data') self.tabWidget.addTab(self.imageWidget,'Masked 2D-data') self.tabWidget.addTab(self.cakedImageWidget,'Reduced Caked Data') self.serverAddress=self.serverAddressLineEdit.text() self.startClientPushButton.clicked.connect(self.startClient) self.stopClientPushButton.clicked.connect(self.stopClient) self.serverAddressLineEdit.returnPressed.connect(self.serverAddressChanged) self.startServerPushButton.clicked.connect(self.startServer) self.stopServerPushButton.clicked.connect(self.stopServer) def startServer(self): serverAddr=self.serverAddressLineEdit.text() dataDir=QFileDialog.getExistingDirectory(self,'Select data folder',options=QFileDialog.ShowDirsOnly) self.serverStatusLabel.setText('<font color="Red">Transmitting</font>') QApplication.processEvents() self.serverThread=QThread() self.zeromq_server=ZeroMQ_Server(serverAddr,dataDir) self.zeromq_server.moveToThread(self.serverThread) self.serverThread.started.connect(self.zeromq_server.loop) self.zeromq_server.messageEmitted.connect(self.updateServerMessage) self.zeromq_server.folderFinished.connect(self.serverDone) QTimer.singleShot(0,self.serverThread.start) def updateServerMessage(self,mesg): #self.serverStatusLabel.setText('<font color="Red">Transmitting</font>') self.serverMessageLabel.setText('Server sends: %s'%mesg) QApplication.processEvents() def serverDone(self): self.serverStatusLabel.setText('<font color="Green">Idle</font>') self.zeromq_server.socket.unbind(self.zeromq_server.socket.last_endpoint) self.serverThread.quit() self.serverThread.wait() self.serverThread.deleteLater() self.zeromq_server.deleteLater() def stopServer(self): try: self.zeromq_server.running=False self.serverStatusLabel.setText('<font color="Green">Idle</font>') self.zeromq_server.socket.unbind(self.zeromq_server.socket.last_endpoint) self.serverThread.quit() self.serverThread.wait() self.serverThread.deleteLater() self.zeromq_server.deleteLater() except: QMessageBox.warning(self,'Server Error','Start the server before stopping it') def enableClient(self,enable=True): self.startClientPushButton.setEnabled(enable) self.stopClientPushButton.setEnabled(enable) def enableServer(self,enable=True): self.startServerPushButton.setEnabled(enable) self.stopServerPushButton.setEnabled(enable) def startClient(self): if self.clientRunning: self.stopClient() else: self.clientFree=True self.clientRunning=True self.files=[] self.listenerThread = QThread() addr=self.clientAddressLineEdit.text() self.zeromq_listener = ZeroMQ_Listener(addr) self.zeromq_listener.moveToThread(self.listenerThread) self.listenerThread.started.connect(self.zeromq_listener.loop) self.zeromq_listener.messageReceived.connect(self.signal_received) QTimer.singleShot(0, self.listenerThread.start) QTimer.singleShot(0,self.clientReduce) self.clientStatusLabel.setText('<font color="red">Connected</font>') def stopClient(self): try: self.clientRunning=False self.clientFree=False self.zeromq_listener.messageReceived.disconnect() self.zeromq_listener.running=False self.listenerThread.quit() self.listenerThread.wait() self.listenerThread.deleteLater() self.zeromq_listener.deleteLater() self.clientStatusLabel.setText('<font color="green">Idle</font>') except: QMessageBox.warning(self,'Client Error', 'Please start the client first before closing.',QMessageBox.Ok) def serverAddressChanged(self): if self.clientRunning: self.startClient() def signal_received(self, message): self.clientMessageLabel.setText('Client receives: %s'%message) if 'dark.edf' not in message: self.files.append(message) def clientReduce(self): while self.clientFree: QApplication.processEvents() if len(self.files)>0: message=self.files[0] self.dataFiles=[message] self.dataFileLineEdit.setText(str(self.dataFiles)) self.extractedBaseFolder=os.path.dirname(message) self.extractedFolder=os.path.join(self.extractedBaseFolder,self.extractedFolderLineEdit.text()) if not os.path.exists(self.extractedFolder): os.makedirs(self.extractedFolder) self.extractedBaseFolderLineEdit.setText(self.extractedBaseFolder) self.set_externally=True self.reduce_multiple() self.set_externally=False self.files.pop(0) def closeEvent(self, event): if self.clientRunning: self.stopClient() event.accept() def polarizationChanged(self): if self.polCorrComboBox.currentText()=='Horizontal': self.polarization_factor=1 elif self.polCorrComboBox.currentText()=='Vertical': self.polarization_factor=-1 elif self.polCorrComboBox.currentText()=='Circular': self.polarization_factor=0 else: self.polarization_factor=None def createMask(self): """ Opens a mask-widget to create mask file """ fname=str(QFileDialog.getOpenFileName(self,'Select an image file', directory=self.curDir,filter='Image file (*.edf *.tif)')[0]) if fname is not None or fname!='': img=fb.open(fname).data self.maskWidget=MaskWidget(img) self.maskWidget.saveMaskPushButton.clicked.disconnect() self.maskWidget.saveMaskPushButton.clicked.connect(self.save_mask) self.maskWidget.show() else: QMessageBox.warning(self,'File error','Please import a data file first for creating the mask',QMessageBox.Ok) def maskFileChanged(self): """ Changes the mask file """ maskFile=str(self.maskFileLineEdit.text()) if str(maskFile)=='': self.maskFile=None elif os.path.exists(maskFile): self.maskFile=maskFile else: self.maskFile=None def save_mask(self): """ Saves the entire mask combining all the shape ROIs """ fname=str(QFileDialog.getSaveFileName(filter='Mask Files (*.msk)')[0]) name,extn=os.path.splitext(fname) if extn=='': fname=name+'.msk' elif extn!='.msk': QMessageBox.warning(self,'File extension error','Please donot provide file extension other than ".msk". Thank you!') return else: tmpfile=fb.edfimage.EdfImage(data=self.maskWidget.full_mask_data.T,header=None) tmpfile.save(fname) self.maskFile=fname self.maskFileLineEdit.setText(self.maskFile) def calibrate(self): """ Opens a calibartion widget to create calibration file """ fname=str(QFileDialog.getOpenFileName(self,'Select calibration image',directory=self.curDir, filter='Calibration image (*.edf *.tif)')[0]) if fname is not None: img=fb.open(fname).data if self.maskFile is not None: try: mask=fb.open(self.maskFile).data except: QMessageBox.warning(self,'Mask File Error','Cannot open %s.\n No masking will be done.'%self.maskFile) mask=None else: mask=None pixel1=79.0 pixel2=79.0 self.calWidget=CalibrationWidget(img,pixel1,pixel2,mask=mask) self.calWidget.saveCalibrationPushButton.clicked.disconnect() self.calWidget.saveCalibrationPushButton.clicked.connect(self.save_calibration) self.calWidget.show() else: QMessageBox.warning(self,'File error','Please import a data file first for creating the calibration file',QMessageBox.Ok) def save_calibration(self): fname=str(QFileDialog.getSaveFileName(self,'Calibration file',directory=self.curDir,filter='Clibration files (*.poni)')[0]) tfname=os.path.splitext(fname)[0]+'.poni' self.calWidget.applyPyFAI() self.calWidget.geo.save(tfname) self.poniFile=tfname self.poniFileLineEdit.setText(self.poniFile) self.openPoniFile(file=self.poniFile) def openPoniFile(self,file=None): """ Select and imports the calibration file """ if file is None: self.poniFile=QFileDialog.getOpenFileName(self,'Select calibration file',directory=self.curDir,filter='Calibration file (*.poni)')[0] self.poniFileLineEdit.setText(self.poniFile) else: self.poniFile=file if os.path.exists(self.poniFile): self.setup_dict['poniFile']=self.poniFile json.dump(self.setup_dict,open('./SetupData/reducer_setup.txt','w')) fh=open(self.poniFile,'r') lines=fh.readlines() self.calib_data={} for line in lines: if line[0]!='#': key,val=line.split(': ') self.calib_data[key]=float(val) self.dist=self.calib_data['Distance'] self.pixel1=self.calib_data['PixelSize1'] self.pixel2=self.calib_data['PixelSize2'] self.poni1=self.calib_data['Poni1'] self.poni2=self.calib_data['Poni2'] self.rot1=self.calib_data['Rot1'] self.rot2=self.calib_data['Rot2'] self.rot3=self.calib_data['Rot3'] self.wavelength=self.calib_data['Wavelength'] self.ai=AzimuthalIntegrator(dist=self.dist,poni1=self.poni1,poni2=self.poni2,pixel1=self.pixel1,pixel2=self.pixel2,rot1=self.rot1,rot2=self.rot2,rot3=self.rot3,wavelength=self.wavelength) #pos=[self.poni2/self.pixel2,self.poni1/self.pixel1] #self.roi=cake(pos,movable=False) #self.roi.sigRegionChangeStarted.connect(self.endAngleChanged) #self.imageView.addItem(self.roi) else: QMessageBox.warning(self,'File error','The calibration file '+self.poniFile+' doesnot exists.',QMessageBox.Ok) def endAngleChanged(self,evt): print(evt.pos()) def nptChanged(self): """ Changes the number of radial points """ try: self.npt=int(self.radialPointsLineEdit.text()) except: QMessageBox.warning(self,'Value error', 'Please input positive integers only.',QMessageBox.Ok) def azimuthalRangeChanged(self): """ Changes the azimuth angular range """ try: self.azimuthalRange=tuple(map(float, self.azimuthalRangeLineEdit.text().split(':'))) except: QMessageBox.warning(self,'Value error','Please input min:max angles in floating point numbers',QMessageBox.Ok) def openDataFile(self): """ Select and imports one data file """ dataFile=QFileDialog.getOpenFileName(self,'Select data file',directory=self.curDir,filter='Data file (*.edf *.tif)')[0] if dataFile!='': self.dataFile=dataFile self.curDir=os.path.dirname(self.dataFile) self.dataFileLineEdit.setText(self.dataFile) self.data2d=fb.open(self.dataFile).data if self.darkFile is not None: self.applyDark() if self.maskFile is not None: self.applyMask() self.imageWidget.setImage(self.data2d,transpose=True) self.tabWidget.setCurrentWidget(self.imageWidget) if not self.set_externally: self.extractedFolder=os.path.join(self.curDir,self.extractedFolderLineEdit.text()) if not os.path.exists(self.extractedFolder): os.makedirs(self.extractedFolder) def openDataFiles(self): """ Selects and imports multiple data files """ self.dataFiles=QFileDialog.getOpenFileNames(self,'Select data files', directory=self.curDir,filter='Data files (*.edf *.tif)')[0] if len(self.dataFiles)!=0: self.imgNumberSpinBox.valueChanged.connect(self.imageChanged) self.imgNumberSpinBox.setMinimum(0) self.imgNumberSpinBox.setMaximum(len(self.dataFiles)-1) self.dataFileLineEdit.setText(str(self.dataFiles)) self.curDir=os.path.dirname(self.dataFiles[0]) self.extractedBaseFolder=self.curDir self.extractedFolder=os.path.abspath(os.path.join(self.extractedBaseFolder,self.extractedFolderLineEdit.text())) if not os.path.exists(self.extractedFolder): os.makedirs(self.extractedFolder) self.extractedBaseFolderLineEdit.setText(self.extractedBaseFolder) self.imgNumberSpinBox.setValue(0) self.imageChanged() def imageChanged(self): self.data2d=fb.open(self.dataFiles[self.imgNumberSpinBox.value()]).data if self.darkFile is not None: self.applyDark() if self.maskFile is not None: self.applyMask() self.imageWidget.setImage(self.data2d,transpose=True) def applyDark(self): if not self.dark_corrected and self.darkFile!='': self.dark2d=fb.open(self.darkFile).data self.data2d=self.data2d-self.dark2d self.dark_corrected=True def applyMask(self): self.mask2d=fb.open(self.maskFile).data self.data2d=self.data2d*(1+self.mask2d)/2.0 self.mask_applied=True def openDarkFile(self): """ Select and imports the dark file """ self.darkFile=QFileDialog.getOpenFileName(self,'Select dark file',directory=self.curDir,filter='Dark file (*.edf)')[0] if self.darkFile!='': self.dark_corrected=False self.darkFileLineEdit.setText(self.darkFile) if self.dataFile is not None: self.data2d=fb.open(self.dataFile).data self.applyDark() def openMaskFile(self,file=None): """ Select and imports the Mask file """ if file is None: self.maskFile=QFileDialog.getOpenFileName(self,'Select mask file',directory=self.curDir,filter='Mask file (*.msk)')[0] else: self.maskFile=file if self.maskFile!='': self.mask_applied=False if os.path.exists(self.maskFile): self.curDir=os.path.dirname(self.maskFile) self.maskFileLineEdit.setText(self.maskFile) self.setup_dict['maskFile']=self.maskFile self.setup_dict['poniFile']=self.poniFile json.dump(self.setup_dict,open('./SetupData/reducer_setup.txt','w')) else: self.openMaskFile(file=None) if self.dataFile is not None: self.applyMask() else: self.maskFile=None self.maskFileLineEdit.clear() def openFolder(self): """ Select the folder to save the reduce data """ oldfolder=self.extractedBaseFolder.text() folder=QFileDialog.getExistingDirectory(self,'Select extracted directory',directory=self.curDir) if folder!='': self.extractedBaseFolder=folder self.extractedBaseFolderLineEdit.setText(folder) self.extractedFolder=os.path.join(folder,self.extractedFolderLineEdit.text()) self.set_externally=True else: self.extractedBaseFolder=oldfolder self.extractedBaseFolderLineEdit.setText(oldfolder) self.extractedFolder = os.path.join(oldfolder, self.extractedFolderLineEdit.text()) self.set_externally = True def extractedFolderChanged(self,txt): self.extractedFolder=os.path.join(self.extractedBaseFolder,txt) self.set_externally=True def reduceData(self): """ Reduces the 2d data to 1d data """ if (self.dataFile is not None) and (os.path.exists(self.dataFile)): if (self.poniFile is not None) and (os.path.exists(self.poniFile)): # self.statusLabel.setText('Busy') # self.progressBar.setRange(0, 0) imageData=fb.open(self.dataFile) #self.data2d=imageData.data #if self.maskFile is not None: # self.applyMask() #self.imageWidget.setImage(self.data2d,transpose=True) #self.tabWidget.setCurrentWidget(self.imageWidget) self.header=imageData.header try: self.ai.set_wavelength(float(self.header['Wavelength'])*1e-10) except: self.ai.set_wavelength(self.wavelength) #print(self.darkFile) if os.path.exists(self.dataFile.split('.')[0]+'_dark.edf') and self.darkCheckBox.isChecked(): self.darkFile=self.dataFile.split('.')[0]+'_dark.edf' dark=fb.open(self.darkFile) self.darkFileLineEdit.setText(self.darkFile) imageDark=dark.data self.header['BSDiode_corr']=max([1.0,(float(imageData.header['BSDiode'])-float(dark.header['BSDiode']))]) self.header['Monitor_corr']=max([1.0,(float(imageData.header['Monitor'])-float(dark.header['Monitor']))]) print("Dark File read from existing dark files") elif self.darkFile is not None and self.darkFile!='' and self.darkCheckBox.isChecked(): dark=fb.open(self.darkFile) imageDark=dark.data self.header['BSDiode_corr']=max([1.0,(float(imageData.header['BSDiode'])-float(dark.header['BSDiode']))]) self.header['Monitor_corr']=max([1.0,(float(imageData.header['Monitor'])-float(dark.header['Monitor']))]) print("Dark File from memory subtracted") else: imageDark=None try: self.header['BSDiode_corr']=float(imageData.header['BSDiode']) self.header['Monitor_corr']=float(imageData.header['Monitor']) self.header['Transmission'] = float(imageData.header['Transmission']) except: self.normComboBox.setCurrentText('None') print("No dark correction done") if str(self.normComboBox.currentText())=='BSDiode': norm_factor=self.header['BSDiode_corr']#/self.header['Monitor_corr']#float(self.header[ # 'count_time']) elif str(self.normComboBox.currentText())=='TransDiode': norm_factor=self.header['Transmission']*self.header['Monitor_corr'] elif str(self.normComboBox.currentText())=='Monitor': norm_factor=self.header['Monitor_corr'] elif str(self.normComboBox.currentText())=='Image Sum': norm_factor=sum(imageData.data) else: norm_factor=1.0 if self.maskFile is not None: imageMask=fb.open(self.maskFile).data else: imageMask=None # QApplication.processEvents() #print(self.azimuthalRange) self.q,self.I,self.Ierr=self.ai.integrate1d(imageData.data,self.npt,error_model='poisson',mask=imageMask,dark=imageDark,unit='q_A^-1',normalization_factor=norm_factor,azimuth_range=self.azimuthalRange,polarization_factor=self.polarization_factor) self.plotWidget.add_data(self.q,self.I,yerr=self.Ierr,name='Reduced data') if not self.set_externally: cakedI,qr,phir=self.ai.integrate2d(imageData.data,self.npt,mask=imageMask,dark=imageDark,unit='q_A^-1',normalization_factor=norm_factor,polarization_factor=self.polarization_factor) self.cakedImageWidget.setImage(cakedI,xmin=qr[0],xmax=qr[-1],ymin=phir[0],ymax=phir[-1],transpose=True,xlabel='Q ', ylabel='phi ',unit=['Å<sup>-1</sup>','degree']) self.cakedImageWidget.imageView.view.setAspectLocked(False) try: self.azimuthalRegion.setRegion(self.azimuthalRange) except: self.azimuthalRegion=pg.LinearRegionItem(values=self.azimuthalRange,orientation=pg.LinearRegionItem.Horizontal,movable=True,bounds=[-180,180]) self.cakedImageWidget.imageView.getView().addItem(self.azimuthalRegion) self.azimuthalRegion.sigRegionChanged.connect(self.azimuthalRegionChanged) self.plotWidget.setTitle(self.dataFile,fontsize=3) # self.progressBar.setRange(0,100) # self.progressBar.setValue(100) # self.statusLabel.setText('Idle') # QApplication.processEvents() self.saveData() #self.tabWidget.setCurrentWidget(self.plotWidget) else: QMessageBox.warning(self,'Calibration File Error','Data reduction failed because either no calibration file provided or the provided file or path do not exists',QMessageBox.Ok) else: QMessageBox.warning(self,'Data File Error','No data file provided', QMessageBox.Ok) def azimuthalRegionChanged(self): minp,maxp=self.azimuthalRegion.getRegion() self.azimuthalRangeLineEdit.setText('%.1f:%.1f'%(minp,maxp)) self.azimuthalRange=[minp,maxp] self.set_externally=True def reduce_multiple(self): """ Reduce multiple files """ try: i=0 self.progressBar.setRange(0,len(self.dataFiles)) self.progressBar.setValue(i) self.statusLabel.setText('<font color="red">Busy</font>') for file in self.dataFiles: self.dataFile=file QApplication.processEvents() self.reduceData() i=i+1 self.progressBar.setValue(i) QApplication.processEvents() self.statusLabel.setText('<font color="green">Idle</font>') self.progressBar.setValue(0) except: QMessageBox.warning(self,'File error','No data files to reduce',QMessageBox.Ok) def saveData(self): """ saves the extracted data into a file """ if not os.path.exists(self.extractedFolder): os.makedirs(self.extractedFolder) filename=os.path.join(self.extractedFolder,os.path.splitext(os.path.basename(self.dataFile))[0]+'.txt') headers='File extracted on '+time.asctime()+'\n' headers='Files used for extraction are:\n' headers+='Data file: '+self.dataFile+'\n' if self.darkFile is not None: headers+='Dark file: '+self.darkFile+'\n' else: headers+='Dark file: None\n' headers+='Poni file: '+self.poniFile+'\n' if self.maskFile is not None: headers+='mask file: '+self.maskFile+'\n' else: headers+='mask file: None\n' for key in self.header.keys(): headers+=key+'='+str(self.header[key])+'\n' headers+="col_names=['Q (inv Angs)','Int','Int_err']\n" headers+='Q (inv Angs)\tInt\tInt_err' data=vstack((self.q,self.I,self.Ierr)).T savetxt(filename,data,header=headers,comments='#')