0%

System.Diagnostics命名空间

提供允许你与系统进程、事件日志和性能计数器进行交互的类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using System.Diagnostics
...
class Program
{
static void Main(string[] args){
InitLog()
Trace.WriteLine($"log is here")
}
private static void InitLog(){
string logDirectory = @"D:\log\"
string timestamp = DateTime.UtcNow.ToString("yyyyMMdd_HHmmss", CultureInfo.InvariantCulture);
logDirectory = $"{logDirectory}_{timestamp}.log";
Trace.Listeners.Add(new TextWriterTraceListener(File.CreateText(logDirectory)));
Trace.AutoFlush = true;
}
}

argparse模块是python用于解析命令行参数和选项的标准模块 对于封装好的py函数文件 可实现在命令行输出—help的效果:

定义函数文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#-*- coding: UTF-8 -*-
import argparse

def parse_args():
"""
:return:进行参数的解析
"""
description = "you should add those parameter"
parser = argparse.ArgumentParser(description=description) # 这些参数都有默认值,当调用parser.print_help()或者运行程序时由于参数不正确(此时python解释器其实也是调用了pring_help()方法)时,
# 会打印这些描述信息,一般只需要传递description参数,如上。
mode_desc = "action mode"
image_desc = "the path of image"
parser.add_argument('--mode', help=mode_desc)
parser.add_argument('--image', help=image_desc)
args = parser.parse_args()
return args

if __name__ == '__main__':
args = parse_args()
if()

命令行输入
1
python arg.py -h

输出提示为
1
2
3
4
5
6
7
8
9
10
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.

you should add those parameter

optional arguments:
-h, --help show this help message and exit
--addresses ADDRESSES
The path of address

ArgumentParser.add_argument(name or flags…[, action][, nargs][, const][, default][, type][, choices][, required][, help][, metavar][, dest])

  • name or flags — 选项字符串的名字或者列表,例如 foo 或者 -f, —foo。
  • action— 命令行遇到参数时的动作,默认值是 store; store_const—表示赋值为const;append—将遇到的值存储成列表,也就是如果参数重复则会保存多个值; append_const—将参数规范中定义的一个值保存到一个列表;count—存储遇到的次数;此外,也可以继承 argparse.Action 自定义参数解析动作;

  • nargs — 应该读取的命令行参数个数,可以是数字,或者通配符如?表示0个或1个;*表示 0 或多个;+表示 1 或多个。

  • const - action 和 nargs 所需要的常量值。

  • default— 不指定参数时的默认值。
  • type — 命令行参数应该被转换成的类型。(str, int, bool..)
  • choices — 参数可允许的值的一个容器。
  • required — 可选参数是否可以省略 (仅针对可选参数)。
  • help — 参数的帮助信息,当指定为 argparse.SUPPRESS 时表示不显示该参数的帮助信息.
  • metavar — 在 usage 说明中的参数名称,对于必选参数默认就是参数名称,对于可选参数默认是全大写的参数名称.
  • dest — 解析后的参数名称,默认情况下,对于可选参数选取最长的名称,中划线转换为下划线.
1
2
3
4
parser.add_argument('--name', type=str, required=True, default='', help='名')

parser.add_argument('--foo', action='store_const', const=42) # 实际foo=42

是否输入了某参数

1
parser.add_argument('--properties', action='store_true', help=properties_desc) 

如上调用python arg.py时,若带参数—properties 则args.properties=True 否则为False

  • 基于阈值的图像分割
  • 边缘检测
  • 分离背景
  • K-Means聚类
  • 均值漂移算法
  • 分水岭算法
  • 图像漫水填充分割

阈值

图像分割的效果取决于临界值的选取

边缘检测

根据像素值变化的特点 根据梯度值确定边界

findContours 和 drawContours是基于边缘检测的统计方法

查找轮廓

GrabCut算法 设置掩码矩阵(蒙版)分离前景和背景

K-Means聚类

  • 第一步 确定K值 即将数据集分为K个族类
  • 第二部 从数据集中随机选择K个数据点作为质心(Centroid)

WebView2使用 Microsoft Edge(要知道Edge也是fork自Chromium的分发版本) 作为绘制引擎, 展示web内容的混合应用控件
可以认为Electron是WebView2的前辈 二者的原理是一样的

