Esempio n. 1
0
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
Esempio n. 3
0
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}"))