Пример #1
0
    def export(self):
        from tifffile import imsave
        from pyqtgraph import FileDialog

        start_doc = getattr(self.catalog, self.stream).metadata['start']
        if 'FileName' in start_doc:
            current_dir = str(Path(start_doc['FileName']).parent)
            current_name = Path(start_doc['FileName']).stem
        elif 'sample_name' in start_doc:
            current_dir = str(
                Path.home())  # sample name is usually just te file stem
            current_name = Path(start_doc['sample_name']).stem
        else:
            current_dir = Path.home()
            current_name = 'xicamNCEM_output'

        # Get a file path to save to in current directory
        fd = FileDialog()
        fd.setNameFilter("TIF (*.tif)")
        fd.setDirectory(current_dir)
        fd.selectFile(current_name)
        fd.setFileMode(FileDialog.AnyFile)
        fd.setAcceptMode(FileDialog.AcceptSave)

        if fd.exec_():
            file_names = fd.selectedFiles()[0]
            outPath = Path(file_names)
        else:
            return

        if outPath.suffix != '.tif':
            outPath = outPath.with_suffix('.tif')

        scale0, units0 = self._get_physical_size()

        # Avoid overflow in field of view later
        if units0[0] == 'm':
            units0 = ['um', 'um']
            scale0 = [ii * 1e6 for ii in scale0]

        # Change scale from pixel to 1/pixel
        FOV = [1 / ii for ii in scale0]

        # Add units to metadata for Imagej type output
        metadata = {'unit': units0[0]}

        # Get the data and change to float
        image = self.xarray[self.currentIndex, :, :].astype('f')

        imsave(outPath, image, imagej=True, resolution=FOV, metadata=metadata)
