Gamer's Show

你知道人生最要紧的事是快乐不停

0%

pyQt5一步到位笔记

QtDesiner的安装与配置

pyuic自动生成代码运行大法

1
2
3
4
5
class Ui_Dialog(QDialog):
def __init__(self, parent=None):
super(Ui_Dialog, self).__init__(parent)
self.showDialog(self)
def showDialog(self, Dialog):

动态加载已保存的界面

注意只有designer中已经保存的界面加载时才会更新

1
2
3
4
class MainForm(QMainWindow, Ui_MainWindow):
def __init__(self):
super().__init__()
self.ui = uic.loadUi("new_UI_full.ui",self)

对于显示,运行操作,写在main函数里!
属性调用:
self.ui.pushButton.clicked.connect(self.bindButton)

槽函数与连接

注意事项(未解决)

如果使用自定义信号,一定要记得信号是类变量,必须在类中定义,不能在实例方法中定义,否则后面发射信号和连接槽方法时都会报错。
不过我是这么解决的:(强行定制函数ing)

1
2
3
4
self.dia.comboBox_ImgType.currentIndexChanged.connect(lambda: self.RadiomicsFunc.ImgType_init(
self.comboBox_ImgType.currentIndex())) # currentIndex返回当前项的序号(int),第一个项的序号为0
self.dia.comboBox_ImgType.currentIndexChanged.connect(lambda: self.stackedWidget.setCurrentIndex(
self.comboBox_ImgType.currentIndex()-1))#对应换页
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
   class configWin(ui_configWin.Ui_configWin,QtWidgets.QWidget):

def __init__(self,parent=None):
super().__init__(parent)
self.setupUi(self)
self.sizeChanged = QtCore.pyqtSignal(int)

def resize(self,width,height):
self.sizeChanged.emit(width)
print("sizeChanged....")
super().resize(width,height)

