zx 1 year ago
commit
d72c4cdd07
10 changed files with 641 additions and 0 deletions
  1. 283 0
      CustomFrame.py
  2. 62 0
      GrpcClient.py
  3. 51 0
      MainWnd.py
  4. 23 0
      def.proto
  5. 32 0
      def_pb2.py
  6. 99 0
      def_pb2_grpc.py
  7. 16 0
      main.py
  8. 1 0
      proto.sh
  9. 70 0
      setup.py
  10. 4 0
      ui_config.json

+ 283 - 0
CustomFrame.py

@@ -0,0 +1,283 @@
+import os
+import subprocess
+
+from PyQt5.QtWidgets import (QWidget,QVBoxLayout,QTabWidget,QSplitter, QFileDialog,QMessageBox,QComboBox,
+                             QLineEdit,QTextEdit,QCheckBox,QLabel,QFrame,QPushButton,QMenu,QAction)
+from PyQt5.QtGui import QPixmap,QImage,QPainter,QResizeEvent,QCloseEvent,QPaintEvent
+from PyQt5.QtCore import QRect,Qt
+import numpy as np
+import def_pb2 as pb
+import GrpcClient as rpc
+import vtk
+from vtkmodules.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
+#from vtk.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
+
+import threading
+import json
+class ControlFrame(QFrame):
+    def __init__(self):
+        QFrame.__init__(self)
+        with open("./ui_config.json",'r',encoding='utf-8') as fp:
+            self.config=json.load(fp)
+        self.setGeometry(0, 0, 500, 500)
+        self.InitUI()
+        self.setFrameShape(self.StyledPanel)
+
+    def OnAction(self,dtype,id):
+        client=rpc.GrpcStream()
+        ip=self.config[self.IPcombox.currentText()]
+        port=int(self.PortEdit.text())
+        try:
+            if dtype==1:
+                client.GrabImage(ip,port,id)
+            if dtype==2:
+                client.GrabCloud(ip,port,id)
+        except Exception as e:
+            QMessageBox.information(self, 'ERROR',str(e),QMessageBox.Ok,QMessageBox.Ok)
+
+    def InitUI(self):
+
+        self.begstatic = QLabel(self)
+        self.begstatic.setText("   IP:")
+        self.begstatic.setGeometry(20, 5, 80, 25)
+        self.IPcombox = QComboBox(self)
+        self.IPcombox.addItems(self.config.keys())
+        self.IPcombox.setGeometry(90, 5, 150, 25)
+
+        self.endstatic = QLabel(self)
+        self.endstatic.setText("Port:")
+        self.endstatic.setGeometry(20, 35, 80, 25)
+        self.PortEdit = QLineEdit(self)
+        self.PortEdit.setText("9876")
+        self.PortEdit.setGeometry(90, 35, 150, 25)
+
+        self.btnDataStreamCheck = QCheckBox("实时显示", self)
+        self.btnDataStreamCheck.setGeometry(150, 65, 120, 30)
+        self.btnDataStreamCheck.clicked.connect(self.RealTimeDatacb)
+
+        self.staticError = QLabel(self)
+        self.staticError.setText("信息:")
+        self.staticError.setGeometry(20, 100, 230, 400)
+        self.staticError.setWordWrap(True)
+        self.staticError.setAlignment(Qt.AlignTop)
+    def DisplayErrorInfo(self,info):
+        self.staticError.setText("信息:"+info)
+    def RealTimeDatacb(self):
+        self.staticError.setText("信息:")
+        try:
+            if self.btnDataStreamCheck.checkState()==Qt.Checked:
+                client=rpc.GrpcStream()
+                ip=self.config[self.IPcombox.currentText()]
+                port=int(self.PortEdit.text())
+                client.OpenDataStream(ip,port)
+            else:
+                client=rpc.GrpcStream()
+                ip=self.config[self.IPcombox.currentText()]
+                port=int(self.PortEdit.text())
+                client.CloseDataStream(ip,port)
+        except Exception as e:
+            QMessageBox.information(self, 'ERROR',str(e),QMessageBox.Ok,QMessageBox.Ok)
+    def closeEvent(self, a0: QCloseEvent) -> None:
+        if self.btnDataStreamCheck.checkState()==Qt.Checked:
+            client=rpc.GrpcStream()
+            ip=self.config[self.IPcombox.currentText()]
+            port=int(self.PortEdit.text())
+            client.CloseDataStream(ip,port)
+        rpc.GrpcStream().close()
+
+class VtkPointCloud:
+    def __init__(self, zMin=-10.0, zMax=10.0, maxNumPoints=1e7):
+        self.maxNumPoints = maxNumPoints
+        self.vtkPolyData = vtk.vtkPolyData()
+        self.clearPoints()
+        mapper = vtk.vtkPolyDataMapper()
+        mapper.SetInputData(self.vtkPolyData)
+        mapper.SetColorModeToDefault()
+        mapper.SetScalarRange(zMin, zMax)
+        mapper.SetScalarVisibility(1)
+        self.vtkActor = vtk.vtkActor()
+
+
+        self.vtkActor.SetMapper(mapper)
+        self.vtkActor.GetProperty().SetPointSize(1)
+        self.vtkActor.GetProperty().SetColor(1, 1, 1)
+
+    def addPoint(self, point):
+        if self.vtkPoints.GetNumberOfPoints() < self.maxNumPoints:
+            pointId = self.vtkPoints.InsertNextPoint(point)
+            self.vtkDepth.InsertNextValue(point[2])
+            self.vtkCells.InsertNextCell(1)
+            self.vtkCells.InsertCellPoint(pointId)
+        self.vtkCells.Modified()
+        self.vtkPoints.Modified()
+        self.vtkDepth.Modified()
+
+    def clearPoints(self):
+        self.vtkPoints = vtk.vtkPoints()
+        self.vtkCells = vtk.vtkCellArray()
+        self.vtkDepth = vtk.vtkDoubleArray()
+        self.vtkDepth.SetName('DepthArray')
+        self.vtkPolyData.SetPoints(self.vtkPoints)
+        self.vtkPolyData.SetVerts(self.vtkCells)
+        #self.vtkPolyData.GetPointData().SetScalars(self.vtkDepth)
+        self.vtkPolyData.GetPointData().SetActiveScalars('DepthArray')
+
+class pclViewer(QVTKRenderWindowInteractor):
+    def __init__(self,parent=None):
+        super(pclViewer,self).__init__(parent=parent)
+    def mousePressEvent(self, ev):
+        btn = ev.button()
+        if btn == Qt.RightButton:
+            print(" r btn")
+        else:
+            pass
+            #QVTKRenderWindowInteractor.mousePressEvent(self,ev)
+
+
+class VtkPointCloudCanvas(QWidget):
+    def __init__(self, *args, **kwargs):
+        super(VtkPointCloudCanvas, self).__init__(*args, **kwargs)
+        self.lock_=threading.RLock()
+
+        self._layout = QVBoxLayout()
+        self.setLayout(self._layout)
+        self._vtk_widget = pclViewer(self)
+        self._vtk_widget.SetInteractorStyle(vtk.vtkInteractorStyleTrackballCamera())  # 设置交互方式==常用的方式 移动摄像机
+
+        self._layout.addWidget(self._vtk_widget)
+
+        self._render = vtk.vtkRenderer()
+        self._vtk_widget.GetRenderWindow().AddRenderer(self._render)
+        self._iren = self._vtk_widget.GetRenderWindow().GetInteractor()
+        self._point_cloud = VtkPointCloud()
+        self._render.AddActor(self._point_cloud.vtkActor)
+
+        transform = vtk.vtkTransform()
+        transform.Translate(0, -0.1, 0.0)
+        axes = vtk.vtkAxesActor()
+        axes.SetUserTransform(transform)
+        axes.SetTotalLength([0.1,0.1,0])
+
+        axes.SetXAxisLabelText('x')
+        axes.SetYAxisLabelText('y')
+        axes.SetZAxisLabelText('')
+        colors = vtk.vtkNamedColors()
+        axes.GetXAxisCaptionActor2D().GetCaptionTextProperty().SetColor(colors.GetColor3d("Red"))
+        axes.GetYAxisCaptionActor2D().GetCaptionTextProperty().SetColor(colors.GetColor3d("Green"))
+        axes.GetZAxisCaptionActor2D().GetCaptionTextProperty().SetColor(colors.GetColor3d("Blue"))
+        self._render.AddActor(axes)
+
+        self.camera=self._render.GetActiveCamera()
+        self.show()
+        self._iren.Initialize()
+
+    def close(self) -> bool:
+        print("  tk close")
+        self._point_cloud.clearPoints()
+
+    def displayPCL(self,cloud:np.array):
+        colors = vtk.vtkUnsignedCharArray()
+        colors.SetNumberOfComponents(3)
+        colors.SetName("Colors")
+
+        self._point_cloud.clearPoints()
+        for point in cloud:
+            self._point_cloud.addPoint(point)
+            colors.InsertNextTypedTuple((255,255,255))
+        self._point_cloud.vtkPolyData.GetPointData().SetScalars(colors)
+        self._vtk_widget.update()
+    def resetViewer(self):
+        self.update()
+
+
+class PointCLViwer(QSplitter):
+    def __init__(self,OnAction,id):
+        super(PointCLViwer, self).__init__()
+        self.OnAction=OnAction
+        self.dtype=2
+        self.id=id
+        self.pointCloud=[]
+        self.InitUI()
+    def InitUI(self):
+        self.pclViewer=VtkPointCloudCanvas(self)
+        self.addWidget(self.pclViewer)
+        self.setStretchFactor(0,4)
+
+    def displayCloud(self,points:np.array):
+        self.pointCloud=points
+        self.pclViewer.displayPCL(points)
+    def resetViewer(self):
+        self.pclViewer.resetViewer()
+    def updataData(self):
+        self.OnAction(self.dtype,self.id-1)
+    def displayAllPoints(self):
+        self.pclViewer.displayPCL(self.pointCloud)
+    def contextMenuEvent(self, a0):
+        pass
+
+
+class ViewerFrame(QFrame):
+    def __init__(self,OnAction):
+        super(ViewerFrame, self).__init__()
+        self.OnAction=OnAction
+        self.InitUI()
+    def InitUI(self):
+
+        self.table1=QTabWidget(self)
+        self.table2=QTabWidget(self)
+        self.table3=QTabWidget(self)
+        self.table4=QTabWidget(self)
+        self.table1.setTabPosition(QTabWidget.TabPosition.North)
+        self.table2.setTabPosition(QTabWidget.TabPosition.North)
+        self.table3.setTabPosition(QTabWidget.TabPosition.North)
+        self.table4.setTabPosition(QTabWidget.TabPosition.North)
+
+        self.pcViewer1=PointCLViwer(self.OnAction,1)
+        self.pcViewer2=PointCLViwer(self.OnAction,2)
+        self.pcViewer3=PointCLViwer(self.OnAction,3)
+        self.pcViewer4=PointCLViwer(self.OnAction,4)
+
+        self.table1.addTab(self.pcViewer1,"C3")
+        self.table2.addTab(self.pcViewer2,"C4")
+        self.table3.addTab(self.pcViewer3,"C5")
+        self.table4.addTab(self.pcViewer4,"C6")
+
+    def closeEvent(self, a0: QCloseEvent) -> None:
+
+        pass
+    def resizeEvent(self, a0: QResizeEvent) -> None:
+        w, h = self.size().width(), self.size().height()
+        w=w-15
+        h=h-15
+        self.table1.setGeometry(5, 5, w/2, h/2)
+        self.table2.setGeometry(10+w/2, 5, w/2, h/2)
+        self.table3.setGeometry(5, 10+h/2, w/2, h/2)
+        self.table4.setGeometry(10+w/2, 10+h/2, w/2, h/2)
+
+    def PbCloud2Pts(self,cloud):
+        size=len(cloud)
+        npdata=np.zeros(shape=[size,3],dtype=np.float32)
+        i=0
+        for point in cloud:
+            x=point.x
+            y=point.y
+            npdata[i,:]=[x,y-0.1,0]
+            i=i+1
+        return npdata
+    def DisplayCloud(self,clouds:pb.clouds):
+        self.pcViewer1.displayCloud(self.PbCloud2Pts(clouds.clamp_1))
+        self.pcViewer2.displayCloud(self.PbCloud2Pts(clouds.clamp_2))
+        self.pcViewer3.displayCloud(self.PbCloud2Pts(clouds.clamp_3))
+        self.pcViewer4.displayCloud(self.PbCloud2Pts(clouds.clamp_4))
+
+
+
+
+
+
+
+
+
+
+
+

