目录

前端界面与后台功能对接

功能1:点击button后,实现界面跳转(不卡顿)

功能2:选择文件路径按钮,并将选择后的路径显示在文本框中。

功能3:【上传】按钮,将本地的文件上传到服务器中,实现进度条显示

功能4:在lable区域读出CT图像后,用鼠标事件,添加 绘图功能,绘制十字坐标线。

功能5. 从服务器下载检测结果到本地,seeJJ.py

功能6:实现 菜单栏的各个跳转功能

功能7:python与excle表格交互:读取信息 + python读写操作

功能8:从服务器下载文件到本地

功能9:从服务器下载结节文件,列表读取本地文件

功能10 :添加【软件说明】界面,添加新界面,并将新界面的button功能实现

功能11:生成报告。python将信息写入word文档。

功能12:选中结节和取消选中 的样式改变,选中后及时保存选中文件

功能13:登录界面的跳转(project-hjq)

功能14:添加新界面,并实现其中的各个功能。

功能15:添加登录界面,账号密码登陆成功实现跳转

功能16:下拉框选中文字,实现在文本框中自动粘贴的功能。

功能17:克服桶排序。显示前24个结节。


前端界面与后台功能对接

1.前端代码由QTdesigner生成的ui文件,经过命令行产生,我们不妨 放在ui文件夹下,ui\seekJJ.py

