def export(self, fileName=None, toBytes=False, copy=False): if toBytes is False and copy is False and fileName is None: self.fileSaveDialog(filter="Scalable Vector Graphics (*.svg)") return #self.svg = QtSvg.QSvgGenerator() #self.svg.setFileName(fileName) #dpi = QtGui.QDesktopWidget().physicalDpiX() ### not really sure why this works, but it seems to be important: #self.svg.setSize(QtCore.QSize(self.params['width']*dpi/90., self.params['height']*dpi/90.)) #self.svg.setResolution(dpi) ##self.svg.setViewBox() #targetRect = QtCore.QRect(0, 0, self.params['width'], self.params['height']) #sourceRect = self.getSourceRect() #painter = QtGui.QPainter(self.svg) #try: #self.setExportMode(True) #self.render(painter, QtCore.QRectF(targetRect), sourceRect) #finally: #self.setExportMode(False) #painter.end() ## Workaround to set pen widths correctly #data = open(fileName).readlines() #for i in range(len(data)): #line = data[i] #m = re.match(r'(<g .*)stroke-width="1"(.*transform="matrix\(([^\)]+)\)".*)', line) #if m is not None: ##print "Matched group:", line #g = m.groups() #matrix = list(map(float, g[2].split(','))) ##print "matrix:", matrix #scale = max(abs(matrix[0]), abs(matrix[3])) #if scale == 0 or scale == 1.0: #continue #data[i] = g[0] + ' stroke-width="%0.2g" ' % (1.0/scale) + g[1] + '\n' ##print "old line:", line ##print "new line:", data[i] #open(fileName, 'w').write(''.join(data)) ## Qt's SVG generator is not complete. (notably, it lacks clipping) ## Instead, we will use Qt to generate SVG for each item independently, ## then manually reconstruct the entire document. xml = generateSvg(self.item) if toBytes: return xml.encode('UTF-8') elif copy: md = QtCore.QMimeData() md.setData('image/svg+xml', QtCore.QByteArray(xml.encode('UTF-8'))) QtGui.QApplication.clipboard().setMimeData(md) else: with open(fileName, 'w') as fh: fh.write(xml.encode('UTF-8'))
def generatePath(self, x, y): prof = debug.Profiler('PlotCurveItem.generatePath', disabled=True) path = QtGui.QPainterPath() ## Create all vertices in path. The method used below creates a binary format so that all ## vertices can be read in at once. This binary format may change in future versions of Qt, ## so the original (slower) method is left here for emergencies: #path.moveTo(x[0], y[0]) #for i in range(1, y.shape[0]): # path.lineTo(x[i], y[i]) ## Speed this up using >> operator ## Format is: ## numVerts(i4) 0(i4) ## x(f8) y(f8) 0(i4) <-- 0 means this vertex does not connect ## x(f8) y(f8) 1(i4) <-- 1 means this vertex connects to the previous vertex ## ... ## 0(i4) ## ## All values are big endian--pack using struct.pack('>d') or struct.pack('>i') n = x.shape[0] # create empty array, pad with extra space on either end arr = np.empty(n + 2, dtype=[('x', '>f8'), ('y', '>f8'), ('c', '>i4')]) # write first two integers prof.mark('allocate empty') arr.data[12:20] = struct.pack('>ii', n, 0) prof.mark('pack header') # Fill array with vertex values arr[1:-1]['x'] = x arr[1:-1]['y'] = y arr[1:-1]['c'] = 1 prof.mark('fill array') # write last 0 lastInd = 20 * (n + 1) arr.data[lastInd:lastInd + 4] = struct.pack('>i', 0) prof.mark('footer') # create datastream object and stream into path buf = QtCore.QByteArray( arr.data[12:lastInd + 4]) # I think one unnecessary copy happens here prof.mark('create buffer') ds = QtCore.QDataStream(buf) prof.mark('create datastream') ds >> path prof.mark('load') prof.finish() return path
def export(self, fileName=None, toBytes=False, copy=False): if toBytes is False and copy is False and fileName is None: self.fileSaveDialog(filter="Scalable Vector Graphics (*.svg)") return ## Qt's SVG generator is not complete. (notably, it lacks clipping) ## Instead, we will use Qt to generate SVG for each item independently, ## then manually reconstruct the entire document. xml = generateSvg(self.item) if toBytes: return xml.encode('UTF-8') elif copy: md = QtCore.QMimeData() md.setData('image/svg+xml', QtCore.QByteArray(xml.encode('UTF-8'))) QtGui.QApplication.clipboard().setMimeData(md) else: with open(fileName, 'wb') as fh: fh.write(asUnicode(xml).encode('utf-8'))
def arrayToQPath(x, y, connect='all'): """Convert an array of x,y coordinats to QPainterPath as efficiently as possible. The *connect* argument may be 'all', indicating that each point should be connected to the next; 'pairs', indicating that each pair of points should be connected, or an array of int32 values (0 or 1) indicating connections. """ ## Create all vertices in path. The method used below creates a binary format so that all ## vertices can be read in at once. This binary format may change in future versions of Qt, ## so the original (slower) method is left here for emergencies: # path.moveTo(x[0], y[0]) # if connect == 'all': # for i in range(1, y.shape[0]): # path.lineTo(x[i], y[i]) # elif connect == 'pairs': # for i in range(1, y.shape[0]): # if i%2 == 0: # path.lineTo(x[i], y[i]) # else: # path.moveTo(x[i], y[i]) # elif isinstance(connect, np.ndarray): # for i in range(1, y.shape[0]): # if connect[i] == 1: # path.lineTo(x[i], y[i]) # else: # path.moveTo(x[i], y[i]) # else: # raise Exception('connect argument must be "all", "pairs", or array') ## Speed this up using >> operator ## Format is: ## numVerts(i4) ## 0(i4) x(f8) y(f8) <-- 0 means this vertex does not connect ## 1(i4) x(f8) y(f8) <-- 1 means this vertex connects to the previous vertex ## ... ## cStart(i4) fillRule(i4) ## ## see: https://github.com/qt/qtbase/blob/dev/src/gui/painting/qpainterpath.cpp ## All values are big endian--pack using struct.pack('>d') or struct.pack('>i') path = QtGui.QPainterPath() n = x.shape[0] # create empty array, pad with extra space on either end arr = np.empty(n + 2, dtype=[('c', '>i4'), ('x', '>f8'), ('y', '>f8')]) # write first two integers byteview = arr.view(dtype=np.ubyte) byteview[:16] = 0 byteview.data[16:20] = struct.pack('>i', n) # Fill array with vertex values arr[1:-1]['x'] = x arr[1:-1]['y'] = y # inf/nans completely prevent the plot from being displayed starting on # Qt version 5.12.3; these must now be manually cleaned out. isfinite = None qtver = [int(x) for x in QtVersion.split('.')] if qtver >= [5, 12, 3]: isfinite = np.isfinite(x) & np.isfinite(y) if not np.all(isfinite): # credit: Divakar https://stackoverflow.com/a/41191127/643629 mask = ~isfinite idx = np.arange(len(x)) idx[mask] = -1 np.maximum.accumulate(idx, out=idx) first = np.searchsorted(idx, 0) if first < len(x): # Replace all non-finite entries from beginning of arr with the first finite one idx[:first] = first arr[1:-1] = arr[1:-1][idx] # decide which points are connected by lines if eq(connect, 'all'): arr[1:-1]['c'] = 1 elif eq(connect, 'pairs'): arr[1:-1]['c'][::2] = 0 arr[1:-1]['c'][ 1::2] = 1 # connect every 2nd point to every 1st one elif eq(connect, 'finite'): # Let's call a point with either x or y being nan is an invalid point. # A point will anyway not connect to an invalid point regardless of the # 'c' value of the invalid point. Therefore, we should set 'c' to 0 for # the next point of an invalid point. if isfinite is None: isfinite = np.isfinite(x) & np.isfinite(y) arr[2:]['c'] = isfinite elif isinstance(connect, np.ndarray): arr[1:-1]['c'] = connect else: raise Exception( 'connect argument must be "all", "pairs", "finite", or array') arr[1]['c'] = 0 # the first vertex has no previous vertex to connect byteview.data[-20:-16] = struct.pack('>i', 0) # cStart byteview.data[-16:-12] = struct.pack('>i', 0) # fillRule (Qt.OddEvenFill) # create datastream object and stream into path ## Avoiding this method because QByteArray(str) leaks memory in PySide # buf = QtCore.QByteArray(arr.data[12:lastInd+4]) # I think one unnecessary copy happens here path.strn = byteview.data[16:-12] # make sure data doesn't run away try: buf = QtCore.QByteArray.fromRawData(path.strn) except TypeError: buf = QtCore.QByteArray(bytes(path.strn)) ds = QtCore.QDataStream(buf) ds >> path return path
def _generateItemSvg(item, nodes=None, root=None): ## This function is intended to work around some issues with Qt's SVG generator ## and SVG in general. ## 1) Qt SVG does not implement clipping paths. This is absurd. ## The solution is to let Qt generate SVG for each item independently, ## then glue them together manually with clipping. ## ## The format Qt generates for all items looks like this: ## ## <g> ## <g transform="matrix(...)"> ## one or more of: <path/> or <polyline/> or <text/> ## </g> ## <g transform="matrix(...)"> ## one or more of: <path/> or <polyline/> or <text/> ## </g> ## . . . ## </g> ## ## 2) There seems to be wide disagreement over whether path strokes ## should be scaled anisotropically. ## see: http://web.mit.edu/jonas/www/anisotropy/ ## Given that both inkscape and illustrator seem to prefer isotropic ## scaling, we will optimize for those cases. ## ## 3) Qt generates paths using non-scaling-stroke from SVG 1.2, but ## inkscape only supports 1.1. ## ## Both 2 and 3 can be addressed by drawing all items in world coordinates. profiler = debug.Profiler() if nodes is None: ## nodes maps all node IDs to their XML element. ## this allows us to ensure all elements receive unique names. nodes = {} if root is None: root = item ## Skip hidden items if hasattr(item, 'isVisible') and not item.isVisible(): return None ## If this item defines its own SVG generator, use that. if hasattr(item, 'generateSvg'): return item.generateSvg(nodes) ## Generate SVG text for just this item (exclude its children; we'll handle them later) tr = QtGui.QTransform() if isinstance(item, QtGui.QGraphicsScene): xmlStr = "<g>\n</g>\n" doc = xml.parseString(xmlStr) childs = [i for i in item.items() if i.parentItem() is None] elif item.__class__.paint == QtGui.QGraphicsItem.paint: xmlStr = "<g>\n</g>\n" doc = xml.parseString(xmlStr) childs = item.childItems() else: childs = item.childItems() tr = itemTransform(item, item.scene()) ## offset to corner of root item if isinstance(root, QtGui.QGraphicsScene): rootPos = QtCore.QPoint(0, 0) else: rootPos = root.scenePos() tr2 = QtGui.QTransform() tr2.translate(-rootPos.x(), -rootPos.y()) tr = tr * tr2 arr = QtCore.QByteArray() buf = QtCore.QBuffer(arr) svg = QtSvg.QSvgGenerator() svg.setOutputDevice(buf) dpi = QtGui.QDesktopWidget().logicalDpiX() svg.setResolution(dpi) p = QtGui.QPainter() p.begin(svg) if hasattr(item, 'setExportMode'): item.setExportMode(True, {'painter': p}) try: p.setTransform(tr) item.paint(p, QtGui.QStyleOptionGraphicsItem(), None) finally: p.end() ## Can't do this here--we need to wait until all children have painted as well. ## this is taken care of in generateSvg instead. #if hasattr(item, 'setExportMode'): #item.setExportMode(False) if USE_PYSIDE: xmlStr = str(arr) else: xmlStr = bytes(arr).decode('utf-8') doc = xml.parseString(xmlStr.encode('utf-8')) try: ## Get top-level group for this item g1 = doc.getElementsByTagName('g')[0] ## get list of sub-groups g2 = [ n for n in g1.childNodes if isinstance(n, xml.Element) and n.tagName == 'g' ] defs = doc.getElementsByTagName('defs') if len(defs) > 0: defs = [ n for n in defs[0].childNodes if isinstance(n, xml.Element) ] except: print(doc.toxml()) raise profiler('render') ## Get rid of group transformation matrices by applying ## transformation to inner coordinates correctCoordinates(g1, defs, item) profiler('correct') ## make sure g1 has the transformation matrix #m = (tr.m11(), tr.m12(), tr.m21(), tr.m22(), tr.m31(), tr.m32()) #g1.setAttribute('transform', "matrix(%f,%f,%f,%f,%f,%f)" % m) #print "=================",item,"=====================" #print g1.toprettyxml(indent=" ", newl='') ## Inkscape does not support non-scaling-stroke (this is SVG 1.2, inkscape supports 1.1) ## So we need to correct anything attempting to use this. #correctStroke(g1, item, root) ## decide on a name for this item baseName = item.__class__.__name__ i = 1 while True: name = baseName + "_%d" % i if name not in nodes: break i += 1 nodes[name] = g1 g1.setAttribute('id', name) ## If this item clips its children, we need to take care of that. childGroup = g1 ## add children directly to this node unless we are clipping if not isinstance(item, QtGui.QGraphicsScene): ## See if this item clips its children if int(item.flags() & item.ItemClipsChildrenToShape) > 0: ## Generate svg for just the path #if isinstance(root, QtGui.QGraphicsScene): #path = QtGui.QGraphicsPathItem(item.mapToScene(item.shape())) #else: #path = QtGui.QGraphicsPathItem(root.mapToParent(item.mapToItem(root, item.shape()))) path = QtGui.QGraphicsPathItem(item.mapToScene(item.shape())) item.scene().addItem(path) try: #pathNode = _generateItemSvg(path, root=root).getElementsByTagName('path')[0] pathNode = _generateItemSvg( path, root=root)[0].getElementsByTagName('path')[0] # assume <defs> for this path is empty.. possibly problematic. finally: item.scene().removeItem(path) ## and for the clipPath element clip = name + '_clip' clipNode = g1.ownerDocument.createElement('clipPath') clipNode.setAttribute('id', clip) clipNode.appendChild(pathNode) g1.appendChild(clipNode) childGroup = g1.ownerDocument.createElement('g') childGroup.setAttribute('clip-path', 'url(#%s)' % clip) g1.appendChild(childGroup) profiler('clipping') ## Add all child items as sub-elements. childs.sort(key=lambda c: c.zValue()) for ch in childs: csvg = _generateItemSvg(ch, nodes, root) if csvg is None: continue cg, cdefs = csvg childGroup.appendChild( cg ) ### this isn't quite right--some items draw below their parent (good enough for now) defs.extend(cdefs) profiler('children') return g1, defs
def initUI(self, e=None): """ Definition and initialisation of the GUI plus staring the measurement. """ self._scanning_logic = self.connector['in']['confocallogic1']['object'] print("Scanning logic is", self._scanning_logic) # setting up the window self._mw = QtGui.QMainWindow() self._mw.setWindowTitle('qudi: Counter Test GUI') self._mw.resize(400, 100) self._cw = QtGui.QWidget() self._mw.setCentralWidget(self._cw) # defining buttons self._start_stop_button = QtGui.QPushButton('Start Scan') self._start_stop_button.released.connect(self.start_clicked) # defining the parameters to edit self._x_label = QtGui.QLabel('x:') self._x_display = QtGui.QSpinBox() self._x_display.setRange(0, 1e3) self._x_display.setValue(10) self._x_display.valueChanged.connect(self.x_changed) self._y_label = QtGui.QLabel('y:') self._y_display = QtGui.QSpinBox() self._y_display.setRange(0, 1e3) self._y_display.setValue(10) self._y_display.valueChanged.connect(self.x_changed) self._z_label = QtGui.QLabel('z:') self._z_display = QtGui.QSpinBox() self._z_display.setRange(0, 1e3) self._z_display.setValue(10) self._z_display.valueChanged.connect(self.x_changed) self._a_label = QtGui.QLabel('a:') self._a_display = QtGui.QSpinBox() self._a_display.setRange(0, 1e3) self._a_display.setValue(10) self._a_display.valueChanged.connect(self.x_changed) # creating a layout for the parameters to live in and aranging it nicely self._hbox_layout = QtGui.QHBoxLayout() self._hbox_layout.addWidget(self._x_label) self._hbox_layout.addWidget(self._x_display) self._hbox_layout.addStretch(1) self._hbox_layout.addWidget(self._y_label) self._hbox_layout.addWidget(self._y_display) self._hbox_layout.addStretch(1) self._hbox_layout.addWidget(self._z_label) self._hbox_layout.addWidget(self._z_display) self._hbox_layout.addStretch(1) self._hbox_layout.addWidget(self._a_label) self._hbox_layout.addWidget(self._a_display) # funny gifs self.movie_screen = QtGui.QLabel() # self.movie_screen.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) self.movie_screen.setFixedHeight(300) self.movie_screen.setFixedWidth(300) self.movie_screen.setAlignment(QtCore.Qt.AlignCenter) self.movie_screen.setStyleSheet( 'QLabel {background-color: white; color: red;}') # Add the QMovie object to the label self.idle_movie = QtGui.QMovie('artwork/idle_smiley.gif', QtCore.QByteArray(), self) self.idle_movie.setCacheMode(QtGui.QMovie.CacheAll) self.idle_movie.setSpeed(100) # self.idle_movie.setBackgroundColor(QtGui.QColor(255,255,255)) self.idle_movie.start() self.active_movie = QtGui.QMovie('artwork/active_smiley.gif', QtCore.QByteArray(), self) self.active_movie.setCacheMode(QtGui.QMovie.CacheAll) self.active_movie.setSpeed(100) self.active_movie.start() self.movie_screen.setMovie(self.idle_movie) self._hbox_layout_icon = QtGui.QHBoxLayout() self._hbox_layout_icon.addStretch(1) self._hbox_layout_icon.addWidget(self.movie_screen) self._hbox_layout_icon.addStretch(1) # kombining the layouts with the plot self._vbox_layout = QtGui.QVBoxLayout() self._vbox_layout.addLayout(self._hbox_layout) self._vbox_layout.addWidget(self._start_stop_button) self._vbox_layout.addStretch(1) self._vbox_layout.addLayout(self._hbox_layout_icon) # applying all the GUI elements to the window self._cw.setLayout(self._vbox_layout) self._mw.show() # starting the physical measurement self._scanning_logic.start_scanner() self.signal_start_scan.connect(self._scanning_logic.start_scanning) self.signal_stop_scan.connect(self._scanning_logic.stop_scanning) self._scanning_logic.signal_scan_updated.connect(self.update_state)