+ 62 - 0
GrpcClient.py

@@ -0,0 +1,62 @@
+import time
+import grpc
+import def_pb2 as pb
+import def_pb2_grpc as mrpc
+import threading
+from concurrent.futures import ThreadPoolExecutor
+def singleton(cls):
+    _instance = {}
+
+    def inner():
+        if cls not in _instance:
+            _instance[cls] = cls()
+        return _instance[cls]
+    return inner
+@singleton
+class GrpcStream(threading.Thread):
+    def __init__(self):
+        threading.Thread.__init__(self)
+        self.threadpool=ThreadPoolExecutor(5)
+        self.exit_=False
+        self.features_=None
+        self.clouds_=None
+        self.cloudCallBack=None
+        self.ErrorCallback=None
+
+    def SetDataCallBack(self,CloudsCallBack,ErrorCallback):
+        self.cloudCallBack=CloudsCallBack
+        self.ErrorCallback=ErrorCallback
+
+    def OpenDataStream(self,ip,port):
+        connectstr='%s:%d'%(ip,port)
+        channel=grpc.insecure_channel(connectstr)
+        stub = mrpc.StreamServerStub(channel)
+        cmd=pb.RequestCmd()
+        self.clouds_ = stub.OpenStream(cmd)
+    def CloseDataStream(self,ip,port):
+        connectstr='%s:%d'%(ip,port)
+        channel=grpc.insecure_channel(connectstr)
+        stub = mrpc.StreamServerStub(channel)
+        cmd=pb.RequestCmd()
+        print(" close stream")
+        stub.CloseStream(cmd)
+
+    def loopDataStream(self):
+        while self.exit_ == False:
+            time.sleep(0.001)
+            if self.clouds_ is not None:
+                try:
+                    for clouds in self.clouds_:
+                        if self.cloudCallBack is not None:
+                            self.cloudCallBack(clouds)
+                except Exception as e:
+                    if self.ErrorCallback is not None:
+                        self.ErrorCallback(str(e))
+                    self.clouds_=None
+    def run(self):
+        self.threadpool.submit(self.loopDataStream)
+        print(" close ")
+    def close(self):
+        self.exit_=True
+        self.threadpool.shutdown()
+        self.join()

