目錄
前端界面與背景功能對接
功能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, 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)