WebView2 runtime is available on most Windows 10 and Windows 11 machines by default. But it may not be available on older platforms.

若缺需要查看WebView2运行时是否安装 Microsoft Docs:检测是否已安装合适的 WebView2 运行时

webview vs webbrowser

WebBrowser类

PyQt 是 Qt 的Python版本,Qt是基于C++的GUI,在Python的UI组件库中,PyQt功能强大,提供QT Designer设计UI。PyQt 可与 C++ Qt无缝整合,另有QtWebEngine结合web前端实现Electron的功能。

题外话:Tkinter 是Python解决UI的原生库,优点在于没有其他依赖。但内容基础功能简单 比其他UI库都有不及

PyQt5类分为很多模块

QtCore 包含了核心的非GUI的功能。主要和时间、文件与文件夹、各种数据、流、URLs、mime类文件、进程与线程一起使用。
QtGui 包含了窗口系统、事件处理、2D图像、基本绘画、字体和文字类。QtWidgets类包含了一系列创建桌面应用的UI元素。 QtMultimedia包含了处理多媒体的内容和调用摄像头API的类。 QtNetwork包含了网络编程的类,这些工具能让TCP/IP和UDP开发变得更加方便和可靠。QtWebSockets包含了WebSocket协议的类。 QtWebKit包含了一个基WebKit2的web浏览器。

安装

1
2
pip install pyqt5
pip install pyqt5-tools

使用国内pip源

1
pip install -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple pyqt5

添加pyqt5-tools路径到环境变量Path

配置PyCharm外部工具
PyCharm -> 文件 -> 设置 -> 工具 -> 外部工具

填入QT designer路径
image.png

另添加 Pyuic 用于将ui文件转为py文件

须填入参数$FileName$ -o $FileNameWithoutExtension$.py
v2-7039dac81f16e3567988b1a16b745067_720w.jpg

类似的 可添加PyRCC

PyQt Hello World

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import sys
from PyQt5.QtWidgets import QApplication, QWidget


if __name__ == '__main__':

app = QApplication(sys.argv)

w = QWidget()
w.resize(250, 150)
w.setFixedSize(250, 150) # 固定大小
w.move(300, 300)
w.setWindowTitle('HelloWorld!')
w.show()

sys.exit(app.exec_())

QT designer

完成如上配置 可在PyCharm的外部工具打开 QT designer,
使用QT designer生成UI文件

从.ui文件到.py文件

1
pyuic demo.ui -o demo.py

生成的py文件是UI的类定义文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralWidget = QtWidgets.QWidget(MainWindow)
self.centralWidget.setObjectName("centralWidget")
self.btn_import = QtWidgets.QToolButton(self.centralWidget)
self.btn_import.setGeometry(QtCore, QRect(610, 20, 71, 22))
self.btn_import.setObjectName("btn_import")
self.btn_import.clicked.connect(self.on_clicked_btn_import)

def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.btn_import.setText(_translate("MainWindow", "import"))

def on_clicked_btn_import(self):
print("btn_import clicked")

.....

运行该UI需要在程序入口(__main__)实例化
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
import sys

from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QMessageBox

from ui.mainWindow import Ui_MainWindow # 引入UI的类定义


class Startup(QMainWindow, Ui_MainWindow):
def __init__(self):
super(Startup, self).__init__()
self.setupUi(self)

def importImg(self):
fname = QFileDialog.getOpenFileName(self, 'import Image', './')

if fname[0]:
QMessageBox.information(self, 'import success', fname[0], QMessageBox.Yes)
print("import img is here")


if __name__ == '__main__':
app = QApplication(sys.argv)
startup = Startup()
startup.show()
sys.exit(app.exec_())


可见这里的Startup类即 Ui_MainWindow 实例化参数object 两个类是相互引用的;Startup类函数即主窗体的槽函数

事件、信号和槽函数

在控件层面 事件如点击按钮(clicked)选择菜单(triggered),事件可以直接connect到响应函数(槽函数)

1
2
3
4
btn = QPushButton('test', self)
btn.resize(btn.sizeHint())
btn.move(50, 50)
btn.clicked.connect(self.showDlg)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# MenuBar -> Menu -> Action -> Slot
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 842, 26))
self.menubar.setObjectName("menubar")
self.menu = QtWidgets.QMenu(self.menubar)
self.menu.setObjectName("menu")
MainWindow.setMenuBar(self.menubar)

