目录
功能3:【上传】按钮,将本地的文件上传到服务器中,实现进度条显示
功能4:在lable区域读出CT图像后,用鼠标事件,添加 绘图功能,绘制十字坐标线。
功能7:python与excle表格交互:读取信息 + python读写操作
功能10 :添加【软件说明】界面,添加新界面,并将新界面的button功能实现
功能12:选中结节和取消选中 的样式改变,选中后及时保存选中文件
前端界面与后台功能对接
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, QtWidgets
class 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.translate
MainWindow.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 = 0
fileLength = len(os.listdir(self.nativePath)) - 1
for 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对象

界面中的相关按键的函数实现。
在新界面中的
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:
return
if width > height:
small = height
else:
small = width
self.ct_img_small_size = small
img = 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 = 512
self.ct_file = ''
width = self.ui.ct_img.width()
height = self.ui.ct_img.height()
self.ui.ct_img.slider = self.ui.layer_slider
if width > height:
self.ct_img_small_size = height
else:
self.ct_img_small_size = width
self.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 == '':
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))
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.46875
cv2.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)
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_()
return
stdin, 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() == '系统首页':
pass
elif action.text() == '裁剪结节':
pass
elif 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:选择文件路径按钮,并将选择后的路径显示在文本框中。
前端选择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.46875
data = (np.minimum(np.maximum(data, -1000), 400) + 1000)/5.46875
# 写入data,[int(cv2.IMWRITE_JPEG_QUALITY), 100]的意思是将画质调整为最好的100
cv2.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/n
self.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的ssh
stdin, 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 = 0
fileLength = len(os.listdir(self.nativePath)) - 1
for 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)
功能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)
# 初始化各个参数
# 设置对其方式,居中对齐 AlignCenter
self.setAlignment(QtCore.Qt.AlignCenter)
self.markX = 0 # 在图像中的标记
self.markY = 0
self.ctX = 256 # 对应CT影像中的真实坐标
self.ctY = 256
self.mark = False
self.slider = None
# 重写绘画事件
def paintEvent(self, event):
# 继承绘画类
super().paintEvent(event)
# 因为self.mark = False,因此初始状态不执行绘画事件,鼠标点击后将mark改为true
if self.mark:
painter = QPainter(self)
# setPen(self,color)方法,(0, 0, 255)设置为蓝色,1为线段的粗细程度为1
painter.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*512
self.ctX = (self.markX - (self.width() - self.height())/2)/self.height()*512
self.ctY = self.markY/self.height()*512
else:
self.ctX = self.markX/self.width()*512
self.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 = x
self.ctY = y
self.mark = True
self.ct2mark()
功能5. 从服务器下载检测结果到本地,seeJJ.py
首先介绍界面以及初始化:
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:
return
if width > height:
small = height
else:
small = width
self.ct_img_small_size = small
img = 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/n
self.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_slider
if width > height:
self.ct_img_small_size = height
else:
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 paramiko
transport = 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.index
self.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 ClassifyJJ
self.classifyJJ = ClassifyJJ.ClassifyJJ()
self.classifyJJ.show()
self.close()
功能7:python与excle表格交互:读取信息 + python读写操作
鼠标点击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.active
i = 2
while True:
# 读入M列的值,空值则停
content = sheet["M%d" % i].value
if 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文件。分类检测
本处附带服务器代码,关于python写入Excel的方法:
from openpyxl import Workbook
import cv2
def write_zb_excel(indexList,jjdx):
wb = Workbook()
sheet = wb.active
sheet.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 = content
sheet["O%d" % (i + 2)].value = 2
sheet["AF%d" % (i + 2)].value = 2020
for i,jj in enumerate(jjdx):
sheet["N%d" % (i + 2)].value = jj
wb.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:从服务器下载结节文件,列表读取本地文件
点击【读取结节文件】按钮,实现列表框中读取。
def _read_jj(self):
# 清空列表
self.ui.jj_listWidget.clear()
jjList = os.listdir('../npy/')
# 加载文件
for jj in jjList:
self.ui.jj_listWidget.addItem(jj)
鼠标点击事件,实现界面的动态变化。
def _jj_listWidget_clicked(self, item):
try:
if item is None:
return
file = item.text()
# 本段显示结节图像
path = '../npy/' + file
image_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.46875
img_pil = Image.fromarray(np.uint8(image_data)).resize((150, 150))
self.ui.jj_img.setPixmap(img_pil.toqpixmap())
# 根据文件后缀数字确定序号,获取xlsx文件中的坐标
# 正则表达式找 10203040-1.npy之类的文件,findall
id = 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:
return
result = 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
右侧的QAction属性栏如下:
调整frame格式:
选中要编辑的frame模块,右键stylesheet,复制里面的格式代码,到新的界面粘贴
参考里面的代码,可写更多样式。
我在【帮助】下添加的是【软件使用说明】,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()
弹出界面不关闭
然后
from frame import gnsm
即可。self.rjsm为新命名而已,不指向其他东西;gnsm.Gnsm()指的是import的gnsm界面下的Gnsm类
添加新界面之后,如果界面中有函数,要初始化函数,并且执行之。这点十分重要,我将在下面举例说明!!!!!!!!
添加完一个软件使用说明以后,在其他界面进行添加就比较容易了。
第一步:修改ui界面,在ui界面添加【帮助】-【软件使用说明】
第二步:添加elif功能
第三步:
from frame import gnsm
功能11:生成报告。python将信息写入word文档。
关于word的操作,大概可以如下:
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:选中结节和取消选中 的样式改变,选中后及时保存选中文件
点击左侧的npy文件,点击【写入检测报告】,选中结节,将结节图片文件保存起来;
点击选中的结节,点击【移除检测报告】,取消选中,并将保存的结节文件删除掉。
具体的
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文件的名字命名jpg
self.ui.jj_img.pixmap().save('../npy_img/%s.jpg' % self.ui.jj_listWidget.item(row).text())
self.put_npy_num += 1
except 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 -= 1
except Exception as e:
print(e)
这俩功能,将结果显示出来,读取坐标值。不好解释,不做解释。
def _jj_listWidget_clicked(self, item):
try:
if item is None:
return
file = item.text()
# 显示结节图像
path = '../npy/' + file
image_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.46875
img_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:
return
result = 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.active
i = 2
while True:
content = sheet["M%d" % i].value
if content is None or content == "":
break
self.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)
# 登陆成功后打开新界面 main
import main
self.hide()
# self.form.hide() # 如果没有self.form.show()这一句,关闭Demo1界面后就会关闭程序
# main界面打开
self.ex = main.Example()
self.ex.show()
self.n=1
if self.n==0:
# 这里将三种报错信息放在了一起,报错信息弹框是一样的,不一样的地方是图标不一样。
QMessageBox.information(self, " ", "登录成功!", QMessageBox.Yes)
QMessageBox.critical(self, " ", "密码错误!", QMessageBox.Yes)
QMessageBox.warning(self, " ", "查无此人!", QMessageBox.Yes)
# 记得关闭txt
f.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就是本主界面的类:
这样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()
这样才可以正常执行,否则不可以!如图:
将用到的初始化的 东西全都写在__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_array
data = (np.minimum(np.maximum(data, 0), 4096)) / 6
###
plt.imsave('../picture/%s.jpg' % (self.picSize-i),
data, cmap='gray', vmin=0, vmax=255)
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)
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)
指的是滑动条的初始化的最大值最小值
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_()
return
stdin, 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 = 0
fileLength = len(os.listdir(self.nativePath)) - 1
for 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 == '':
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))
鼠标的滑轮实现图片的滚动浏览等会再写
功能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=0
f=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 Index
self.hide()
# self.form.hide() # 如果没有self.form.show()这一句,关闭Demo1界面后就会关闭程序
self.ex = Index.Index()
self.ex.show()
self.n=1
if self.n==0:
# QMessageBox.information(self, " ", "登录成功!", QMessageBox.Yes)
QMessageBox.critical(self, " ", "账号或密码错误!", QMessageBox.Yes)
# QMessageBox.warning(self, " ", "查无此人!", QMessageBox.Yes)
f.close()
if __name__ == "__main__":
import sys
app = 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.active
i = 2
while True:
content = sheet["M%d" % i].value
if content is None or content == "":
break
self.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 = 0
for jj in jjList:
# print(jj)
# s = jj.split("-")[-1]
self.ui.jj_listWidget.addItem(jj)
i += 1
if i >= 24:
break
# self.ui.jj_listWidget.setSortingEnabled(True)
def jj_sort2(self,x,y):
return len(x)-len(y)