class mainWin(QtWidgets.QMainWindow,ui_mainWin.Ui_mainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.configWin = configWin(self)
self.configWin.sizeChanged.connect(self.sizeChanged)

报错AttributeError: 'builtin_function_or_method' object has no attribute 'connect'
修正:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtGui import QWindow
class configWin(ui_configWin.Ui_configWin,QtWidgets.QWidget):

sizeChanged =pyqtSignal(int)#写在这里

def __init__(self,parent=None):
super().__init__(parent)
self.setupUi(self)


def resize(self,width,height):
self.sizeChanged.emit(width)
super().resize(width,height)

class mainWin(QtWidgets.QMainWindow,ui_mainWin.Ui_mainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.configWin = configWin(self)
self.configWin.sizeChanged.connect(self.sizeChanged)

关于.connect()函数

需要注意的是,如果你的connect()里边是一个函数,以及你的函数传参是函数,你需要注意你书写函数的格式
一个改了很久的两行:

1
2
timer.timeout.connect(lambda: Bar.setValue(Bar.value()+10)) # 设置参数
timer.timeout.connect(lambda: checkState(Bar.value(),timer))# 检查时间
  1. 报错:传入的组件参数是NoneType
    这就需要区分progressBar.setValue()progressBar.setValue的区别:
  • 前者其实默认是一种call(None),而返回值是NoneType
  • 后者是一个函数(Reference the Method)
  1. 关于lambda
  • 可以认为是函数的声明,整个式子其实是对函数的调用,但是在.connect()中调用函数时,必须要使用该声明

讨论区有一个类似的例子

几种连接方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 第一种信号与槽连接的方法
cb_font = QFontComboBox(currentFontChanged=self.changeLabelFont)
cb_font.pyqtConfigure(objectName='fontCombo', editable=False)
# 第二种信号与槽连接的方法
cb_font.currentFontChanged.connect(self.changeButtonFont)
main_layout.addWidget(cb_font)

label = QLabel()
label.pyqtConfigure(text='示例文本!', objectName='label')

main_layout.addWidget(label)

closeButton = QPushButton('关闭')
# 第三种连接信号与槽的方法
closeButton.pyqtConfigure(objectName='button', clicked=self.close)
main_layout.addWidget(closeButton)

vhbox = QVBoxLayout()
vhbox.addLayout(main_layout)
vhbox.addStretch(1)
self.setLayout(vhbox)

# 第四种连接信号与槽的方法
QMetaObject.connectSlotsByName(self)

MainWindow(主窗口)系列设置

菜单键的相关函数

self.menu_1.setTitle(_translate(“MainWindow”, “####”)) #菜单名称
self.action_1.setText(_translate(“MainWindow”, “#####”)) #菜单选项
self.action_1.triggered.connect(self.actionHandler_1) #点击菜单选项绑定响应(def 函数)

提供四种Action信号

changed():修改Action的属性时触发(如修改toolTip的信息)
hovered():Action关联的菜单项或者鼠标停留或者按下快捷键时触发
toggled(bool checked):Action设置checkable属性,关联的菜单项或toolBar在点击后会改变选中状态,触发toggled信号,参数为是否选中的最新状态。
triggered(bool checked):鼠标点击或者快捷键时触发。

  • 给动作绑定事件:
    self.savelog.triggered.connect(self.saveLogs)
    其中self.saveLog是动作名称,self.aveLogs是需要绑定的事件。
    表示self.saveLog被触发时调用self.aveLogs函数事件

Scroll Area

设置 ScrollAreaWidgetContents 的minimumSize属性,只有窗口大小小于这个值的时候,滑块才生效。
实际上相当于scrollArea内部创建了一个子区域,如果子区域大于最外区域,则出现滑块,如果只想出现一边有滑块,没有的那一边建议子块的最小值设置为0

ComboBox

访问QComboBox的列表项

1
2
3
4
5
6
int currentlndex():返回当前项的序号,第一个项的序号为0
QString currentText():返回当前项的文本
QVariant currentData(int role = Qt::UserRole):返回当前项的关联数据
QString itemText(int index) 返回指定索引号的项的文本
QVariant itemData(int index, int role = Qt%:UserRole) 返回指定索引号的项的关联数据。
int count():返回项的个数。

槽函数

1
2
3
4
5
6
7
8
9
10
11
self.reco_comboBox.currentIndexChanged.connect(
lambda: self.model_init(self.reco_comboBox.currentIndex()))


#model_init函数的实现的核心代码
# 加载相关参数,并初始化模型
def model_init(self,tag):
if tag == 1: #当下拉框选中"1"触发事件
"""
代码省略
"""

QSlider

重写slider点哪里到哪里

新建包mySlider(或者直接新建类也行),就差个导入么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QSlider


class MySlider(QSlider): # 继承QSlider
customSliderClicked = pyqtSignal(str) # 创建信号

def __init__(self, parent=None):
super(QSlider, self).__init__(parent)

def mousePressEvent(self, QMouseEvent): # 重写的鼠标点击事件
super().mousePressEvent(QMouseEvent)
pos = QMouseEvent.pos().x() / self.width()
self.setValue(round(pos * (self.maximum() - self.minimum()) + self.minimum())) # 设定滑动条滑块位置为鼠标点击处
self.customSliderClicked.emit("mouse Press") # 发送信号

class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(487, 402)
self.horizontalSlider = QtWidgets.QSlider(Form) # 滑动条1
self.horizontalSlider.setGeometry(QtCore.QRect(60, 140, 361, 31))
self.horizontalSlider.setOrientation(QtCore.Qt.Horizontal)
self.horizontalSlider.setObjectName("horizontalSlider")
self.horizontalSlider_2 = MySlider(Form) # 滑动条2,修改此处
self.horizontalSlider_2.setGeometry(QtCore.QRect(60, 230, 361, 31))
self.horizontalSlider_2.setOrientation(QtCore.Qt.Horizontal)
self.horizontalSlider_2.setObjectName("horizontalSlider_2")

self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)

def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form"))

主程序调用,connect是自定义slot

1
2
3
4
5
6
7
class MainWindow(Ui_Form, QMainWindow):  # 创建窗口
def __init__(self):
super().__init__()
self.setupUi(self)
self.horizontalSlider.valueChanged.connect(self.horizontalSlider_change) # 滑动条1数值变化时触发horizontalSlider_change
self.horizontalSlider_2.customSliderClicked.connect(
self.horizontalSlider_2_change)

美化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/*horizontal :水平QSlider*/
QSlider::groove:horizontal {
border: 0px solid #bbb;
}

/*1.滑动过的槽设计参数*/
QSlider::sub-page:horizontal {
/*槽颜色*/
background: rgb(90,49,255);
/*外环区域倒圆角度*/
border-radius: 2px;
/*上遮住区域高度*/
margin-top:8px;
/*下遮住区域高度*/
margin-bottom:8px;
/*width在这里无效,不写即可*/
}

/*2.未滑动过的槽设计参数*/
QSlider::add-page:horizontal {
/*槽颜色*/
background: rgb(255,255, 255);
/*外环大小0px就是不显示,默认也是0*/
border: 0px solid #777;
/*外环区域倒圆角度*/
border-radius: 2px;
/*上遮住区域高度*/
margin-top:9px;
/*下遮住区域高度*/
margin-bottom:9px;
}

/*3.平时滑动的滑块设计参数*/
QSlider::handle:horizontal {
/*滑块颜色*/
background: rgb(193,204,208);
/*滑块的宽度*/
width: 5px;
/*滑块外环为1px,再加颜色*/
border: 1px solid rgb(193,204,208);
/*滑块外环倒圆角度*/
border-radius: 2px;
/*上遮住区域高度*/
margin-top:6px;
/*下遮住区域高度*/
margin-bottom:6px;
}

/*4.手动拉动时显示的滑块设计参数*/
QSlider::handle:horizontal:hover {
/*滑块颜色*/
background: rgb(193,204,208);
/*滑块的宽度*/
width: 10px;
/*滑块外环为1px,再加颜色*/
border: 1px solid rgb(193,204,208);
/*滑块外环倒圆角度*/
border-radius: 5px;
/*上遮住区域高度*/
margin-top:4px;
/*下遮住区域高度*/
margin-bottom:4px;
}
QSlider::sub-page:horizontal {
background:rgb(170, 255, 127);
border-radius: 0.1px;
margin-top:9px;
margin-bottom:9px;
}
QSlider::add-page:horizontal {
background: rgb(170,255,127);
border-radius: 1px;
margin-top:9px;
margin-bottom:9px;
}

QCheckBox

常用方法

方法 描述
setChecked() 设置复选框的状态,True为选中,False为取消选中复选框
setText() 设置复选框的显示文本
text() 返回复选框的显示文本
isChecked() 检查复选框是否被选中
setCheckState() 设置复选框的勾选状态:2为选中(Checked);1为半选中(ParticallyChecked);0为没有选中(Unchecked)
setTristate(bool) 三态模式

isChecked()不能单独使用,要用stateChanged.connect连接起来
但是更建议使用toggle.connect()

1
2
3
4
5
6
self.dia.checkBox_2.stateChanged.connect(lambda: self.test())
def test(self):
chk1Status = self.dia.checkBox_2.text() + ", isChecked=" + str(
self.dia.checkBox_2.isChecked()) + ', chekState=' + str(
self.dia.checkBox_2.checkState()) + "\n"
print(chk1Status)

我改了无数次的代码是这样的,如果只是ischecked不会更新!!!
这个故事告诉我们,状态类的函数需要手动对他一直进行状态检查

1
2
3
4
self.checkBox1.stateChanged.connect(lambda: self.btnstate(self.checkBox1))
self.checkBox2.toggled.connect(lambda: self.btnstate(self.checkBox2))
self.checkBox3.stateChanged.connect(lambda: self.btnstate(self.checkBox3))
#btnstate为一个打印函数

常用信号

信号 描述
clicked(bool) 鼠标左键被按下,一直按着或者释放时,或者快捷键被按着或者释放时触发该信号
pressed() 当鼠标指针在按钮上并按下左键时触发该信号,一直按着或者按下并释放都会产生
released() 鼠标左键被释放时触发
toggled(checked) checkable设置为True时,状态发生改变时触发信号

测试代码

chk1Status = self.dia.checkBox_2.text() + ", isChecked=" + str( self.dia.checkBox_2.isChecked()) + ', chekState=' + str( self.dia.checkBox_2.checkState()) + "\n" print(chk1Status)
输出:
Normalization, isChecked=True, chekState=2

RadioButton

一个很不错的教程

  1. 单选框默认开启自动互斥(autoExclusive)。如果启用了自动互斥,属于同一个父部件的单选框的行为就和属于一个互斥按钮组的一样。如果你需要为属于同一父部件的单选框设置多个互斥按钮组,把它们加入QButtonGroup中。
  2. 每当一个按钮切换选中或未选中状态时,会发出的toggled()信号。如果希望每个按钮切换状态时触发一个动作,连接到这个信号。使用isChecked()来查看特定按钮是否被选中。
  3. 单选框可以显示文本,以及可选的小图标。图标使用setIcon()来设置,文本可以在构造函数或通过setText()来设置。可以指定快捷键,通过在文本中的特定字符前指定一个&。

QTextBrowser(文本浏览框)

适用于多行不可修改文字在UI中的呈现

修改函数

  1. 调用append方法可以向文本浏览框中添加文本
    1
    self.textBrowser.append("Hello World!") 

样式表

  • 不建议在designer里直接敲字,我发现这东西有一个雷点在于如果你直接往上边敲字是样式是没什么改变的,但是如果是append方法就可以按样式表的来

QPushBUtton

参数设置&样式表美化

  1. toolTip:在点击按钮时会鼠标下方会出现的小提示
  2. 蓝黑科技感样式表
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    QPushButton{
    background-color: transparent;
    color:rgb(137, 195, 235);
    border:1px solid #89c3eb;
    font: 75 13pt "Bahnschrift";
    }
    QPushButton:hover{
    color:rgb(53, 171, 239);
    border-radius:6px;
    border:2px solid #2980b6;
    }

    QPushButton:pressed{
    border: 1px solid #3C3C3C;
    border-radius:6px;
    background:rgb(124, 195, 255);
    }
  3. 当需要单独设置某个按钮的样式表时,如果有状态限制需要每一个后边都限定状态(比如下边的hover状态设置)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #pushButton_ori_2,#pushButton_ori,#pushButton_2,#pushButton{
    background-color: transparent;
    color:rgb(137, 195, 235);
    font: 75 13pt "Bahnschrift";
    border:none;
    }
    #pushButton_ori_2:hover,
    #pushButton_ori:hover,
    #pushButton_2:hover,
    #pushButton:hover{
    color:rgb(52, 231, 254);
    border:none;
    }

Example

好用的腾讯会议高仿例子:
最小化按钮样式表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
QPushButton{
background-color: rgb(255, 255, 255);
font: 15pt "宋体" ;
border:none;
border-radius:none;
color: gray;
}

QPushButton:hover{
background-color: rgb(218, 218, 218);
}

QToolTip{
background-color: rgb(255, 255, 255);
}

关闭按钮样式表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
QPushButton{
background-color: rgb(255, 255, 255);
font: 10pt "宋体" ;
border:none;
border-top-right-radius:4px;
border-top-left-radius:0px;
border-bottom-right-radius:0px;
border-bottom-left-radius:0px;
color: gray;
}

QPushButton:hover{
background-color: rgb(218, 218, 218);
}

QToolTip{
background-color: rgb(255, 255, 255);
}

设置按键样式表:
tip:把鼠标移到按钮上时的图片改为原图片的淡化版,大小35左右为佳。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
QPushButton{
background-color: rgb(255, 255, 255);
font: 10pt "宋体" ;
border:none;
border-top-right-radius:4px;
border-top-left-radius:0px;
border-bottom-right-radius:0px;
border-bottom-left-radius:0px;
color: gray;
}

QPushButton:hover{
background-color: rgb(218, 218, 218);
}

QToolTip{
background-color: rgb(255, 255, 255);
}

QProgressBar

常用方法

setRange(): 设置进度条的取值范围(最小值和最大值)
setMinimum(): 设置进度条的最小值
setFont(): 设置文本字体
setMaximum(): 设置进度条的最大值
setValue(): 设置进度条的值
reset(): 让进度条重新回到开始位置
setOrientation(): 设置进度条方向(水平: Qt.Horizontal, 垂直: Qt.Vertical)
setTextVisible(): 设置进度条的文本是否可见
setTextDirection(): 设置文本方向,只对垂直进度条有效
setInvertedAppearance(): 设置进度条的方向(True/False: 正反方向)
setFormat(): 设置文本字符串的格式(%p, 百分比显示,这是默认情况, %v: 当前进度, %m :总步数)

美化

1
2
3
4
5
6
7
8
9
10
11
QProgressBar {
background-color: rgb(114, 163, 195);
color:rgb(44, 62, 80);
border-style:none;
text-align:center;
border-radius:10px;
}
QProgressBar::chunk{
border-radius:10px;
background-color:qlineargradient(spread:pad, x1:0,y1:0.511364, x2:2, y2:0.523, stop:0 rgb(41, 128, 182), stop:1 rgb(43, 6, 255));
}

其中可以调整x2:2的数值,比如改为x2:1会使得渐变色变化速度更快且最终的颜色最深。

进度条更新并跳出新界面(可以做启动画面)

效果展示
原教程在这里,笨人对代码作了一些注释,后面附有修改

作者源代码(不完全)

背景建议使用QframeQprogressBar样式表设置参见上美化

  1. 设置定时器,实际上这个Bar并不是根据加载进度走的,而是100ms更新一次
    • 实际上如果你想与你的程序进度连接的话,只要把self.load_progress_bar放在你需要的位置就行
      1
      2
      3
      4
      def set_loader(self):
      self.timer = QtCore.QTimer() #建立一个计时器
      self.timer.timeout.connect(self.load_progress_bar)
      self.timer.start(100) #更新时间间隔
  2. 对进度条操作,每次加一,大于既定值时执行操作;此处是关闭本界面,调用打开新界面的函数,并停止计时
    1
    2
    3
    4
    5
    6
    7
    8
    def load_progress_bar(self):
    self.progressBar.setValue(self.progressBar.value() + 1)
    #self.cont_label_title[0] += 1 #这两行暂时不知道是什么意思,但是好像对功能实现没有影响
    #self.cont_label_title[1] += 1
    if self.progressBar.value() >= 100:
    self.window.close()
    self.open_table_main()
    self.timer.stop()
  3. 调用新界面产生函数
    1
    2
    3
    4
    5
    def open_table_main(self):
    self.MainWindow = QtWidgets.QMainWindow()
    self.ui = Ui_MainWindow()
    self.ui.setupUi(self.MainWindow)
    self.MainWindow.show()

笨人修改

QFileDialog

教程
获取文件夹路径,对话框获取文件

QLineEdit

常用API

  1. QLineEdit.text():返回输入框的当前文本。

  2. QLineEdit.addAction(Action,QLineEdit.ActionPosition):添加动作到文本输入栏,上面已经举过例子了。

  3. QLineEdit.setAlignment(Qt.Alignment flag):属性保存了输入框的对齐方式(水平和垂直方向。

  4. QLineEdit.setCompleter() :输入栏的自动补全就是靠这个实现的,下下章我们讲解。

  5. QLineEdit.deselect() :取消选中任何已选中的文本。

  6. QLineEdit.displayText():返回显示的文本。默认值为一个空字符串。

  7. setEchoMode():如果echoMode是Normal,和text()返回的一样;如果EchoMode是Password或PasswordEchoOnEdit,会返回平台相关的密码掩码字符;如果EchoMode是NoEcho,返回一个空字符串””。

  8. QLineEdit.selectedText():返回选中的的文本。如果没有选中,返回一个空字符串。默认为一个空字符串。

  9. QLineEdit.setCursorPosition(QLineEdit.cursorPosition):设置输入框当前光标的位置。

  10. QLineEdit.setMaxLength(int):此属性包含文本的最大允许长度。如果文本太长,将从限制的位置截断。默认值为32767。

  11. QLineEdit.setReadOnly(bool):此属性保存输入框是否为只读。在只读模式下,用户仍然可以将文本复制到剪贴板,但不能编辑它,且不显示光标。

  12. QLineEdit.setSelection(int start, int length) :从位置start选择文本为length个字符,允许负长度。我们一启动程序是否设置setSelection的,效果如下:

  13. QLineEdit.setValidator():设置输入框的验证器,将限制任意可能输入的文本

  14. placeholderText用于输入前的提示显示文字该属性包含行编辑的占位符文本。只要行编辑为空,设置此属性将使行编辑显示一个灰色的占位符文本。

通常情况下,即使具有焦点,空行编辑也会显示占位符文本。但是,如果内容是水平居中的,则行编辑具有焦点时,占位符文本不会显示在光标下方。默认情况下,该属性包含一个空字符串。

  1. QLineEdit.isClearButtonEnabled(bool) :是否设置清除内容的按钮。

  2. QLineEdit.setInputMask():设置掩码,效果就是我们演示视频中的License输入。

信号

  1. selectionChanged() :只要选择改变这个信号就会被发射。

  2. cursorPositionChanged(int old, int new) :只要光标移动,这个信号就会发射。前面的位置old,新的位置是new。

  3. editingFinished():按下返回或回车键或线条编辑失去焦点时发出此信号。

  4. returnPressed():按下返回或回车键时发出此信号。

  5. textChanged(str):只要文字发生变化就会发出此信号。文本参数是新文本。与textEdited()不同,当通过调用setText()以编程方式更改文本时,也会发出此信号。

  6. textEdited(str) :无论何时编辑文本都会发出此信号。文本参数是新文本。与textChanged()不同,当以编程方式更改文本时,不会发出此信号,例如通过调用setText()。

函数

  1. clear() :清除输入框内容

  2. copy():如果echoMode()是Normal,将选中的文本复制到剪贴板。

  3. cut() :如果echoMode()是Normal,将所选文本复制到剪贴板并删除它。 如果当前的验证不允许删除选定的文本,cut()将复制而不删除。

  4. paste() :如果输入框不是只读的,插入剪贴板中的文本到光标所在位置,删除任何选定的文本。如果最终的结果不被当前的验证器接受,将没有任何反应。

  5. redo() :重做上次操作,如果redo可用(isRedoAvailable() )。

  6. selectAll() :选中所有文本(即:高亮),并将光标移动到末尾。当一个默认值被插入时,这非常有用,因为如果用户在点击部件之前就输入,选中的文本将被删除。

  7. setText(str) :设置输入框显示的文本。

  8. undo() :撤消上次操作(如果撤销可用)

QGroupBox

  1. 去掉不需要的标题栏
    1
    QGroupBox::setStyleSheet("QGroupBox{ margin-top:0px;} QGroupBox:title {margin-top: 0px;}");

Widget(通用窗口)系列设置

通用窗口不包含菜单栏、工具栏!

侧边栏修改

特定对口修改

对于文字颜色或者背景色背景图片填充的修改建议直接在相应参数的styleSheet中修改

注意这个修改需要选中后每一条进行修改,比方说菜单栏,每一个菜单栏的下属栏等等。
示例如下:

  1. color,background-color等等直接用
  2. 贴图:
    1
    2
    3
    4
    background-image:url(:/Img/main_bgimg.jpg);
    #直接给背景贴图
    MainWindow{background-image:url(:/Img/main_bgimg.jpg);}
    #直接给背景贴图
  • 值得注意的是,如果较大类中设置了背景,建议使用#name来限定插图范围,不然大类里边的小组件样式表是不起作用的

TabWidget Setting

  • 修改表头CurrentTabText
  • 修改是否可选:setTabEnabled(int index, bool enable)

QlineEdit

  • .setPlaceholderText(""):提示输入内容

泛泛修改

只改动MainWindow即可
注意所有对样式表的修改直接在他给的条目里选择就可以,不需要手动输入标签,只要把类写出来就行
QMainWindow主窗口
QLabel纯文字

1
2
3
4
5
6
7
{
color:
#修改字体颜色
background-color:
#背景颜色

}

特殊修改

QCombobox

外观

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
/*QCombobox主体*/
QComboBox {
border: 2px solid #f3f3f3;/*设置线宽*/
background-color: rgb(237, 242, 255);/*背景颜色*/
border-radius: 15px;/*圆角*/
padding: 1px 2px 1px 2px; /*针对于组合框中的文本内容*/
text-align:bottom;
min-width: 9em; /*# 组合框的最小宽度*/
/*min-height: 5em;*/

border-style:solid;/*边框为实线型*/
border-width:2px;/*边框宽度*/
border-color:rgb(77, 123, 255);/*边框颜色*/

padding-left: 10px;/*左侧边距*/
}
/*QCombobox右侧按钮*/
QComboBox::drop-down {
subcontrol-origin: padding;
subcontrol-position: top right;/*放于右方顶部*/
width: 50px;/*设置按钮范围宽度*/
/*border-radius: 15px;
border-left-width: 1px;
border-left-color: darkgray;
border-left-style: solid;*/

border-top-right-radius: 3px;/*设置边框圆角*/
border-bottom-right-radius: 3px;
/*padding-right: 50px;*/
}
/*QCombobox右侧按钮的箭头图标*/
QComboBox::down-arrow {
border-image: url(:/image/down_list.png);/*自定义图片填充*/
width: 10px;/*设置该图标的宽高*/
height: 10px;
}

#程序在运行过程中,动态对QCombobox填充可选项。比如登录时填充人名数据、选择摄像头分辨率时的分辨率列表等等不固定的元素。

/* 下拉后,整个下拉窗体样式 */
QComboBox QAbstractItemView {
border: 2px solid #f3f3f3;/*边框宽度、线形、颜色*/
background-color: rgba(237, 242, 255, 1);/*背景颜色*/
border-radius: 15px;/*圆角*/
padding: 1px 2px 1px 2px; /*针对于组合框中的文本内容*/
min-width: 9em; /*# 组合框的最小宽度*/
}

/* 下拉后,整个下拉窗体每项的样式 */
QComboBox QAbstractItemView::item {
border-radius: 15px;/*圆角*/
height: 30px; /* 项的高度(设置pComboBox->setView(new QListView());后,该项才起作用) */
background-color: rgb(237, 242, 255);

}

/*以下部分不知为何不生效,有待调试*/
/* 下拉后,整个下拉窗体越过每项的样式 */
QComboBox QAbstractItemView::item:hover {
color: #FFFFF0;
/* 整个下拉窗体越过每项的背景色 */
background-color: rgb(98, 0, 255);
}

/* 下拉后,整个下拉窗体被选择的每项的样式 */
QComboBox QAbstractItemView::item:selected {
color: #FFFFF0;
background-color: rgb(0, 85, 200);
}


//填充下拉选项
ui->comboBox->clear();//清空combobox
QStandardItemModel *pItemModel = qobject_cast<QStandardItemModel*>(ui->comboBox->model());

//字体设置
int combobox_item_fontsize = 9;
QFont font;
//font.setPixelSize(combobox_item_fontsize*scale);
font.setPointSize(combobox_item_fontsize);
font.setFamily("黑体");

//填充默认项(在没有任何数据时,可以先做一个默认的提示项给用户,然后让用户自己输入)
QString tip_string(u8"请选择用户名");
ui->comboBox->addItem(tip_string);
pItemModel->item(0)->setIcon(QIcon(":/image/account.png")); //修改某项图标
pItemModel->item(0)->setForeground(QColor(255, 0, 0)); //修改某项文本颜色
pItemModel->item(0)->setBackground(QColor(220,220,220)); //修改某项背景颜色
pItemModel->item(0)->setFont(font);
pItemModel->item(0)->setTextAlignment(Qt::AlignVCenter | Qt::AlignHCenter); //修改某项文本对齐方式

//填充正式项
if(ui->comboBox->currentText() == tip_string)
ui->comboBox->clear();

int i= 0;
QStringList m_list;//随便来点填充数据
m_list<<"AAA"<<"BBB"<<"CCC"<<"DDD";
foreach (QString name, m_list)
{
qDebug()<<"combobox additem:"<<name;
ui->comboBox->addItem(name);

pItemModel->item(i)->setIcon(QIcon(":/image/account.png")); //修改某项图标
//pItemModel->item(i)->setText("修改的文本 " + QString::number(i + 1)); //修改某项文本
//pItemModel->item(i)->setForeground(QColor(255, 0, 0)); //修改某项文本颜色
//pItemModel->item(i)->setBackground(QColor(220,220,220)); //修改某项背景颜色(若样式表中已经设置了表项的背景颜色,则不会生效)
pItemModel->item(i)->setFont(font);
pItemModel->item(i)->setTextAlignment(Qt::AlignVCenter | Qt::AlignHCenter); //修改某项文本对齐方式
i++;
}

//以上设置完,会默认选择第一项。可以手动选择-1项,即为未选择状态
//ui->comboBox->setCurrentIndex(-1);

常用页操作

StakedWight实现点击按钮更换界面布局

  1. 在containers中找到stacked Widget(注意不是建立新的界面)并拖动成自己需要的大小
  2. 对界面进行编辑,需要的buttons拖动到上边便会成为其子部件
  3. pyuid生成代码
  4. 添加连接函数,注意menu用triggered button用clicked
    1
    2
    3
    4
    5
    6
    #添加换页槽函数
    self.actionRadiomic.triggered.connect(self.display1)

    def display1(self):
    self.stackedWidget.setCurrentIndex(1)
    #注意页码子控件一般是从0开始,1为第二页,默认初始显示0

窗口与窗口调用

子窗口的调用

子窗口部件无反应问题

笨人已经搞到崩溃,等查到debug方式的时候整个人已经萎了喝喝,父窗口改成WindowModel就可以了
Img
这是模式窗口属性,有三个值:NoModal指没有模式,也就是不会阻塞其他窗口应用的模式;WindowModal就是单窗口层次模式,只准其本身与其子窗口可以使用;ApplicationModal就是应用模式,除了本身其他窗口都不能使用。

PyQt5的QSS美化

样式表调用

样式表写出,但不显示

前边的限制用#name加括号,限定对象,在多页切换的工具中要单选出改页面(建议在右侧的工具栏中选择并进行修改)

qss调用

主函数 全局调用

1
2
3
4
5
styleFile = './pyqt5/qss/style.qss'
with open(styleFile, "r") as f:
style = f.read()
win.setStyleSheet(style)
f.close()

设置图片样式表

但是你一定要知道图片要用png格式,只有你能显示出来那个缩略图才说明成功了
border-image:url(D:/www/xxx/Pytorch/Braintumor/PyQt_test/Img/Hydrogen.jpg);

一些主题

一个很简单的有重定义边框的主题

好看的字体加颜色

  1. 深蓝色+柔和体(松散衡水体感觉是)
    1
    2
    color: rgb(44, 62, 80);
    font: italic 11pt "Cascadia Code SemiLight";
  2. 适合做题目的可爱点字体
    1
    font: 15pt "Cooper Black";
  3. 圆润的正常体
    1
    font: 9pt "Arial Rounded MT Bold";

qtawesome使用总结

  1. 展示图标库
    1

对某个控件的所有子控件进行集体美化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
self.left_widget.setStyleSheet('''
QPushButton{border:none;color:white;padding-left:5px;
height:35px;
font-size:15px;
padding-right:10px;}
QPushButton#left_label{
border:none;
border-bottom:1px solid white;
font-size:20px;
font-weight:700;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}

QWidget#left_widget{
background:Gray;

border-top:1px solid white;
border-bottom:1px solid white;
border-left:1px solid white;
border-top-left-radius:10px;
border-bottom-left-radius:10px;
}
QPushButton#left_button:hover{ color:white;
border:2px solid #F3F3F5;
border-radius:15px;
background:black;}
''')

QTabWidget样式

各个组件的实际指向

QTabWidget

  1. QTabWidget显示区域的属性设置
    要在大控件里添加
    1
    2
    3
    4
    5
    6
    7
    8
    QTabWidget::pane {
    border-top: 1px solid #E5E5E5;
    border-left:1px solid #E5E5E5;
    position: absolute;
    font-size: 14px;
    background-color:#FFFFFF;
    top:-1px #稍微遮一点点选项卡的上部
    }
  2. QTabWidget 选择项的属性设置
    自己的代码~一定要注意伪状态后边是没有空格的!要不然没反应
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    QTabBar::tab{
    background-color: rgb(41, 128, 182);
    min-width:250px;
    min-height:70px;
    border-top-left-radius: 15px;
    border-top-right-radius: 15px;
    color: rgb(44, 62, 80);
    font: italic 11pt "Cascadia Code SemiLight";
    }
    QTabBar::tab:hover{
    background-color: rgb(137, 195, 235);
    border-right:2px solid;
    border-top:2px solid;
    }
    别人的代码(我就加注释啦)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    QTabBar::tab{
    background-color: rgb(41, 128, 182);
    min-width:250px;#宽度
    min-height:70px;#高度
    border-top-left-radius: 15px;
    border-top-right-radius: 15px;
    color: rgb(44, 62, 80);
    font: italic 11pt "Cascadia Code SemiLight";
    }
    QTabBar::tab:hover{
    background-color: rgb(137, 195, 235);
    border-right:2px solid; #如果要设置颜色的话在solid之后加一个#颜色
    border-top:2px solid;
    }
    只有出现TabBar才会设置标签项的颜色
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    QTabBar::tab {
    border: none;
    border-bottom-color: #FFFFFF; /* same as the pane color */
    border-top-left-radius: 4px;
    border-top-right-radius: 4px;
    min-width: 8ex;
    padding: 2px;
    font-size: 14px;
    background-color:#FFFFFF;
    }
    QTabBar::tab:selected, QTabBar::tab:hover {
    background-color:#FFFFFF;//选中背景色
    }
    QTabBar::tab:selected {
    border:none; #去掉边框
    color:#2080F7;//选中颜色
    border-bottom: 2px solid #2080F7;
    font-weight:bold;
    background-color:#FFFFFF;
    }
  3. QTabWidget 头部属性设置
    1
    2
    3
    4
    5
    6
    7
    8
    QTabWidget::tab-bar {
    border-top: 2px solid #E5E5E5;
    border-bottom: 2px solid #E5E5E5;
    border-left:1px solid #E5E5E5;
    alignment: center;//居中显示
    font-size: 14px;
    background-color:#FFFFFF;
    }
  4. 头部选项卡QTabBar::tab
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    QTabBar::tab {
    background: #051322;
    color:#7F8997;
    border: 2px;
    border-top-left-radius: 10px;
    border-top-right-radius: 10px;
    min-width: 200px;
    min-height: 35px;
    padding: 2px;
    }
    QTabBar::tab:selected{
    background-color: white;
    color:#001330;
    }
    QTabBar::tab:first{
    min-width: 35px;
    background-color: #2489F2;
    border-top-left-radius: 3px;
    border-top-right-radius: 3px;
    background-image: url(:/Resources/image/homepage.png);
    background-position: center;
    background-repeat: no-repeat;
    }
    QTabBar::tab:first:hover{
    min-width: 35px;
    background-color: #2489F2;
    border-top-left-radius: 3px;
    border-top-right-radius: 3px;
    background-image: url(:/Resources/image/homepage_hover.png);
    background-position: center;
    background-repeat: no-repeat;
    }
    QTabBar::close-button{
    border-image: url(:/Resources/image/close.png);
    }
    QTabBar::close-button:hover{
    border-image: url(:/Resources/image/close_hover.png);
    }
  5. 内容区美化
    1
    2
    3
    4
    5
    6
    7
    8
    //设置内容区域边框
    QTabWidget::pane{
    border:none;
    }
    //标题栏左侧间距
    QTabWidget::tab-bar {
    left: 1px;
    }
  6. 头部属性设置
    1
    2
    3
    4
    5
    6
    7
    8
    QTabWidget::tab-bar {
    border-top: 2px solid #E5E5E5;
    border-bottom: 2px solid #E5E5E5;
    border-left:1px solid #E5E5E5;
    alignment: center;
    font-size: 14px;
    background-color:#FFFFFF;
    }
  7. 图片插入的一个小例子
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    #最左边标签未选中时显示(因为需要左边打圆角,所以和其他背景图片不一样,注意字体颜色)
    QTabBar::tab:first:!selected {
    color:#000000;
    border-image: url(:/common/images/common/左_normal.png);
    }
    #最左边标签被选中
    QTabBar::tab:first:selected {
    color:#FFFFFF;
    border-image: url(:/common/images/common/左_pressed.png);
    }
    #最右边标签未选中时显示(因为需要右边打圆角,所以和其他背景图片不一样)
    QTabBar::tab:last:!selected {
    color:#000000;
    border-image: url(:/common/images/common/右_normal.png);
    }
    #最右边标签被选中
    QTabBar::tab:last:selected {
    color:#FFFFFF;
    border-image: url(:/common/images/common/右_pressed.png);
    }
    #中间的标签未被选择的显示
    QTabBar::tab:!selected {
    color:#000000;
    border-image: url(:/common/images/common/中_normal.png);
    }
    #中间标签选中显示的图片
    QTabBar::tab:selected {
    color:#FFFFFF;
    border-image: url(:/common/images/common/中_pressed.png);
    }
  8. 改变左右滑动的按钮
    1
    2
    3
    4
    5
    6
    7
    8
    9
    QTabBar QToolButton {
    border: none;
    color: rgb(255, 206, 6);
    background-color: #0b0e11;
    }

    QTabBar QToolButton:hover {
    background-color: rgb(44, 62, 80) ;
    }

利用QProxyStyle改变TabBar位置并改变文字方向

但是是C语言()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <QPainter>
#include <QProxyStyle>

class CustomTabStyle : public QProxyStyle
{
public:
QSize sizeFromContents(ContentsType type, const QStyleOption *option,
const QSize &size, const QWidget *widget) const
{
QSize s = QProxyStyle::sizeFromContents(type, option, size, widget);
if (type == QStyle::CT_TabBarTab) {
s.transpose();
s.rwidth() = 90; // 设置每个tabBar中item的大小
s.rheight() = 44;
}
return s;
}

void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const
{
//设置lab
if (element == CE_TabBarTabLabel) {
if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(option)) {
QRect allRect = tab->rect;
//选中状态
if (tab->state & QStyle::State_Selected) {
painter->save();
painter->setPen(0xffffff);
painter->setBrush(QBrush(0xffffff));
//painter->drawRect(allRect.adjusted(6, 6, -6, -6));
painter->drawRect(allRect.adjusted(0, 0, 0, 0));
painter->restore();
}
//hover状态 鼠标移动状态
else if (tab->state & QStyle::State_MouseOver) {
painter->save();
painter->setPen(0xECECEC);//画框
painter->setBrush(QBrush(0xECECEC));
painter->drawRect(allRect.adjusted(0, 0, 0, 0));
painter->restore();
} else {
painter->setPen(0x33CCFF);
}
//字体
QTextOption option;
option.setAlignment(Qt::AlignCenter);
painter->setFont(QFont("楷体", 12, QFont::Bold));
painter->setPen(0x0A0A0A);
painter->drawText(allRect, tab->text, option);
return;
}
}
if (element == CE_TabBarTab) {
QProxyStyle::drawControl(element, option, painter, widget);
}
}
};

调用:

1
2
ui->tabWidget->setTabPosition(QTabWidget::West);
ui->tabWidget->tabBar()->setStyle(new CustomTabStyle);

自己做的好看的成品嘿嘿

  1. 蓝色的选项卡
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    QTabBar::tab{
    background-color: rgb(41, 128, 182);
    min-width:250px;
    min-height:70px;
    color: rgb(44, 62, 80);
    font: italic 11pt "Cascadia Code SemiLight";
    }
    QTabBar::tab:hover,QTabBar::tab:selected{
    border:5px solid #bce2e8;
    background-color: rgb(137, 195, 235);
    border-right:2px solid;
    border-top:2px solid;
    }

QFrame制作无边框窗口

1
2
3
4
5
6
7
#frame
{
background-color:rgb(39, 146, 195);
border: 5px, white;
border-radius: 20px;
margin: 5px;
}

QGruopBox

1
2
3
4
5
6
7
8
9
10
QGroupBox{
border:2px solid gray;
border-radius:4px;
margin-top:0.5em;
}
QGroupBox::title{
subcontrol-origin:margin;
background-color:white;
padding:0 3px;
}

Scroll Area

设置背景图片要设定里边那个Widget的样式,要不然所有子控件都会被改

1
2
3
4
5
6
#scrollAreaWidgetContents{
border-image: url(./Img/5deepblue.png);
}
QScrollArea{
border:none;
}

QComboBox

指定下拉箭头图片自定义,图片文件为name.png

  1. 所有的下拉箭头
    1
    QConboBox:drop-down{image:url(name.png)}
  2. 指定ID的下拉箭头
    1
    QComboBox#myQComboBox::drop-down {image:url(dropdown.png)}

QSS伪状态

QSS的伪状态选择器是一个以冒号开头的选择表达式,限制控件在某种状态时才可以使用QSS规则,只能描述某一个控件或者一个复合控件的自控件的状态,只能放在选择器的最后边。

hover:鼠标指针经过的状态

1
2
3
4
5
6
QComboBox:hover{background-color:red}
#经过combobox时其背景变为红色
QComboBox::drop-down:hover{background-color:red}
#经过其下拉箭头的时候,下拉箭头的背景变成12
QCheckBox:hover:checked{color:white}
#多种伪状态可以同时使用:表示当鼠标指针经过一个选中的QCheckBox时,设置其文字的前景色为白色12

:!hove可表示没有鼠标经过的状态
实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import sys
from PyQt5.QtWidgets import *

class WindowDemo(QWidget):
def __init__(self):
super(WindowDemo, self).__init__()
self.initUI()
def initUI(self):
#实例化列表控件
combo=QComboBox(self)
#设置列表控件的名称
combo.setObjectName('myQComboBox')

#添加条目到列表控件
combo.addItem('Window')
combo.addItem('Ubuntu')
combo.addItem('Red Hat')
#控件移动到指定位置
combo.move(50,50)
#设置窗口的标题与初始窗口的属性
self.setGeometry(250,200,320,150)
self.setWindowTitle('QComboBox样式')

#设置样式

qssStyle='''
QComboBox#myQComboBox::drop-down{
image:url(./images/dropdown.png)
}
QComboBox#myQComboBox::drop-down:hover{
background-color:red
}
'''
self.setStyleSheet(qssStyle)
if __name__ == '__main__':
app=QApplication(sys.argv)
win=WindowDemo()
win.show()
sys.exit(app.exec_())

MainWindow:直接在retranslateUi后边设置

1
2
3
4
5
6
7
8
9
10
11
#MainWindow.setWindowOpacity(0.9) # 设置窗口透明度
MainWindow.setAttribute(QtCore.Qt.WA_TranslucentBackground) # 设置窗口背景透明
#MainWindow.setWindowFlag(QtCore.Qt.FramelessWindowHint) # 隐藏边框
pe = QPalette()
MainWindow.setAutoFillBackground(True)
pe.setColor(QPalette.Window,Qt.lightGray) #设置背景色
#pe.setColor(QPalette.Background,Qt.blue)
MainWindow.setPalette(pe)

Ui_MainWindow3.setWindowTitle("语音识别")
Ui_MainWindow3.setWindowIcon(QIcon('Amg.jpg')) #设置图标,可以用图片,也可以用qtawesome

PushButton:直接在初始setup后设置

  1. 设置按钮前的图标
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    spin_icon = qtawesome.icon('fa5s.microphone-alt', color='white')
    #图标名参考示例格式,以及qtawesome的readme中对于库的提示来写,格式就是 库.图标名
    self.pushButton.setIcon(spin_icon)#设置图标
    self.pushButton.setIconSize(QtCore.QSize(50,50))#需要导入库from PyQt5.QtCore import QSize
    self.pushButton.setStyleSheet('''QPushButton{background-color: rgb(70, 70, 70);
    color: rgb(255, 255, 255);}
    QPushButton:hover{color:white;
    border:2px solid #F3F3F5;
    border-radius:35px;
    background:darkGray;}''')
  2. 图标+圆角(图标用的是flaticon)
    在qss文件添加以下内容:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    /*设置控件的背景*/
    QTabWidget
    {
    background-color:rgb(104,191,249);
    }
    /*设置控件下面板的背景颜色*/
    QTabWidget::pane
    {
    background-color: rgb(228, 233, 242);
    border:none;

    }
    /*设置控件下选择页的颜色*/
    QTabBar::tab
    {
    font: 15pt "Chinese fine black";
    background-color:rgb(104,191,249);
    min-width: 60px;
    min-height: 30px;
    padding: 2px;
    }
    /*设置控件下选择页被选中的颜色*/
    QTabBar::tab:selected
    {
    background-color: rgb(228, 233, 242);
    }

将最大化最小化按钮设置为像苹果的三个点点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class gui_view(QWidget):
def __init__(self):
super(gui_view, self).__init__()

self.resize(500, 350)
self.setWindowFlags(Qt.FramelessWindowHint) # 去边框
# # self.setAttribute(Qt.WA_TranslucentBackground) # 设置窗口背景透明

button_red = QPushButton(self)
button_red.move(20, 20)
button_red.setFixedSize(20, 20)
button_red.setStyleSheet("QPushButton{\n"
" background:#CE0000;\n"
" color:white;\n"
" box-shadow: 1px 1px 3px;border-radius: 10px;\n"
"}\n"
"QPushButton:hover{ \n"
" background:red;\n"
"}\n"
"QPushButton:pressed{\n"
" border: 1px solid #3C3C3C!important;\n"
" background:black;\n"
"}")
button_red.clicked.connect(self.quit_button)

button_orange = QPushButton(self)
button_orange.move(50, 20)
button_orange.setFixedSize(20, 20)
button_orange.setStyleSheet("QPushButton{\n"
" background:orange;\n"
" color:white;\n"
" box-shadow: 1px 1px 3px;border-radius: 10px;\n"
"}\n"
"QPushButton:hover{ \n"
" background:yellow;\n"
"}\n"
"QPushButton:pressed{\n"
" border: 1px solid #3C3C3C!important;\n"
" background:black;\n"
"}")

button_green = QPushButton(self)
button_green.move(80, 20)
button_green.setFixedSize(20, 20)
button_green.setStyleSheet("QPushButton{\n"
" background:green;\n"
" color:white;\n"
" box-shadow: 1px 1px 3px;border-radius: 10px;\n"
"}\n"
"QPushButton:hover{ \n"
" background:#08BF14;\n"
"}\n"
"QPushButton:pressed{\n"
" border: 1px solid #3C3C3C!important;\n"
" background:black;\n"
"}")

def quit_button(self):
quit()

最小化

1
2
3
4
5
6
7
8
9
10
11
QPushButton{
background:#6C6C6C;
color:white;
box-shadow: 1px 1px 3px rgba(0,0,0,0.3);font-size:16px;border-radius: 8px;font-family: 微软雅黑;
}
QPushButton:hover{
background:#9D9D9D;
}
QPushButton:pressed{
border: 1px solid #3C3C3C!important;
}

关闭

1
2
3
4
5
6
7
8
9
10
11
12
QPushButton{
background:#CE0000;
color:white;
box-shadow: 1px 1px 3px rgba(0,0,0,0.3);font-size:16px;border-radius: 8px;font-family: 微软雅黑;
}
QPushButton:hover{
background:#FF2D2D;
}
QPushButton:pressed{
border: 1px solid #3C3C3C!important;
background:#AE0000;
}

少许动画操作

静态图片+动态文字

1
2
3
4
5
6
7
8
9
10
11
12
13
app = QApplication(sys.argv)
pixmap = QPixmap("Img/brain1.jpeg")
splash = QSplashScreen(pixmap)
splash.show()
#splash.setCursor(PyQt5.QtWidgets.BlankCursor) # 设置点击图标关闭事件
splash.showMessage("加载中") # 第二个参数为字的位置,第三个参数为颜色
app.processEvents() # 使程序还能响应其他事件

dialog = Ui_MainDialog() #先创建好实例后取消
splash.finish(dialog) # main为主界面的实例

dailog.exec()
sys.exit(app.exec_())

动态图gif

使用label实现:

1
2
3
self.gif = QMovie('bg2.gif')
self.label.setMovie(self.gif)
self.gif.start()

完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
app = QApplication(sys.argv)
label = QLabel("")
mv = QMovie(":/gif/loading.gif")
label.setMovie(mv) #其实是把label当成了一个载体,把mv放到label上
label.setWindowFlags(Qt.FramelessWindowHint) # label窗口无边框设置
label.setAttribute(Qt.WA_TranslucentBackground) # label背景透明
label.move((app.desktop()->width() - window.width()) / 2, (app.desktop()->height() - window.height()) / 2) # 调整位置
label.setScaledContents(True)
mv.start()
label.show()
main = None #把main改成你的界面名字
while True:
app.processEvents() # 使动画正常播放,不影响主界面构造
if not main:
main = MainWinodw() #实例创建你应该有的实例
break
main.show()
label.close()
sys.exit(app.exec_())

源代码

class_test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import sys
import PyQt5
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QMovie
from PyQt5.QtWidgets import *

from dictest import Ui_MainDialog

if __name__ == "__main__":
app = QApplication(sys.argv)

label = QLabel("")
mv = QMovie("./Img/11.jpeg")
label.setMovie(mv)
label.setWindowFlags(Qt.FramelessWindowHint) # label窗口无边框设置
label.setScaledContents(True)
mv.start()
label.show() #这句一定要有啊。。uic加载的时候也需要show

dailog = None
while True:
app.processEvents() # 使动画正常播放,不影响主界面构造
if not dailog:
dailog = Ui_MainDialog()
break
label.close()
dailog.exec()

sys.exit(app.exec_())

showImage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 定义MyFigure类的一个实例

self.F = MyFigure(width=5, height=4, dpi=100)
# 在GUI的groupBox中创建一个布局,用于添加MyFigure类的实例(即图形)后其他部件。
self.gridlayout_1 = QGridLayout(self.graphicsView_2) # 继承容器
self.gridlayout_1.addWidget(self.F, 0, 1)
#self.showimage()

self.F2 = MyFigure(width=5, height=4, dpi=100)
self.gridlayout_2 = QGridLayout(self.graphicsView_3) # 继承容器
self.gridlayout_2.addWidget(self.F2, 0, 1)

self.F3 = MyFigure(width=5, height=4, dpi=100)
self.gridlayout_3 = QGridLayout(self.graphicsView_4) # 继承容器
self.gridlayout_3.addWidget(self.F3, 0, 1)

# self.horizontalSlider.valueChanged.connect(self.bindSlider)
self.showimage()
# SimpleView.VisNii(self,self.ori_path)

VTK(Vedo)实现

可以进行3D可视化,显示感兴趣区域,2D切割,使用的mySlicer是在vedo3dslicer上修改的,只是三个切片分别分装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
import pandas as pd
from PyQt5 import Qt
from PyQt5.QtWidgets import QGridLayout, QDialog, QGraphicsDropShadowEffect
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QColor
from PyQt5.QtWidgets import *
from qtpy import uic

import matplotlib
from vtkmodules.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
import itk
from vedo import *
from vedo import Volume, Plotter
#用来做数据处理

matplotlib.use("Qt5Agg") # 声明使用QT5

class child(QDialog):
def __init__(self,path,ori_path,label_path):
super().__init__()
self.openingUI = uic.loadUi("visualization.ui", self)
self.read(path,ori_path,label_path)
def read(self,path,ori_path,label_path):
readData.read_csv(self.openingUI,path,ori_path,label_path)

class readData:
def __init__(self):
pass
def is_number(self,s):
try:
float(s)
return True
except ValueError:
pass
try:
import unicodedata
unicodedata.numeric(s)
return True
except (TypeError, ValueError):
pass
return False
def read_csv(self,path,ori_path,label_path):
df = pd.read_csv(path)
numdata = pd.DataFrame() #数字特征
for idx, row in df.iterrows(): # 遍历 DataFrame
if readData.is_number(self,row['Value']):#存储数字结果
numdata = numdata.append({'Feature': row['Feature'], 'Value': row['Value']}, ignore_index=True)
#for idx, row in numdata.iterrows(): # 遍历 特征文件并提取所有的有数字特征
#print(idx, row['Value'])
DataProsess.visData(self,numdata,ori_path,label_path)

class DataProsess:
def __init__(self):
pass
def visData(self,numdata,ori_path,label_path):
self.ori_path = ori_path
self.label_path = label_path
self.numdata = numdata

self.vtkWidget = QVTKRenderWindowInteractor()
self.gridlayout = QGridLayout(self.graphicsView_2)
self.gridlayout.addWidget(self.vtkWidget)

self.vtkWidget_2 = QVTKRenderWindowInteractor()
self.gridlayout_2 = QGridLayout(self.graphicsView_3)
self.gridlayout_2.addWidget(self.vtkWidget_2)

Vis_3D.readData(self,ori_path,label_path)

class Vis_3D(QDialog):
def __init__(self):
pass

def readData(self,ori_path,label_path):
normal = [0, 0, 1]
self.cmap = "gist_stern_r"
self.cmap2 = "viridis_r"

def func(w, _):
c, n = self.pcutter.origin, self.pcutter.normal
vslice = self.vol.slice_plane(c, n, autocrop=True, border=1.0).cmap('bone') # 给动态的切割面一个原点和一条法线
vslice.name = "Slice"
vslice2 = self.vol2.slice_plane(c, n, autocrop=True, border=1.0).cmap(self.cmap2) # 给动态的切割面一个原点和一条法线
vslice2.name = "Slice2"
self.plt2.show(vslice, __doc__) # 少加点就行了...

itk_img = itk.imread(filename=ori_path)
vtk_img = itk.vtk_image_from_image(l_image=itk_img)
self.vol = Volume(vtk_img).cmap("gist_stern_r")
itk_img = itk.imread(filename=label_path)
vtk_img = itk.vtk_image_from_image(l_image=itk_img)
self.vol2 = Volume(vtk_img).cmap("viridis_r")

vslice = self.vol.slice_plane(self.vol.center(), normal).cmap("bone")
vslice.name = "Slice"
vslice2 = self.vol2.slice_plane(self.vol2.center(), normal).cmap("bone")
vslice.name = "Slice2"

self.plt = Plotter(axes=8, N=1, bg="k", bg2="bb", interactive=True,qt_widget=self.vtkWidget) # N:desired renderers,可以qt_windows,sharecam,
self.plt.interactive()
self.plt.show(self.vol,self.vol2, __doc__, zoom=1.5)#

self.plt2 = Plotter(axes=8, N=1, bg="k", bg2="bb", interactive=True, qt_widget=self.vtkWidget_2)
#右侧的切面模型
self.pcutter = PlaneCutter(
vslice,
normal=normal, # 平面的法线,此处赋值为平面法线
alpha=0, # 输入网络截止部分的透明度
c=(0.25, 0.25, 0.25),
padding=0,
can_translate=True,
can_scale=True, # 启用部件的缩放功能
)
self.pcutter.add_observer("interaction", func)
self.plt2.at(0).add(self.pcutter)
self.plt2.interactive()
self.plt2.show(vslice, __doc__) # 少加点就行了...

self.arr = vslice.pointdata[0] # retrieve vertex array data

def flatFunc(evt):
if not evt.actor:
return
try:
pid = evt.actor.closest_point(evt.picked3d, return_point_id=True)
except AttributeError:
pass
else:
txt = f"Position:{precision(evt.actor.picked3d, 3)}\noriginal_shape_MeshVolume = {self.arr[pid]}"

pts = evt.actor.points()
sph = Sphere(pts[pid], c='orange7').pickable(True)
fp = sph.flagpole(txt, s=7, offset=(-150, 15), font=2).follow_camera()
# remove old and add the two new objects
self.plt.remove('Sphere', 'FlagPole').add(sph, fp).render()

self.arr = vslice.pointdata[0] # retrieve vertex array data

def flatFunc_3D(evt):
if not evt.actor:
return
try:
pid = evt.actor.closest_point(evt.picked2d, return_point_id=True)
except AttributeError:
pass
else:
for idx,row in self.numdata[:5].iterrows():
txt = idx['Feature'] + f"idx:{row['Value']}\noriginal_shape_MeshVolume = {self.arr[pid]}"
pts = evt.actor.vertics()
sph = Sphere(pts[pid], c='orange7').pickable(True)
fp = sph.flagpole(txt, s=7, offset=(-150, 15), font=2).follow_camera()
# remove old and add the two new objects
self.plt2.remove('Sphere', 'FlagPole').add(sph, fp).render()

self.plt.add_callback('as my mouse moves please call', flatFunc) # be kind to vedo ;)
self.plt2.add_callback('MouseClick', flatFunc_3D)

New_UI单模态的可视化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
import nibabel
from PyQt5 import uic
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QColor
from PyQt5.QtWidgets import QDialog, QGraphicsDropShadowEffect, QMainWindow, QFileDialog, QGridLayout

import nibabel as nib
from pathlib import Path

from RadiomicsPage import RadiomicsFunc
from VTK_new import SimpleView

#import HeterogeneityCAD
from DlpFeatures import DLPExtractor
#from RadioMLPage import RadioML

import matplotlib

matplotlib.use("Qt5Agg") # 声明使用QT5

class Ui_MainDialog(QDialog):
def __init__(self):
super().__init__()
self.dia = uic.loadUi("New_UI.ui", self)
self.initDia()

def initDia(self):
self.dia.textBrowser_2.append(
"Extracmator is a free open source software platform for medical image progressing and 3D visualization of image data."
"This mudule contains some basic methods to visualize and give anlaysis of medical image computing data sets.\n")
self.dia.textBrowser_3.append(
"You can perform basic traditional radiomics analysis by doing this:\n"
"1.Click 'Input Label' and select your ROI\n"
"2.Click 'Input Image' and select your brain image\n"
"3.Choose Pyradiomics and select the settings you need. If you're not sure about that, commonSettings and choosing your output table path will be fine.\n"
"4.You can name the output csv.\n"
"5.Click the 'Apply' to see the visual analysis.If you're satisfied with that, click 'Save Data' to save it.")

# 设置页面样式
self.dia.setWindowFlag(Qt.FramelessWindowHint) # 将界面设置为无框
self.dia.setAttribute(Qt.WA_TranslucentBackground) # 将界面属性设置为半透明
self.dia.shadow = QGraphicsDropShadowEffect() # 设定一个阴影,半径为10,颜色为#444444,定位为0,0
self.dia.shadow.setBlurRadius(10)
self.dia.shadow.setColor(QColor("#444444"))
self.dia.shadow.setOffset(0, 0)
self.dia.frame.setGraphicsEffect(self.dia.shadow) # 为frame设定阴影效果
self.dia.progressBar = self.progressBar
self.progressBar.setMinimum(0)# 将进度条最小值设为0

self.pushButton_close.clicked.connect(self.quit_button)
self.dia.listWidget.itemClicked.connect(self.ChangePage)
self.dia.listWidget_2.itemClicked.connect(self.ChangePage)

self.Actions()

def Actions(self):
# 初始化属性
self.ori_path = ''
# 上传图片
self.data2 =''
self.dia.pushButton.clicked.connect(self.bindButton)
self.dia.pushButton_ori.clicked.connect(lambda: self.oriImg()) # 上传图像
self.RadiomicsPart()
self.DLPart()

def getImage(self):
try:
data_nii = nib.load(self.ori_path)
except FileNotFoundError:
pass
else:
data = data_nii.get_fdata()
return data

# 获取并展示全图
def oriImg(self):
file_name = QFileDialog.getOpenFileName(None, "Open File", "./", "nii(*.nii.gz;*.nii;*.nrrd)")
try:
self.ori_path = file_name[0] # 提取文件路径
except nibabel.filebasedimages.ImageFileError:
pass
else:
SimpleView.VisNii(self, self.nii_path, self.ori_path)
self.showimage()

# 展示切片
def showimage(self):
self.data = self.getImage()
data1 = self.data

def RadiomicsPart(self):
# radiomics函数调用
self.nii_path = ' '
self.RadiomicsFunc = RadiomicsFunc()

self.dia.radioButton_Para_Cus.clicked.connect(lambda: self.RadiomicsFunc.cusParaSet(self.lineEdit_fileName_2))

if self.dia.groupBox_2.isChecked:
self.RadiomicsFunc.setupParaFile()
self.dia.comboBox_ImgType.currentIndexChanged.connect(lambda: self.Change(
self.comboBox_ImgType.currentIndex()))#检查是否为第一项,如果如果是,允许选择特征
self.dia.comboBox_ImgType.currentIndexChanged.connect(lambda: self.RadiomicsFunc.ImgType_init(
self.comboBox_ImgType.currentIndex())) # currentIndex返回当前项的序号(int),第一个项的序号为0

self.dia.comboBox_ImgType.currentIndexChanged.connect(lambda: self.stackedWidget.setCurrentIndex(
self.comboBox_ImgType.currentIndex()-1))#对应换页

self.PycheckStatus()
self.save_name = 'Features_Result' #给特征文件一个初始的名字

self.save_path = ' '
self.dia.pushButton_4.clicked.connect(
lambda: self.getSavePath()) # 获取文件输出地址
self.dia.lineEdit_fileName.textEdited[str].connect(lambda: self.onChange1(self.dia.lineEdit_fileName)) # 实时获取文件名

self.dia.pushButton_6.clicked.connect(lambda: self.RadiomicsFunc.outPuts(self.save_path,
self.save_name,
self.nii_path,True,
self.ori_path,
self.label_path)) #保存数据
self.dia.pushButton_2.clicked.connect(lambda: self.RadiomicsFunc.outPuts(self.save_path,
self.save_name,
self.nii_path,True,
self.ori_path,
self.label_path)) #保存数据

def DLPart(self):
self.batch_path = ' '
self.dia.pushButton_8.clicked.connect(
lambda: self.getBatchPath()) # 批量导入

#选择网络类型,序号从0开始
self.dia.comboBox_NetType.currentIndexChanged.connect(lambda: self.DlpFeatures.netSet(
self.comboBox_NetType.currentIndex()))

self.DlpFeatures = DLPExtractor(self.dia.progressBar)

#选择网络

# 实时获取文件名
self.dia.lineEdit_fileName_3.textEdited[str].connect(lambda: self.onChange1(self.dia.lineEdit_fileName_3))

# 点击Apply图片转化
self.save_path = 'D:/www/xxx/Pytorch/Braintumor/PyQt_test/Result/'
self.dia.pushButton_7.clicked.connect(
lambda: self.DlpFeatures.converts(self.batch_path,self.save_path,self.dia.textBrowser,self.save_name) ) #nii_path, outputfile

"""
def RadioML(self):
self.dia.pushButton_11(lambda : RadioML.Main())
self.dia.pushButton_17(lambda: RadioML.Features())
self.dia.pushButton_18(lambda: RadioML.Score())
"""

"""
def HCADPart(self):
self.dia.pushButton_Reload.connect(self.bindButton)
self.dia.pushButton_AllNodes.connect(self.getBatchPath())
self.dia.pushButton_Remove.connect(self.delete()) #删除路径函数
self.dia.pushButton_RemoveAll(self.deleteAll()) #删除所有
self.dia.pushButton_SecROI(self.getROI()) #获取ROI
"""
def getBatchPath(self): #批量导入
batch_path = QFileDialog.getExistingDirectory(self, "choose directory", "./")
batch_path = batch_path + '/' #在这里提取了地址之后给他加一个下级,转字符,这样可以直接调用下部文件夹
self.batch_path = batch_path
print(self.batch_path)

def getSavePath(self): # 获取存储地址
self.save_path = QFileDialog.getExistingDirectory(self, "choose directory", "./")

def onChange1(self,lineEdit): #实时获取文件名
self.VAR = lineEdit.text()# 设置保存特征的csv文件名
self.save_name = self.VAR
print(self.VAR)

def Change(self,tag):
if tag == 1:
self.groupBox.setCheckable(True)

def ChangePage(self,item): #list中点击对应右侧换行
index = self.dia.listWidget.row(item)
if index==2:
self.display1()
elif index==1:
self.display2()
elif index==3:
self.display3()
elif index == 0:
self.display5()
index = self.dia.listWidget_2.row(item)
if index== 0:
self.display4()

def display1(self): # Dlp
self.stackedWidget_2.setCurrentIndex(1)

def display2(self): #Pyradiomics
self.stackedWidget_2.setCurrentIndex(2)

def display3(self):#Here
self.stackedWidget_2.setCurrentIndex(3)

def display4(self): #RadioML
self.stackedWidget_2.setCurrentIndex(4)

def display5(self): # Wel
self.stackedWidget_2.setCurrentIndex(0)

def PycheckStatus(self):
self.dia.checkBox_Nor1.stateChanged.connect(lambda: self.RadiomicsFunc.Add_Nor(self.dia.checkBox_Nor1.isChecked()))
self.dia.checkBox_Nor2.stateChanged.connect(lambda: self.RadiomicsFunc.Add_Nor(self.dia.checkBox_Nor2.isChecked()))
self.dia.checkBox_Nor3.stateChanged.connect(lambda: self.RadiomicsFunc.Add_Nor(self.dia.checkBox_Nor3.isChecked()))
self.dia.checkBox_Nor4.stateChanged.connect(lambda: self.RadiomicsFunc.Add_Nor(self.dia.checkBox_Nor4.isChecked()))
self.dia.checkBox_15.stateChanged.connect(
lambda: self.RadiomicsFunc.Add_FirstSpe(self.dia.checkBox_15.isChecked()))
self.dia.checkBox_14.stateChanged.connect(
lambda: self.RadiomicsFunc.Add_FirstSpe2(self.dia.checkBox_14.isChecked()))
self.dia.checkBox_12.stateChanged.connect(
lambda: self.RadiomicsFunc.Add_NFirstSpe2(self.dia.checkBox_12.isChecked()))
self.dia.checkBox_13.stateChanged.connect(
lambda: self.RadiomicsFunc.Add_FirstSpe2(self.dia.checkBox_13.isChecked()))
self.dia.checkBox_Mas4.stateChanged.connect(
lambda: self.RadiomicsFunc.Add_Mask(self.dia.checkBox_Mas4.isChecked()))
self.dia.checkBox_Mas3.stateChanged.connect(
lambda: self.RadiomicsFunc.Add_Mask(self.dia.checkBox_Mas3.isChecked()))
self.dia.checkBox_Mas2.stateChanged.connect(
lambda: self.RadiomicsFunc.Add_Mask(self.dia.checkBox_Mas2.isChecked()))
self.dia.checkBox_Mas1.stateChanged.connect(
lambda: self.RadiomicsFunc.Add_Mask(self.dia.checkBox_Mas1.isChecked()))
self.dia.checkBox_Mis2.stateChanged.connect(
lambda: self.RadiomicsFunc.Add_Misc(self.dia.checkBox_Mis2.isChecked()))
self.dia.checkBox_Res1.stateChanged.connect(
lambda: self.RadiomicsFunc.Add_Resampling(self.dia.checkBox_Res1.isChecked()))
self.dia.checkBox_Res3.stateChanged.connect(
lambda: self.RadiomicsFunc.Add_Resampling(self.dia.checkBox_Res3.isChecked()))
self.dia.checkBox_2D1.stateChanged.connect(
lambda: self.RadiomicsFunc.Add_2D(self.dia.checkBox_2D.isChecked()))
self.dia.checkBox_Voxel.stateChanged.connect(
lambda: self.RadiomicsFunc.Add_Voxel(self.dia.checkBox_Voxel.isChecked()))

def bindButton(self):
file_name = QFileDialog.getOpenFileName(None, "Open File", "./", "nii(*.nii.gz;*.nii;*.nrrd)")
self.nii_path = file_name[0] # 提取文件路径
try:
self.data_mask = nib.load(Path(self.nii_path))
except nibabel.filebasedimages.ImageFileError:
pass
else:
self.data2 = self.data_mask.get_fdata()
SimpleView.VisNii(self, self.nii_path, self.ori_path)

def bindSlider(self):
slice_idx = self.horizontalSlider.value()
self.showimage(slice_idx)

def quit_button(self):
quit()

def mousePressEvent(self, event): # 鼠标左键按下时获取鼠标坐标,按下右键取消
if event.button() == Qt.LeftButton:
self.m_flag = True
self.m_Position = event.globalPos() - self.pos()
event.accept()
elif event.button() == Qt.RightButton:
self.m_flag = False

def mouseMoveEvent(self, QMouseEvent): # 鼠标在按下左键的情况下移动时,根据坐标移动界面
try:
if Qt.LeftButton and self.m_flag:
self.move(QMouseEvent.globalPos() - self.m_Position)
QMouseEvent.accept()
except AttributeError:
pass

def mouseReleaseEvent(self, QMouseEvent): # 鼠标按键释放时,取消移动
self.m_flag = False

PyRadiomicsPage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
from __future__ import print_function

import sys

import pandas as pd
from PyQt5.QtCore import pyqtSignal
from radiomics import featureextractor as FEE # This module is used for interaction with pyradiomics
import yaml

from PyQt5 import uic
from PyQt5.QtWidgets import QDialog, QFileDialog, QApplication

from DataProsessing import child


class RadiomicsFunc(QDialog):
isChecked = pyqtSignal(int)
def __init__(self):
super().__init__()
self.dia = uic.loadUi("New_UI.ui", self)

#创建参数文件
def setupParaFile(self):
self.para_path = './ParaFile/Param.yml'

def ImgType_init(self,tag):
self.para_path = './ParaFile/Param.yml'
settingData = {

}
with open(self.para_path, 'w', encoding='utf-8') as f:
if tag == 1: #Original
self.displayOri()
elif tag == 2:
self.displayCT()
elif tag == 3:
self.displayMR3()
elif tag == 4:
self.displayMR5()
elif tag == 5:
self.displayMR()
yaml.dump(data=settingData, stream=f, allow_unicode=True)

def featureSet(self):#get10PercentileFeatureValue():获取百分之十的特征值
# 写入的数据类型是字典
featureData = {

}
with open(self.para_path, 'w', encoding='utf-8') as f:#如果没有选择特征计算,那么自动全选,排除不推荐的功能
if self.checkBox_First.isChecked():
featureData["featureClass"] = {"firstorder": [] }
#self.tabWidget.setTabEnabled(1)
#else:
if self.checkBox_shape.isChecked():
featureData["featureClass"] = {"shape": [] }
#self.tabWidget.setTabEnabled(2)
if self.checkBox_glcm.isChecked():
featureData["featureClass"] = {"glcm": [] }
#self.tabWidget.setTabEnabled(3)
if self.checkBox_glrlm.isChecked():
featureData["featureClass"] = {"glrlm": []}
#self.tabWidget.setTabEnabled(4)
if self.checkBox_glszm.isChecked():
featureData["featureClass"] = {"glszm": [] }
#self.tabWidget.setTabEnabled(5)
if self.checkBox_gldm.isChecked():
featureData["featureClass"] = {"gldm": [] }
#self.tabWidget.setTabEnabled(6)
if self.checkBox_ngtdm.isChecked():
featureData["featureClass"] = {"ngtdm": [] }
#self.tabWidget.setTabEnable(7)
yaml.dump(data=featureData, stream=f, allow_unicode=True)

def outPuts(self,place,name,nii_path,ifsave,ori_path,label_path):
self.ori_path = ori_path
self.label_path = label_path
# 使用配置文件初始化特征抽取器
extractor = FEE.RadiomicsFeatureExtractor(parameter_file=self.para_path)
save_path = name + '.csv'
#print(save_path)

# 运行程序,提取特征
try:
result = extractor.execute(ori_path, nii_path) # 抽取特征,第一个是原图像,第二个是ROI
except ValueError:
result = extractor.execute(ori_path, nii_path, label=2)
type(result)

# 储存数据
df = pd.DataFrame()
for key, value in result.items():
# 如果当前特征是所选特征之一,则将其添加到 DataFrame
# if key in selected_features:
df = df.append({'Feature': key, 'Value': value}, ignore_index=True)
# 清空,进行下一次遍历
ori_path = None
lab_path = None

if ifsave == True :
# 将 DataFrame 保存为 CSV 文件
df.to_csv(save_path, index=False)
RadiomicsFunc.invokeDialog_1(self, save_path)

# 界面弹出
def invokeDialog_1(self,path):
#app1 = QApplication(sys.argv)
dialog = child(path,self.ori_path,self.label_path)
#child.read(dialog,path,self.ori_path,self.label_path) #数据可视化
dialog.exec()
#sys.exit(app1.exec_())

def displayOri(self):
with open('./ParaFile/exampleCT.yaml', "rb") as ff:
content = ff.read()
with open(self.para_path, "ab") as f2:
# 将读取的数据写入到新的对象中
f2.write(content)
def displayCT(self):
with open('./ParaFile/exampleCT.yaml', "rb") as ff:
content = ff.read()
with open(self.para_path, "ab") as f2:
# 将读取的数据写入到新的对象中
f2.write(content)
def displayMR3(self):
with open('./ParaFile/exampleMR_3mm.yaml', "rb") as ff:
content = ff.read()
with open(self.para_path, "ab") as f2:
# 将读取的数据写入到新的对象中
f2.write(content)
def displayMR5(self):
with open('./ParaFile/exampleMR_5mm.yaml', "rb") as ff:
content = ff.read()
with open(self.para_path, "ab") as f2:
# 将读取的数据写入到新的对象中
f2.write(content)
def displayMR(self):
with open('./ParaFile/exampleMR_NoResampling.yaml.yaml', "rb") as ff:
content = ff.read()
with open(self.para_path, "ab") as f2:
# 将读取的数据写入到新的对象中
f2.write(content)

def Add_Nor(self, state):
data = {

} # 有一个问题是会一直先输出一个空字典,还不知道怎么避免捏
with open(self.para_path, 'a', encoding='utf-8') as f:
# print(str(state))
if state:
data["setting"] = {"normalize": "True", "normalizeScale": 500}
yaml.dump(data=data, stream=f, allow_unicode=True)

def Add_Mask(self, state):
data = {

} # 有一个问题是会一直先输出一个空字典,还不知道怎么避免捏
with open(self.para_path, 'a', encoding='utf-8') as f:
if state:
data["setting"] = {"minimumROIDimensions": "2", "minimumROISize": "50"}
yaml.dump(data=data, stream=f, allow_unicode=True)

def Add_Bin(self, state): # 修改为带有BinWidth的输入项
data = {

} # 有一个问题是会一直先输出一个空字典,还不知道怎么避免捏
with open(self.para_path, 'a', encoding='utf-8') as f:
if state: # 可以改成binwidth的输入项
data["setting"] = {"minimumROISize": "50"}
yaml.dump(data=data, stream=f, allow_unicode=True)

def Add_Misc(self, state):
data = {

} # 有一个问题是会一直先输出一个空字典,还不知道怎么避免捏
with open(self.para_path, 'a', encoding='utf-8') as f:
if state:
data["setting"] = {"label": 1}
yaml.dump(data=data, stream=f, allow_unicode=True)

def Add_FirstSpe(self, state):
data = {

} # 有一个问题是会一直先输出一个空字典,还不知道怎么避免捏
with open(self.para_path, 'a', encoding='utf-8') as f:
if state:
data["setting"] = {"voxelArrayShift": "1000"}
yaml.dump(data=data, stream=f, allow_unicode=True)

def Add_FirstSpe2(self, state):
data = {

} # 有一个问题是会一直先输出一个空字典,还不知道怎么避免捏
with open(self.para_path, 'a', encoding='utf-8') as f:
if state:
data["setting"] = {"voxelArrayShift": "300"}
yaml.dump(data=data, stream=f, allow_unicode=True)

def Add_Resampling(self, state):
data = {

} # 有一个问题是会一直先输出一个空字典,还不知道怎么避免捏
with open(self.para_path, 'a', encoding='utf-8') as f:
if state:
data["setting"] = {"interpolator ": "'sitkBSpline'", "resampledPixelSpacing": " [2, 2, 2]"}
yaml.dump(data=data, stream=f, allow_unicode=True)

def Add_Resampling2(self, state):
data = {

} # 有一个问题是会一直先输出一个空字典,还不知道怎么避免捏
with open(self.para_path, 'a', encoding='utf-8') as f:
if state:
data["setting"] = {"preCrop ": "true"}
yaml.dump(data=data, stream=f, allow_unicode=True)

"""
if self.dia.checkBox_4.isChecked():
data["setting"] = {"minimumROIDimensions": "2","minimumROISize": "50"}
elif self.dia.checkBox_5.isChecked(): #可以改成binwidth的输入项
data["setting"] = {"minimumROISize": "50"}
elif self.dia.checkBox_6.isChecked():
data["setting"] = {"voxelArrayShift": "1000"}
elif self.dia.checkBox.isChecked():#label数的输入项
data["setting"] = {"label": 1}
"""

def Add_2D(self, state):
data = {

} # 有一个问题是会一直先输出一个空字典,还不知道怎么避免捏
with open(self.para_path, 'a', encoding='utf-8') as f:
if state:
data["setting"] = {"force2D ": "true"} # 获取一个dimension,输入
yaml.dump(data=data, stream=f, allow_unicode=True)

def Add_Voxel(self,state):
data = {

} # 有一个问题是会一直先输出一个空字典,还不知道怎么避免捏
with open(self.para_path, 'a', encoding='utf-8') as f:
if state:
data["voxelSetting"] = {"kernelRadius ": 2,"maskedKernel":"true","initValue":"nan","voxelBatch":10000} # 获取一个dimension,输入
yaml.dump(data=data, stream=f, allow_unicode=True)

def cusParaSet(self,place):
file_name = QFileDialog.getOpenFileName(None, "Open File", "./", "yaml(*.yml;*.yaml)")
self.para_path = file_name[0]
place.setText(self.para_path)
#读取yaml文件

"""
if tag == 1: #Original
settingData["imageType"] = {"Original": {}} #写入参数设定文件
self.displayOri(settingData)
elif tag == 2:
settingData["imageType"] = {"Original": {}}
self.displayCT(settingData)
elif tag == 3:
settingData["imageType"] = {"LoG": {}}
elif tag == 4:
settingData["imageType"] = {"Square": {}}
elif tag == 5:
settingData["imageType"] = {"SquareRoot": {}}
elif tag == 6:
settingData["imageType"] = {"Logarithm": {}}
elif tag == 7:
settingData["imageType"] = {"Exponential": {}}
elif tag == 8:
settingData["imageType"] = {"Gradient": {}}
elif tag == 9:
settingData["imageType"] = {"LocalBinaryPattern2D": {}}
else :
settingData["imageType"] = {"LocalBinaryPattern3D": {}}
"""

RadioML

直接使用python对R语言调用

``ruby
#from rpy2 import robjects

import logging
import os
import platform
from functools import lru_cache
from rpy2.situation import get_r_home, get_rlib_path

def _fix_r_home():
“””
Fix the R_HOME env var if we need to
“””
r_home = os.environ.get(“R_HOME”)
#print(r_home)

if r_home:
    os.environ["R_HOME"] = r_home.replace("\\", "/")

if not get_r_home():
    raise OSError("R_HOME is not set.")

def _add_dll_directory():
“””
Adds the platform’s R library path to the allowed library
load dirs. Stops weird DLL loading issues if you are using
R in a different path than the system install dir.

Only applicable on Windows
"""

if "add_dll_directory" not in dir(os):
    return  # not windows

r_home = get_r_home()
system = platform.system()
dll_location = get_rlib_path(r_home, system)
dll_files_location = os.path.dirname(dll_location)

if dll_files_location not in os.getenv("PATH"):
    logging.warning(f"R DLL location is not in the path: {dll_files_location}")

# fix security in recent Python versions
os.add_dll_directory(dll_files_location)

@lru_cache
def get_robjects():
“””
Fix path and DLL loading issues before loading rpy2
“””
_fix_r_home()
_add_dll_directory()

from rpy2 import robjects
from rpy2.robjects import pandas2ri
pandas2ri.activate()

return robjects

class RadioML():
def init(self):
pass
def Main(self,robjects):
robjects.r.source(‘./RadioML-main.R’)
def Features(self,robjects):
robjects.r.source(‘./RadioML_features.R’)
def Score(self,robjects):
robjects.r.source(‘./RadioML_score.R’)

new_robjects = get_robjects()
test = RadioML()
test.Main(new_robjects)
#”D:/www/xxx/Pytorch/Braintumor/PyQt_test/lib/RadioML-main/RadioML_main.R”

1
2
3
4
5
6
7
8
9
10

# PyQt5的一些报错
1. 点击run里面的edit configurations
2. 将Emulate terminal in output console勾上后点击apply应用
## ComboBox报错
> argument 1 has unexpected type 'NoneType'

[解决方法](https://deepinout.com/pyqt5/pyqt5-questions/47_pyqt5_argument_1_has_unexpected_type_nonetype.html):`connect`中传的函数加一个`lambda`
```ruby
self.ui.comboBox_ImgType.currentIndexChanged.connect(lambda : self.RadiomicsFunc.ImgType_init(self.comboBox_ImgType.currentIndex()))

graphicsView刷新

QLayout: Attempting to add QLayout “” to QGraphicsView “graphicsView_4”, which already has a layout

DataProgress

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
from radiomics import featureextractor
import pandas as pd
import numpy as np

class RadioMLProgress(object):

def extract(self, images, name, nii_path, ori_path):
self.ori_path = ori_path
#self.ori_path_2 = ori_path_2
#self.ori_path_3 = ori_path_3
#self.ori_path_4 = ori_path_4
# 创建一个特征提取器对象, ori_path_2, ori_path_3, ori_path_4
'''
#特征提取器与分组
settings = {}
settings['sigma'] = [3, 5]
extractor.enableImageTypeByName('LoG')


settings['binWidth'] = 25 # 5

#settings['Interpolator'] = sitk.sitkBSpline
settings['resampledPixelSpacing'] = [1, 1, 1] # 3,3,3
settings['voxelArrayShift'] = 1000 # 300
settings['normalize'] = True
settings['normalizeScale'] = 100


extractor.enableImageTypeByName('Wavelet')

extractor.enableImageTypeByName('Wavelet')
extractor.enableImageTypeByName('Wavelet')
extractor.enableImageTypeByName('Square')
'''
# 定义要计算的特征
#params = {}
features = [
'exponential_glrlm_GrayLevelNonUniformity',
'lbp-3D-m1_glszm_LargeAreaLowGrayLevelEmphasis',
'logarithm_gldm_GrayLevelNonUniformity',
'logarithm_glrlm_GrayLevelNonUniformity',
'logarithm_glszm_GrayLevelNonUniformity',
'log-sigma-3-0-mm-3D_glrlm_LowGrayLevelRunEmphasis',
'log-sigma-3-0-mm-3D_glszm_ZoneEntropy',
'log-sigma-4-0-mm-3D_firstorder_RootMeanSquared',
'log-sigma-4-0-mm-3D_glrlm_GrayLevelNonUniformity',
'log-sigma-4-0-mm-3D_glszm_ZoneEntropy',
'log-sigma-5-0-mm-3D_glrlm_GrayLevelNonUniformity',
'log-sigma-5-0-mm-3D_glszm_ZoneEntropy',
'original_firstorder_Kurtosis',
'original_glrlm_GrayLevelNonUniformity',
'original_glszm_GrayLevelNonUniformity',
'original_shape_Maximum2DDiameterColumn',
'original_shape_Maximum2DDiameterRow',
'original_shape_Maximum2DDiameterSlice',
'original_shape_Maximum3DDiameter',
'original_shape_Sphericity',
'original_shape_SurfaceArea',
'square_glcm_Idmn',
'square_glszm_GrayLevelNonUniformity',
'squareroot_gldm_GrayLevelNonUniformity',
'squareroot_glrlm_GrayLevelNonUniformity',
'squareroot_glszm_GrayLevelNonUniformity',
'wavelet-HHL_glszm_LargeAreaEmphasis',
'wavelet-HHL_glszm_ZoneVariance',
'wavelet-HLH_glszm_LargeAreaEmphasis',
'wavelet-HLH_glszm_LargeAreaLowGrayLevelEmphasis',
'wavelet-HLH_glszm_ZoneVariance',
'wavelet-LHH_glszm_LargeAreaEmphasis',
'wavelet-LHH_glszm_LargeAreaLowGrayLevelEmphasis',
'wavelet-LLL_firstorder_Kurtosis',
'wavelet-LLL_glcm_Imc2',
'wavelet-LLL_glrlm_GrayLevelNonUniformity',
'wavelet-LLL_glszm_GrayLevelNonUniformity'
]
# 创建特征提取器,并设置要提取的特征列表
params = {'enabledFeatures': features}
extractor = featureextractor.RadiomicsFeatureExtractor(**params)
#params['normalize'] = True # 对图像进行标准化
#params['enabledImageTypes'] = {'Original': {}, 'Wavelet': {}} # 只使用原始图像和小波图像特征
#params['enabledFeatures'] = ['glrlm', 'glszm'] # 指定只提取 glrlm 和 glszm 特征
#extractor = featureextractor.RadiomicsFeatureExtractor(**params)
#extractor.enableFeatureClassByName()

# 加载图像数据
# 注意:需要替换为你的实际图像路径
# 使用 extract 方法来计算特征
result = {}
for modality, file_path in images.items():
if nii_path:
result[modality] = extractor.execute(file_path, nii_path)
else:
result[modality] = extractor.execute(file_path)
# 输出特征结果
for modality, features in result.items():
print(f"Features for {modality}:")
for feature_name, value in features.items():
print(f"\t{feature_name}: {value}")
# 储存数据
for modality, features in result.items():
save_path = name + '_for_' + modality + '.csv'
save_path_txt = name + '_for_' + modality + '.txt'
df = pd.DataFrame()
for key, value in features.items():
# 如果当前特征是所选特征之一,则将其添加到 DataFrame
# if key in selected_features:
df = df.append({'Feature': key, 'Value': value}, ignore_index=True)
# 将 DataFrame 保存为 CSV 文件
df.to_csv(save_path, index=False)
df.to_csv(save_path_txt, index=False)

self.saveFile(result)

def saveFile(self, result):
def is_number(s):
try:
float(s)
return True
except (TypeError, ValueError):
pass
try:
import unicodedata
unicodedata.numeric(s)
return True
except (TypeError, ValueError):
pass
return False
#df = pd.read_csv('./Features_Result_for_T1.csv')
numdata = pd.DataFrame() # 数字特征
# 储存数据
for modality, features in result.items():
save_path = './lib/Estimate/Prediction_for_' + modality + '.csv'
df = pd.DataFrame()
for key, value in features.items():
# 如果当前特征是所选特征之一,则将其添加到 DataFrame
# if key in selected_features:
df = df.append({'Feature': key, 'Value': value}, ignore_index=True)

for idx, row in df.iterrows(): # 遍历 DataFrame
if is_number(row['Value']): # 存储数字结果
numdata = numdata.append({'Feature': row['Feature'], 'Value': row['Value']}, ignore_index=True)
numdata = numdata.drop(df.index[0])
numdata.to_csv(save_path, sep=',', index=False)