class JsonReader(QWidget): """ """ def __init__(self): super().__init__() self.title = "Json metadata reader" self.left = 100 self.top = 100 self.width = 500 self.height = 500 self.initUI() def initUI(self): """ """ self.setWindowTitle(self.title) self.setGeometry(self.left, self.top, self.width, self.height) self.getbtn = QPushButton("Select file") self.getbtn.clicked.connect(self.get_file) self.draglbl = DragDropLabel(self) self.draglbl.setText("... or drop .{} file here".format( DragDropLabel.acceptedFormat.upper())) self.draglbl.setAlignment(QtCore.Qt.AlignCenter) self.draglbl.droppedFile.connect(self.open_file) self.layout = QGridLayout(self) self.layout.addWidget(self.getbtn, 0, 0) self.layout.addWidget(self.draglbl, 1, 0) self.layout.setColumnMinimumWidth(0, 500) self.setLayout(self.layout) self.show() def get_file(self): """ """ self.path, _ = QFileDialog.getOpenFileName( filter="Json files (*.json)") if not self.path: pass else: self.open_file(self.path) def open_file(self, filename): """ Parameters ---------- filename : Returns ------- """ self.filename = filename with open(filename) as json_file: self.metadata_dict = json.load(json_file) self.display_tree_widget(self.metadata_dict) def display_tree_widget(self, metadata): """Display the parameter tree from the experiment metadata. Parameters ---------- metadata : json metadata Returns ------- """ # Close previous tree widget self.close_tree() # Create a variable checking whether changes have been made to the parameter tree values self.has_changed = False # Create list with parameters for the tree self.parameters = self.create_parameters(self.fix_types(metadata)) # Add parameters for Save buttons and Image options self.parameters.append({ "name": "Metadata Image Options", "type": "group", "children": [ { "name": "Append Image", "type": "action" }, { "name": "View Image", "type": "action" }, ], }) self.parameters.append({ "name": "Save/Reset metadata", "type": "group", "children": [ { "name": "Save changes", "type": "action" }, { "name": "Reset changes", "type": "action" }, ], }) # Create tree of Parameter objects self.p = Parameter.create(name="params", type="group", children=self.parameters) # Check if Image parameters already exist. If they don't, # add them and update Parameter object self.children_list = [] for child in self.p.children(): self.children_list.append(str(child).split("'")[1]) # Save original state self.original_state = self.p.saveState() # Connect Save/Reset buttons to respective functions self.p.param("Save/Reset metadata", "Save changes").sigActivated.connect(self.save_treevals) self.p.param("Save/Reset metadata", "Reset changes").sigActivated.connect(self.reset) # Create ParameterTree widget self.tree = ParameterTree() self.tree.setParameters(self.p, showTop=False) self.tree.setWindowTitle("pyqtgraph example: Parameter Tree") # Display tree widget self.layout.addWidget(self.tree, 2, 0) # Send signal when any entry is changed self.p.sigTreeStateChanged.connect(self.change) def append_img(self): """Attach a .png image to the metadata file.""" self.imagefile, _ = QFileDialog.getOpenFileName( filter="png images (*.png)") if not self.imagefile: pass else: with open(self.imagefile, "rb") as f: self.img = f.read() self.encoded_img = base64.b64encode(self.img) self.encoded_img = self.encoded_img.decode("utf8") self.p.param("Metadata Image").setValue(self.encoded_img) self.p.param("Metadata Image").setDefault(self.encoded_img) def display_img(self): """Show image appended to the metadata file""" try: self.layout.removeWidget(self.imglbl) self.imglbl.deleteLater() self.imglbl = None except AttributeError: pass if not self.p.param("Metadata Image").defaultValue(): self.imglbl = QLabel("No image associated to this metadata") self.layout.addWidget(self.imglbl, 2, 1) try: self.layout.removeWidget(self.viewbtn) self.viewbtn.deleteLater() self.viewbtn = None except AttributeError: pass else: self.figstring = self.p.param("Metadata Image").defaultValue() self.figbytes = self.figstring.encode("utf8") self.figbytes = base64.b64decode(self.figbytes) self.viewbtn = QPushButton("Zoom image") self.viewbtn.clicked.connect(self.image_viewer) self.image = QtGui.QPixmap() self.image.loadFromData(self.figbytes) self.image = self.image.scaledToHeight(500) self.imglbl = QLabel() self.imglbl.setPixmap(self.image) self.layout.addWidget(self.imglbl, 0, 1, 3, 1) self.layout.addWidget(self.viewbtn, 3, 1) def image_viewer(self): """Open metadata image in the Image Viewer from PyQtGraph.""" self.win = QtGui.QMainWindow() self.win.resize(800, 800) self.fignp = np.array(Image.open(io.BytesIO(self.figbytes))) self.fignp = np.swapaxes(self.fignp, 0, 1) self.imv = pg.ImageView() self.imv.setImage(self.fignp) self.win.setCentralWidget(self.imv) self.win.setWindowTitle("Metadata Image") self.win.show() def save_treevals(self): """Save current values of the parameter tree into a dictionary.""" # Recover data from tree and store it in a dict self.treevals_dict = self.p.getValues() self.metadata_dict_mod = self.get_mod_dict(self.treevals_dict) # Nasty way to make new dict (with modified metadata) with same structure as the original one self.metadata_dict_mod.pop("Save/Reset metadata") self.metadata_dict_mod.pop("Metadata Image Options") self.metadata_dict_mod["stimulus"]["log"] = self.metadata_dict[ "stimulus"]["log"] self.metadata_dict_mod["stimulus"]["display_params"][ "pos"] = json.loads( self.metadata_dict_mod["stimulus"]["display_params"]["pos"]) self.metadata_dict_mod["stimulus"]["display_params"][ "size"] = json.loads( self.metadata_dict_mod["stimulus"]["display_params"]["size"]) self.show_warning() def show_warning(self): """Upon saving, display a warning message to choose whether to create a new metadata file or replace the existing one. Parameters ---------- Returns ------- """ if self.has_changed: self.msg = QMessageBox() self.msg.setIcon(QMessageBox.Warning) self.setWindowTitle("Saving Warning") self.msg.setText("Some parameters have changed") self.msg.setInformativeText( "Do you want to overwrite the original .json metadata file?") self.msg.addButton("Create new file", QMessageBox.AcceptRole) self.msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No) self.ret = self.msg.exec_() if self.ret == QMessageBox.Yes: self.overwrite_metadata_file(self.metadata_dict_mod) elif self.ret == QMessageBox.AcceptRole: self.create_metadata_file(self.metadata_dict_mod) else: pass else: self.msg2 = QMessageBox() self.msg2.setIcon(QMessageBox.Information) self.setWindowTitle("Saving Warning") self.msg2.setText("No changes have been made.") self.msg2.addButton("OK", QMessageBox.AcceptRole) self.ret = self.msg2.exec_() def overwrite_metadata_file(self, metadata_dict_mod): """ Parameters ---------- metadata_dict_mod : Returns ------- """ # Overwritte original metadata file with open(self.filename, "w") as file: json.dump(metadata_dict_mod, file) def create_metadata_file(self, metadata_dict_mod): """ Parameters ---------- metadata_dict_mod : Returns ------- """ # Overwritte original metadata file self.name, self.ext = self.filename.split(".") with open("{}_modified.{}".format(self.name, self.ext), "w") as file: json.dump(metadata_dict_mod, file) def reset(self): """Reset parameter tree values to the original state after loading.""" self.p.restoreState(self.original_state, recursive=True) self.tree.setParameters(self.p, showTop=False) def fix_types(self, datadict): """Modify metadata dict so only accepted types are found. Parameters ---------- datadict : Returns ------- """ param_dict = dict() for key, value in datadict.items(): if isinstance(value, list): param_dict[key] = str(value) elif isinstance(value, dict): param_dict[key] = self.fix_types(value) else: param_dict[key] = value return param_dict def create_parameters(self, datadict): """Create list with parameters and Children to which the tree will be built from. Parameters ---------- datadict : Returns ------- """ parameters = [] for key, value in datadict.items(): if key == "log": pass else: if isinstance(value, dict): parameters.append({ "name": "{}".format(key), "type": "group", "children": self.create_parameters(value), }) else: parameters.append({ "name": "{}".format(key), "type": "{}".format(type(value).__name__), "value": value, }) return parameters def get_mod_dict(self, treevals_dict): """Recursive function to convert into dict output of getValues function. Parameters ---------- treevals_dict : Returns ------- """ metadata_dict_mod = dict() for key, value in treevals_dict.items(): if value[0] is None: metadata_dict_mod[key] = dict(self.get_mod_dict(value[1])) else: metadata_dict_mod[key] = value[0] return metadata_dict_mod def change(self, param, changes): """ Parameters ---------- param : changes : Returns ------- """ print("tree changes:") for param, change, data in changes: path = self.p.childPath(param) if path is not None: childName = ".".join(path) else: childName = param.name() print(" parameter: %s" % childName) print(" change: %s" % change) print(" data: %s" % str(data)) print(" ----------") if change == "activated": pass else: self.has_changed = True def close_tree(self): """ """ try: self.layout.removeWidget(self.tree) self.tree.deleteLater() self.tree = None except AttributeError: pass
class JsonReader(QWidget): def __init__(self): super().__init__() self.title = 'Json metadata reader' self.initUI() def initUI(self): self.setWindowTitle(self.title) self.getbtn = QPushButton("Select file") self.getbtn.clicked.connect(self.get_file) self.draglbl = DragDropLabel(self) self.draglbl.setText("... or drop .{} file here".format( DragDropLabel.acceptedFormat.upper())) self.draglbl.setAlignment(QtCore.Qt.AlignCenter) self.draglbl.droppedFile.connect(self.open_file) self.layout = QGridLayout(self) self.layout.addWidget(self.getbtn, 0, 0, 1, 2) self.layout.addWidget(self.draglbl, 1, 0, 1, 2) self.setLayout(self.layout) self.show() def get_file(self): self.path, _ = QFileDialog.getOpenFileName( filter="Json files (*.json)") if not self.path: pass else: self.open_file(self.path) def open_file(self, filename): self.filename = filename with open(filename) as json_file: self.metadata_dict = json.load(json_file) self.display_tree_widget(self.metadata_dict) def display_tree_widget(self, metadata): """Display the parameter tree from the experiment metadata. :param metadata: .json metadata """ # Close previous tree widget self.close_tree() # Create a variable checking whether changes have been made to the parameter tree values self.has_changed = False # Create list with parameters for the tree self.parameters = self.create_parameters(self.fix_types(metadata)) # Create tree of Parameter objects self.p = Parameter.create(name='params', type='group', children=self.parameters) # Save original state self.original_state = self.p.saveState() # Create ParameterTree widget self.tree = ParameterTree() self.tree.setParameters(self.p, showTop=False) self.tree.setWindowTitle('pyqtgraph example: Parameter Tree') # Display tree widget self.layout.addWidget(self.tree, 2, 0, 1, 2) # And buttons self.savebtn = QPushButton("Save changes") self.resetbtn = QPushButton("Reset changes") self.layout.addWidget(self.savebtn, 3, 0) self.layout.addWidget(self.resetbtn, 3, 1) self.savebtn.clicked.connect(self.save_treevals) self.resetbtn.clicked.connect(self.reset) # Send signal when any entry is changed self.p.sigTreeStateChanged.connect(self.change) def save_treevals(self): """Save current values of the parameter tree into a dictionary. """ # Recover data from tree and store it in a dict self.treevals_dict = self.p.getValues() self.metadata_dict_mod = self.get_mod_dict(self.treevals_dict) # Nasty way to make new dict (with modified metadata) with same structure as the original one self.metadata_dict_mod['stimulus']['log'] = self.metadata_dict[ 'stimulus']['log'] self.metadata_dict_mod['stimulus']['display_params']['pos'] = \ json.loads(self.metadata_dict_mod['stimulus']['display_params']['pos']) self.metadata_dict_mod['stimulus']['display_params']['size'] = \ json.loads(self.metadata_dict_mod['stimulus']['display_params']['size']) self.show_warning() def show_warning(self): """Upon saving, display a warning message to choose whether to create a new metadata file or replace the existing one. """ if self.has_changed: self.msg = QMessageBox() self.msg.setIcon(QMessageBox.Warning) self.setWindowTitle("Saving Warning") self.msg.setText("Some parameters have changed") self.msg.setInformativeText( "Do you want to overwrite the original .json metadata file?") self.msg.addButton('Create new file', QMessageBox.AcceptRole) self.msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No) self.ret = self.msg.exec_() if self.ret == QMessageBox.Yes: self.overwrite_metadata_file(self.metadata_dict_mod) elif self.ret == QMessageBox.AcceptRole: self.create_metadata_file(self.metadata_dict_mod) else: pass else: self.msg2 = QMessageBox() self.msg2.setIcon(QMessageBox.Information) self.setWindowTitle("Saving Warning") self.msg2.setText("No changes have been made.") self.msg2.addButton('OK', QMessageBox.AcceptRole) self.ret = self.msg2.exec_() def overwrite_metadata_file(self, metadata_dict_mod): # Overwritte original metadata file with open(self.filename, 'w') as file: json.dump(metadata_dict_mod, file) def create_metadata_file(self, metadata_dict_mod): # Overwritte original metadata file self.name, self.ext = self.filename.split('.') with open('{}_modified.{}'.format(self.name, self.ext), 'w') as file: json.dump(metadata_dict_mod, file) def reset(self): """Reset parameter tree values to the original state after loading. """ self.p.restoreState(self.original_state, recursive=True) #self.tree.setParameters(self.p, showTop=False) def fix_types(self, datadict): """Modify metadata dict so only accepted types are found. """ param_dict = dict() for key, value in datadict.items(): if isinstance(value, list): param_dict[key] = str(value) elif isinstance(value, dict): param_dict[key] = self.fix_types(value) else: param_dict[key] = value return param_dict def create_parameters(self, datadict): """Create list with parameters and Children to which the tree will be built from. """ parameters = [] for key, value in datadict.items(): if key == 'log': pass else: if isinstance(value, dict): parameters.append({ 'name': '{}'.format(key), 'type': 'group', 'children': self.create_parameters(value) }) else: parameters.append({ 'name': '{}'.format(key), 'type': '{}'.format(type(value).__name__), 'value': value }) return parameters def get_mod_dict(self, treevals_dict): """Recursive function to convert into dict output of getValues function. """ metadata_dict_mod = dict() for key, value in treevals_dict.items(): if value[0] is None: metadata_dict_mod[key] = dict(self.get_mod_dict(value[1])) else: metadata_dict_mod[key] = value[0] return metadata_dict_mod def change(self, param, changes): print("tree changes:") for param, change, data in changes: path = self.p.childPath(param) if path is not None: childName = '.'.join(path) else: childName = param.name() print(' parameter: %s' % childName) print(' change: %s' % change) print(' data: %s' % str(data)) print(' ----------') if change == 'activated': pass else: self.has_changed = True def close_tree(self): try: self.layout.removeWidget(self.tree) self.tree.deleteLater() self.tree = None except AttributeError: pass
class SettingsTab(object): def __init__(self, parent, tab_name: str, types_list: Dict[str, object], obj_list: Dict[str, ConfigurableObject]): self.parent = parent self.tab_name = tab_name self.types_list = types_list self.obj_list = obj_list self.activated = None self.currentTree = None self.currentParams = None self.currentParamsTree = None # self.activated = [] def _on_parametr_change(self, param, changes): print("tree changes:") for param, change, data in changes: path = self.currentParams.childPath(param) if path is not None: childName = '.'.join(path) else: childName = param.name() print(f' parameter: {childName}') print(f' change: {change}') print(f' data: {data}, {type(data)}') print(' ----------') conf = self.obj_list[self.activated].config_vars if change == "value": if "." not in childName: conf[childName] = data else: conf[path[0]][int(path[1])] = data elif change == "childAdded": conf[path[0]].append(data[0].value()) elif change == "childRemoved": del conf[path[0]][int(data.name())] for i in self.currentParamsTree: if isinstance(i, ArrayGroup) and i.name( ) == path[0]: # WTF: crazy shit, fix this i.rebuildID() print(f"Internal state: {conf[path[0]]}") def _on_item_clicked(self, item): t = item.text() if t != self.activated: var = self.obj_list[t] self.currentParamsTree = [] for i, v in var.config_vars.items(): if isinstance(v, list): self.currentParamsTree.append( ArrayGroup(add_type=v[0].__class__.__name__, name=i, children=[{ 'name': str(j), 'type': k.__class__.__name__, 'value': k, 'removable': True, 'renamable': False } for j, k in enumerate(v)])) else: self.currentParamsTree.append({ 'name': i, 'type': v.__class__.__name__, 'value': v }) self.currentParams = Parameter.create( name='params', type='group', children=self.currentParamsTree) self.currentParams.sigTreeStateChanged.connect( self._on_parametr_change) self.currentTree = ParameterTree(self.tab) self.currentTree.setParameters(self.currentParams, showTop=False) self.gridLayout.addWidget(self.currentTree, 0, 1, 4, 1) self.activated = t print(f"Activating {t}") def _on_new_item_create(self): item = self.classSelector.currentText() cl: ConfigurableObject = self.types_list[item](lambda x: None) cl.config_vars[ "name"] = f"{cl.config_vars['type']}.{''.join(random.sample(string.hexdigits, 8))}" self.obj_list[cl.short()] = cl self.listWidget.addItem(cl.short()) print(f"Creating {item}") def _on_item_remove(self): self.clearActivation() item = self.listWidget.currentItem() del self.obj_list[item.text()] self.listWidget.takeItem(self.listWidget.currentRow()) def clearActivation(self): if self.activated is not None: self.gridLayout.removeWidget(self.currentTree) self.currentTree.deleteLater() self.currentTree = None self.currentParams = None self.currentParamsTree = None self.activated = None def setupData(self): for i in self.obj_list.values(): self.listWidget.addItem(i.short()) for i in self.types_list: self.classSelector.addItem(i) def setupUi(self): # entire tab self.tab = QtWidgets.QWidget() self.tab.setObjectName(f"tab_{self.tab_name}") # grid self.gridLayout = QtWidgets.QGridLayout(self.tab) self.gridLayout.setObjectName(f"gridLayout_{self.tab_name}") # list with things self.listWidget = QtWidgets.QListWidget(self.tab) self.listWidget.setObjectName(f"listWidget_{self.tab_name}") self.listWidget.itemClicked.connect(self._on_item_clicked) # class selector for adding new items self.classSelector = QtWidgets.QComboBox(self.tab) self.classSelector.setObjectName("classSelector") # add button self.addButton = QtWidgets.QPushButton(self.tab) self.addButton.setObjectName("addButton") self.addButton.clicked.connect(self._on_new_item_create) # remove button self.removeButton = QtWidgets.QPushButton(self.tab) self.removeButton.setObjectName("removeButton") self.removeButton.clicked.connect(self._on_item_remove) # add everything to grid self.gridLayout.addWidget(self.classSelector, 0, 0, 1, 1) self.gridLayout.addWidget(self.addButton, 1, 0, 1, 1) self.gridLayout.addWidget(self.removeButton, 2, 0, 1, 1) self.gridLayout.addWidget(self.listWidget, 3, 0, 1, 1) # add tab to form self.parent.tabWidget.addTab(self.tab, "") self.retranslateUi() print(f"{self.tab_name} tab created") def retranslateUi(self): _translate = QtCore.QCoreApplication.translate self.parent.tabWidget.setTabText( self.parent.tabWidget.indexOf(self.tab), _translate("MainWindow", f"Tab {self.tab_name}")) self.addButton.setText(_translate("MainWindow", f"Add {self.tab_name}")) self.removeButton.setText( _translate("MainWindow", f"Remove {self.tab_name}"))