self.actionimport = QtWidgets.QAction(MainWindow)
self.actionimport.setObjectName("actionimport")
self.menu.addAction(self.actionimport)
self.menubar.addAction(self.menu.menuAction())

self.retranslateUi(MainWindow)
self.actionimport.triggered.connect(MainWindow.importImg)
QtCore.QMetaObject.connectSlotsByName(MainWindow)

或者,不同事件发送某信号,而某个模块监听到该信号而触发响应
1
2
3
4
5
6
7
8
9
10
11
# 创建一个信号closeApp。当触发鼠标点击事件时信号会被发射。信号连接到QMainWindow的close()方法。
class Communicate(QObject):
closeApp = pyqtSignal()
# 信号使用了pyqtSignal()方法创建,并且成为外部类Communicate类的属性。
self.c = Communicate()
self.c.closeApp.connect(self.close)
# 把自定义的closeApp信号连接到QMainWindow的close()槽上。
def mousePressEvent(self, event):

self.c.closeApp.emit()
# 当我们在窗口上点击一下鼠标,closeApp信号会被发射。应用中断。

页面间的信号

子页面发射信号 槽函数在父页面
子页面:

1
2
3
4
5
6
class Child_Widget(QWidget):
status_signal = pyqtSignal(str)
def __init__(self):
super(Child_Widget, self).__init__()
def updateStatus(self):
self.status_signal.emit('update')

父页面:
1
2
3
4
5
6
7
8
class Parent_Widget(QWidget):
...
def child_init(self):
self.child_widget = Child_Widget()
self.child_widget.status_signal.connect(self.update_child_status)
self.child_widget.show()
def update_child_status(self):
self.line.setText('status updated')

ui控件

Basic Widget
输入框QLineEdit
按钮QPushButton
整数调整器QPinBox

对话框

dialog class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from PyQt5.QtWidgets import QDialog