Пример #2
0
class Flowchart(Node):
    sigFileLoaded = QtCore.Signal(object)
    sigFileSaved = QtCore.Signal(object)
    sigNodeCreated = QtCore.Signal(object)
    sigNodeChanged = QtCore.Signal(object)

    # called when output is expected to have changed

    def __init__(self,
                 name=None,
                 filePath=None,
                 library=None,
                 broker_addr="",
                 graphmgr_addr="",
                 checkpoint_addr="",
                 prometheus_dir=None,
                 hutch=None):
        super().__init__(name)
        self.socks = []
        self.library = library or LIBRARY
        self.graphmgr_addr = graphmgr_addr
        self.source_library = None

        self.ctx = zmq.asyncio.Context()
        self.broker = self.ctx.socket(
            zmq.PUB)  # used to create new node processes
        self.broker.connect(broker_addr)
        self.socks.append(self.broker)

        self.graphinfo = self.ctx.socket(zmq.SUB)
        self.graphinfo.setsockopt_string(zmq.SUBSCRIBE, '')
        self.graphinfo.connect(graphmgr_addr.info)
        self.socks.append(self.graphinfo)

        self.checkpoint = self.ctx.socket(
            zmq.SUB)  # used to receive ctrlnode updates from processes
        self.checkpoint.setsockopt_string(zmq.SUBSCRIBE, '')
        self.checkpoint.connect(checkpoint_addr)
        self.socks.append(self.checkpoint)

        self.filePath = filePath

        self._graph = nx.MultiDiGraph()

        self.nextZVal = 10
        self._widget = None
        self._scene = None

        self.deleted_nodes = []

        self.prometheus_dir = prometheus_dir
        self.hutch = hutch

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.close()

    def close(self):
        for sock in self.socks:
            sock.close(linger=0)
        if self._widget is not None:
            self._widget.graphCommHandler.close()
        self.ctx.term()

    def start_prometheus(self):
        port = Ports.Prometheus
        while True:
            try:
                pc.start_http_server(port)
                break
            except OSError:
                port += 1

        if self.prometheus_dir:
            if not os.path.exists(self.prometheus_dir):
                os.makedirs(self.prometheus_dir)
            pth = f"drpami_{socket.gethostname()}_client.json"
            pth = os.path.join(self.prometheus_dir, pth)
            conf = [{"targets": [f"{socket.gethostname()}:{port}"]}]
            try:
                with open(pth, 'w') as f:
                    json.dump(conf, f)
            except PermissionError:
                logging.error("Permission denied: %s", pth)
                pass

    def setLibrary(self, lib):
        self.library = lib
        self.widget().chartWidget.buildMenu()

    def nodes(self, **kwargs):
        return self._graph.nodes(**kwargs)

    def createNode(self, nodeType=None, name=None, pos=None):
        """Create a new Node and add it to this flowchart.
        """
        if name is None:
            n = 0
            while True:
                name = "%s.%d" % (nodeType, n)
                if name not in self._graph.nodes():
                    break
                n += 1

        # create an instance of the node
        node = self.library.getNodeType(nodeType)(name)

        self.addNode(node, pos)
        return node

    def addNode(self, node, pos=None):
        """Add an existing Node to this flowchart.

        See also: createNode()
        """
        if pos is None:
            pos = [0, 0]
        if type(pos) in [QtCore.QPoint, QtCore.QPointF]:
            pos = [pos.x(), pos.y()]
        item = node.graphicsItem()
        item.setZValue(self.nextZVal * 2)
        self.nextZVal += 1
        self.viewBox.addItem(item)
        pos = (find_nearest(pos[0]), find_nearest(pos[1]))
        item.moveBy(*pos)
        self._graph.add_node(node.name(), node=node)
        node.sigClosed.connect(self.nodeClosed)
        node.sigTerminalConnected.connect(self.nodeConnected)
        node.sigTerminalDisconnected.connect(self.nodeDisconnected)
        node.sigNodeEnabled.connect(self.nodeEnabled)
        self.sigNodeCreated.emit(node)
        if node.isChanged(True, True):
            self.sigNodeChanged.emit(node)

    @asyncSlot(object, object)
    async def nodeClosed(self, node, input_vars):
        self._graph.remove_node(node.name())
        await self.broker.send_string(node.name(), zmq.SNDMORE)
        await self.broker.send_pyobj(fcMsgs.CloseNode())
        ctrl = self.widget()
        name = node.name()

        if hasattr(node, 'to_operation'):
            self.deleted_nodes.append(name)
            self.sigNodeChanged.emit(node)
        elif isinstance(node, SourceNode):
            await ctrl.features.discard(name, name)
            await ctrl.graphCommHandler.unview(name)
        elif node.viewable():
            views = []
            for term, in_var in input_vars.items():
                discarded = await ctrl.features.discard(name, in_var)
                if discarded:
                    views.append(in_var)
            if views:
                await ctrl.graphCommHandler.unview(views)

    def nodeConnected(self, localTerm, remoteTerm):
        if remoteTerm.isOutput() or localTerm.isCondition():
            t = remoteTerm
            remoteTerm = localTerm
            localTerm = t

        localNode = localTerm.node().name()
        remoteNode = remoteTerm.node().name()
        key = localNode + '.' + localTerm.name(
        ) + '->' + remoteNode + '.' + remoteTerm.name()
        if not self._graph.has_edge(localNode, remoteNode, key=key):
            self._graph.add_edge(localNode,
                                 remoteNode,
                                 key=key,
                                 from_term=localTerm.name(),
                                 to_term=remoteTerm.name())
        self.sigNodeChanged.emit(localTerm.node())

    def nodeDisconnected(self, localTerm, remoteTerm):
        if remoteTerm.isOutput() or localTerm.isCondition():
            t = remoteTerm
            remoteTerm = localTerm
            localTerm = t

        localNode = localTerm.node().name()
        remoteNode = remoteTerm.node().name()
        key = localNode + '.' + localTerm.name(
        ) + '->' + remoteNode + '.' + remoteTerm.name()
        if self._graph.has_edge(localNode, remoteNode, key=key):
            self._graph.remove_edge(localNode, remoteNode, key=key)
        self.sigNodeChanged.emit(localTerm.node())

    @asyncSlot(object)
    async def nodeEnabled(self, root):
        enabled = root._enabled

        outputs = [n for n, d in self._graph.out_degree() if d == 0]
        sources_targets = list(it.product([root.name()], outputs))
        ctrl = self.widget()
        views = []

        for s, t in sources_targets:
            paths = list(nx.algorithms.all_simple_paths(self._graph, s, t))

            for path in paths:
                for node in path:
                    node = self._graph.nodes[node]['node']
                    name = node.name()
                    node.nodeEnabled(enabled)
                    if not enabled:
                        if hasattr(node, 'to_operation'):
                            self.deleted_nodes.append(name)
                        elif node.viewable():
                            for term, in_var in node.input_vars().items():
                                discarded = await ctrl.features.discard(
                                    name, in_var)
                                if discarded:
                                    views.append(in_var)
                    else:
                        node.changed = True
                    if node.conditions():
                        preds = self._graph.predecessors(node.name())
                        preds = filter(lambda n: n.startswith("Filter"), preds)
                        for filt in preds:
                            node = self._graph.nodes[filt]['node']
                            node.nodeEnabled(enabled)

        if views:
            await ctrl.graphCommHandler.unview(views)
        await ctrl.applyClicked()

    def connectTerminals(self, term1, term2, type_file=None):
        """Connect two terminals together within this flowchart."""
        term1.connectTo(term2, type_file=type_file)

    def chartGraphicsItem(self):
        """
        Return the graphicsItem that displays the internal nodes and
        connections of this flowchart.

        Note that the similar method `graphicsItem()` is inherited from Node
        and returns the *external* graphical representation of this flowchart."""
        return self.viewBox

    def widget(self):
        """
        Return the control widget for this flowchart.

        This widget provides GUI access to the parameters for each node and a
        graphical representation of the flowchart.
        """
        if self._widget is None:
            self._widget = FlowchartCtrlWidget(self, self.graphmgr_addr)
            self.scene = self._widget.scene()
            self.viewBox = self._widget.viewBox()
        return self._widget

    def saveState(self):
        """
        Return a serializable data structure representing the current state of this flowchart.
        """
        state = {}
        state['nodes'] = []
        state['connects'] = []
        state['viewbox'] = self.viewBox.saveState()

        for name, node in self.nodes(data='node'):
            cls = type(node)
            clsName = cls.__name__
            ns = {'class': clsName, 'name': name, 'state': node.saveState()}
            state['nodes'].append(ns)

        for from_node, to_node, data in self._graph.edges(data=True):
            from_term = data['from_term']
            to_term = data['to_term']
            state['connects'].append((from_node, from_term, to_node, to_term))

        state['source_configuration'] = self.widget(
        ).sourceConfigure.saveState()
        state['library'] = self.widget().libraryEditor.saveState()
        return state

    def restoreState(self, state):
        """
        Restore the state of this flowchart from a previous call to `saveState()`.
        """
        self.blockSignals(True)
        try:
            if 'source_configuration' in state:
                src_cfg = state['source_configuration']
                self.widget().sourceConfigure.restoreState(src_cfg)
                if src_cfg['files']:
                    self.widget().sourceConfigure.applyClicked()

            if 'library' in state:
                lib_cfg = state['library']
                self.widget().libraryEditor.restoreState(lib_cfg)
                self.widget().libraryEditor.applyClicked()

            if 'viewbox' in state:
                self.viewBox.restoreState(state['viewbox'])

            nodes = state['nodes']
            nodes.sort(key=lambda a: a['state']['pos'][0])
            for n in nodes:
                if n['class'] == 'SourceNode':
                    try:
                        ttype = eval(n['state']['terminals']['Out']['ttype'])
                        n['state']['terminals']['Out']['ttype'] = ttype
                        node = SourceNode(name=n['name'],
                                          terminals=n['state']['terminals'])
                        self.addNode(node=node)
                    except Exception:
                        printExc(
                            "Error creating node %s: (continuing anyway)" %
                            n['name'])
                else:
                    try:
                        node = self.createNode(n['class'], name=n['name'])
                    except Exception:
                        printExc(
                            "Error creating node %s: (continuing anyway)" %
                            n['name'])

                node.restoreState(n['state'])
                if hasattr(node, "display"):
                    node.display(topics=None, terms=None, addr=None, win=None)
                    if hasattr(node.widget,
                               'restoreState') and 'widget' in n['state']:
                        node.widget.restoreState(n['state']['widget'])

            connections = {}
            with tempfile.NamedTemporaryFile(mode='w') as type_file:
                type_file.write("from mypy_extensions import TypedDict\n")
                type_file.write("from typing import *\n")
                type_file.write("import numbers\n")
                type_file.write("import amitypes\n")
                type_file.write("T = TypeVar('T')\n\n")

                nodes = self.nodes(data='node')

                for n1, t1, n2, t2 in state['connects']:
                    try:
                        node1 = nodes[n1]
                        term1 = node1[t1]
                        node2 = nodes[n2]
                        term2 = node2[t2]

                        self.connectTerminals(term1, term2, type_file)
                        if term1.isInput() or term1.isCondition:
                            in_name = node1.name() + '_' + term1.name()
                            in_name = in_name.replace('.', '_')
                            out_name = node2.name() + '_' + term2.name()
                            out_name = out_name.replace('.', '_')
                        else:
                            in_name = node2.name() + '_' + term2.name()
                            in_name = in_name.replace('.', '_')
                            out_name = node1.name() + '_' + term1.name()
                            out_name = out_name.replace('.', '_')

                        connections[(in_name, out_name)] = (term1, term2)
                    except Exception:
                        print(node1.terminals)
                        print(node2.terminals)
                        printExc("Error connecting terminals %s.%s - %s.%s:" %
                                 (n1, t1, n2, t2))

                type_file.flush()
                status = subprocess.run(
                    ["mypy", "--follow-imports", "silent", type_file.name],
                    capture_output=True,
                    text=True)
                if status.returncode != 0:
                    lines = status.stdout.split('\n')[:-1]
                    for line in lines:
                        m = re.search(r"\"+(\w+)\"+", line)
                        if m:
                            m = m.group().replace('"', '')
                            for i in connections:
                                if i[0] == m:
                                    term1, term2 = connections[i]
                                    term1.disconnectFrom(term2)
                                    break
                                elif i[1] == m:
                                    term1, term2 = connections[i]
                                    term1.disconnectFrom(term2)
                                    break

        finally:
            self.blockSignals(False)

        for name, node in self.nodes(data='node'):
            self.sigNodeChanged.emit(node)

    @asyncSlot(str)
    async def loadFile(self, fileName=None):
        """
        Load a flowchart (*.fc) file.
        """
        with open(fileName, 'r') as f:
            state = json.load(f)

        ctrl = self.widget()
        await ctrl.clear()
        self.restoreState(state)
        self.viewBox.autoRange()
        self.sigFileLoaded.emit(fileName)
        await ctrl.applyClicked(build_views=False)

        nodes = []
        for name, node in self.nodes(data='node'):
            if node.viewed:
                nodes.append(node)

        await ctrl.chartWidget.build_views(nodes, ctrl=True, export=True)

    def saveFile(self,
                 fileName=None,
                 startDir=None,
                 suggestedFileName='flowchart.fc'):
        """
        Save this flowchart to a .fc file
        """
        if fileName is None:
            if startDir is None:
                startDir = self.filePath
            if startDir is None:
                startDir = '.'
            self.fileDialog = FileDialog(None, "Save Flowchart..", startDir,
                                         "Flowchart (*.fc)")
            self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
            self.fileDialog.show()
            self.fileDialog.fileSelected.connect(self.saveFile)
            return

        if not fileName.endswith('.fc'):
            fileName += ".fc"

        state = self.saveState()
        state = json.dumps(state,
                           indent=2,
                           separators=(',', ': '),
                           sort_keys=True,
                           cls=amitypes.TypeEncoder)

        with open(fileName, 'w') as f:
            f.write(state)
            f.write('\n')

        ctrl = self.widget()
        ctrl.graph_info.labels(self.hutch,
                               ctrl.graph_name).info({'graph': state})
        ctrl.chartWidget.updateStatus(f"Saved graph to: {fileName}")
        self.sigFileSaved.emit(fileName)

    async def clear(self):
        """
        Remove all nodes from this flowchart except the original input/output nodes.
        """
        for name, gnode in self._graph.nodes().items():
            node = gnode['node']
            await self.broker.send_string(name, zmq.SNDMORE)
            await self.broker.send_pyobj(fcMsgs.CloseNode())
            node.close(emit=False)

        self._graph = nx.MultiDiGraph()

    async def updateState(self):
        while True:
            node_name = await self.checkpoint.recv_string()
            # there is something wrong with this
            # in ami.client.flowchart.NodeProcess.send_checkpoint we send a
            # fcMsgs.NodeCheckPoint but we are only ever receiving the state
            new_node_state = await self.checkpoint.recv_pyobj()
            node = self._graph.nodes[node_name]['node']
            current_node_state = node.saveState()
            restore_ctrl = False
            restore_widget = False

            if 'ctrl' in new_node_state:
                if current_node_state['ctrl'] != new_node_state['ctrl']:
                    current_node_state['ctrl'] = new_node_state['ctrl']
                    restore_ctrl = True

            if 'widget' in new_node_state:
                if current_node_state['widget'] != new_node_state['widget']:
                    restore_widget = True
                    current_node_state['widget'] = new_node_state['widget']

            if 'geometry' in new_node_state:
                node.geometry = QtCore.QByteArray.fromHex(
                    bytes(new_node_state['geometry'], 'ascii'))

            if restore_ctrl or restore_widget:
                node.restoreState(current_node_state)
                node.changed = node.isChanged(restore_ctrl, restore_widget)
                if node.changed:
                    self.sigNodeChanged.emit(node)

            node.viewed = new_node_state['viewed']

    async def updateSources(self, init=False):
        num_workers = None

        while True:
            topic = await self.graphinfo.recv_string()
            source = await self.graphinfo.recv_string()
            msg = await self.graphinfo.recv_pyobj()

            if topic == 'sources':
                source_library = SourceLibrary()
                for source, node_type in msg.items():
                    pth = []
                    for part in source.split(':')[:-1]:
                        if pth:
                            part = ":".join((pth[-1], part))
                        pth.append(part)
                    source_library.addNodeType(source,
                                               amitypes.loads(node_type),
                                               [pth])

                self.source_library = source_library

                if init:
                    break

                ctrl = self.widget()
                tree = ctrl.ui.source_tree
                ctrl.ui.clear_model(tree)
                ctrl.ui.create_model(ctrl.ui.source_tree,
                                     self.source_library.getLabelTree())

                ctrl.chartWidget.updateStatus("Updated sources.")

            elif topic == 'event_rate':
                if num_workers is None:
                    ctrl = self.widget()
                    compiler_args = await ctrl.graphCommHandler.compilerArgs
                    num_workers = compiler_args['num_workers']
                    events_per_second = [None] * num_workers
                    total_events = [None] * num_workers

                time_per_event = msg[ctrl.graph_name]
                worker = int(re.search(r'(\d)+', source).group())
                events_per_second[worker] = len(time_per_event) / (
                    time_per_event[-1][1] - time_per_event[0][0])
                total_events[worker] = msg['num_events']

                if all(events_per_second):
                    events_per_second = int(np.sum(events_per_second))
                    total_num_events = int(np.sum(total_events))
                    ctrl = self.widget()
                    ctrl.ui.rateLbl.setText(
                        f"Num Events: {total_num_events} Events/Sec: {events_per_second}"
                    )
                    events_per_second = [None] * num_workers
                    total_events = [None] * num_workers

            elif topic == 'error':
                ctrl = self.widget()
                if hasattr(msg, 'node_name'):
                    node_name = ctrl.metadata[msg.node_name]['parent']
                    node = self.nodes(data='node')[node_name]
                    node.setException(msg)
                    ctrl.chartWidget.updateStatus(
                        f"{source} {node.name()}: {msg}", color='red')
                else:
                    ctrl.chartWidget.updateStatus(f"{source}: {msg}",
                                                  color='red')

    async def run(self):
        await asyncio.gather(self.updateState(), self.updateSources())