+ 51 - 0
MainWnd.py

@@ -0,0 +1,51 @@
+
+from PyQt5.QtGui import *
+from PyQt5.QtWidgets import *
+from CustomFrame import *
+import os
+import datetime
+
+class MainWindow(QMainWindow):
+    """docstring for Mainwindow"""
+
+    def __init__(self, parent = None):
+        super(MainWindow,self).__init__(parent)
+        self.basic()
+        self.Controller = ControlFrame()
+        self.viewerFrame = ViewerFrame(self.Controller.OnAction)
+
+        splitter= self.split_()
+        self.setCentralWidget(splitter)
+
+    def basic(self):
+        #设置标题,大小,图标
+        self.setWindowTitle("RPC3DView")
+        self.resize(490*2+300, 370*2)
+        #self.setWindowIcon(QIcon("./image/Gt.png"))
+        #居中显示
+        screen = QDesktopWidget().geometry()
+        self_size = self.geometry()
+        self.move(int((screen.width() - self_size.width())/2),int((screen.height() - self_size.height())/2))
+
+    def closeEvent(self, QCloseEvent):
+        self.Controller.close()
+        self.viewerFrame.close()
+
+    #分割窗口
+    def split_(self):
+
+        splitter = QSplitter(Qt.Horizontal)
+
+        splitter.addWidget(self.viewerFrame)
+        splitter.addWidget(self.Controller)
+
+        splitter.setStretchFactor(0,11)
+        splitter.setStretchFactor(1,4)
+        return splitter
+
+
+    def OnCloudHandle(self,clouds:pb.clouds):
+        self.viewerFrame.DisplayCloud(clouds)
+    def OnErrorHandle(self,info:str):
+        self.Controller.DisplayErrorInfo(info)
+

