def updateMotionHeatmap(self) -> None: """Repaint the motion heatmap when we enter this screen. We never record while on the playback screen, so we don't have to live-update here. This is partially due to the fact that the camera is modal around this, it can either record xor playback.""" return #Heatmap got delayed. Just return for now… heatmapHeight = 16 motionData = QByteArray.fromRawData(api.control('waterfallMotionMap', {'segment':'placeholder', 'startFrame':400})["heatmap"]) # 16×(n<1024) heatmap. motionData: {"startFrame": int, "endFrame": int, "heatmap": QByteArray} assert len(motionData) % heatmapHeight == 0, f"Incompatible heatmap size {len(motionData)}; must be a multiple of {heatmapHeight}." self.motionHeatmap = ( QImage( #Rotated 90°, since the data is packed line-by-line. We'll draw it counter-rotated. heatmapHeight, len(motionData)//heatmapHeight, QImage.Format_Grayscale8) .transformed(QTransform().rotate(-90).scale(-1,1)) .scaled( self.uiTimelineVisualization.width(), self.uiTimelineVisualization.height(), transformMode=QtCore.Qt.SmoothTransformation) ) self.uiTimelineVisualization.update() #Invokes self.paintMotionHeatmap if needed.
def createPath(cls, x, y, fill=Qt.OddEvenFill): # https://code.woboq.org/qt5/qtbase/src/gui/painting/qpainterpath.cpp.html#_ZrsR11QDataStreamR12QPainterPath # http://doc.qt.io/qt-5/qpainterpath.html#ElementType-enum # http://doc.qt.io/qt-5/qt.html#FillRule-enum # QDataStream &QPainterPath::operator>>(QDataStream &s, QPainterPath &p) # offset size type description # 0 4 int32 element count (N) # 4 4 int32 element type (0 -- 3) # 8 8 double x # 16 8 double y # ... # 20*i+ 4 4 int32 element type (0 -- 3) # 20*i+ 8 8 double x # 20*i+16 8 double y # ... # 20*(N-1)+ 4 4 int32 element type (0 -- 3) # 20*(N-1)+ 8 8 double x # 20*(N-1)+16 8 double y # 20*(N-1)+20 4 int32 next starting i (N-1) # 20*(N-1)+24 4 int32 fill rule path = QPainterPath() N = x.shape[0] if N == 0: return path data = np.empty(N + 2, dtype=[('type', '<i4'), ('x', '<f8'), ('y', '<f8')]) data[1]['type'] = 0 data[2:N + 1]['type'] = 1 data[1:N + 1]['x'] = x data[1:N + 1]['y'] = y fpos = 20 * (N + 1) view = data.view(dtype=np.ubyte) view[:16] = 0 view.data[16:20] = struct.pack('<i', N) view.data[fpos:fpos + 8] = struct.pack('<ii', N - 1, int(fill)) buf = QByteArray.fromRawData(view.data[16:fpos + 8]) ds = QDataStream(buf) ds.setByteOrder(ds.LittleEndian) ds >> path return path
def array2Path(x, y): """Convert array to QPainterPath.""" path = QPainterPath() if len(x) >= 2: # see: https://github.com/qt/qtbase/blob/dev/src/gui/painting/qpainterpath.cpp n = len(x) buf = np.empty(n + 2, dtype=[('c', '>i4'), ('x', '>f8'), ('y', '>f8')]) byteview = buf.view(dtype=np.ubyte) # header (size) byteview[:16] = 0 byteview.data[16:20] = struct.pack('>i', n) # data data = buf[1:-1] data['c'], data['x'], data['y'] = 1, x, y data['c'][0] = 0 # tail (cStart, fillRule) byteview.data[-20:-16] = struct.pack('>i', 0) byteview.data[-16:-12] = struct.pack('>i', 0) # take the pointer without copy arr = QByteArray.fromRawData(byteview.data[16:-12]) QDataStream(arr) >> path return path