Пример #3
0
class LogWidget(QtGui.QWidget):
    """A widget to show log entries and filter them.
    """

    sigDisplayEntry = QtCore.Signal(object)  ## for thread-safetyness
    sigAddEntry = QtCore.Signal(object)  ## for thread-safetyness
    sigScrollToAnchor = QtCore.Signal(object)  # for internal use.

    Stylesheet = """
        body {color: #000; font-family: sans;}
        .entry {}
        .error .message {color: #900}
        .warning .message {color: #740}
        .user .message {color: #009}
        .status .message {color: #090}
        .logExtra {margin-left: 40px;}
        .traceback {color: #555; height: 0px;}
        .timestamp {color: #000;}
    """
    pageTemplate = """
        <html>
        <head>
            <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
            <style type="text/css">
            %s    </style>
    
            <script type="text/javascript">
                function showDiv(id) {
                    div = document.getElementById(id);
                    div.style.visibility = "visible";
                    div.style.height = "auto";
                }
            </script>
        </head>
        <body>
        </body>
        </html> """ % Stylesheet

    def __init__(self, parent):
        """Creates the log widget.

        @param object parent: Qt parent object for log widet

        """
        QtGui.QWidget.__init__(self, parent)
        self.ui = LogWidgetTemplate.Ui_Form()
        self.ui.setupUi(self)
        #self.ui.input.hide()
        self.ui.filterTree.topLevelItem(1).setExpanded(True)

        self.entries = []  ## stores all log entries in memory
        self.cache = {
        }  ## for storing html strings of entries that have already been processed
        self.displayedEntries = []
        self.typeFilters = []
        self.importanceFilter = 0
        self.dirFilter = False
        self.entryArrayBuffer = np.zeros(
            1000,
            dtype=[  ### a record array for quick filtering of entries
                ('index', 'int32'), ('importance', 'int32'),
                ('msgType', '|S10'), ('directory', '|S100'),
                ('entryId', 'int32')
            ])
        self.entryArray = self.entryArrayBuffer[:0]

        self.filtersChanged()

        self.sigDisplayEntry.connect(self.displayEntry,
                                     QtCore.Qt.QueuedConnection)
        self.sigAddEntry.connect(self.addEntry, QtCore.Qt.QueuedConnection)
        self.ui.exportHtmlBtn.clicked.connect(self.exportHtml)
        self.ui.filterTree.itemChanged.connect(self.setCheckStates)
        self.ui.importanceSlider.valueChanged.connect(self.filtersChanged)
        self.ui.output.anchorClicked.connect(self.linkClicked)
        self.sigScrollToAnchor.connect(self.scrollToAnchor,
                                       QtCore.Qt.QueuedConnection)

    def loadFile(self, f):
        """Load a log file for display.

          @param str f: path to file that should be laoded.

        f must be able to be read by pyqtgraph configfile.py
        """
        log = configfile.readConfigFile(f)
        self.entries = []
        self.entryArrayBuffer = np.zeros(len(log),
                                         dtype=[('index', 'int32'),
                                                ('importance', 'int32'),
                                                ('msgType', '|S10'),
                                                ('directory', '|S100'),
                                                ('entryId', 'int32')])
        self.entryArray = self.entryArrayBuffer[:]

        i = 0
        for k, v in log.items():
            v['id'] = k[
                9:]  ## record unique ID to facilitate HTML generation (javascript needs this ID)
            self.entries.append(v)
            self.entryArray[i] = np.array(
                [(i, v.get('importance', 5), v.get('msgType', 'status'),
                  v.get('currentDir', ''), v.get('entryId', v['id']))],
                dtype=[('index', 'int32'), ('importance', 'int32'),
                       ('msgType', '|S10'), ('directory', '|S100'),
                       ('entryId', 'int32')])
            i += 1

        self.filterEntries(
        )  ## puts all entries through current filters and displays the ones that pass

    def addEntry(self, entry):
        """Add a log entry to the list.
        """
        ## All incoming messages begin here
        ## for thread-safetyness:
        isGuiThread = QtCore.QThread.currentThread(
        ) == QtCore.QCoreApplication.instance().thread()
        if not isGuiThread:
            self.sigAddEntry.emit(entry)
            return

        self.entries.append(entry)
        i = len(self.entryArray)

        entryDir = entry.get('currentDir', None)
        if entryDir is None:
            entryDir = ''
        arr = np.array([
            (i, entry['importance'], entry['msgType'], entryDir, entry['id'])
        ],
                       dtype=[('index', 'int32'), ('importance', 'int32'),
                              ('msgType', '|S10'), ('directory', '|S100'),
                              ('entryId', 'int32')])

        ## make more room if needed
        if len(self.entryArrayBuffer) == len(self.entryArray):
            newArray = np.empty(
                len(self.entryArrayBuffer) + 1000, self.entryArrayBuffer.dtype)
            newArray[:len(self.entryArray)] = self.entryArray
            self.entryArrayBuffer = newArray
        self.entryArray = self.entryArrayBuffer[:len(self.entryArray) + 1]
        self.entryArray[i] = arr
        self.checkDisplay(
            entry)  ## displays the entry if it passes the current filters

    def setCheckStates(self, item, column):
        if item == self.ui.filterTree.topLevelItem(1):
            if item.checkState(0):
                for i in range(item.childCount()):
                    item.child(i).setCheckState(0, QtCore.Qt.Checked)
        elif item.parent() == self.ui.filterTree.topLevelItem(1):
            if not item.checkState(0):
                self.ui.filterTree.topLevelItem(1).setCheckState(
                    0, QtCore.Qt.Unchecked)
        self.filtersChanged()

    def filtersChanged(self):
        ### Update self.typeFilters, self.importanceFilter, and self.dirFilter to reflect changes.
        tree = self.ui.filterTree

        self.typeFilters = []
        for i in range(tree.topLevelItem(1).childCount()):
            child = tree.topLevelItem(1).child(i)
            if tree.topLevelItem(1).checkState(0) or child.checkState(0):
                text = child.text(0)
                self.typeFilters.append(str(text))

        self.importanceFilter = self.ui.importanceSlider.value()
        #        self.updateDirFilter()
        self.filterEntries()