# -*- coding: utf-8 -*-# Form implementation generated from reading ui file 'SeekJJ.ui'
#
# Created by: PyQt5 UI code generator 5.15.3
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.from PyQt5 import QtCore, QtGui, QtWidgetsclass Ui_MainWindow(object):def setupUi(self, MainWindow):MainWindow.setObjectName("MainWindow")MainWindow.resize(814, 694)icon = QtGui.QIcon()icon.addPixmap(QtGui.QPixmap("../image/icon.jpg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)MainWindow.setWindowIcon(icon)self.centralwidget = QtWidgets.QWidget(MainWindow)self.centralwidget.setObjectName("centralwidget")self.horizontalLayout_9 = QtWidgets.QHBoxLayout(self.centralwidget)self.horizontalLayout_9.setObjectName("horizontalLayout_9")self.groupBox = QtWidgets.QGroupBox(self.centralwidget)self.groupBox.setMaximumSize(QtCore.QSize(250, 16777215))self.groupBox.setTitle("")self.groupBox.setObjectName("groupBox")self.horizontalLayout_8 = QtWidgets.QHBoxLayout(self.groupBox)self.horizontalLayout_8.setObjectName("horizontalLayout_8")self.verticalLayout = QtWidgets.QVBoxLayout()self.verticalLayout.setSizeConstraint(QtWidgets.QLayout.SetMaximumSize)self.verticalLayout.setObjectName("verticalLayout")self.read_ct_label = QtWidgets.QLabel(self.groupBox)self.read_ct_label.setMinimumSize(QtCore.QSize(0, 35))self.read_ct_label.setMaximumSize(QtCore.QSize(16777215, 40))font = QtGui.QFont()font.setFamily("黑体")font.setPointSize(12)self.read_ct_label.setFont(font)self.read_ct_label.setStyleSheet("background-color:rgb(190, 217, 238)")self.read_ct_label.setAlignment(QtCore.Qt.AlignCenter)self.read_ct_label.setObjectName("read_ct_label")self.verticalLayout.addWidget(self.read_ct_label)self.horizontalLayout_6 = QtWidgets.QHBoxLayout()self.horizontalLayout_6.setObjectName("horizontalLayout_6")self.ct_path = QtWidgets.QLineEdit(self.groupBox)self.ct_path.setMinimumSize(QtCore.QSize(0, 30))self.ct_path.setObjectName("ct_path")self.horizontalLayout_6.addWidget(self.ct_path)self.verticalLayout.addLayout(self.horizontalLayout_6)self.horizontalLayout_7 = QtWidgets.QHBoxLayout()self.horizontalLayout_7.setObjectName("horizontalLayout_7")spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)self.horizontalLayout_7.addItem(spacerItem)self.choose_ct_path = QtWidgets.QPushButton(self.groupBox)self.choose_ct_path.setMinimumSize(QtCore.QSize(0, 30))self.choose_ct_path.setObjectName("choose_ct_path")self.horizontalLayout_7.addWidget(self.choose_ct_path)self.upload_ct = QtWidgets.QPushButton(self.groupBox)self.upload_ct.setMinimumSize(QtCore.QSize(0, 30))self.upload_ct.setObjectName("upload_ct")self.horizontalLayout_7.addWidget(self.upload_ct)self.verticalLayout.addLayout(self.horizontalLayout_7)self.upload_progressBar = QtWidgets.QProgressBar(self.groupBox)self.upload_progressBar.setProperty("value", 0)self.upload_progressBar.setObjectName("upload_progressBar")self.verticalLayout.addWidget(self.upload_progressBar)self.now_layers_label = QtWidgets.QLabel(self.groupBox)self.now_layers_label.setObjectName("now_layers_label")self.verticalLayout.addWidget(self.now_layers_label)self.horizontalLayout_5 = QtWidgets.QHBoxLayout()self.horizontalLayout_5.setObjectName("horizontalLayout_5")self.ajust_layer_label = QtWidgets.QLabel(self.groupBox)self.ajust_layer_label.setMinimumSize(QtCore.QSize(0, 30))self.ajust_layer_label.setObjectName("ajust_layer_label")self.horizontalLayout_5.addWidget(self.ajust_layer_label)self.layer_slider = QtWidgets.QSlider(self.groupBox)self.layer_slider.setMinimumSize(QtCore.QSize(0, 25))self.layer_slider.setOrientation(QtCore.Qt.Horizontal)self.layer_slider.setObjectName("layer_slider")self.horizontalLayout_5.addWidget(self.layer_slider)self.verticalLayout.addLayout(self.horizontalLayout_5)self.horizontalLayout_4 = QtWidgets.QHBoxLayout()self.horizontalLayout_4.setObjectName("horizontalLayout_4")self.ajust_light_label = QtWidgets.QLabel(self.groupBox)self.ajust_light_label.setObjectName("ajust_light_label")self.horizontalLayout_4.addWidget(self.ajust_light_label)self.light_slider = QtWidgets.QSlider(self.groupBox)self.light_slider.setMinimumSize(QtCore.QSize(0, 25))self.light_slider.setOrientation(QtCore.Qt.Horizontal)self.light_slider.setObjectName("light_slider")self.horizontalLayout_4.addWidget(self.light_slider)self.verticalLayout.addLayout(self.horizontalLayout_4)spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)self.verticalLayout.addItem(spacerItem1)self.ct_pre_deal_label = QtWidgets.QLabel(self.groupBox)self.ct_pre_deal_label.setMinimumSize(QtCore.QSize(0, 35))self.ct_pre_deal_label.setMaximumSize(QtCore.QSize(16777215, 40))font = QtGui.QFont()font.setFamily("黑体")font.setPointSize(12)self.ct_pre_deal_label.setFont(font)self.ct_pre_deal_label.setStyleSheet("background-color:rgb(190, 217, 238)")self.ct_pre_deal_label.setAlignment(QtCore.Qt.AlignCenter)self.ct_pre_deal_label.setObjectName("ct_pre_deal_label")self.verticalLayout.addWidget(self.ct_pre_deal_label)self.horizontalLayout_3 = QtWidgets.QHBoxLayout()self.horizontalLayout_3.setObjectName("horizontalLayout_3")self.generate_mhd = QtWidgets.QPushButton(self.groupBox)self.generate_mhd.setMinimumSize(QtCore.QSize(0, 30))self.generate_mhd.setObjectName("generate_mhd")self.horizontalLayout_3.addWidget(self.generate_mhd)self.verticalLayout.addLayout(self.horizontalLayout_3)self.generate_clean_label = QtWidgets.QPushButton(self.groupBox)self.generate_clean_label.setMinimumSize(QtCore.QSize(0, 30))self.generate_clean_label.setObjectName("generate_clean_label")self.verticalLayout.addWidget(self.generate_clean_label)self.generate_lbb_pbb = QtWidgets.QPushButton(self.groupBox)self.generate_lbb_pbb.setMinimumSize(QtCore.QSize(0, 30))self.generate_lbb_pbb.setObjectName("generate_lbb_pbb")self.verticalLayout.addWidget(self.generate_lbb_pbb)spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)self.verticalLayout.addItem(spacerItem2)self.vessel_apart_label = QtWidgets.QLabel(self.groupBox)self.vessel_apart_label.setMinimumSize(QtCore.QSize(0, 35))self.vessel_apart_label.setMaximumSize(QtCore.QSize(16777215, 40))font = QtGui.QFont()font.setFamily("黑体")font.setPointSize(12)self.vessel_apart_label.setFont(font)self.vessel_apart_label.setStyleSheet("background-color:rgb(190, 217, 238)")self.vessel_apart_label.setAlignment(QtCore.Qt.AlignCenter)self.vessel_apart_label.setObjectName("vessel_apart_label")self.verticalLayout.addWidget(self.vessel_apart_label)self.horizontalLayout_2 = QtWidgets.QHBoxLayout()self.horizontalLayout_2.setObjectName("horizontalLayout_2")self.ct_2_jpg = QtWidgets.QPushButton(self.groupBox)self.ct_2_jpg.setMinimumSize(QtCore.QSize(0, 30))self.ct_2_jpg.setObjectName("ct_2_jpg")self.horizontalLayout_2.addWidget(self.ct_2_jpg)self.verticalLayout.addLayout(self.horizontalLayout_2)self.vessel_line_mark = QtWidgets.QPushButton(self.groupBox)self.vessel_line_mark.setMinimumSize(QtCore.QSize(0, 30))self.vessel_line_mark.setObjectName("vessel_line_mark")self.verticalLayout.addWidget(self.vessel_line_mark)self.line_deal = QtWidgets.QPushButton(self.groupBox)self.line_deal.setMinimumSize(QtCore.QSize(0, 30))self.line_deal.setObjectName("line_deal")self.verticalLayout.addWidget(self.line_deal)spacerItem3 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)self.verticalLayout.addItem(spacerItem3)self.generate_jj_zb_label = QtWidgets.QLabel(self.groupBox)self.generate_jj_zb_label.setMinimumSize(QtCore.QSize(0, 35))self.generate_jj_zb_label.setMaximumSize(QtCore.QSize(16777215, 40))font = QtGui.QFont()font.setFamily("黑体")font.setPointSize(12)self.generate_jj_zb_label.setFont(font)self.generate_jj_zb_label.setStyleSheet("background-color:rgb(190, 217, 238)")self.generate_jj_zb_label.setAlignment(QtCore.Qt.AlignCenter)self.generate_jj_zb_label.setObjectName("generate_jj_zb_label")self.verticalLayout.addWidget(self.generate_jj_zb_label)self.horizontalLayout = QtWidgets.QHBoxLayout()self.horizontalLayout.setObjectName("horizontalLayout")self.generate_jj_zb = QtWidgets.QPushButton(self.groupBox)self.generate_jj_zb.setMinimumSize(QtCore.QSize(0, 30))self.generate_jj_zb.setObjectName("generate_jj_zb")self.horizontalLayout.addWidget(self.generate_jj_zb)self.see_jjzb = QtWidgets.QPushButton(self.groupBox)self.see_jjzb.setMinimumSize(QtCore.QSize(0, 30))self.see_jjzb.setObjectName("see_jjzb")self.horizontalLayout.addWidget(self.see_jjzb)self.verticalLayout.addLayout(self.horizontalLayout)spacerItem4 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)self.verticalLayout.addItem(spacerItem4)self.horizontalLayout_8.addLayout(self.verticalLayout)self.horizontalLayout_9.addWidget(self.groupBox)self.ct_img = PaintArea(self.centralwidget)self.ct_img.setMinimumSize(QtCore.QSize(600, 600))self.ct_img.setText("")self.ct_img.setScaledContents(False)self.ct_img.setAlignment(QtCore.Qt.AlignCenter)self.ct_img.setObjectName("ct_img")self.horizontalLayout_9.addWidget(self.ct_img)MainWindow.setCentralWidget(self.centralwidget)self.menubar = QtWidgets.QMenuBar(MainWindow)self.menubar.setGeometry(QtCore.QRect(0, 0, 814, 23))self.menubar.setObjectName("menubar")self.file_menu = QtWidgets.QMenu(self.menubar)self.file_menu.setObjectName("file_menu")self.seek_jj_menu = QtWidgets.QMenu(self.menubar)self.seek_jj_menu.setObjectName("seek_jj_menu")self.classify_jj_menu = QtWidgets.QMenu(self.menubar)self.classify_jj_menu.setObjectName("classify_jj_menu")self.setting_menu = QtWidgets.QMenu(self.menubar)self.setting_menu.setObjectName("setting_menu")self.help_menu = QtWidgets.QMenu(self.menubar)self.help_menu.setObjectName("help_menu")MainWindow.setMenuBar(self.menubar)self.exit_action = QtWidgets.QAction(MainWindow)self.exit_action.setObjectName("exit_action")self.seek_jj_action = QtWidgets.QAction(MainWindow)self.seek_jj_action.setObjectName("seek_jj_action")self.cut_jj_action = QtWidgets.QAction(MainWindow)self.cut_jj_action.setObjectName("cut_jj_action")self.classify_jj_action = QtWidgets.QAction(MainWindow)self.classify_jj_action.setObjectName("classify_jj_action")self.to_index_action = QtWidgets.QAction(MainWindow)self.to_index_action.setObjectName("to_index_action")self.file_menu.addAction(self.to_index_action)self.file_menu.addAction(self.exit_action)self.seek_jj_menu.addAction(self.seek_jj_action)self.seek_jj_menu.addAction(self.cut_jj_action)self.classify_jj_menu.addAction(self.classify_jj_action)self.menubar.addAction(self.file_menu.menuAction())self.menubar.addAction(self.seek_jj_menu.menuAction())self.menubar.addAction(self.classify_jj_menu.menuAction())self.menubar.addAction(self.setting_menu.menuAction())self.menubar.addAction(self.help_menu.menuAction())self.retranslateUi(MainWindow)QtCore.QMetaObject.connectSlotsByName(MainWindow)def retranslateUi(self, MainWindow):_translate = QtCore.QCoreApplication.translateMainWindow.setWindowTitle(_translate("MainWindow", "肺结节多种病理类型人工智能检测系统"))self.read_ct_label.setText(_translate("MainWindow", "读取CT文件"))self.choose_ct_path.setText(_translate("MainWindow", "选择CT路径"))self.upload_ct.setText(_translate("MainWindow", "上传CT"))self.now_layers_label.setText(_translate("MainWindow", "当前层数:0/0"))self.ajust_layer_label.setText(_translate("MainWindow", "调整层数:"))self.ajust_light_label.setText(_translate("MainWindow", "调整亮度:"))self.ct_pre_deal_label.setText(_translate("MainWindow", "影像预处理"))self.generate_mhd.setText(_translate("MainWindow", "生成mhd"))self.generate_clean_label.setText(_translate("MainWindow", "生成clean和label"))self.generate_lbb_pbb.setText(_translate("MainWindow", "生成lbb和pbb"))self.vessel_apart_label.setText(_translate("MainWindow", "血管分割"))self.ct_2_jpg.setText(_translate("MainWindow", "影像生成图片"))self.vessel_line_mark.setText(_translate("MainWindow", "血管轮廓标记"))self.line_deal.setText(_translate("MainWindow", "轮廓处理"))self.generate_jj_zb_label.setText(_translate("MainWindow", "生成结节坐标"))self.generate_jj_zb.setText(_translate("MainWindow", "生成结节坐标"))self.see_jjzb.setText(_translate("MainWindow", "查看结节坐标"))self.file_menu.setTitle(_translate("MainWindow", "文件"))self.seek_jj_menu.setTitle(_translate("MainWindow", "肺结节检测"))self.classify_jj_menu.setTitle(_translate("MainWindow", "分类诊断"))self.setting_menu.setTitle(_translate("MainWindow", "设置"))self.help_menu.setTitle(_translate("MainWindow", "帮助"))self.exit_action.setText(_translate("MainWindow", "退出"))self.exit_action.setShortcut(_translate("MainWindow", "Esc"))self.seek_jj_action.setText(_translate("MainWindow", "肺结节检测"))self.cut_jj_action.setText(_translate("MainWindow", "裁剪肺结节"))self.classify_jj_action.setText(_translate("MainWindow", "肺结节影像良恶性诊断"))self.to_index_action.setText(_translate("MainWindow", "系统首页"))
from frame.myQLabel import PaintArea

2.后端代码由我们新建,frame\seekJJ.py

导入本界面

from ui.SeekJJ import Ui_MainWindow

然后对里面的button函数的书写构造。线程类和函数类。

class UploadThread(QThread):sigout = pyqtSignal(float)def __init__(self):super().__init__()self.nativePath = ''self.uploadPath = ''def run(self):n = 0fileLength = len(os.listdir(self.nativePath)) - 1for i in os.listdir(self.nativePath):print(self.nativePath+i)sftp.put(self.nativePath + i, self.uploadPath + i)self.sigout.emit((n/fileLength)*100)n += 1

记得这段代码:调用界面的,下面会说到。 def __init__(self, parent=None):函数初始化各个参数用的。

    def __init__(self, parent=None):super().__init__(parent)  # 调用父类构造函数,创建窗体self.ui = Ui_MainWindow()  # 创建UI对象

QTdesigner前后端交互–结节算法实战-编程知识网
标题

 界面中的相关按键的函数实现。

在新界面中的

class SeeJJ(QMainWindow):类

表示这是个新窗口类  SeekJJ.show()函数就是打开这个界面的

class SeekJJ(QMainWindow):def resizeEvent(self, e):try:img = QPixmap(self.ct_file)width = self.ui.ct_img.width()height = self.ui.ct_img.height()if img is None:returnif width > height:small = heightelse:small = widthself.ct_img_small_size = smallimg = img.scaled(small, small, Qt.KeepAspectRatio, Qt.SmoothTransformation)self.ui.ct_img.setPixmap(img)self.ui.ct_img.ct2mark()except Exception as e:print(e)def __init__(self, parent=None):super().__init__(parent)  # 调用父类构造函数,创建窗体self.ui = Ui_MainWindow()  # 创建UI对象self.ui.setupUi(self)  # 构造UI界面self.serverWorkSpace = 'E:/LYC/lungDetection-V1/'self.upload_thread = UploadThread()self.upload_thread.uploadPath = self.serverWorkSpace + 'detection/test_mask/0006/'self.serverPython = 'D:/ProgramData/Anaconda3/envs/lung/python'self.ct_path_text = ''self.picSize = 0# self.ct_img_small_size = 512self.ct_file = ''width = self.ui.ct_img.width()height = self.ui.ct_img.height()self.ui.ct_img.slider = self.ui.layer_sliderif width > height:self.ct_img_small_size = heightelse:self.ct_img_small_size = widthself.function()def function(self):self.ui.choose_ct_path.clicked.connect(lambda: self._choose_ct_path())self.ui.upload_ct.clicked.connect(lambda: self._upload_ct())self.ui.generate_mhd.clicked.connect(lambda: self._generate_mhd())self.ui.generate_clean_label.clicked.connect(lambda: self._generate_clean_lable())self.ui.generate_lbb_pbb.clicked.connect(lambda: self._generate_lbb_pbb())self.ui.ct_2_jpg.clicked.connect(lambda: self._ct_2_jpg())self.ui.vessel_line_mark.clicked.connect(lambda: self._vessel_line_mark())self.ui.line_deal.clicked.connect(lambda: self._line_deal())self.ui.generate_jj_zb.clicked.connect(lambda: self._generate_jj_zb())self.ui.layer_slider.valueChanged.connect(self._layer_slider_changed)self.ui.see_jjzb.clicked.connect(lambda: self._to_see_jjzb())self.ui.menubar.triggered[QAction].connect(self.processtrigger)def _to_see_jjzb(self):self.seeJJ = SeeJJ()self.close()self.seeJJ.show()def _layer_slider_changed(self, value):if self.ct_path_text == '':returnself.ct_file = "../picture/%d.jpg" % (value+1)img = QtGui.QPixmap(self.ct_file)img = img.scaled(self.ct_img_small_size, self.ct_img_small_size, Qt.KeepAspectRatio)self.ui.ct_img.setPixmap(img)self.ui.now_layers_label.setText('当前层数:' + str(value+1) + '/' + str(self.picSize))def _choose_ct_path(self):try:self.ct_path_text = QFileDialog.getExistingDirectory(None, '请选择CT文件路径')if self.ct_path_text != "":for i in os.listdir('../picture/'):os.remove('../picture/' + i)self.picSize = len(os.listdir(self.ct_path_text))for i, id in enumerate(os.listdir(self.ct_path_text)):RefDs = sitk.ReadImage(self.ct_path_text + '/' + id)data = sitk.GetArrayFromImage(RefDs)[0]###data = (np.minimum(np.maximum(data, -1000), 400) + 1000)/5.46875cv2.imwrite('../picture/%s.jpg' % (self.picSize-i), data, [int(cv2.IMWRITE_JPEG_QUALITY), 100])else:returnself.ui.ct_path.setText(self.ct_path_text)self.upload_thread.nativePath = self.ct_path_text + '/'self.ui.layer_slider.setMaximum(self.picSize-1)self.ui.layer_slider.setMinimum(0)self.ui.now_layers_label.setText('当前层数:1/' + str(self.picSize))self.ct_file = "../picture/1.jpg"img = QtGui.QPixmap(self.ct_file)img = img.scaled(self.ct_img_small_size, self.ct_img_small_size, Qt.KeepAspectRatio)self.ui.ct_img.setPixmap(img)except Exception as e:print(e)def _upload_ct(self):try:if self.ui.ct_path.text() == '':QMessageBox().information(self, '提示', '请选择CT文件路径', QMessageBox.Yes)# QtWidgets.QMessageBox(QMessageBox.Warning, '提示', '请选择CT文件路径').exec_()returnstdin, stdout, stderr = ssh.exec_command('cd ' + self.serverWorkSpace + ';python clearCT.py')print(stdout.read())print(stderr.read())self.upload_thread.start()self.upload_thread.sigout.connect(self._change_progress)except Exception as e:print(e)def _change_progress(self, value):self.ui.upload_progressBar.setValue(value)print(value)if value == 100:QMessageBox().information(self, '提示', "上传完成", QMessageBox.Yes)def _generate_mhd(self):try:stdin, stdout, stderr = ssh.exec_command('cd ' + self.serverWorkSpace +'detection/;' + self.serverPython + ' dcm2mhd.py')print(stdout.read())print(stderr.read())QMessageBox().information(self, '提示', "成功生成mhd文件", QMessageBox.Yes)except Exception as e:print(e)def _generate_clean_lable(self):try:print('cd ' + self.serverWorkSpace + 'detection/;' + self.serverPython + ' prepares.py')stdin, stdout, stderr = ssh.exec_command('cd ' + self.serverWorkSpace +'detection/;' + self.serverPython + ' prepares.py')print(stdout.read())print(stderr.read())QMessageBox().information(self, '提示', "成功生成clean和label文件", QMessageBox.Yes)except Exception as e:print(e)def _generate_lbb_pbb(self):try:stdin, stdout, stderr = ssh.exec_command('cd ' + self.serverWorkSpace +'detection/;' + self.serverPython + ' detections.py')print(stdout.read())print(stderr.read())QMessageBox().information(self, '提示', "成功生成lbb和pbb文件", QMessageBox.Yes)except Exception as e:print(e)def _ct_2_jpg(self):try:stdin, stdout, stderr = ssh.exec_command('cd ' + self.serverWorkSpace +'vessel_seg/;' + self.serverPython + ' FrangiFilter2D.py')print(stdout.read())print(stderr.read())QMessageBox().information(self, '提示', "成功生成影像图片", QMessageBox.Yes)except Exception as e:print(e)def _vessel_line_mark(self):try:stdin, stdout, stderr = ssh.exec_command('cd ' + self.serverWorkSpace +'vessel_seg/;' + self.serverPython + ' meijering.py')print(stdout.read())print(stderr.read())QMessageBox().information(self, '提示', "成功标记血管轮廓", QMessageBox.Yes)except Exception as e:print(e)def _line_deal(self):try:stdin, stdout, stderr = ssh.exec_command('cd ' + self.serverWorkSpace +'vessel_seg/;' + self.serverPython + ' vesselMask.py')print(stdout.read())print(stderr.read())QMessageBox().information(self, '提示', "成功处理轮廓", QMessageBox.Yes)except Exception as e:print(e)def _generate_jj_zb(self):try:stdin, stdout, stderr = ssh.exec_command('cd ' + self.serverWorkSpace +'merge/;' + self.serverPython + ' Isvessel.py')print(stdout.read())print(stderr.read())QMessageBox().information(self, '提示', "成功生成结节坐标", QMessageBox.Yes)except Exception as e:print(e)def processtrigger(self, action):if action.text() == '退出':self.close()elif action.text() == '系统首页':passelif action.text() == '裁剪结节':passelif action.text() == '肺结节影像良恶性诊断':pass# ============窗体测试程序 ================================
if __name__ == "__main__":  # 用于当前窗体测试app = QApplication(sys.argv)  # 创建GUI应用程序form = SeekJJ()  # 创建窗体form.show()sys.exit(app.exec_())

功能1:点击button后,实现界面跳转(不卡顿)

函数:

    def _to_see_jjzb(self):
# 在class SeekJJ(QMainWindow)下,需要self表示当前类self.seeJJ = SeeJJ() # 打开seeJJ界面self.close()         # 当前界面关闭self.seeJJ.show()    # seeJJ界面开启

点击 【查看结节坐标界面】,

self.ui.see_jjzb.clicked.connect(lambda: self._to_see_jjzb())

ui.SeekJJ.py界面的相关接口:(在做ui时自动生成的)

        self.see_jjzb = QtWidgets.QPushButton(self.groupBox)self.see_jjzb.setMinimumSize(QtCore.QSize(0, 30))self.see_jjzb.setObjectName("see_jjzb")self.horizontalLayout.addWidget(self.see_jjzb)

 .setText函数里面的参数设置了button的名字

        self.see_jjzb.setText(_translate("MainWindow", "查看结节坐标"))

功能2:选择文件路径按钮,并将选择后的路径显示在文本框中。

QTdesigner前后端交互–结节算法实战-编程知识网

前端选择CT路径的button的触发事件函数:

    def function(self):self.ui.choose_ct_path.clicked.connect(lambda: self._choose_ct_path())

 选择CT路径的触发函数

    def _choose_ct_path(self):try:# 弹出选择文件夹路径的标题栏title名字self.ct_path_text = QFileDialog.getExistingDirectory(None, '请选择CT文件路径')# 如果路径的文本框是空的,清空picture文件夹下的所有文件if self.ct_path_text != "":for i in os.listdir('../picture/'):os.remove('../picture/' + i)# os.listdir()返回指定路径下的文件和文件夹列表。self.picSize = len(os.listdir(self.ct_path_text))for i, id in enumerate(os.listdir(self.ct_path_text)):RefDs = sitk.ReadImage(self.ct_path_text + '/' + id)data = sitk.GetArrayFromImage(RefDs)[0]# -1000到400,的CT影像转化为256个灰度值,1400除以256等于5.46875data = (np.minimum(np.maximum(data, -1000), 400) + 1000)/5.46875
# 写入data,[int(cv2.IMWRITE_JPEG_QUALITY), 100]的意思是将画质调整为最好的100cv2.imwrite('../picture/%s.jpg' % (self.picSize-i), data, [int(cv2.IMWRITE_JPEG_QUALITY), 100])else:return# 将路径的名字读入到文本框中self.ui.ct_path.setText(self.ct_path_text)
# 设置多线程上传文件夹的路径self.upload_thread.nativePath = self.ct_path_text + '/'
# 设置【调整层数】滑动条的最大值和最小值self.ui.layer_slider.setMaximum(self.picSize-1)self.ui.layer_slider.setMinimum(0)
# 初始化当前层数,1/nself.ui.now_layers_label.setText('当前层数:1/' + str(self.picSize))
# 默认将1.jpg作为主界面的显示self.ct_file = "../picture/1.jpg"img = QtGui.QPixmap(self.ct_file)
# 显示图片,保持横纵比img = img.scaled(self.ct_img_small_size, self.ct_img_small_size, Qt.KeepAspectRatio)self.ui.ct_img.setPixmap(img)
# 打印报错信息except Exception as e:print(e)

初始化了主界面的滑动条是1/n,默认将第一幅图像作为显示图像,在滑动进度条的时候,动态改变进度条的层数,动态改变图片的显示。

因此这俩动态变化的功能需要在滑动条函数中写:

前端滑动条的鼠标点击触发事件:

self.ui.layer_slider.valueChanged.connect(self._layer_slider_changed)

 滑动条函数:

    def _layer_slider_changed(self, value):
# 判断非空路径if self.ct_path_text == '':return
# 设置显示的图片的路径self.ct_file = "../picture/%d.jpg" % (value+1)
# 继续保持横纵比不变img = QtGui.QPixmap(self.ct_file)img = img.scaled(self.ct_img_small_size, self.ct_img_small_size, Qt.KeepAspectRatio)self.ui.ct_img.setPixmap(img)
# 动态显示当前层数self.ui.now_layers_label.setText('当前层数:' + str(value+1) + '/' + str(self.picSize))

功能3:【上传】按钮,将本地的文件上传到服务器中,实现进度条显示

前端upload_ctbutton组件的鼠标点击触发事件:

self.ui.upload_ct.clicked.connect(lambda: self._upload_ct())

 上传函数_upload_ct的具体实现:

    def _upload_ct(self):try:
# 判断路径非空if self.ui.ct_path.text() == '':
# 路径空值时弹出提示框QMessageBox().information(self, '提示', '请选择CT文件路径', QMessageBox.Yes)return
# stdin, stdout, stderr,输入信息,命令行输出信息,输出错误信息,需要引入paramiko的sshstdin, stdout, stderr = ssh.exec_command('cd ' + self.serverWorkSpace + ';python clearCT.py')
# 打印命令行产生的输出和错误信息print(stdout.read())print(stderr.read())
# upload_thread多线程上传self.upload_thread.start()
# 上传进度条事件self.upload_thread.sigout.connect(self._change_progress)except Exception as e:print(e)

 关于多线程上传的,在此一并说了:

重写run函数,线程启动的时候会直接执行run()方法,我们在此处进行重写,这样线程启动的 时候会执行我们写的这个run方法。
我们使用 self.upload_thread.start()方法这个来启用线程,但是线程的启用会先来找run方法

class UploadThread(QThread):
# 设置多线程类
# 刚才看到的sigout在此设置的,pyqtSignal(float)是信号的传递,操作系统中的哲学家就餐问题的一个简
# 化,防止进程死锁,以float类型的参数进行传递sigout = pyqtSignal(float)# 初始化,设置常参数def __init__(self):
# 继承 __init__ 类中的所有super().__init__()
# 在继承的基础上添加初始化的路径参数self.nativePath = ''self.uploadPath = ''# 重写run函数,线程启动的时候会直接执行run()方法,我们在此处进行重写,这样线程启动的 时候会执行我# 们写的这个run方法。
# 我们使用 self.upload_thread.start()。这个来启用线程,但是线程的启用会先来找run方法def run(self):n = 0fileLength = len(os.listdir(self.nativePath)) - 1for i in os.listdir(self.nativePath):print(self.nativePath+i)
# 将本地文件上传到服务器nativePath + i是本地路径+i文件的意思sftp.put(self.nativePath + i, self.uploadPath + i)
# 发送信号self.sigout.emit((n/fileLength)*100)n += 1
self.sigout.emit((n/fileLength)*100),上传到100%

前端界面的上传进度条的进度百分比的显示功能函数的书写:

    def _change_progress(self, value):
# 进度条传入的value值self.ui.upload_progressBar.setValue(value)print(value)if value == 100:
# 弹出框显示上传完成QMessageBox().information(self, '提示', "上传完成", QMessageBox.Yes)

QTdesigner前后端交互–结节算法实战-编程知识网

功能4:在lable区域读出CT图像后,用鼠标事件,添加 绘图功能,绘制十字坐标线。

from PyQt5 import QtGui, QtCore
from PyQt5.QtCore import QRect, QPoint, Qt
from PyQt5.QtGui import QPen, QBrush, QPainter, QPixmap
from PyQt5.QtWidgets import QLabel
# 导入依赖包# 定义绘画类,这个地方注意一下,因为是(QLabel)参数,因此绘画的都是QLabel区域,坐标也是这个里面# 的区域
class PaintArea(QLabel):def __init__(self, parent=None):super().__init__(parent)
# 初始化各个参数
# 设置对其方式,居中对齐 AlignCenterself.setAlignment(QtCore.Qt.AlignCenter)self.markX = 0  # 在图像中的标记self.markY = 0self.ctX = 256  # 对应CT影像中的真实坐标self.ctY = 256self.mark = Falseself.slider = None# 重写绘画事件def paintEvent(self, event):
# 继承绘画类super().paintEvent(event)
# 因为self.mark = False,因此初始状态不执行绘画事件,鼠标点击后将mark改为trueif self.mark:painter = QPainter(self)
# setPen(self,color)方法,(0, 0, 255)设置为蓝色,1为线段的粗细程度为1painter.setPen(QtGui.QPen(QtGui.QColor(0, 0, 255), 1))
# .drawLine(self,l)方法画线,这都是建立在self.mark = True的基础之上的painter.drawLine(self.markX, 0, self.markX, self.height())painter.drawLine(0, self.markY, self.width(), self.markY)def wheelEvent(self, e):if e.angleDelta().y() < 0:# 放大图片if self.slider is not None:self.slider.setValue(self.slider.value()+1)elif e.angleDelta().y() > 0:# 缩小图片if self.slider is not None:self.slider.setValue(self.slider.value()-1)# 重写鼠标点击事件def mousePressEvent(self, event):
# 获取鼠标点击的横纵坐标self.markX = event.x()self.markY = event.y()self.mark2ct()self.mark = True
# repaint方法重画界面,算是刷新self.repaint()# biaoji zuobiao def mark2ct(self):if self.width() > self.height():
# 点击鼠标事件,标记坐标
# 这个地方需要注意,CT是512*512的,所以初始化ctx应该是(256,256)位中间点的坐标
# ctX是指图片的ctx的坐标,和lable没关系
# ctx指的是坐标在长>高的时候,x的相对位置,{x-[(b-a)/2]}/a*512self.ctX = (self.markX - (self.width() - self.height())/2)/self.height()*512self.ctY = self.markY/self.height()*512else:self.ctX = self.markX/self.width()*512self.ctY = (self.markY-(self.height()-self.width())/2)/self.width()*512
# 如果已经有坐标,拖动窗口的大小,鼠标坐标自适应改变,不会错位def ct2mark(self):if self.width() > self.height():self.markX = (self.width()-self.height())/2+(self.ctX/512)*self.height()self.markY = (self.ctY/512)*self.height()else:self.markX = (self.ctX/512)*self.width()self.markY = (self.height()-self.width())/2+(self.ctY/512)*self.width()
# 自适应改完重新画坐标点self.repaint()def setCtXY(self, x, y):self.ctX = xself.ctY = yself.mark = Trueself.ct2mark()

功能5. 从服务器下载检测结果到本地,seeJJ.py

 QTdesigner前后端交互–结节算法实战-编程知识网

 首先介绍界面以及初始化:

class SeeJJ(QMainWindow):def resizeEvent(self, e):try:img = QPixmap(self.ct_file)width = self.ui.ct_img.width()height = self.ui.ct_img.height()if img is None:returnif width > height:small = heightelse:small = widthself.ct_img_small_size = smallimg = img.scaled(small, small, Qt.KeepAspectRatio, Qt.SmoothTransformation)self.ui.ct_img.setPixmap(QPixmap(''))self.ui.ct_img.setPixmap(img)self.ui.ct_img.ct2mark()except Exception as e:print(e)# 初始化参数def __init__(self, parent=None):super().__init__(parent)  # 调用父类构造函数,创建窗体
# Ui_MainWindow()为SeeJJ.py中的类名self.ui = Ui_MainWindow()  # 创建UI对象self.ui.setupUi(self)  # 构造UI界面self.serverWorkSpace_jiance = 'E:/LYC/lungDetection-V1/'self.serverWorkSpace_fenlei = 'E:/LYC/fenlei/'self.serverPython = 'D:/ProgramData/Anaconda3/envs/G_capsenv/python'self.zb_list = []self.ui.zb_listWidget.addItem('序号          x             y             z')
# 在服务器中的文件保存在'../picture/'路径下,因此要读取本路径下的文件的规模self.picSize = len(os.listdir('../picture/'))
# 滑动块的最大值self.ui.layer_ajust_slider.setMaximum(self.picSize-1)self.ui.layer_ajust_slider.setMinimum(0)
# 初始化滑块的值1/nself.ui.now_layer_label.setText('当前层数:1/' + str(self.picSize))# self.ct_img_small_size = 512  # ct影像尺寸标准,以宽高小者为准
# 宽度 = ct_img的宽度width = self.ui.ct_img.width()height = self.ui.ct_img.height()
# 滑块?将ui里面的滑块赋值给这个类里面的滑块self.ui.ct_img.slider = self.ui.layer_ajust_sliderif width > height:self.ct_img_small_size = heightelse:self.ct_img_small_size = width
# 记录图片层数,初始化界面显示为第一层self.ct_file = '../picture/1.jpg'  # 记录当前ct层数的图片文件img = QPixmap(self.ct_file)
# img.scaled(self,width,height,aspectRatioMode,TransformMode)方法
# 最小值作为边长的正方形,保持横纵比,光滑变换img = img.scaled(self.ct_img_small_size, self.ct_img_small_size, Qt.KeepAspectRatio, Qt.SmoothTransformation)self.ui.ct_img.setPixmap(img)
# 执行lambda函数集合self.function()

从服务器下载文件到本地:

# 定义组件的接口函数def function(self):self.ui.down_load.clicked.connect(lambda: self._down_load_zb())# 对接口函数 _down_load_zb() 的实现def _down_load_zb(self):try:# 从服务器将2020NewData.xlsx文件下载到本地的data文件夹下sftp.get(self.serverWorkSpace_jiance + '2020NewData.xlsx', '../data/new data.xlsx')
# 完事以后,弹出成功界面QMessageBox().information(self, '提示', "成功下载到本地", QMessageBox.Yes)except Exception as e:print(e)

关于 sftp 方法,用的是 paramiko 函数库进行实现,关于这个服务器功能单独写一个connect.py文件实现与服务器的连接:

import paramikotransport = paramiko.Transport(('服务器ip', 端口号))
# 建立连接
transport.connect(username='Fizz', password='密码')
# 将sshclient的对象的transport指定为以上的transport
ssh = paramiko.SSHClient()
ssh._transport = transport
# sftp在此处赋值
sftp = paramiko.SFTPClient.from_transport(transport)

用这么一句代码 :sftp.get(self.serverWorkSpace_jiance + '2020NewData.xlsx', '../data/new data.xlsx')

即可以实现文件从服务器的下载。

功能6:实现 菜单栏的各个跳转功能

    def processtrigger(self, action):if action.text() == '退出':self.close()elif action.text() == '系统首页':
# Index.py文件中的Index()类赋值给本类的self.indexself.index = Index.Index()self.index.show()self.close()elif action.text() == '裁剪肺结节':self.seeJJ = SeeJJ.SeeJJ()self.seeJJ.show()self.close()elif action.text() == '肺结节影像良恶性诊断':
# from frame import ClassifyJJself.classifyJJ = ClassifyJJ.ClassifyJJ()self.classifyJJ.show()self.close()

功能7:python与excle表格交互:读取信息 + python读写操作

QTdesigner前后端交互–结节算法实战-编程知识网

 鼠标点击button事件:

self.ui.read_zb.clicked.connect(lambda: self._read_zb())

 左侧坐标显示栏的函数实现:

    def _read_zb(self):
# 初始化清空列表self.ui.zb_listWidget.clear()self.zb_list.clear()self.ui.zb_listWidget.addItem('序号          x             y             z')
# from openpyxl import load_workbook;加载excle的方法,读入wb = load_workbook('../data/new data.xlsx')sheet = wb.activei = 2while True:
# 读入M列的值,空值则停content = sheet["M%d" % i].valueif content is None or content == "":break
# 从左侧的第一个元素,到倒数第一个元素。content = content[1:-1]
# 逗号分隔开,这样就把坐标中的三个数取出来了index = content.split(",")x, y, z = index[0], index[1], index[2]self.zb_list.append((index[0], index[1], index[2]))self.ui.zb_listWidget.addItem(' ' + str(i-1) + '          ' + x + '          ' + y + '          ' + z)i += 1

 Classfy.py文件。分类检测

QTdesigner前后端交互–结节算法实战-编程知识网

本处附带服务器代码,关于python写入Excel的方法:

from openpyxl import Workbook
import cv2def write_zb_excel(indexList,jjdx):wb = Workbook()sheet = wb.activesheet.title='Sheet1'sheet['A1'].value = '影像号'sheet['M1'].value = '坐标'sheet['N1'].value = '结节大小'sheet['O1'].value = '病理类型'sheet['AF1'].value = '年份'sheet["A2"].value = '10203040'img = cv2.imread('E:/LYC/lungDetection-V1/detection/savejpg2/0006/0.jpg')w, h = img.shape[0], img.shape[1]f = open(r'..\detection\extendbox.txt', 'r')extendbox = f.readlines()startX = int(extendbox[0])startY = int(extendbox[2])startZ = int(extendbox[4])f.close()f = open(r'..\detection\spacing.txt', 'r')space = f.readline()space = space.split(',')spaceZ = float(space[0])spaceXY = float(space[1])f.close()for i, index in enumerate(indexList):x, y, z = index[0], index[1], index[2]x = round((x + startX) / spaceXY)y = round((y + startY) / spaceXY)z = round((z + startZ + 1) / spaceZ)content = '(' + str(x) + ',' + str(y) + ',' + str(z) + ')'sheet["M%d" % (i + 2)].value = contentsheet["O%d" % (i + 2)].value = 2sheet["AF%d" % (i + 2)].value = 2020for i,jj in enumerate(jjdx):sheet["N%d" % (i + 2)].value = jjwb.save('../2020NewData.xlsx')

如下,jjdx(结节大小)为一列表[1,2,3,1,1]之类似,enumerate函数的作用是一个数一个数的去拿;第二行是存在N列的每一行中。 

    for i,jj in enumerate(jjdx):sheet["N%d" % (i + 2)].value = jj

功能8:从服务器下载文件到本地

self.ui.download_jj_file.clicked.connect(lambda: self._download_jj_file())

函数实现:

    def _download_jj_file(self):try:npyList = os.listdir('../npy')for npy in npyList:os.remove('../npy/' + npy)fileArray = sftp.listdir(self.serverWorkSpace + 'prework/dataset17-20_cf/test30')for file in fileArray:sftp.get(self.serverWorkSpace + 'prework/dataset17-20_cf/test30/' + file, '../npy/' + file)QMessageBox().information(self, '提示', "成功下载到本地", QMessageBox.Yes)except Exception as e:print(e)

功能9:从服务器下载结节文件,列表读取本地文件

点击【读取结节文件】按钮,实现列表框中读取。

QTdesigner前后端交互–结节算法实战-编程知识网QTdesigner前后端交互–结节算法实战-编程知识网

 QTdesigner前后端交互–结节算法实战-编程知识网QTdesigner前后端交互–结节算法实战-编程知识网

    def _read_jj(self):
# 清空列表self.ui.jj_listWidget.clear()jjList = os.listdir('../npy/')
# 加载文件for jj in jjList:self.ui.jj_listWidget.addItem(jj)

 鼠标点击事件,实现界面的动态变化。

QTdesigner前后端交互–结节算法实战-编程知识网

    def _jj_listWidget_clicked(self, item):try:if item is None:returnfile = item.text()# 本段显示结节图像path = '../npy/' + fileimage_data = np.load(path)
# //取整,/浮点数image_data = image_data[image_data.shape[0] // 2]###image_data = (np.minimum(np.maximum(image_data, -1000), 400) + 1000)/5.46875img_pil = Image.fromarray(np.uint8(image_data)).resize((150, 150))self.ui.jj_img.setPixmap(img_pil.toqpixmap())# 根据文件后缀数字确定序号,获取xlsx文件中的坐标
# 正则表达式找 10203040-1.npy之类的文件,findallid = re.findall(r'.*?-(.*?).npy', file)[0]index = self.zbList[int(id)]index = index[1:-1]print(index)index = index.split(',')# 在结节部分显示坐标self.ui.jj_zb_label.setText('坐标: X:%s  Y:%s  Z:%s' % (index[0], index[1], index[2]))# 在CT中标记结节self.ui.ct_img.setCtXY(int(index[0]), int(index[1]))# ct切换到相应页面self.ui.layer_ajust_slider.setValue(int(index[2]))self.ct_file = '../picture/' + index[2] + '.jpg'img = QPixmap(self.ct_file)img = img.scaled(self.ct_img_small_size, self.ct_img_small_size, Qt.KeepAspectRatio,Qt.SmoothTransformation)self.ui.ct_img.setPixmap(img)if len(self.resList) == 0:returnresult = list(map(float, self.resList[int(id)]))result = np.exp(result)/np.sum(np.exp(result), axis=0)
# 显示百分比self.ui.result_lx_progressBar.setValue(float(result[0])*100)self.ui.result_la_progressBar.setValue(float(result[1])*100)self.ui.result_xa_progressBar.setValue(float(result[2])*100)except Exception as e:print(e)

功能10 :添加【软件说明】界面,添加新界面,并将新界面的button功能实现

1.在UI界面【帮助】下添加【软件使用说明】的QAction,添加方法为,先点击type here,输入wohenshuai

QTdesigner前后端交互–结节算法实战-编程知识网                     QTdesigner前后端交互–结节算法实战-编程知识网

右侧的QAction属性栏如下:

QTdesigner前后端交互–结节算法实战-编程知识网

QTdesigner前后端交互–结节算法实战-编程知识网              QTdesigner前后端交互–结节算法实战-编程知识网

调整frame格式:

选中要编辑的frame模块,右键stylesheet,复制里面的格式代码,到新的界面粘贴

QTdesigner前后端交互–结节算法实战-编程知识网

参考里面的代码,可写更多样式。

我在【帮助】下添加的是【软件使用说明】,QAction是rjsm

    def processtrigger(self, action):if action.text() == '退出':self.close()elif action.text() == '系统首页':self.index = Index.Index()self.index.show()self.close()elif action.text() == '肺结节检测':self.seekJJ = SeekJJ.SeekJJ()self.seekJJ.show()self.close()elif action.text() == '肺结节影像良恶性诊断':self.classifyJJ = ClassifyJJ.ClassifyJJ()self.classifyJJ.show()self.close()elif action.text() == '软件使用说明':self.rjsm = gnsm.Gnsm()self.rjsm.show()

 弹出界面不关闭

QTdesigner前后端交互–结节算法实战-编程知识网

然后

from frame import gnsm

即可。self.rjsm为新命名而已,不指向其他东西;gnsm.Gnsm()指的是import的gnsm界面下的Gnsm类

添加新界面之后,如果界面中有函数,要初始化函数,并且执行之。这点十分重要,我将在下面举例说明!!!!!!!!

QTdesigner前后端交互–结节算法实战-编程知识网

添加完一个软件使用说明以后,在其他界面进行添加就比较容易了。

第一步:修改ui界面,在ui界面添加【帮助】-【软件使用说明】

第二步:添加elif功能

QTdesigner前后端交互–结节算法实战-编程知识网

第三步:

from frame import gnsm

功能11:生成报告。python将信息写入word文档。

关于word的操作,大概可以如下:

QTdesigner前后端交互–结节算法实战-编程知识网

    def _submit(self):
# 判断信息完整性if self.ui.patient_name.text() == ''\or self.ui.patient_id.text() == ''\or self.ui.ct_describe_textEdit.toPlainText() == ''\or self.ui.doctor_advice_textEdit.toPlainText() == '':
# 弹出未完善的提示信息QMessageBox().information(self, '提示', '请完善信息', QMessageBox.Ok)return
# 选择保存路径的选择框save_path_text = QFileDialog.getExistingDirectory(None, '请选择报告保存路径')
# 判断保存路径非空if save_path_text == '':QMessageBox().information(self, '提示', '未选择保存路径', QMessageBox.Ok)return
# 读入word模板doc = docx.Document('../word/model.docx')  # 读取模板
# 写入信息doc.paragraphs[1].text = '病历号:%s             姓名:%s                 检查时间:%s' % \(self.ui.patient_id.text(), self.ui.patient_name.text(), self.ui.diagnose_date.text())# 第三段添加图片,选中的肺结节图片run = doc.paragraphs[3].add_run()for file in os.listdir('../npy_img/'):run.add_picture('../npy_img/' + file)run = doc.paragraphs[3].add_run('  ')
# 将文档中的文字信息写入word的表格中doc.tables[0].rows[0].cells[0].text = self.ui.ct_describe_textEdit.toPlainText()doc.tables[1].rows[0].cells[0].text = self.ui.doctor_advice_textEdit.toPlainText()
# 将写好的文档保存在本地的绝对路径中,命名使用患者id命名doc.save(save_path_text + '/' + self.ui.patient_id.text() + '.docx')  # 要写绝对路径QMessageBox().information(self, '提示', '成功生成报告', QMessageBox.Ok)

功能12:选中结节和取消选中 的样式改变,选中后及时保存选中文件

QTdesigner前后端交互–结节算法实战-编程知识网

 点击左侧的npy文件,点击【写入检测报告】,选中结节,将结节图片文件保存起来;

点击选中的结节,点击【移除检测报告】,取消选中,并将保存的结节文件删除掉。

QTdesigner前后端交互–结节算法实战-编程知识网

具体的

self.ui.put_jj_in_report.clicked.connect(lambda: self._put_jj_in_report())

的实现如下:

    def _put_jj_in_report(self):
# 我们规定选的结节个数不超过6个try:if self.put_npy_num >= 6:QMessageBox().information(self, '提示', '写入结节数不能超过6个', QMessageBox.Ok)return
# 选中结节后row = self.ui.jj_listWidget.currentRow()
# 设置选中时的颜色,设置选中时的icon图标self.ui.jj_listWidget.item(row).setBackground(QColor(190, 217, 238))self.ui.jj_listWidget.item(row).setIcon(QIcon('../image/t7.ico'))
# 并将本结节文件保存起来,生成报告的时候直接使用,用npy文件的名字命名jpgself.ui.jj_img.pixmap().save('../npy_img/%s.jpg' % self.ui.jj_listWidget.item(row).text())self.put_npy_num += 1except Exception as e:print(e)

移除结节文件的函数实现:

    def _remove_jj_from_report(self):try:
# 选中本行row = self.ui.jj_listWidget.currentRow()
# 设置颜色,图标设置为空值self.ui.jj_listWidget.item(row).setBackground(QColor(255, 255, 255))self.ui.jj_listWidget.item(row).setIcon(QIcon(''))
# 删除保存的文件os.remove('../npy_img/%s.jpg' % self.ui.jj_listWidget.item(row).text())self.put_npy_num -= 1except Exception as e:print(e)

这俩功能,将结果显示出来,读取坐标值。不好解释,不做解释。

    def _jj_listWidget_clicked(self, item):try:if item is None:returnfile = item.text()# 显示结节图像path = '../npy/' + fileimage_data = np.load(path)image_data = image_data[image_data.shape[0] // 2]###image_data = (np.minimum(np.maximum(image_data, -1000), 400) + 1000)/5.46875img_pil = Image.fromarray(np.uint8(image_data)).resize((150, 150))self.ui.jj_img.setPixmap(img_pil.toqpixmap())# 根据文件后缀数字确定序号,获取xlsx文件中的坐标id = re.findall(r'.*?-(.*?).npy', file)[0]index = self.zbList[int(id)]index = index[1:-1]print(index)index = index.split(',')# 在结节部分显示坐标self.ui.zb_info_label.setText('坐标: X:%s  Y:%s  Z:%s' % (index[0], index[1], index[2]))if len(self.resList) == 0:returnresult = list(map(float, self.resList[int(id)]))result = np.exp(result)/np.sum(np.exp(result), axis=0)self.ui.res_lx_progressBar.setValue(float(result[0])*100)self.ui.res_la_progressBar.setValue(float(result[1])*100)self.ui.res_xa_progressBar.setValue(float(result[2])*100)except Exception as e:print(e)def _read_jj(self):# 读取已经下载的坐标值self.zbList.clear()wb = load_workbook('../data/new data.xlsx')sheet = wb.activei = 2while True:content = sheet["M%d" % i].valueif content is None or content == "":breakself.zbList.append(content)i += 1# 读取已经下载的识别结果self.resList.clear()resNum = len(os.listdir('../result'))for i in range(resNum):f = open('../result/tes_prob_batch' + str(i) + '.txt')batches = f.readlines()for batch in batches:batch = batch[0:-2]  # 去除换行符number = batch.split(' ')self.resList.append((number[1][0:8], number[2][0:8], number[3][0:8]))f.close()# 将结节文件显示在列表self.ui.jj_listWidget.clear()jjList = os.listdir('../npy/')for jj in jjList:self.ui.jj_listWidget.addItem(jj)

功能13:登录界面的跳转(project-hjq)

    def yanzheng(self):self.n=0
# 账号密码存放在同一级目录的11.txt下f=open('./11.txt')ma=self.username.text()+'.'+self.password.text()for i in f:
# 判定登陆成功if i[:-1]==ma:QMessageBox.information(self, " ", "登录成功!", QMessageBox.Yes)
# 登陆成功后打开新界面 mainimport mainself.hide()# self.form.hide()  # 如果没有self.form.show()这一句,关闭Demo1界面后就会关闭程序
# main界面打开self.ex = main.Example()self.ex.show()self.n=1if self.n==0:
# 这里将三种报错信息放在了一起,报错信息弹框是一样的,不一样的地方是图标不一样。QMessageBox.information(self, " ", "登录成功!", QMessageBox.Yes)QMessageBox.critical(self, " ", "密码错误!", QMessageBox.Yes)QMessageBox.warning(self, " ", "查无此人!", QMessageBox.Yes)# 记得关闭txtf.close()

功能14:添加新界面,并实现其中的各个功能。

第一步:制作ui界面,并且将ui界面的各个button记录好,然后用 pyuic5 -o 命令转化为py程序。这一步不在赘述

第二步:在后台程序的文件夹(frame)中新建实现本界面以及功能的py文件,

提示:ui文件和第一步生成的界面py文件在ui文件夹下;我要实现的后台程序的py文件在frame文件夹下,因此,我在frame新建的程序yjjc.py引入ui文件即可。

from ui.Yjjc import Ui_MainWindow

然后实现主方法,显示本界面:

if __name__ == "__main__":  # 用于当前窗体测试app = QApplication(sys.argv)  # 创建GUI应用程序form = Yjjc()  # 创建窗体form.show()sys.exit(app.exec_())

这段代码为避免将来忘记,在此解释一下,只需要改 form = Yjjc() 这一句即可,就是声明一个新界面为form,这个Yjjc就是本主界面的类:

QTdesigner前后端交互–结节算法实战-编程知识网

这样ui界面的内容就可以显示了

第三步:实现界面中的按键的功能

首先写

def __init__(self, parent=None):

这个函数,继承父类的窗体,固定代码:

    def __init__(self, parent=None):super().__init__(parent)  # 调用父类构造函数,创建窗体self.ui = Ui_MainWindow()  # 创建UI对象self.ui.setupUi(self)  # 构造UI界面

这说明这个界面已经继承了父类的窗体。

继承完窗体,然后找ui界面中的一键检测的按钮的名字。我命名为yjjc

于是,实现这个功能

    def function(self):self.ui.yjjc.clicked.connect(lambda: self._Yjjcsb())

新建一个function类,声明各个组件的对应的函数,在这里一定要注意注意再注意,声明的function函数类必须要初始化!!!

也就是在def __init__ 中添加这么一句代码:

self.function()

这样才可以正常执行,否则不可以!如图:

QTdesigner前后端交互–结节算法实战-编程知识网

将用到的初始化的 东西全都写在__init__函数中

第四步:写具体的实现函数,就是function中lambda的函数

    def _Yjjcsb(self):try:stdin, stdout, stderr = ssh.exec_command('cd ' + self.serverWorkSpace +'detection/;' + self.serverPython + ' dcm2mhd.py')print(stdout.read())print(stderr.read())QMessageBox().information(self, '提示', "成功生成mhd文件", QMessageBox.Yes)except Exception as e:print(e)

这样就可以了。

以上四步实现的是按钮的功能的具体实现,下面写的是选择文件路径,并将文件上传的路基顺序:

第一步:实现选择文件路径按钮的函数

self.ui.choose_ct_path.clicked.connect(lambda: self._choose_ct_path())

第二步:实现           lambda: self._choose_ct_path() 函数

    def _choose_ct_path(self):try:self.ct_path_text = QFileDialog.getExistingDirectory(None, '请选择CT文件路径')if self.ct_path_text != "":for i in os.listdir('../picture/'):os.remove('../picture/' + i)self.picSize = len(os.listdir(self.ct_path_text))for i, id in enumerate(os.listdir(self.ct_path_text)):data = pydicom.dcmread(self.ct_path_text + '/' + id).pixel_arraydata = (np.minimum(np.maximum(data, 0), 4096)) / 6###plt.imsave('../picture/%s.jpg' % (self.picSize-i),data, cmap='gray', vmin=0, vmax=255)else:returnself.ui.ct_path.setText(self.ct_path_text)self.upload_thread.nativePath = self.ct_path_text + '/'self.ui.layer_slider.setMaximum(self.picSize-1)self.ui.layer_slider.setMinimum(0)self.ui.now_layers_label.setText('当前层数:1/' + str(self.picSize))self.ct_file = "../picture/1.jpg"img = QtGui.QPixmap(self.ct_file)img = img.scaled(self.ct_img_small_size, self.ct_img_small_size, Qt.KeepAspectRatio)self.ui.ct_img.setPixmap(img)except Exception as e:print(e)

这段代码下面的    self.ui….  属于初始化的参数值 

 self.ui.ct_path.setText(self.ct_path_text)  指的是显示路径的文本框;

下面这两句:

self.ui.layer_slider.setMaximum(self.picSize-1)self.ui.layer_slider.setMinimum(0)

指的是滑动条的初始化的最大值最小值

QTdesigner前后端交互–结节算法实战-编程知识网

ct_path.setText和layer_slider在上图已标注。

接下来要实现上传功能,首先书写上传按钮的实现方法:

    def _upload_ct(self):try:if self.ui.ct_path.text() == '' or self.ct_path_text == '':QMessageBox().information(self, '提示', '请选择CT文件路径', QMessageBox.Yes)# QtWidgets.QMessageBox(QMessageBox.Warning, '提示', '请选择CT文件路径').exec_()returnstdin, stdout, stderr = ssh.exec_command('cd ' + self.serverWorkSpace + ';python clearCT.py')print(stdout.read())print(stderr.read())self.upload_thread.start()self.upload_thread.sigout.connect(self._change_progress)except Exception as e:print(e)

书写上传的 进程类

class UploadThread(QThread):sigout = pyqtSignal(float)def __init__(self):super().__init__()self.nativePath = ''self.uploadPath = ''def run(self):n = 0fileLength = len(os.listdir(self.nativePath)) - 1for i in os.listdir(self.nativePath):print(self.nativePath+i)sftp.put(self.nativePath + i, self.uploadPath + i)self.sigout.emit((n/fileLength)*100)n += 1

因为本处的 self.uploadPath = '' 上传路径设置为空值,因此需要在初始化里面设置好上传路径的具体指,否则程序虽然会上传但是却传不到指定路径下。

self.upload_thread.uploadPath = self.serverWorkSpace + 'detection/test_mask/0006/'

一定要记得初始化!!

self.upload_thread = UploadThread()

初始化函数里有这句话才行的。重写了run方法实现上传功能。

上传进度为百分之百的时候,弹窗提示上传完成:

    def _change_progress(self, value):self.ui.upload_progressBar.setValue(value)print(value)if value == 100:QMessageBox().information(self, '提示', "上传完成", QMessageBox.Yes)

另一个功能:实现滑块的拖动

self.ui.layer_slider.valueChanged.connect(self._layer_slider_changed)

实现本函数的方法:

    def _layer_slider_changed(self, value):if self.ct_path_text == '':returnself.ct_file = "../picture/%d.jpg" % (value+1)img = QtGui.QPixmap(self.ct_file)img = img.scaled(self.ct_img_small_size, self.ct_img_small_size, Qt.KeepAspectRatio)self.ui.ct_img.setPixmap(img)self.ui.now_layers_label.setText('当前层数:' + str(value+1) + '/' + str(self.picSize))

鼠标的滑轮实现图片的滚动浏览等会再写

功能15:添加登录界面,账号密码登陆成功实现跳转

在之前的界面基础上,添加新界面。思路比较简单,添加login.py界面即可,使登陆成功后,跳转至index.py。

只需打包login.py即可。

具体实现方法如下:

      界面添加上txt文本验证,账号密码在文本中,就登陆成功。否则失败。

      至于txt的账号密码内容,可在服务器端实现修改,每次运行下载至本地。

# -*- coding: utf-8 -*-
import numpy as np
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtGui import QFont
from PyQt5.QtWidgets import QFileDialog, QMessageBox
from PyQt5 import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import paramiko,os
import socket,time
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import *
import paramiko
transport = paramiko.Transport(('ip', 端口))
# 建立连接
transport.connect(username='服务器账号', password='密码')
# 将sshclient的对象的transport指定为以上的transport
ssh = paramiko.SSHClient()
ssh._transport = transport
sftp = paramiko.SFTPClient.from_transport(transport)
sftp.get('E:/LYC/lungDetection-V1/users.txt', '../data/users.txt')
class Example(QWidget):def __init__(self):super().__init__()self.setupUi()def setupUi(self):self.resize(993, 550)# self.setWindowFlags(QtCore.Qt.WindowMinimizeButtonHint)# self.centralwidget = QtWidgets.QWidget(MainWindow)self.setFixedSize(self.width(),self.height())s1 = '肺结节多种病理类型人工智能检测系统's2 = '公司's3 = '山东大学'self.setWindowTitle('肺结节多种病理类型人工智能检测系统-登录界面')self.background = QtWidgets.QLabel(self)self.background.setGeometry(QtCore.QRect(0, 0, 1025, 550))self.background.setPixmap(QtGui.QPixmap("../image/background.png"))self.left = QtWidgets.QLabel(self)self.left.setGeometry(QtCore.QRect(20, 180, 261, 211))self.left.setPixmap(QtGui.QPixmap("../image/pic.png"))self.left.setScaledContents(True)self.pic_1 = QtWidgets.QLabel(self)self.pic_1.setGeometry(QtCore.QRect(320, 170, 150, 150))self.pic_1.setPixmap(QtGui.QPixmap("../image/1.jpg"))self.pic_1.setScaledContents(True)self.pic_2 = QtWidgets.QLabel(self)self.pic_2.setGeometry(QtCore.QRect(500, 170, 150, 150))self.pic_2.setPixmap(QtGui.QPixmap("../image/2.jpg"))self.pic_2.setScaledContents(True)self.pic_3 = QtWidgets.QLabel(self)self.pic_3.setGeometry(QtCore.QRect(320, 340, 150, 150))self.pic_3.setPixmap(QtGui.QPixmap("../image/3.jpg"))self.pic_3.setScaledContents(True)self.pic_4 = QtWidgets.QLabel(self)self.pic_4.setGeometry(QtCore.QRect(500, 340, 150, 150))self.pic_4.setPixmap(QtGui.QPixmap("../image/4.jpg"))self.pic_4.setScaledContents(True)self.font = QtGui.QFont()self.font.setFamily("黑体")self.font.setPointSize(30)self.font.setItalic(True)self.text_1 = QtWidgets.QLabel(self)self.text_1.setGeometry(QtCore.QRect(300, 160, 261, 71))self.text_1.setFont(self.font)self.text_2 = QtWidgets.QLabel(self)self.text_2.setGeometry(QtCore.QRect(360, 290, 271, 61))self.text_2.setFont(self.font)self.text_3 = QtWidgets.QLabel(self)self.text_3.setGeometry(QtCore.QRect(420, 420, 261, 61))self.text_3.setFont(self.font)# self.text_1.setText("<html><head/><body><p><span style=\" color:#ff0000;\">智能数据获取</span></p></body></html>")# self.text_2.setText("<html><head/><body><p><span style=\" color:#ff0000;\">智能数据处理</span></p></body></html>")# self.text_3.setText("<html><head/><body><p><span style=\" color:#ff0000;\">智能诊断应用</span></p></body></html>")self.frame = QtWidgets.QFrame(self)self.frame.setGeometry(QtCore.QRect(690, 180, 261, 271))self.frame.setStyleSheet("background:rgb(240, 240, 240)")h2_font = QtGui.QFont()h2_font.setFamily("新宋体")h2_font.setPointSize(11)h2_font.setBold(True)h2_font.setWeight(75)self.h2 = QtWidgets.QLabel(self.frame)self.h2.setGeometry(QtCore.QRect(96, 20, 71, 21))self.h2.setFont(h2_font)self.h2.setText("账号登录")font = QtGui.QFont()font.setFamily("新宋体")font.setPointSize(10)self.zhl = QtWidgets.QLabel(self.frame)self.zhl.setGeometry(QtCore.QRect(20, 80, 41, 25))self.zhl.setFont(font)self.zhl.setText("账号:")self.mml = QtWidgets.QLabel(self.frame)self.mml.setGeometry(QtCore.QRect(20, 130, 41, 25))self.mml.setFont(font)self.mml.setText("密码:")self.checkBox = QtWidgets.QCheckBox(self.frame)self.checkBox.setGeometry(QtCore.QRect(170, 170, 71, 21))self.checkBox.setText("记住密码")self.login = QtWidgets.QPushButton(self.frame)self.login.setGeometry(QtCore.QRect(75, 210, 111, 31))self.login.setStyleSheet("background:rgb(255, 97, 76)")self.login.clicked.connect(self.yanzheng)self.login.setText("登  录")self.login.setShortcut('enter')self.username = QtWidgets.QLineEdit(self.frame)self.username.setGeometry(QtCore.QRect(70, 80, 171, 25))self.password = QtWidgets.QLineEdit(self.frame)self.password.setGeometry(QtCore.QRect(70, 130, 171, 25))self.password.setEchoMode(QtWidgets.QLineEdit.Password)self.title = QtWidgets.QLabel(self)self.title.setGeometry(QtCore.QRect(110, 50, 801, 51))title_font = QtGui.QFont()title_font.setFamily("黑体")title_font.setPointSize(32)self.title.setFont(title_font)self.title.setText("<html><head/><body><p><span style=\" color:#aaffff;\">肺结节多种病理类型人工智能检测系统</span></p></body></html>")# self.setCentralWidget(self)# self.menubar = QtWidgets.QMenuBar(self)# self.menubar.setGeometry(QtCore.QRect(0, 0, 993, 23))# self.setMenuBar(self.menubar)def yanzheng(self):self.n=0f=open('../data/users.txt')ma=self.username.text()+'.'+self.password.text()for i in f:# a,b=str(i[-1]).split('.',2)# print(a,b)if i[:-1]==ma:QMessageBox.information(self, " ", "登录成功!", QMessageBox.Yes)import Indexself.hide()# self.form.hide()  # 如果没有self.form.show()这一句,关闭Demo1界面后就会关闭程序self.ex = Index.Index()self.ex.show()self.n=1if self.n==0:# QMessageBox.information(self, " ", "登录成功!", QMessageBox.Yes)QMessageBox.critical(self, " ", "账号或密码错误!", QMessageBox.Yes)# QMessageBox.warning(self, " ", "查无此人!", QMessageBox.Yes)f.close()if __name__ == "__main__":import sysapp = QApplication(sys.argv)ex = Example()ex.show()sys.exit(app.exec_())

功能16:下拉框选中文字,实现在文本框中自动粘贴的功能。

      # pyqt界面的  ct_discribe和doctor_advice组件点击粘贴事件
self.ui.ct_discribe.currentTextChanged.connect(lambda :self.Clickme_ct())
self.ui.doctor_advice.currentTextChanged.connect(lambda :self.Clickme_doc())

 点击将文字粘贴至下拉框中。

    def Clickme_ct(self):print(self.ui.ct_discribe.currentText())ct_dis = self.ui.ct_describe_textEdit.toPlainText()ct_dis += self.ui.ct_discribe.currentText()self.ui.ct_describe_textEdit.setText(ct_dis)print("文字粘贴到CT影像描述文本框")def Clickme_doc(self):print(self.ui.doctor_advice.currentText())doc_adv = self.ui.doctor_advice_textEdit.toPlainText()doc_adv += self.ui.doctor_advice.currentText()self.ui.doctor_advice_textEdit.setText(doc_adv)

功能17:克服桶排序。显示前24个结节。

思路是:桶排序按照字符长度排序的,同一字符同一排序,因此按照len()方法长度排序即可。

显示前24个结节也比较简单,循环到24,break即可。

    def _read_jj(self):# 读取已经下载的坐标值self.zbList.clear()wb = load_workbook('../data/new data.xlsx')sheet = wb.activei = 2while True:content = sheet["M%d" % i].valueif content is None or content == "":breakself.zbList.append(content)i += 1# 读取已经下载的识别结果self.resList.clear()resNum = len(os.listdir('../result'))for i in range(resNum):f = open('../result/tes_prob_batch' + str(i) + '.txt')batches = f.readlines()for batch in batches:batch = batch[0:-2]  # 去除换行符number = batch.split(' ')self.resList.append((number[1][0:8], number[2][0:8], number[3][0:8]))f.close()# 将结节文件显示在列表self.ui.jj_listWidget.clear()jjList = os.listdir('../npy/')jjList.sort(key=functools.cmp_to_key(self.jj_sort2))i = 0for jj in jjList:# print(jj)# s = jj.split("-")[-1]self.ui.jj_listWidget.addItem(jj)i += 1if i >= 24:break# self.ui.jj_listWidget.setSortingEnabled(True)def jj_sort2(self,x,y):return len(x)-len(y)