def show_items(self,filedict,number): layout=QVBoxLayout() self.listView[number] = QtWidgets.QListView() self.listView[number].setObjectName("listView"+str(number)) slm=QStringListModel() slm.title=number name=list() # print(filedict) for i in filedict: name.append(i) slm.qList=name slm.setStringList(slm.qList) slm.data=filedict self.listView[number].setModel(slm) self.listView[number].clicked.connect(self.show_detail) layout.addWidget(self.listView[number]) self.tablist[number].setLayout(layout)
class PostgresProjectListModel(QObject): """Qt list model representing available projects saved in PostgreSQL.""" model_changed = pyqtSignal() def __init__(self, host, port, dbname, schema, authcfg, sslmode, *args, **kwargs): super().__init__(*args, **kwargs) self.host = host self.port = port self.dbname = dbname self.schema = schema self.authcfg = authcfg self.sslmode = sslmode self.model = QStringListModel() def refresh_data(self): """Refresh the list of projects saved in PostgreSQL.""" fetch_projects = PostgresProjectDownloader( host=self.host, port=self.port, dbname=self.dbname, schema=self.schema, authcfg=self.authcfg, sslmode=self.sslmode, ) self.model.setStringList(list(fetch_projects())) self.model_changed.emit() def project_at_index(self, index): """Return the name of project at given index.""" return self.model.data(index, Qt.DisplayRole) def index_for_project_name(self, project_name): """Return the index of the given name in project list.""" indices = self.model.match(self.model.index(0, 0), Qt.DisplayRole, project_name) return indices[0] if indices else None
class SleaZynth(QMainWindow): autoSaveFile = 'auto.save' texsize = 512 samplerate = 44100 def __init__(self): QMainWindow.__init__(self) self.ui = Ui_MainWindow() self.ui.setupUi(self) self.show() self.initModelView() self.initSignals() self.initState() self.autoLoad() self.initAMaySyn() self.initAudio() def keyPressEvent(self, event): if event.key() == Qt.Key_Escape: self.close() elif event.key() == Qt.Key_F1: self.debugOutput() if event.modifiers() & Qt.ControlModifier: if event.key() == Qt.Key_S: self.autoSave() elif event.key() == Qt.Key_L: self.autoLoad() elif event.key() == Qt.Key_T: self.renderWhateverWasLast() def closeEvent(self, event): QApplication.quit() def initSignals(self): self.ui.btnChooseFilename.clicked.connect(self.loadAndImportMayson) self.ui.btnImport.clicked.connect(self.importMayson) self.ui.btnExport.clicked.connect(self.exportChangedMayson) self.ui.checkAutoReimport.clicked.connect(self.toggleAutoReimport) self.ui.checkAutoRender.clicked.connect(self.toggleAutoRender) self.ui.editFilename.editingFinished.connect( partial(self.updateStateFromUI, only='maysonFile')) self.ui.editBPM.editingFinished.connect( partial(self.updateStateFromUI, only='BPM')) self.ui.spinBOffset.valueChanged.connect( partial(self.updateStateFromUI, only='B_offset')) self.ui.spinBStop.valueChanged.connect( partial(self.updateStateFromUI, only='B_stop')) self.ui.spinLevelSyn.valueChanged.connect( partial(self.updateStateFromUI, only='level_syn')) self.ui.spinLevelDrum.valueChanged.connect( partial(self.updateStateFromUI, only='level_drum')) self.ui.checkWriteWAV.clicked.connect( partial(self.updateStateFromUI, only='writeWAV')) self.ui.spinTimeShift.valueChanged.connect( partial(self.updateStateFromUI, only='extraTimeShift')) self.ui.editTrackName.textChanged.connect(self.trackSetName) self.ui.spinTrackVolume.valueChanged.connect(self.trackSetVolume) self.ui.checkTrackMute.stateChanged.connect(self.trackSetMute) self.ui.spinModOn.valueChanged.connect(self.moduleSetModOn) self.ui.spinModTranspose.valueChanged.connect(self.moduleSetTranspose) self.ui.btnApplyPattern.clicked.connect(self.moduleSetPattern) self.ui.btnApplyNote.clicked.connect(self.noteApplyChanges) self.ui.btnTrackClone.clicked.connect(self.trackClone) self.ui.btnTrackDelete.clicked.connect(self.trackDelete) self.ui.btnRandomSynth.clicked.connect(self.trackSetRandomSynth) self.ui.btnRandomizeSynth.clicked.connect(self.synthRandomize) self.ui.btnSaveSynth.clicked.connect(self.synthHardClone) self.ui.btnApplySynthName.clicked.connect(self.synthChangeName) self.ui.btnReloadSyn.clicked.connect(self.loadSynthsFromSynFile) self.ui.btnRenderModule.clicked.connect(self.renderModule) self.ui.btnRenderTrack.clicked.connect(self.renderTrack) self.ui.btnRenderSong.clicked.connect(self.renderSong) self.ui.btnStopPlayback.clicked.connect(self.stopPlayback) # model/view signals self.ui.trackList.selectionModel().currentChanged.connect( self.trackLoad) self.ui.patternCombox.currentIndexChanged.connect(self.patternLoad) self.ui.moduleList.selectionModel().currentChanged.connect( self.moduleLoad) self.ui.synthList.selectionModel().currentChanged.connect( self.trackSetSynth) self.ui.noteList.selectionModel().currentChanged.connect(self.noteLoad) self.ui.drumList.selectionModel().currentChanged.connect( self.noteSetDrum) def initModelView(self): self.trackModel = TrackModel() self.ui.trackList.setModel(self.trackModel) self.moduleModel = ModuleModel() self.ui.moduleList.setModel(self.moduleModel) self.patternModel = PatternModel() self.ui.patternCombox.setModel(self.patternModel) self.noteModel = NoteModel() self.ui.noteList.setModel(self.noteModel) self.synthModel = QStringListModel() self.ui.synthList.setModel(self.synthModel) self.drumModel = QStringListModel() self.ui.drumList.setModel(self.drumModel) self.noteModel.dataChanged.connect( self.updateModulesWithChangedPattern) self.noteModel.reloadNoteParameters.connect(self.noteLoad) def initState(self): self.state = { 'maysonFile': '', 'autoReimport': False, 'autoRender': False, 'lastRendered': '', 'writeWAV': False, 'selectedTrack': 0, 'selectedModule': 0, 'extraTimeShift': 0, } self.info = {} self.patterns = [] self.synths = [] self.drumkit = [] self.amaysyn = None self.fileObserver = None def loadAndImportMayson(self): name, _ = QFileDialog.getOpenFileName( self, 'Load MAYSON file', '', 'aMaySyn export *.mayson(*.mayson)') if name == '': return self.state['maysonFile'] = name self.state['title'], self.state[ 'synFile'] = self.getTitleAndSynFromMayson(name) self.autoSave() self.importMayson() def importMayson(self): maysonData = {} try: file = open(self.state['maysonFile'], 'r') maysonData = json.load(file) except FileNotFoundError: print( f"{self.state['maysonFile']} could not be imported. make sure that it exists, or choose another one." ) self.loadAndImportMayson() except json.decoder.JSONDecodeError: print( f"{self.state['maysonFile']} is changing right now, pause for 1 sec..." ) sleep(1) self.importMayson() finally: file.close() if maysonData == {}: return self.info = maysonData['info'] self.info.update({'title': self.state['title']}) if self.amaysyn is not None: self.amaysyn.updateState(info=self.info) self.trackModel.setTracks(maysonData['tracks']) self.patternModel.setPatterns(maysonData['patterns']) self.synthModel.setStringList(maysonData['synths']) self.drumModel.setStringList(maysonData['drumkit']) self.trackModel.layoutChanged.emit() if self.state['selectedTrack'] >= self.trackModel.rowCount(): self.state['selectedTrack'] = 0 self.selectIndex(self.ui.trackList, self.trackModel, self.state['selectedTrack']) if self.state['selectedModule'] >= self.moduleModel.rowCount(): self.state['selectedModule'] = 0 self.selectIndex(self.ui.moduleList, self.moduleModel, self.state['selectedModule']) self.noteModel.layoutChanged.emit() if self.noteModel.rowCount() > 0: self.selectIndex(self.ui.noteList, self.noteModel, 0) self.synthModel.layoutChanged.emit() self.drumModel.layoutChanged.emit() if self.drumIndex().isValid(): self.selectIndex(self.ui.drumList, self.drumModel, self.drumIndex().row()) self.applyStateToUI() def exportChangedMayson(self): name, _ = QFileDialog.getSaveFileName( self, 'Export with Changes', self.state['maysonFile'], 'aMaySyn export *.mayson(*.mayson)') if name == '': return data = { 'info': self.info, 'tracks': self.trackModel.tracks, 'patterns': self.patternModel.patterns, 'synths': self.synthModel.stringList(), 'drumkit': self.drumModel.stringList(), } file = open(name, 'w') json.dump(data, file) file.close() def updateStateFromUI(self, only=None): if only is None or only == 'maysonFile': self.state.update({'maysonFile': self.ui.editFilename.text()}) title, synFile = self.getTitleAndSynFromMayson( self.state['maysonFile']) self.state.update({'synFile': synFile}) self.state.update({'title': title}) self.info['title'] = title if only is None or only == 'BPM': self.info['BPM'] = self.ui.editBPM.text() if only is None or only == 'B_offset': self.info['B_offset'] = self.ui.spinBOffset.value() if only is None or only == 'B_stop': self.info['B_stop'] = self.ui.spinBStop.value() if only is None or only == 'level_syn': self.info['level_syn'] == self.ui.spinLevelSyn.value() if only is None or only == 'level_drum': self.info['level_drum'] == self.ui.spinLevelDrum.value() if only is None or only == 'writeWAV': self.state['writeWAV'] = self.ui.checkWriteWAV.isChecked() if only is None or only == 'extraTimeShift': self.state['extraTimeShift'] = self.ui.spinTimeShift.value() if self.amaysyn is not None: self.amaysyn.updateState(info=self.info, synFile=synFile) def applyStateToUI(self): self.ui.editFilename.setText(self.state['maysonFile']) # TODO: think about - do I want self.state['override']['BPM'] etc.?? self.ui.editBPM.setText(self.info['BPM']) self.ui.spinBOffset.setValue(self.info['B_offset']) self.ui.spinBStop.setValue(self.info['B_stop']) self.ui.spinLevelSyn.setValue(self.info['level_syn']) self.ui.spinLevelDrum.setValue(self.info['level_drum']) self.ui.checkAutoReimport.setChecked(self.state['autoReimport']) self.ui.checkAutoRender.setChecked(self.state['autoRender']) self.ui.checkWriteWAV.setChecked(self.state['writeWAV']) self.ui.spinTimeShift.setValue(self.state['extraTimeShift']) def autoSave(self): file = open(self.autoSaveFile, 'w') json.dump(self.state, file) file.close() def autoLoad(self): loadState = {} try: file = open(self.autoSaveFile, 'r') loadState = json.load(file) file.close() except FileNotFoundError: pass for key in loadState: self.state[key] = loadState[key] if 'autoReimport' in self.state: self.toggleAutoReimport(self.state['autoReimport']) if 'maysonFile' not in self.state or self.state['maysonFile'] == '': self.loadAndImportMayson() else: self.importMayson() def toggleAutoRender(self, checked): self.state['autoRender'] = checked self.autoSave() def toggleAutoReimport(self, checked): self.state['autoReimport'] = checked self.autoSave() if self.fileObserver is not None: self.fileObserver.stop() self.fileObserver.join() self.fileObserver = None if checked: file = self.state['maysonFile'] eventHandler = FileModifiedHandler(file) eventHandler.fileChanged.connect( self.importAndRender if self.state['autoRender'] else self. importMayson) self.fileObserver = Observer() self.fileObserver.schedule(eventHandler, path=path.dirname(file), recursive=False) self.fileObserver.start() def importAndRender(self): self.importMayson() if self.amaysyn is None: print( "You want to Reimport&Render, but why is aMaySyn not initialized? do some rendering first!" ) return self.renderWhateverWasLast() #################################### GENERAL HELPERS ########################################### def selectIndex(self, list, model, index): list.selectionModel().setCurrentIndex( model.createIndex(index, 0), QItemSelectionModel.SelectCurrent) def patternIndexOfName(self, name): patternNames = [p['name'] for p in self.patternModel.patterns] if name in patternNames: return patternNames.index(name) else: return None def getTitleAndSynFromMayson(self, maysonFile): synFile = '.'.join(maysonFile.split('.')[:-1]) + '.syn' title = '.'.join(path.basename(maysonFile).split('.')[:-1]) return title, synFile def placeholder(self): print("FUNCTION NOT IMPLEMENTED. Sorrriiiiiiieee! (not sorry.)") #################################### TRACK FUNCTIONALITY ####################################### def track(self): return self.trackModel.tracks[self.trackIndex().row( )] if self.trackModel.rowCount() > 0 else None def trackIndex(self): return self.ui.trackList.currentIndex() def trackModelChanged(self): self.trackModel.dataChanged.emit(self.trackIndex(), self.trackIndex()) def trackLoad(self, currentIndex): cTrack = self.trackModel.tracks[currentIndex.row()] self.ui.editTrackName.setText(cTrack['name']) self.ui.spinTrackVolume.setValue(100 * cTrack['par_norm']) self.ui.checkTrackMute.setChecked(not cTrack['mute']) self.moduleModel.setModules(cTrack['modules']) if len(cTrack['modules']) > 0: self.selectIndex(self.ui.moduleList, self.moduleModel, cTrack['current_module']) self.moduleLoad() self.selectIndex(self.ui.synthList, self.synthModel, cTrack['current_synth']) self.state['selectedTrack'] = currentIndex.row() def trackClone(self): self.trackModel.cloneRow(self.trackIndex().row()) def trackDelete(self): self.trackModel.removeRow(self.trackIndex().row()) def trackSetName(self, name): self.track()['name'] = name self.trackModelChanged() def trackSetVolume(self, value): self.track()['par_norm'] = round(value * .01, 3) self.trackModelChanged() def trackSetMute(self, state): self.track()['mute'] = (state != Qt.Checked) self.trackModelChanged() def trackSetSynth(self, index): self.track()['current_synth'] = self.synthModel.stringList().index( self.synthModel.data(index, Qt.DisplayRole)) self.ui.editSynthName.setText(self.synthName()) self.trackModelChanged() if self.synth()[0] == 'D': self.noteModel.useDrumkit(self.drumModel.stringList()) else: self.noteModel.useDrumkit(None) def trackSetRandomSynth(self): randomIndex = self.synthModel.createIndex( randint(0, len(self.instrumentSynths()) - 1), 0) self.ui.synthList.setCurrentIndex(randomIndex) #################################### MODULE FUNCTIONALITY ###################################### def module(self): return self.moduleModel.modules[self.moduleIndex().row( )] if self.moduleModel.rowCount() > 0 else None def moduleIndex(self): return self.ui.moduleList.currentIndex() def moduleModelChanged(self): self.moduleModel.dataChanged.emit(self.moduleIndex(), self.moduleIndex()) def moduleLoad(self, currentIndex=None): if currentIndex is None: cModule = self.module() else: cModule = self.moduleModel.modules[currentIndex.row()] self.state['selectedModule'] = currentIndex.row() self.ui.patternCombox.setCurrentIndex( self.patternIndexOfName(cModule['pattern']['name'])) self.ui.spinModOn.setValue(cModule['mod_on']) self.ui.spinModTranspose.setValue(cModule['transpose']) def moduleAssignPattern(self, pattern): self.module()['pattern'] = pattern # deepcopy(pattern) def moduleSetPattern(self): self.moduleAssignPattern(self.pattern()) self.moduleModelChanged() def moduleSetModOn(self, value): self.module()['mod_on'] = self.ui.spinModOn.value() self.moduleModelChanged() def moduleSetTranspose(self, value): self.module()['transpose'] = self.ui.spinModTranspose.value() self.moduleModelChanged() #################################### PATTERN FUNCTIONALITY ##################################### def pattern(self): return self.patternModel.patterns[ self.patternIndex()] if self.patternModel.rowCount() > 0 else None def patternIndex(self): return self.ui.patternCombox.currentIndex() def patternLoad(self, currentIndex): cPattern = self.patternModel.patterns[currentIndex] self.noteModel.setNotes(cPattern['notes']) def updateModulesWithChangedPattern(self, rowBegin, rowEnd): self.moduleAssignPattern(self.pattern()) self.moduleModelChanged() self.trackModel.updateModulesWithChangedPattern(self.pattern()) self.trackModelChanged() #################################### NOTE FUNCTIONALITY ######################################## def note(self): return self.noteModel.notes[ self.noteIndex().row()] if self.noteModel.rowCount() > 0 else None def noteIndex(self): return self.ui.noteList.currentIndex() def noteModelChanged(self): self.noteModel.dataChanged.emit(self.noteIndex(), self.noteIndex()) def noteLoad(self, currentIndex): self.ui.editNote.setText( self.noteModel.data(currentIndex, Qt.DisplayRole)) self.ui.editNote.setCursorPosition(0) def noteApplyChanges(self): self.noteModel.changeByString(self.noteIndex(), self.ui.editNote.text()) def noteSetDrum(self, currentIndex): if self.drum() is not None: self.noteModel.changeDrumTo(self.noteIndex(), self.drum()) ################################ SYNTH / DRUM FUNCTIONALITY #################################### def synth(self): return self.synthModel.data(self.ui.synthList.currentIndex(), Qt.DisplayRole) def synthName(self): return self.synth()[2:] def instrumentSynths(self): return [ I_synth for I_synth in self.synthModel.stringList() if I_synth[0] == 'I' ] def drum(self): if not self.drumIndex(): print("LOLOLOL DRUM INDEX IS NONE (should never happen)") if not self.drumIndex().isValid(): print("LOLOLOL DRUM INDEX NOT VALID") return self.drumModel.data(self.drumIndex(), Qt.DisplayRole) def drumIndex(self): return self.ui.drumList.currentIndex() def synthRandomize(self): self.amaysyn.aMaySynatize(reshuffle_randoms=True) def synthHardClone(self): if self.synth()[0] == 'D': self.synthHardCloneDrum(self) return else: count = 0 oldID = self.synthName() synths = self.instrumentSynths() while True: formID = oldID + '.' + str(count) print("TRYING", formID, synths) if 'I_' + formID not in synths: break count += 1 try: formTemplate = next( form for form in self.amaysyn.last_synatized_forms if form['id'] == oldID) formType = formTemplate['type'] formMode = formTemplate['mode'] formBody = ' '.join(key + '=' + formTemplate[key] for key in formTemplate if key not in ['type', 'id', 'mode']) if formMode: formBody += ' mode=' + ','.join(formMode) except StopIteration: print( "Current synth is not compiled yet. Do so and try again.") return except: print("could not CLONE HARD:", formID, formTemplate) raise else: with open(self.state['synFile'], mode='a') as filehandle: filehandle.write('\n' + formType + 4 * ' ' + formID + 4 * ' ' + formBody) self.loadSynthsFromSynFile() def synthHardDrum(self): print("NOT IMPLEMENTED YET") return count = 0 oldID = self.synthName() synths = self.instrumentSynths() while True: formID = oldID + '.' + str(count) print("TRYING", formID, synths) if 'I_' + formID not in synths: break count += 1 try: formTemplate = next(form for form in self.amaysyn.last_synatized_forms if form['id'] == oldID) formType = formTemplate['type'] formMode = formTemplate['mode'] formBody = ' '.join(key + '=' + formTemplate[key] for key in formTemplate if key not in ['type', 'id', 'mode']) if formMode: formBody += ' mode=' + ','.join(formMode) except StopIteration: print("Current synth is not compiled yet. Do so and try again.") return except: print("could not CLONE HARD:", formID, formTemplate) raise else: with open(self.state['synFile'], mode='a') as filehandle: filehandle.write('\n' + formType + 4 * ' ' + formID + 4 * ' ' + formBody) self.loadSynthsFromSynFile() def synthChangeName(self): if self.synth()[0] != 'I': print("Nah. Select an instrument synth (I_blabloo)") return newID = self.ui.editSynthName.text() if newID == '': return formID = self.synthName() tmpFile = self.state['synFile'] + '.tmp' move(self.state['synFile'], tmpFile) with open(tmpFile, mode='r') as tmp_handle: with open(self.state['synFile'], mode='w') as new_handle: for line in tmp_handle.readlines(): lineparse = line.split() if len(lineparse) > 2 and lineparse[0] in [ 'main', 'maindrum' ] and lineparse[1] == formID: new_handle.write( line.replace(' ' + formID + ' ', ' ' + newID + ' ')) else: new_handle.write(line) self.loadSynthsFromSynFile() def loadSynthsFromSynFile(self): self.amaysyn.aMaySynatize() self.synthModel.setStringList(self.amaysyn.synths) self.synthModel.dataChanged.emit( self.synthModel.createIndex(0, 0), self.synthModel.createIndex(self.synthModel.rowCount(), 0)) self.drumModel.setStringList(self.amaysyn.drumkit) self.drumModel.dataChanged.emit( self.drumModel.createIndex(0, 0), self.drumModel.createIndex(self.drumModel.rowCount(), 0)) self.trackModel.setSynthList(self.amaysyn.synths) self.trackModelChanged() # TODO: function to change drumkit order / assignment? ######################################## SleaZYNTHesizer ####################################### def initAMaySyn(self): self.amaysyn = aMaySynBuilder(self, self.state['synFile'], self.info) def initAudio(self): self.audioformat = QAudioFormat() self.audioformat.setSampleRate(self.samplerate) self.audioformat.setChannelCount(2) self.audioformat.setSampleSize(32) self.audioformat.setCodec('audio/pcm') self.audioformat.setByteOrder(QAudioFormat.LittleEndian) self.audioformat.setSampleType(QAudioFormat.Float) self.audiooutput = QAudioOutput(self.audioformat) self.audiooutput.setVolume(1.0) def stopPlayback(self): self.audiooutput.stop() def renderWhateverWasLast(self): if self.state['lastRendered'] == 'module': self.renderModule() elif self.state['lastRendered'] == 'track': self.renderTrack() else: self.renderSong() def renderModule(self): print(self.track(), self.module()) self.state['lastRendered'] = 'module' restoreMute = self.track()['mute'] self.track()['mute'] = False modInfo = deepcopy(self.info) modInfo['B_offset'] = self.module()['mod_on'] modInfo['B_stop'] = self.module()['mod_on'] + self.module( )['pattern']['length'] self.amaysyn.info = modInfo self.amaysyn.extra_time_shift = self.state['extraTimeShift'] shader = self.amaysyn.build(tracks=[self.track()], patterns=[self.module()['pattern']]) self.amaysyn.info = self.info self.track()['mute'] = restoreMute self.executeShader(shader) def renderTrack(self): self.state['lastRendered'] = 'track' restoreMute = self.track()['mute'] self.track()['mute'] = False self.amaysyn.extra_time_shift = self.state['extraTimeShift'] shader = self.amaysyn.build(tracks=[self.track()], patterns=self.patternModel.patterns) self.track()['mute'] = restoreMute self.executeShader(shader) def renderSong(self): self.state['lastRendered'] = 'song' self.amaysyn.extra_time_shift = self.state['extraTimeShift'] shader = self.amaysyn.build(tracks=self.trackModel.tracks, patterns=self.patternModel.patterns) self.executeShader(shader) def executeShader(self, shader): self.ui.codeEditor.clear() self.ui.codeEditor.insertPlainText( shader.replace(4 * ' ', '\t').replace(3 * ' ', '\t')) self.ui.codeEditor.ensureCursorVisible() sequenceLength = len( self.amaysyn.sequence) if self.amaysyn.sequence is not None else 0 if not self.amaysyn.useSequenceTexture and sequenceLength > pow(2, 14): QMessageBox.critical( self, "I CAN'T", f"Either switch to using the Sequence Texture (ask QM), or reduce the sequence size by limiting the offset/stop positions or muting tracks.\nCurrent sequence length is:\n{sequenceLength} > {pow(2,14)}" ) return self.bytearray = self.amaysyn.executeShader( shader, self.samplerate, self.texsize, renderWAV=self.state['writeWAV']) self.audiobuffer = QBuffer(self.bytearray) self.audiobuffer.open(QIODevice.ReadOnly) self.audiooutput.stop() self.audiooutput.start(self.audiobuffer) ###################################### DEBUG STUFF ############################################# def debugOutput(self): print("TRACKS:", self.trackModel.rowCount()) print("===== TRACK ACCUMULATION =====") track_accumulate = 0 for t in self.trackModel.tracks: delta = len(t['modules']) print( f"{t['name']:>20} {track_accumulate:>10} {track_accumulate + delta:>10}" ) track_accumulate += delta print("END AT", track_accumulate) print() print("PATTERNS:", self.patternModel.rowCount()) print("===== PATTERN ACCUMULATION =====") pattern_accumulate = 0 for p in self.patternModel.patterns: delta = len(p['notes']) print( f"{p['name']:>20} {pattern_accumulate:>10} {pattern_accumulate + delta:>10}" ) pattern_accumulate += delta print("END AT", pattern_accumulate) print()
class MyControlPanel(QWidget): flagselected = pyqtSignal(int) def __init__(self, model, parent=None): QWidget.__init__(self, parent) self.ui = Ui_ControlPanel() self.ui.setupUi(self) self.model = model sortmenu = QMenu() lIinitial = None searchLegend = model.datafield(model.cfg.legendInitial) for i, col in enumerate( sorted(set(self.model.cols) - self.model.cfg.internalCols, key=self.model.prettyname)): # Sort Menu a = sortmenu.addAction(self.model.prettyname(col)) a.setData(col) a.triggered.connect(self.onAddSortField) # Combo boxes self.ui.partComboBox.addItem(self.model.prettyname(col), col) self.ui.legendComboBox.addItem(self.model.prettyname(col), col) if col == searchLegend: lIinitial = i # Sort self.ui.addSortField.setMenu(sortmenu) self.ui.sortByListWidget.itemSelectionChanged.connect( self.onSelectionChange) self.onSelectionChange() self.ui.removeSortField.clicked.connect(self.onRemoveSortField) self.ui.moveSortField.clicked.connect(self.onMoveSortFieldUp) self.ui.sortAscendingCheckBox.clicked.connect( self.onSortAscendingChange) # Legend self.legend = None self.ui.legendGroupBox.setChecked(lIinitial is not None) self.legendModel = QStandardItemModel() self.ui.legendListView.setModel(self.legendModel) self.ui.legendListView.doubleClicked.connect(self.selectLegendColor) if lIinitial is not None: self.ui.legendComboBox.setCurrentIndex(lIinitial) self.ui.legendComboBox.currentIndexChanged.connect( self.onLegendComboChange) self.onLegendComboChange() self.ui.legendGroupBox.toggled.connect(self.calcLegend) self.ui.legendBinSpinBox.editingFinished.connect(self.calcLegend) self.ui.legendMaxSpinBox.editingFinished.connect(self.calcLegend) self.ui.legendMinSpinBox.editingFinished.connect(self.calcLegend) self.legendModel.dataChanged.connect(self.updateModelLegend) # Partition self.partitions = None self.partitionModel = QStringListModel() self.ui.partitionList.setModel(self.partitionModel) self.ui.partitionList.selectionModel().currentChanged.connect( self.onSelectPartition) self.ui.partComboBox.currentIndexChanged.connect( self.onPartitionComboChange) self.onPartitionComboChange() self.ui.partitionBox.toggled.connect(self.calcPartitions) self.ui.partBinSpinBox.valueChanged.connect(self.calcPartitions) self.ui.partMaxSpinBox.valueChanged.connect(self.calcPartitions) self.ui.partMinSpinBox.valueChanged.connect(self.calcPartitions) # Flags if self.model.flagFilter.isActive(): self.ui.flagIgnore.setChecked(True) if self.model.flagFilter.invert: self.ui.flagExclude.setChecked(True) else: self.ui.flagKeep.setChecked(True) else: self.ui.flagIgnore.setChecked(True) self.model.flagFilter.filterchange.connect(self.onFlagChange) self.flagModel = QStringListModel() self.ui.flaggedList.setModel(self.flagModel) self.ui.flagIgnore.clicked.connect(self.onIgnoreFlags) self.ui.flagExclude.clicked.connect(self.onExcludeFlags) self.ui.flagKeep.clicked.connect(self.onOnlyFlags) self.ui.flaggedList.selectionModel().currentChanged.connect( self.onSelectFlag) # Limits self.ui.limitCheckBox.clicked.connect(self.onLimitChange) self.ui.limTopButton.clicked.connect(self.onLimitChange) self.ui.limRandomButton.clicked.connect(self.onLimitChange) self.ui.limitSpinBox.editingFinished.connect(self.onLimitChange) self.onLimitChange() # Filters self.filtermodel = FilterModel(model.topfilter, model) self.ui.filterTreeView.setEditTriggers( QAbstractItemView.AllEditTriggers) self.ui.filterTreeView.setModel(self.filtermodel) if qt5: self.ui.filterTreeView.header().setSectionResizeMode( 0, QHeaderView.ResizeToContents) self.ui.filterTreeView.header().setSectionResizeMode( 1, QHeaderView.ResizeToContents) else: self.ui.filterTreeView.header().setResizeMode( 0, QHeaderView.ResizeToContents) self.ui.filterTreeView.header().setResizeMode( 1, QHeaderView.ResizeToContents) self.ui.filterTreeView.setItemDelegate( filterEditDelegate.FilterItemDelegate()) self.filtermodel.rowsInserted.connect(self.ui.filterTreeView.expandAll) self.ui.filterTreeView.expandAll() # Filters Save/Load self.ui.saveFiltersButton.clicked.connect(self.onSaveFilters) self.ui.loadFiltersButton.clicked.connect(self.onLoadFilters) # Sorting def onAddSortField(self): field = self.sender().data() self.ui.sortByListWidget.addItem(self.model.prettyname(field)) self.model.sortlst.append(field) self.model.onSortChange() def onSelectionChange(self): hasSelection = bool(len(self.ui.sortByListWidget.selectedItems())) self.ui.removeSortField.setEnabled(hasSelection) canMove = hasSelection for item in self.ui.sortByListWidget.selectedItems(): canMove &= self.ui.sortByListWidget.row(item) != 0 self.ui.moveSortField.setEnabled(canMove) def onRemoveSortField(self): rows = [] for item in self.ui.sortByListWidget.selectedItems(): rows.append(self.ui.sortByListWidget.row(item)) for r in sorted(rows, reverse=True): del self.model.sortlst[r] self.ui.sortByListWidget.takeItem(r) self.model.onSortChange() self.onSelectionChange() def onMoveSortFieldUp(self): rows = [] for item in self.ui.sortByListWidget.selectedItems(): rows.append(self.ui.sortByListWidget.row(item)) for r in sorted(rows): self.model.sortlst.insert(r - 1, self.model.sortlst.pop(r)) self.ui.sortByListWidget.insertItem( r - 1, self.ui.sortByListWidget.takeItem(r)) self.ui.sortByListWidget.setCurrentItem( self.ui.sortByListWidget.item(r - 1)) self.model.onSortChange() def setSort(self, lst): for field in lst: self.ui.sortByListWidget.addItem(self.model.prettyname(field)) self.model.sortlst.append(field) self.model.onSortChange() def onSortAscendingChange(self): self.model.reverseSort = not self.ui.sortAscendingCheckBox.isChecked() self.model.onSortChange() # Legends def onLegendComboChange(self): field = self.ui.legendComboBox.itemData( self.ui.legendComboBox.currentIndex()) enable = not self.model.isCategorical(field) if self.ui.legendGroupBox.isChecked() or not enable: self.ui.legendBinSpinBox.setEnabled(enable) self.ui.legendMinSpinBox.setEnabled(enable) self.ui.legendMaxSpinBox.setEnabled(enable) self.ui.legendMinSpinBox.setMinimum(self.model.fieldmin(field)) self.ui.legendMaxSpinBox.setMinimum(self.model.fieldmin(field)) self.ui.legendMinSpinBox.setMaximum(self.model.fieldmax(field)) self.ui.legendMaxSpinBox.setMaximum(self.model.fieldmax(field)) self.ui.legendMinSpinBox.setValue(self.model.fieldmin(field)) self.ui.legendMaxSpinBox.setValue(self.model.fieldmax(field)) self.calcLegend() def calcLegend(self): self.legendModel.clear() if not self.ui.legendGroupBox.isChecked(): self.legend = None self.legendModel.clear() self.updateModelLegend() return field = self.ui.legendComboBox.itemData( self.ui.legendComboBox.currentIndex()) num = None minimum = None maximum = None if self.ui.legendBinSpinBox.isEnabled(): num = self.ui.legendBinSpinBox.value() minimum = self.ui.legendMinSpinBox.value() maximum = self.ui.legendMaxSpinBox.value() self.legend = self.model.partition(field, minimum, maximum, num) lst = list(sorted(self.legend.keys())) for i, s in enumerate(lst): c = self.model.cfg.color(field, s, i) item = QStandardItem(s) item.setData(QColor(c), Qt.DecorationRole) item.setData(s) self.legendModel.appendRow(item) self.updateModelLegend() def selectLegendColor(self, index): c = QColorDialog.getColor( self.legendModel.data(index, Qt.DecorationRole), self) if c.isValid(): self.legendModel.setData(index, c, Qt.DecorationRole) def updateModelLegend(self): stack = None if self.legend is not None: stack = [[], [], []] for i in range(self.legendModel.rowCount()): it = self.legendModel.item(i) stack[0].append(self.legend[it.data()]) stack[1].append(it.data(Qt.DecorationRole).name()) stack[2].append(it.text()) self.model.setStacks(stack) # Partitions def onPartitionComboChange(self): field = self.ui.partComboBox.itemData( self.ui.partComboBox.currentIndex()) enable = not self.model.isCategorical(field) if self.ui.partitionBox.isChecked() or not enable: self.ui.partBinSpinBox.setEnabled(enable) self.ui.partMinSpinBox.setEnabled(enable) self.ui.partMaxSpinBox.setEnabled(enable) self.ui.partMinSpinBox.setMinimum(self.model.fieldmin(field)) self.ui.partMaxSpinBox.setMinimum(self.model.fieldmin(field)) self.ui.partMinSpinBox.setMaximum(self.model.fieldmax(field)) self.ui.partMaxSpinBox.setMaximum(self.model.fieldmax(field)) self.ui.partMinSpinBox.setValue(self.model.fieldmin(field)) self.ui.partMaxSpinBox.setValue(self.model.fieldmax(field)) self.calcPartitions() def calcPartitions(self): self.model.clearPartition() if not self.ui.partitionBox.isChecked(): self.partitions = None self.partitionModel.setStringList([]) return field = self.ui.partComboBox.itemData( self.ui.partComboBox.currentIndex()) num = None minimum = None maximum = None if self.ui.partBinSpinBox.isEnabled(): num = self.ui.partBinSpinBox.value() minimum = self.ui.partMinSpinBox.value() maximum = self.ui.partMaxSpinBox.value() self.partitions = self.model.partition(field, minimum, maximum, num) self.partitionModel.setStringList(sorted(self.partitions.keys())) def onSelectPartition(self): part = self.partitionModel.data( self.ui.partitionList.selectionModel().currentIndex(), Qt.DisplayRole) self.model.setPartition(self.partitions[part]) # Flags def onFlagChange(self): slist = [] for i in np.where(self.model.flagFilter.keep)[0]: slist.append(str(i)) self.flagModel.setStringList(slist) def onIgnoreFlags(self, checked): if checked: self.model.flagFilter.setActive(False) def onExcludeFlags(self, checked): if checked: self.model.flagFilter.setInvert(True) self.model.flagFilter.setActive(True) def onOnlyFlags(self, checked): if checked: self.model.flagFilter.setInvert(False) self.model.flagFilter.setActive(True) def onSelectFlag(self): row = int( self.flagModel.data( self.ui.flaggedList.selectionModel().currentIndex(), Qt.DisplayRole)) self.flagselected.emit(row) # Limits def onLimitChange(self): if self.ui.limitCheckBox.isChecked(): self.model.limit = self.ui.limitSpinBox.value() else: self.model.limit = None self.model.limitModeRandom = self.ui.limRandomButton.isChecked() def setLimit(self, l): self.ui.limitSpinBox.setValue(l) self.ui.limitCheckBox.setChecked(True) self.onLimitChange() # Filters def onSaveFilters(self): name = QFileDialog.getSaveFileName(self, 'Save Filters As', filter='*.xml') if qt5: if name: self.model.saveFilters(name[0]) elif name is not None and len(name): self.model.saveFilters(name) def onLoadFilters(self): name = QFileDialog.getOpenFileName(self, 'Load Filter File', filter='*.xml') if qt5: if name: self.model.loadFilters(name[0]) elif name is not None and len(name): self.model.loadFilters(name)
class MyControlPanel(QWidget): flagselected = pyqtSignal(int) def __init__(self, model, parent=None): QWidget.__init__(self, parent) self.ui = Ui_ControlPanel() self.ui.setupUi(self) self.model = model sortmenu = QMenu() for i, col in enumerate( sorted(set(self.model.cols) - self.model.cfg.internalCols, key=self.model.prettyname)): # Sort Menu a = sortmenu.addAction(self.model.prettyname(col)) a.setData(col) a.triggered.connect(self.onAddSortField) # Sort self.ui.addSortField.setMenu(sortmenu) self.ui.sortByListWidget.itemSelectionChanged.connect( self.onSelectionChange) self.onSelectionChange() self.ui.removeSortField.clicked.connect(self.onRemoveSortField) self.ui.moveSortField.clicked.connect(self.onMoveSortFieldUp) self.ui.sortAscendingCheckBox.clicked.connect( self.onSortAscendingChange) # Legend self.legendWidget = partitionWidget.MyPartitionWidget( model, self.model.setStacks, model.cfg.legendInitial, True, True, self) self.legendWidget.ui.description.setText( "Change field and colors for stacked histograms. Double click to edit color. Single click on selected to edit label. Checkboxes only affect visibility, not the selected items." ) self.legendWidget.ui.groupBox.setTitle("Histogram Legends") self.ui.verticalLayout_7.insertWidget(1, self.legendWidget) # Partition self.partWidget = partitionWidget.MyPartitionWidget( model, self.model.setPartition, model.cfg.partitionInitial, True, False, self) self.partWidget.ui.description.setText( "Partition the data. Selecting a partition changes the full dataset shown in plots to just match the partition. When partitions are active, output is split with one output for each partition." ) self.partWidget.ui.groupBox.setTitle("Partitions") self.ui.verticalLayout_7.insertWidget(2, self.partWidget) # Flags if self.model.flagFilter.isActive(): self.ui.flagIgnore.setChecked(True) if self.model.flagFilter.invert: self.ui.flagExclude.setChecked(True) else: self.ui.flagKeep.setChecked(True) else: self.ui.flagIgnore.setChecked(True) self.model.flagFilter.filterchange.connect(self.onFlagChange) self.flagModel = QStringListModel() self.ui.flaggedList.setModel(self.flagModel) self.ui.flagIgnore.clicked.connect(self.onIgnoreFlags) self.ui.flagExclude.clicked.connect(self.onExcludeFlags) self.ui.flagKeep.clicked.connect(self.onOnlyFlags) self.ui.flaggedList.selectionModel().currentChanged.connect( self.onSelectFlag) # Limits self.ui.limitCheckBox.clicked.connect(self.onLimitChange) self.ui.limTopButton.clicked.connect(self.onLimitChange) self.ui.limRandomButton.clicked.connect(self.onLimitChange) self.ui.limitSpinBox.editingFinished.connect(self.onLimitChange) self.onLimitChange() # Filters self.filtermodel = FilterModel(model.topfilter, model) self.ui.filterTreeView.setEditTriggers( QAbstractItemView.AllEditTriggers) self.ui.filterTreeView.setModel(self.filtermodel) if qt5: self.ui.filterTreeView.header().setSectionResizeMode( 0, QHeaderView.ResizeToContents) self.ui.filterTreeView.header().setSectionResizeMode( 1, QHeaderView.ResizeToContents) else: self.ui.filterTreeView.header().setResizeMode( 0, QHeaderView.ResizeToContents) self.ui.filterTreeView.header().setResizeMode( 1, QHeaderView.ResizeToContents) self.ui.filterTreeView.setItemDelegate( filterEditDelegate.FilterItemDelegate()) self.filtermodel.rowsInserted.connect(self.ui.filterTreeView.expandAll) self.ui.filterTreeView.expandAll() # Filters Save/Load self.ui.saveFiltersButton.clicked.connect(self.onSaveFilters) self.ui.loadFiltersButton.clicked.connect(self.onLoadFilters) # Sorting def onAddSortField(self): field = self.sender().data() self.ui.sortByListWidget.addItem(self.model.prettyname(field)) self.model.sortlst.append(field) self.model.onSortChange() def onSelectionChange(self): hasSelection = bool(len(self.ui.sortByListWidget.selectedItems())) self.ui.removeSortField.setEnabled(hasSelection) canMove = hasSelection for item in self.ui.sortByListWidget.selectedItems(): canMove &= self.ui.sortByListWidget.row(item) != 0 self.ui.moveSortField.setEnabled(canMove) def onRemoveSortField(self): rows = [] for item in self.ui.sortByListWidget.selectedItems(): rows.append(self.ui.sortByListWidget.row(item)) for r in sorted(rows, reverse=True): del self.model.sortlst[r] self.ui.sortByListWidget.takeItem(r) self.model.onSortChange() self.onSelectionChange() def onMoveSortFieldUp(self): rows = [] for item in self.ui.sortByListWidget.selectedItems(): rows.append(self.ui.sortByListWidget.row(item)) for r in sorted(rows): self.model.sortlst.insert(r - 1, self.model.sortlst.pop(r)) self.ui.sortByListWidget.insertItem( r - 1, self.ui.sortByListWidget.takeItem(r)) self.ui.sortByListWidget.setCurrentItem( self.ui.sortByListWidget.item(r - 1)) self.model.onSortChange() def setSort(self, lst): for field in lst: self.ui.sortByListWidget.addItem(self.model.prettyname(field)) self.model.sortlst.append(field) self.model.onSortChange() def onSortAscendingChange(self): self.model.reverseSort = not self.ui.sortAscendingCheckBox.isChecked() self.model.onSortChange() # Flags def onFlagChange(self): slist = [] for i in np.where(self.model.flagFilter.keep)[0]: slist.append(str(i)) self.flagModel.setStringList(slist) def onIgnoreFlags(self, checked): if checked: self.model.flagFilter.setActive(False) def onExcludeFlags(self, checked): if checked: self.model.flagFilter.setInvert(True) self.model.flagFilter.setActive(True) def onOnlyFlags(self, checked): if checked: self.model.flagFilter.setInvert(False) self.model.flagFilter.setActive(True) def onSelectFlag(self): row = int( self.flagModel.data( self.ui.flaggedList.selectionModel().currentIndex(), Qt.DisplayRole)) self.flagselected.emit(row) # Limits def onLimitChange(self): if self.ui.limitCheckBox.isChecked(): self.model.limit = self.ui.limitSpinBox.value() else: self.model.limit = None self.model.limitModeRandom = self.ui.limRandomButton.isChecked() def setLimit(self, l): self.ui.limitSpinBox.setValue(l) self.ui.limitCheckBox.setChecked(True) self.onLimitChange() # Filters def onSaveFilters(self): name = QFileDialog.getSaveFileName(self, 'Save Filters As', filter='*.xml') if qt5: if name: self.model.saveFilters(name[0]) elif name is not None and len(name): self.model.saveFilters(name) def onLoadFilters(self): name = QFileDialog.getOpenFileName(self, 'Load Filter File', filter='*.xml') if qt5: if name: self.model.loadFilters(name[0]) elif name is not None and len(name): self.model.loadFilters(name)
class MainApp(QWidget): STRANGER_DANGER = 350 IMAGE_SIZE = (100, 100) stranger_color = (179, 20, 20) recognized_color = (59, 235, 62) def __init__(self, fps=30, parent=None): # type: (int, Optional[QWidget]) -> None super().__init__(parent=parent) self.pkg_path = path.dirname(path.dirname(path.abspath(__file__))) self.training_data_dir = path.join(self.pkg_path, 'train') self.models_dir = path.join(self.pkg_path, 'models') self.model_fname = 'fisherfaces.p' try: self.model = data_provider.load_model( path.join(self.models_dir, self.model_fname)) except AssertionError: self.model = None self.existing_labels = QStringListModel(self.get_existing_labels()) self.fps = fps self.video_size = QSize(640, 480) self.gray_image = None self.detected_faces = [] # Setup the UI self.main_layout = QHBoxLayout() self.setLayout(self.main_layout) self.control_layout = QVBoxLayout() self.control_layout.setSpacing(8) self.main_layout.addItem(self.control_layout) # Setup the existing label view self.labels_view = QListView(parent=self) self.labels_view.setModel(self.existing_labels) self.labels_view.setSelectionMode(QListView.SingleSelection) self.labels_view.setItemDelegate(CapitalizeDelegate(self)) self.control_layout.addWidget(self.labels_view) self.new_label_txt = QLineEdit(self) self.new_label_txt.returnPressed.connect(self.add_new_label) self.new_label_txt.returnPressed.connect(self.new_label_txt.clear) self.control_layout.addWidget(self.new_label_txt) self.add_button = QPushButton('Add Label', self) self.add_button.clicked.connect(self.add_new_label) self.control_layout.addWidget(self.add_button) # Setup the training area train_box = QGroupBox('Train', self) train_box_layout = QVBoxLayout() train_box.setLayout(train_box_layout) self.control_layout.addWidget(train_box) self.train_btn = QPushButton('Train', self) self.train_btn.clicked.connect(self.train) train_box_layout.addWidget(self.train_btn) self.control_layout.addStretch(0) # Add take picture shortcut self.take_picture_btn = QPushButton('Take picture', self) self.take_picture_btn.clicked.connect(self.take_picture) self.control_layout.addWidget(self.take_picture_btn) shortcut = QShortcut(QKeySequence('Space'), self, self.take_picture) shortcut.setWhatsThis('Take picture and add to training data.') # Add quit shortcut shortcut = QShortcut(QKeySequence('Esc'), self, self.close) shortcut.setWhatsThis('Quit') # Setup the main camera area self.image_label = QLabel(self) self.image_label.setFixedSize(self.video_size) self.main_layout.addWidget(self.image_label) # Setup the camera self.capture = cv2.VideoCapture(0) self.capture.set(cv2.CAP_PROP_FRAME_WIDTH, self.video_size.width()) self.capture.set(cv2.CAP_PROP_FRAME_HEIGHT, self.video_size.height()) self.timer = QTimer() self.timer.timeout.connect(self.display_video_stream) self.timer.start(int(1000 / self.fps)) def classify_face(self, image): if self.model is None: return label_idx, distances = self.model.predict(image.ravel(), True) label_idx, distance = label_idx[0], distances[0][label_idx] labels = self.existing_labels.stringList() return labels[label_idx], distance def get_training_data(self): """Read the images from disk into an n*(w*h) matrix.""" return data_provider.get_image_data_from_directory( self.training_data_dir) def train(self): X, y, mapping = self.get_training_data() # Inspect scree plot to determine appropriate number of PCA components classifier = PCALDAClassifier( n_components=2, pca_components=200, metric='euclidean', ).fit(X, y) # Replace the existing running model self.model = classifier # Save the classifier to file data_provider.save_model( classifier, path.join(self.models_dir, self.model_fname)) def add_new_label(self): new_label = self.new_label_txt.text() new_label = new_label.lower() # Prevent empty entries if len(new_label) < 3: return string_list = self.existing_labels.stringList() if new_label not in string_list: string_list.append(new_label) string_list.sort() self.existing_labels.setStringList(string_list) # Automatically select the added label selection_model = self.labels_view.selectionModel() index = self.existing_labels.index(string_list.index(new_label)) selection_model.setCurrentIndex(index, QItemSelectionModel.Select) def display_video_stream(self): """Read frame from camera and repaint QLabel widget.""" _, frame = self.capture.read() frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) frame = cv2.flip(frame, 1) # Use the Viola-Jones face detector to detect faces to classify face_cascade = cv2.CascadeClassifier(path.join( self.pkg_path, 'resources', 'haarcascade_frontalface_default.xml')) self.gray_image = gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) self.detected_faces = face_cascade.detectMultiScale(gray, 1.3, 5) for x, y, w, h in self.detected_faces: # Label the detected face as per the model face = gray[y:y + h, x:x + w] face = cv2.resize(face, self.IMAGE_SIZE) result = self.classify_face(face) # If a model is loaded, we can predict if result: predicted, distance = self.classify_face(face) if distance > self.STRANGER_DANGER: predicted = 'Stranger danger!' color = self.stranger_color else: predicted = predicted.capitalize() color = self.recognized_color # Draw a rectangle around the detected face cv2.rectangle(frame, (x, y), (x + w, y + h), color, 2) text = '%s (%.1f)' % (predicted, distance) cv2.putText(frame, text, (x, y + h + 15), cv2.FONT_HERSHEY_TRIPLEX, 0.5, color) else: # Draw a rectangle around the detected face cv2.rectangle(frame, (x, y), (x + w, y + h), self.stranger_color, 2) cv2.putText(frame, 'Stranger danger!', (x, y + h + 15), cv2.FONT_HERSHEY_TRIPLEX, 0.5, self.stranger_color) # Display the image in the image area image = QImage(frame, frame.shape[1], frame.shape[0], frame.strides[0], QImage.Format_RGB888) self.image_label.setPixmap(QPixmap.fromImage(image)) @contextmanager def stop_camera_feed(self): """Temporarly stop the feed and face detection.""" try: self.timer.stop() yield finally: self.timer.start(int(1000 / self.fps)) def take_picture(self): # Notify the user there were no faces detected if self.detected_faces is None or len(self.detected_faces) < 1: return raise NoFacesError() if len(self.detected_faces) > 1: return raise MultipleFacesError() with self.stop_camera_feed(): x, y, w, h = self.detected_faces[0] face = self.gray_image[y:y + h, x:x + w] face = cv2.resize(face, self.IMAGE_SIZE) denoised_image = cv2.fastNlMeansDenoising(face) if not self.selected_label: return self.save_image(denoised_image, self.selected_label) @property def selected_label(self): index = self.labels_view.selectedIndexes() if len(index) < 1: return None label = self.existing_labels.data(index[0], Qt.DisplayRole) return label def get_existing_labels(self): """Get a list of the currently existing labels""" return data_provider.get_folder_names(self.training_data_dir) def save_image(self, image: np.ndarray, label: str) -> None: """Save an image to disk in the appropriate directory.""" if not path.exists(self.training_data_dir): mkdir(self.training_data_dir) label_path = path.join(self.training_data_dir, label) if not path.exists(label_path): mkdir(label_path) existing_files = listdir(label_path) existing_files = map(lambda p: path.splitext(p)[0], existing_files) existing_files = list(map(int, existing_files)) last_fname = sorted(existing_files)[-1] if len(existing_files) else 0 fname = path.join(label_path, '%03d.png' % (last_fname + 1)) cv2.imwrite(fname, image)