#    def updateDirFilter(self, dh=None):
#        if self.ui.filterTree.topLevelItem(0).checkState(0):
#            if dh==None:
#                self.dirFilter = self.manager.getDirOfSelectedFile().name()
#            else:
#                self.dirFilter = dh.name()
#        else:
#            self.dirFilter = False

    def filterEntries(self):
        """Runs each entry in self.entries through the filters and displays if it makes it through."""
        ### make self.entries a record array, then filtering will be much faster (to OR true/false arrays, + them)
        typeMask = self.entryArray['msgType'] == b''
        for t in self.typeFilters:
            typeMask += self.entryArray['msgType'] == t.encode('ascii')
        mask = (self.entryArray['importance'] >
                self.importanceFilter) * typeMask
        #if self.dirFilter != False:
        #    d = np.ascontiguousarray(self.entryArray['directory'])
        #    j = len(self.dirFilter)
        #    i = len(d)
        #    d = d.view(np.byte).reshape(i, 100)[:, :j]
        #    d = d.reshape(i*j).view('|S%d' % j)
        #    mask *= (d == self.dirFilter)
        self.ui.output.clear()
        self.ui.output.document().setDefaultStyleSheet(self.Stylesheet)
        indices = list(self.entryArray[mask]['index'])
        self.displayEntry([self.entries[i] for i in indices])

    def checkDisplay(self, entry):
        ### checks whether entry passes the current filters and displays it if it does.
        if entry['msgType'] not in self.typeFilters:
            return
        elif entry['importance'] < self.importanceFilter:
            return
        elif self.dirFilter is not False:
            if entry['currentDir'][:len(self.dirFilter)] != self.dirFilter:
                return
        else:
            self.displayEntry([entry])

    def displayEntry(self, entries):
        ## entries should be a list of log entries

        ## for thread-safetyness:
        isGuiThread = QtCore.QThread.currentThread(
        ) == QtCore.QCoreApplication.instance().thread()
        if not isGuiThread:
            self.sigDisplayEntry.emit(entries)
            return

        for entry in entries:
            if id(entry) not in self.cache:
                self.cache[id(entry)] = self.generateEntryHtml(entry)

            html = self.cache[id(entry)]
            sb = self.ui.output.verticalScrollBar()
            isMax = sb.value() == sb.maximum()
            self.ui.output.append(html)
            self.displayedEntries.append(entry)

            if isMax:
                ## can't scroll to end until the web frame has processed the html change
                #frame.setScrollBarValue(QtCore.Qt.Vertical, frame.scrollBarMaximum(QtCore.Qt.Vertical))

                ## Calling processEvents anywhere inside an error handler is forbidden
                ## because this can lead to Qt complaining about paint() recursion.
                self.sigScrollToAnchor.emit(str(
                    entry['id']))  ## queued connection

    def scrollToAnchor(self, anchor):
        self.ui.output.scrollToAnchor(anchor)

    def generateEntryHtml(self, entry):
        msg = self.cleanText(entry['message'])

        reasons = ""
        docs = ""
        exc = ""
        if 'reasons' in entry:
            reasons = self.formatReasonStrForHTML(entry['reasons'])
        if 'docs' in entry:
            docs = self.formatDocsStrForHTML(entry['docs'])
        if entry.get('exception', None) is not None:
            exc = self.formatExceptionForHTML(entry, entryId=entry['id'])

        extra = reasons + docs + exc
        if extra != "":
            #extra = "<div class='logExtra'>" + extra + "</div>"
            extra = "<table class='logExtra'><tr><td>" + extra + "</td></tr></table>"

        #return """
        #<div class='entry'>
        #<div class='%s'>
        #<span class='timestamp'>%s</span>
        #<span class='message'>%s</span>
        #%s
        #</div>
        #</div>
        #""" % (entry['msgType'], entry['timestamp'], msg, extra)
        return """
        <a name="%s"/><table class='entry'><tr><td>
            <table class='%s'><tr><td>
                <span class='timestamp'>%s</span>
                <span class='message'>%s</span>
                %s
            </td></tr></table>
        </td></tr></table>
        """ % (str(
            entry['id']), entry['msgType'], entry['timestamp'], msg, extra)

    @staticmethod
    def cleanText(text):
        text = re.sub(r'&', '&amp;', text)
        text = re.sub(r'>', '&gt;', text)
        text = re.sub(r'<', '&lt;', text)
        text = re.sub(r'\n', '<br/>\n', text)
        return text

    def formatExceptionForHTML(self,
                               entry,
                               exception=None,
                               count=1,
                               entryId=None):
        ### Here, exception is a dict that holds the message, reasons, docs, traceback and oldExceptions (which are also dicts, with the same entries)
        ## the count and tracebacks keywords are for calling recursively
        if exception is None:
            exception = entry['exception']
        #if tracebacks is None:
        #tracebacks = []

        indent = 10

        text = self.cleanText(exception['message'])
        text = re.sub(r'^HelpfulException: ', '', text)
        messages = [text]

        if 'reasons' in exception:
            reasons = self.formatReasonsStrForHTML(exception['reasons'])
            text += reasons
            #self.displayText(reasons, entry, color, clean=False)
        if 'docs' in exception:
            docs = self.formatDocsStrForHTML(exception['docs'])
            #self.displayText(docs, entry, color, clean=False)
            text += docs

        traceback = [
            self.formatTracebackForHTML(exception['traceback'], count)
        ]
        text = [text]

        if 'oldExc' in exception:
            exc, tb, msgs = self.formatExceptionForHTML(entry,
                                                        exception['oldExc'],
                                                        count=count + 1)
            text.extend(exc)
            messages.extend(msgs)
            traceback.extend(tb)

        #else:
        #if len(tracebacks)==count+1:
        #n=0
        #else:
        #n=1
        #for i, tb in enumerate(tracebacks):
        #self.displayTraceback(tb, entry, number=i+n)
        if count == 1:
            exc = "<div class=\"exception\"><ol>" + "\n".join(
                ["<li>%s</li>" % ex for ex in text]) + "</ol></div>"
            tbStr = "\n".join([
                "<li><b>%s</b><br/><span class='traceback'>%s</span></li>" %
                (messages[i], tb) for i, tb in enumerate(traceback)
            ])
            #traceback = "<div class=\"traceback\" id=\"%s\"><ol>"%str(entryId) + tbStr + "</ol></div>"
            entry['tracebackHtml'] = tbStr

            #return exc + '<a href="#" onclick="showDiv(\'%s\')">Show traceback</a>'%str(entryId) + traceback
            return exc + '<a href="exc:%s">Show traceback %s</a>' % (
                str(entryId), str(entryId))
        else:
            return text, traceback, messages

    def formatTracebackForHTML(self, tb, number):
        try:
            tb = [
                line for line in tb
                if not line.startswith("Traceback (most recent call last)")
            ]
        except:
            print("\n" + str(tb) + "\n")
            raise
        return re.sub(" ", "&nbsp;", ("").join(map(self.cleanText, tb)))[:-1]
        #tb = [self.cleanText(strip(x)) for x in tb]
        #lines = []
        #prefix = ''
        #for l in ''.join(tb).split('\n'):
        #if l == '':
        #continue
        #if l[:9] == "Traceback":
        #prefix = ' ' + str(number) + '. '
        #continue
        #spaceCount = 0
        #while l[spaceCount] == ' ':
        #spaceCount += 1
        #if prefix is not '':
        #spaceCount -= 1
        #lines.append("&nbsp;"*(spaceCount*4) + prefix + l)
        #prefix = ''
        #return '<div class="traceback">' + '<br />'.join(lines) + '</div>'
        #self.displayText('<br />'.join(lines), entry, color, clean=False)

    def formatReasonsStrForHTML(self, reasons):
        #indent = 6
        reasonStr = "<table class='reasons'><tr><td>Possible reasons include:\n<ul>\n"
        for r in reasons:
            r = self.cleanText(r)
            reasonStr += "<li>" + r + "</li>\n"
            #reasonStr += "&nbsp;"*22 + chr(97+i) + ". " + r + "<br>"
        reasonStr += "</ul></td></tr></table>\n"
        return reasonStr

    def formatDocsStrForHTML(self, docs):
        #indent = 6
        docStr = "<div class='docRefs'>Relevant documentation:\n<ul>\n"
        for d in docs:
            d = self.cleanText(d)
            docStr += "<li><a href=\"doc:%s\">%s</a></li>\n" % (d, d)
        docStr += "</ul></div>\n"
        return docStr

    def exportHtml(self, fileName=False):
        if fileName is False:
            self.fileDialog = FileDialog(self, "Save HTML as...",
                                         "htmltemp.log")
            #self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile)
            self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
            self.fileDialog.show()
            self.fileDialog.fileSelected.connect(self.exportHtml)
            return
        if fileName[-5:] != '.html':
            fileName += '.html'

        #doc = self.ui.output.document().toHtml('utf-8')
        #for e in self.displayedEntries:
        #if e.has_key('tracebackHtml'):
        #doc = re.sub(r'<a href="exc:%s">(<[^>]+>)*Show traceback %s(<[^>]+>)*</a>'%(str(e['id']), str(e['id'])), e['tracebackHtml'], doc)

        doc = self.pageTemplate
        for e in self.displayedEntries:
            doc += self.cache[id(e)]
        for e in self.displayedEntries:
            if 'tracebackHtml' in e:
                doc = re.sub(
                    r'<a href="exc:%s">(<[^>]+>)*Show traceback %s(<[^>]+>)*</a>'
                    % (str(e['id']), str(e['id'])), e['tracebackHtml'], doc)
        f = open(fileName, 'w')
        f.write(doc)
        f.close()

    def linkClicked(self, url):
        url = url.toString()
        if url[:4] == 'doc:':
            #self.manager.showDocumentation(url[4:])
            print("Not implemented")
        elif url[:4] == 'exc:':
            cursor = self.ui.output.document().find('Show traceback %s' %
                                                    url[4:])
            try:
                tb = self.entries[int(url[4:]) - 1]['tracebackHtml']
            except IndexError:
                try:
                    tb = self.entries[self.entryArray[
                        self.entryArray['entryId'] == (
                            int(url[4:]))]['index']]['tracebackHtml']
                except:
                    print("requested index %d, but only %d entries exist." %
                          (int(url[4:]) - 1, len(self.entries)))
                    raise
            cursor.insertHtml(tb)

    def clear(self):
        self.ui.output.clear()
        self.displayedEntryies = []