+ 23 - 0
def.proto

@@ -0,0 +1,23 @@
+syntax = "proto2";
+
+message PointXY {
+  required float x = 1;
+  required float y = 2;
+}
+
+message clouds {
+  repeated PointXY clamp_1 = 1;
+  repeated PointXY clamp_2 = 2;
+  repeated PointXY clamp_3 = 3;
+  repeated PointXY clamp_4 = 4;
+}
+
+message RequestCmd {
+  optional bool start = 1;
+}
+
+service StreamServer{
+  rpc OpenStream(RequestCmd) returns(stream clouds){}
+  rpc CloseStream(RequestCmd) returns(stream clouds){}
+}
+

File diff suppressed because it is too large
+ 32 - 0
def_pb2.py


+ 99 - 0
def_pb2_grpc.py

@@ -0,0 +1,99 @@
+# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
+"""Client and server classes corresponding to protobuf-defined services."""
+import grpc
+
+import def_pb2 as def__pb2
+
+
+class StreamServerStub(object):
+    """Missing associated documentation comment in .proto file."""
+
+    def __init__(self, channel):
+        """Constructor.
+
+        Args:
+            channel: A grpc.Channel.
+        """
+        self.OpenStream = channel.unary_stream(
+                '/StreamServer/OpenStream',
+                request_serializer=def__pb2.RequestCmd.SerializeToString,
+                response_deserializer=def__pb2.clouds.FromString,
+                )
+        self.CloseStream = channel.unary_stream(
+                '/StreamServer/CloseStream',
+                request_serializer=def__pb2.RequestCmd.SerializeToString,
+                response_deserializer=def__pb2.clouds.FromString,
+                )
+
+
+class StreamServerServicer(object):
+    """Missing associated documentation comment in .proto file."""
+
+    def OpenStream(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def CloseStream(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+
+def add_StreamServerServicer_to_server(servicer, server):
+    rpc_method_handlers = {
+            'OpenStream': grpc.unary_stream_rpc_method_handler(
+                    servicer.OpenStream,
+                    request_deserializer=def__pb2.RequestCmd.FromString,
+                    response_serializer=def__pb2.clouds.SerializeToString,
+            ),
+            'CloseStream': grpc.unary_stream_rpc_method_handler(
+                    servicer.CloseStream,
+                    request_deserializer=def__pb2.RequestCmd.FromString,
+                    response_serializer=def__pb2.clouds.SerializeToString,
+            ),
+    }
+    generic_handler = grpc.method_handlers_generic_handler(
+            'StreamServer', rpc_method_handlers)
+    server.add_generic_rpc_handlers((generic_handler,))
+
+
+ # This class is part of an EXPERIMENTAL API.
+class StreamServer(object):
+    """Missing associated documentation comment in .proto file."""
+
+    @staticmethod
+    def OpenStream(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_stream(request, target, '/StreamServer/OpenStream',
+            def__pb2.RequestCmd.SerializeToString,
+            def__pb2.clouds.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def CloseStream(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_stream(request, target, '/StreamServer/CloseStream',
+            def__pb2.RequestCmd.SerializeToString,
+            def__pb2.clouds.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)

+ 16 - 0
main.py

@@ -0,0 +1,16 @@
+
+import sys
+from PyQt5.QtWidgets import  QApplication
+import MainWnd as mw
+import GrpcClient as Stream
+if __name__=="__main__":
+    app = QApplication(sys.argv)
+    win = mw.MainWindow()
+    win.show()
+
+    stream=Stream.GrpcStream()
+    stream.SetDataCallBack(win.OnCloudHandle,win.OnErrorHandle)
+    stream.start()
+    sys.exit(app.exec_())
+
+

+ 1 - 0
proto.sh

@@ -0,0 +1 @@
+python -m grpc_tools.protoc -I. --python_out=. --pyi_out=. --grpc_python_out=. def.proto

+ 70 - 0
setup.py

@@ -0,0 +1,70 @@
+from setuptools import setup
+
+
+def readme():
+    with open('README.md', encoding='utf-8') as f:
+        content = f.read()
+    return content
+
+
+setup(
+    # 包名称
+    name='ClampViewer',
+
+    # 版本
+    version='1.0.0',
+
+    # 作者
+    author='zzw',
+
+    # 作者邮箱
+    author_email='lz2297519360@outlook.com',
+
+    # 长文描述
+    long_description=readme(),
+
+    # 长文描述的文本格式
+    long_description_content_type='text/markdown',
+
+    # 关键词
+    keywords='DataAcquisition',
+
+    # 包的分类信息,见https://pypi.org/pypi?%3Aaction=list_classifiers
+    classifiers=[
+    ],
+    # 许可证
+    license='Apache License 2.0',
+
+    # python版本要求
+    python_requires='>=3.7',
+
+    # 表明当前模块依赖哪些包,若环境中没有,则会从pypi中自动下载安装!!!
+    install_requires=[
+        # vzense tof3d-sdk need
+        "numpy",
+        "opencv-python",     # 如果无法自动安装,可以尝试在终端调用 pip install opencv-python 进行安装
+        # zx sdk
+        'protobuf == 4.23.4',
+        # 工具
+        'pyqt5',
+        'PyQt5-tools'
+        'grpcio'
+        'grpcio-tools'
+        'vtk'
+    ],
+
+    # setup.py 本身要依赖的包,这通常是为一些setuptools的插件准备的配置,这里列出的包,不会自动安装。
+    setup_requires=[],
+
+    # 仅在测试时需要使用的依赖,在正常发布的代码中是没有用的。
+    # 在执行python setup.py test时,可以自动安装这三个库,确保测试的正常运行。
+    tests_require=[
+    ],
+
+    # install_requires 在安装模块时会自动安装依赖包
+    # 而 extras_require 不会,这里仅表示该模块会依赖这些包
+    # 但是这些包通常不会使用到,只有当你深度使用模块时,才会用到,这里需要你手动安装
+    extras_require={
+    }
+
+)

+ 4 - 0
ui_config.json

@@ -0,0 +1,4 @@
+{
+  "C1": "10.211.31.140",
+  "C2":"10.211.32.140"
+}