class Ui_ModeEditor(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.__mainWindow = parent
.....

def setupUi(self):
self.buttonsLayout = QtWidgets.QHBoxLayout(self)
self.buttonsLayout.setObjectName("buttonsLayout")
btn_save = QtWidgets.QToolButton(self)
btn_save.setObjectName("btn_save")
btn_save.setText("保存")
btn_save.clicked.connect(self.accept)
btn_cancel = QtWidgets.QToolButton(self)
btn_cancel.setObjectName("btn_cancel")
btn_cancel.setText("取消")
btn_cancel.clicked.connect(self.reject)
self.buttonsLayout.addWidget(btn_save)
self.buttonsLayout.addWidget(btn_cancel)

use dialog
1
2
3
4
5
6
7
8
self.dialog = Ui_ModeEditor(self)
self.modeEditor.setWindowTitle("编辑模式")

retVal = self.dialog.exec()
if retVal == QDialog.Accepted:
# return accepted
else:
# close without accept

封装

pyinstaller vs Nuitka

Nuitka(音妞卡)是将python程序转换成C语言的可执行elf文件。这样在运行时就可以享受到C语言处理过程中的优化,提高速度。

1
pip install -U nuitka

编译依赖Python和C compiler,c compiler需支持C11或C++03 这意味着需安装MinGW64 C11编译器 基于gcc11.2或更高版本,或visual studio

1
2
pip install pyinstaller
pyinstaller -F -w main.py -n "测量工具"

http缓存区别于浏览器缓存,浏览器缓存指localStorage(max 5Mb)、sessionStorage和cookies(max 4kb)。这些功能主要用于缓存一些必要的数据,比如用户信息,有些数据则是传到后端的参数.http缓存是当发送http请求时,与本地副本比对,如果本地已缓存则从本地副本读取而不是服务器端。

目的:

  • 减少不必要的网络传输
  • 减少不必要的服务器负载
  • 提高加载速度

分类

  • 强制缓存
  • 协商缓存
    强制/协商指的是与服务器交互与否, Cache-Control:max-age=N 有效期内从缓存读取 不需与服务器交互,即强制缓存;
    1
    2
    3
    4
    5
    //服务端往响应头中写入需要缓存的时间
    res.writeHead(200,{
    'Cache-Control':'max-age=10'
    });

    参数:
  • max-age 决定客户端资源被缓存多久。
  • s-maxage 决定代理服务器缓存的时长。
  • no-cache 强制进行协商缓存。
  • no-store 禁止任何缓存策略。
  • public 资源即可以被浏览器缓存也可以被代理服务器缓存。
  • private 资源只能被浏览器缓存。

协商缓存工作流中,Cache-Control须设置 no-cache 以进行协商缓存,其响应header中要携带资源的修改时间

1
2
res.setHeader('last-modified', modifiedTime.toUTCString());
res.serHeader('Cache-Control', 'no-cache');

客户端读取到last-modified,则下次请求标头中须携带该修改时间以请求服务端确认资源是否已发生修改
1
2
3
4
5
accept:image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
accept-encoding:gzip, deflate, br, zstd
accept-language:en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
cache-control: no-cache
If-Modified-Since: Tue, 18 Jan 2022 01:30:00 GMT

服务端收到请求还需核对该时间
1
2
3
4
5
6
7
8
9
if(req.headers.ifModifiedSince === modifiedTime.toUTCString()){
res.statusCode = 304;
res.end();
}else{
res.setHeader('last-modified', modifiedTime.toUTCString());
res.serHeader('Cache-Control', 'no-cache');
res.end(data);
}


此外,为防止时间精度或其他因素使资源修改后,拿到的ModifiedTime相同,可使用Etag携带文件指纹(hash code) 前端收到Etag会携带If-None-Match到下次请求中确认

用户刷新/访问资源行为的不同方式,会采用不同的缓存策略

  • 地址栏/链接访问url 强制缓存 在缓存数据未失效(max-age范围内)的情况下,可以直接使用缓存数据,不需要再请求服务器
  • F5/刷新/右键菜单重新加载
  • Ctl+F5 (完全不使用HTTP缓存)

对比缓存: 浏览器第一次请求数据时,服务器会将缓存标识与数据一起返回给浏览器,浏览器将二者备份至缓存数据库中。
当浏览器再次请求数据时,浏览器将备份的缓存标识发送给服务器,服务器根据缓存标识进行判断,判断成功后,返回304状态码,通知客户端比较成功,可以使用缓存数据。

对于对比缓存,响应 header 中会有两个字段来标明规则:Last-Modified / If-Modified-Since
服务器响应请求时,会告诉浏览器资源的最后修改时间:Last-Modified,浏览器之后再请求的时候,会带上一个头:If-Modified-Since,这个值就是服务器上一次给的 Last-Modified 的时间,服务器会比对资源当前最后的修改时间,如果大于If-Modified-Since,则说明资源修改过了,浏览器不能再使用缓存,否则浏览器可以继续使用缓存,并返回304状态码。Etag / If-None-Match(优先级高于Last-Modified / If-Modified-Since)

私有缓存和共享缓存

私有缓存通常存在浏览器端,只存在本地,不会与其他客户端共享,因此可以保存个性化设置,部分资料中区分“浏览器缓存”和私有Http缓存的概念,“浏览器缓存”指localstorage sessionstorage cookies等

1
Cache-Control: private

共享缓存可细分为代理缓存和托管缓存,代理缓存是网络中间代理提供的缓存,在Https普及的现状下应用收到局限,托管缓存由服务开发人员明确部署,以降低源服务器负载并有效地交付内容。如反向代理、CDN 和 service worker 与缓存 API 的组合。

开发

对前端来说,缓存是缓存在前端,但实际上代码是后端的同学来写的。如果你需要实现前端缓存的话啊,通知后端的同学加响应头就好了。

以node.js为例 对于需要强缓存的资源

1
res.setHeader('Cache-Control', 'public, max-age=xxx');

协商缓存
1
2
3
res.setHeader('Cache-Control', 'public, max-age=0');
res.setHeader('Last-Modified', xxx);
res.setHeader('ETag', xxx);

npmjs: fresh

App Services

部署单页应用

appservices.PNG

Publish 内容为 Code, 将构建好的architects上传到服务器wwwroot目录
在App Services的通用设置(Configuration->General Settings)中设置startup command

1
pm2 serve /home/site/wwwroot --no-daemon --spa

Troubleshooting

Could not detect any platform in the source directory.

在App Services的应用设置(Configuration->Application Settings)中添加配置项
SCM_DO_BUILD_DURING_DEPLOYMENT = false

API Management