Пример #4
0
class ExportWidget(QtWidgets.QWidget):
    def __init__(self, node, text):
        super().__init__()

        self.node = node
        self.text = text

        self.setWindowTitle("Export")
        self.layout = QtWidgets.QFormLayout(self)
        self.setLayout(self.layout)

        self.name = QtWidgets.QLineEdit(parent=self)
        self.docstring = QtWidgets.QTextEdit(parent=self)
        self.ok = QtWidgets.QPushButton("Ok", parent=self)
        self.ok.clicked.connect(self.ok_clicked)

        self.layout.addRow("Name:", self.name)
        self.layout.addRow("Docstring:", self.docstring)
        self.layout.addWidget(self.ok)

    def ok_clicked(self):
        self.fileDialog = FileDialog(None, "Save File..", '.', "Python (*.py)")
        self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
        self.fileDialog.show()
        self.fileDialog.fileSelected.connect(self.saveFile)

    def saveFile(self, fileName):
        node_name = self.name.text()
        docstring = self.docstring.toPlainText()

        terminals = {}
        for name, term in self.node.terminals.items():
            state = term.saveState()
            state['ttype'] = fullname(term._type)
            terminals[name] = state

        template = self.export(node_name, docstring, terminals, self.text)
        if not fileName.endswith('.py'):
            fileName += '.py'
        with open(fileName, 'w') as f:
            f.write(template)

    def export(self, name, docstring, terminals, text):
        template = f"""
from typing import Any
from amitypes import Array1d, Array2d, Array3d
from ami.flowchart.Node import Node
import ami.graph_nodes as gn


{text}


class {name}(Node):

    \"""
    {docstring}
    \"""

    nodeName = "{name}"

    def __init__(self, name):
        super().__init__(name, terminals={terminals})

    def to_operation(self, **kwargs):
        proc = EventProcessor()

        return gn.Map(name=self.name()+"_operation", **kwargs,
                      func=proc.on_event,
                      begin_run=proc.begin_run,
                      end_run=proc.end_run,
                      begin_step=proc.begin_step,
                      end_step=proc.end_step)
        """

        return template