未验证 提交 f1ef3a0c 编写于 作者: W Walter 提交者: GitHub

Merge pull request #2183 from RainFrost1/index_management

add first version for index manager server
../../docs/zh_CN/inference_deployment/shitu_gallery_manager.md
\ No newline at end of file
# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
import mod.mainwindow
from paddleclas.deploy.utils import config, logger
from paddleclas.deploy.python.predict_rec import RecPredictor
from fastapi import FastAPI
import uvicorn
import numpy as np
import faiss
from typing import List
import pickle
import cv2
import socket
import json
import operator
from multiprocessing import Process
"""
完整的index库如下:
root_path/ # 库存储目录
|-- image_list.txt # 图像列表,每行:image_path label。由前端生成及修改。后端只读
|-- features.pkl # 建库之后,保存的embedding向量,后端生成,前端无需操作
|-- images # 图像存储目录,由前端生成及增删查等操作。后端只读
| |-- md5.jpg
| |-- md5.jpg
| |-- ……
|-- index # 真正的生成的index库存储目录,后端生成及操作,前端无需操作。
| |-- vector.index # faiss生成的索引库
| |-- id_map.pkl # 索引文件
"""
class ShiTuIndexManager(object):
def __init__(self, config):
self.root_path = None
self.image_list_path = "image_list.txt"
self.image_dir = "images"
self.index_path = "index/vector.index"
self.id_map_path = "index/id_map.pkl"
self.features_path = "features.pkl"
self.index = None
self.id_map = None
self.features = None
self.config = config
self.predictor = RecPredictor(config)
def _load_pickle(self, path):
if os.path.exists(path):
return pickle.load(open(path, 'rb'))
else:
return None
def _save_pickle(self, path, data):
if not os.path.exists(os.path.dirname(path)):
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, 'wb') as fd:
pickle.dump(data, fd)
def _load_index(self):
self.index = faiss.read_index(
os.path.join(self.root_path, self.index_path))
self.id_map = self._load_pickle(
os.path.join(self.root_path, self.id_map_path))
self.features = self._load_pickle(
os.path.join(self.root_path, self.features_path))
def _save_index(self, index, id_map, features):
faiss.write_index(index, os.path.join(self.root_path, self.index_path))
self._save_pickle(os.path.join(self.root_path, self.id_map_path),
id_map)
self._save_pickle(os.path.join(self.root_path, self.features_path),
features)
def _update_path(self, root_path, image_list_path=None):
if root_path == self.root_path:
pass
else:
self.root_path = root_path
if not os.path.exists(os.path.join(root_path, "index")):
os.mkdir(os.path.join(root_path, "index"))
if image_list_path is not None:
self.image_list_path = image_list_path
def _cal_featrue(self, image_list):
batch_images = []
featrures = None
cnt = 0
for idx, image_path in enumerate(image_list):
image = cv2.imread(image_path)
if image is None:
return "{} is broken or not exist. Stop"
else:
image = image[:, :, ::-1]
batch_images.append(image)
cnt += 1
if cnt % self.config["Global"]["batch_size"] == 0 or (
idx + 1) == len(image_list):
if len(batch_images) == 0:
continue
batch_results = self.predictor.predict(batch_images)
featrures = batch_results if featrures is None else np.concatenate(
(featrures, batch_results), axis=0)
batch_images = []
return featrures
def _split_datafile(self, data_file, image_root):
'''
data_file: image path and info, which can be splitted by spacer
image_root: image path root
delimiter: delimiter
'''
gallery_images = []
gallery_docs = []
gallery_ids = []
with open(data_file, 'r', encoding='utf-8') as f:
lines = f.readlines()
for _, ori_line in enumerate(lines):
line = ori_line.strip().split()
text_num = len(line)
assert text_num >= 2, f"line({ori_line}) must be splitted into at least 2 parts, but got {text_num}"
image_file = os.path.join(image_root, line[0])
gallery_images.append(image_file)
gallery_docs.append(ori_line.strip())
gallery_ids.append(os.path.basename(line[0]).split(".")[0])
return gallery_images, gallery_docs, gallery_ids
def create_index(self,
image_list: str,
index_method: str = "HNSW32",
image_root: str = None):
if not os.path.exists(image_list):
return "{} is not exist".format(image_list)
if index_method.lower() not in ['hnsw32', 'ivf', 'flat']:
return "The index method Only support: HNSW32, IVF, Flat"
self._update_path(os.path.dirname(image_list), image_list)
# get image_paths
image_root = image_root if image_root is not None else self.root_path
gallery_images, gallery_docs, image_ids = self._split_datafile(
image_list, image_root)
# gernerate index
if index_method == "IVF":
index_method = index_method + str(
min(max(int(len(gallery_images) // 32), 2), 65536)) + ",Flat"
index = faiss.index_factory(
self.config["IndexProcess"]["embedding_size"], index_method,
faiss.METRIC_INNER_PRODUCT)
self.index = faiss.IndexIDMap2(index)
features = self._cal_featrue(gallery_images)
self.index.train(features)
index_ids = np.arange(0, len(gallery_images)).astype(np.int64)
self.index.add_with_ids(features, index_ids)
self.id_map = dict()
for i, d in zip(list(index_ids), gallery_docs):
self.id_map[i] = d
self.features = {
"features": features,
"index_method": index_method,
"image_ids": image_ids,
"index_ids": index_ids.tolist()
}
self._save_index(self.index, self.id_map, self.features)
def open_index(self, root_path: str, image_list_path: str) -> str:
self._update_path(root_path)
_, _, image_ids = self._split_datafile(image_list_path, root_path)
if os.path.exists(os.path.join(self.root_path, self.index_path)) and \
os.path.exists(os.path.join(self.root_path, self.id_map_path)) and \
os.path.exists(os.path.join(self.root_path, self.features_path)):
self._update_path(root_path)
self._load_index()
if operator.eq(set(image_ids), set(self.features['image_ids'])):
return ""
else:
return "The image list is different from index, Please update index"
else:
return "File not exist: features.pkl, vector.index, id_map.pkl"
def update_index(self, image_list: str, image_root: str = None) -> str:
if self.index and self.id_map and self.features:
image_paths, image_docs, image_ids = self._split_datafile(
image_list,
image_root if image_root is not None else self.root_path)
# for add image
add_ids = list(
set(image_ids).difference(set(self.features["image_ids"])))
add_indexes = [i for i, x in enumerate(image_ids) if x in add_ids]
add_image_paths = [image_paths[i] for i in add_indexes]
add_image_docs = [image_docs[i] for i in add_indexes]
add_image_ids = [image_ids[i] for i in add_indexes]
self._add_index(add_image_paths, add_image_docs, add_image_ids)
# delete images
delete_ids = list(
set(self.features["image_ids"]).difference(set(image_ids)))
self._delete_index(delete_ids)
self._save_index(self.index, self.id_map, self.features)
return ""
else:
return "Failed. Please create or open index first"
def _add_index(self, image_list: List, image_docs: List, image_ids: List):
if len(image_ids) == 0:
return
featrures = self._cal_featrue(image_list)
index_ids = (np.arange(0, len(image_list)) + max(self.id_map.keys()) +
1).astype(np.int64)
self.index.add_with_ids(featrures, index_ids)
for i, d in zip(index_ids, image_docs):
self.id_map[i] = d
self.features['features'] = np.concatenate(
[self.features['features'], featrures], axis=0)
self.features['image_ids'].extend(image_ids)
self.features['index_ids'].extend(index_ids.tolist())
def _delete_index(self, image_ids: List):
if len(image_ids) == 0:
return
indexes = [
i for i, x in enumerate(self.features['image_ids'])
if x in image_ids
]
self.features["features"] = np.delete(self.features["features"],
indexes,
axis=0)
self.features["image_ids"] = np.delete(np.asarray(
self.features["image_ids"]),
indexes,
axis=0).tolist()
index_ids = np.delete(np.asarray(self.features["index_ids"]),
indexes,
axis=0).tolist()
id_map_values = [self.id_map[i] for i in index_ids]
self.index.reset()
ids = np.arange(0, len(id_map_values)).astype(np.int64)
self.index.add_with_ids(self.features['features'], ids)
self.id_map.clear()
for i, d in zip(ids, id_map_values):
self.id_map[i] = d
self.features["index_ids"] = ids
app = FastAPI()
@app.get("/new_index")
def new_index(image_list_path: str,
index_method: str = "HNSW32",
index_root_path: str = None,
force: bool = False):
result = ""
try:
if index_root_path is not None:
image_list_path = os.path.join(index_root_path, image_list_path)
index_path = os.path.join(index_root_path, "index", "vector.index")
id_map_path = os.path.join(index_root_path, "index", "id_map.pkl")
if not (os.path.exists(index_path)
and os.path.exists(id_map_path)) or force:
manager.create_index(image_list_path, index_method, index_root_path)
else:
result = "There alrealy has index in {}".format(index_root_path)
except Exception as e:
result = e.__str__()
data = {"error_message": result}
return json.dumps(data).encode()
@app.get("/open_index")
def open_index(index_root_path: str, image_list_path: str):
result = ""
try:
image_list_path = os.path.join(index_root_path, image_list_path)
result = manager.open_index(index_root_path, image_list_path)
except Exception as e:
result = e.__str__()
data = {"error_message": result}
return json.dumps(data).encode()
@app.get("/update_index")
def update_index(image_list_path: str, index_root_path: str = None):
result = ""
try:
if index_root_path is not None:
image_list_path = os.path.join(index_root_path, image_list_path)
result = manager.update_index(image_list=image_list_path,
image_root=index_root_path)
except Exception as e:
result = e.__str__()
data = {"error_message": result}
return json.dumps(data).encode()
def FrontInterface(server_process=None):
front = QtWidgets.QApplication([])
main_window = mod.mainwindow.MainWindow(process=server_process)
main_window.showMaximized()
sys.exit(front.exec_())
def Server(args):
[app, host, port] = args
uvicorn.run(app, host=host, port=port)
if __name__ == '__main__':
args = config.parse_args()
model_config = config.get_config(args.config,
overrides=args.override,
show=True)
manager = ShiTuIndexManager(model_config)
try:
ip = socket.gethostbyname(socket.gethostname())
except:
ip = '127.0.0.1'
port = 8000
p_server = Process(target=Server, args=([app, ip, port],))
p_server.start()
# p_client = Process(target=FrontInterface, args=())
# p_client.start()
# p_client.join()
FrontInterface(p_server)
p_server.terminate()
sys.exit(0)
import os
from PyQt5 import QtCore, QtWidgets
from mod import image_list_manager as imglistmgr
from mod import utils
from mod import ui_addclassifydialog
from mod import ui_renameclassifydialog
class ClassifyUiContext(QtCore.QObject):
# 分类界面相关业务
selected = QtCore.pyqtSignal(str) # 选择分类信号
def __init__(self, ui: QtWidgets.QListView, parent: QtWidgets.QMainWindow,
image_list_mgr: imglistmgr.ImageListManager):
super(ClassifyUiContext, self).__init__()
self.__ui = ui
self.__parent = parent
self.__imageListMgr = image_list_mgr
self.__menu = QtWidgets.QMenu()
self.__initMenu()
self.__initUi()
self.__connectSignal()
@property
def ui(self):
return self.__ui
@property
def parent(self):
return self.__parent
@property
def imageListManager(self):
return self.__imageListMgr
@property
def menu(self):
return self.__menu
def __initUi(self):
"""初始化分类界面"""
self.__ui.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
def __connectSignal(self):
"""连接信号"""
self.__ui.clicked.connect(self.uiClicked)
self.__ui.doubleClicked.connect(self.uiDoubleClicked)
def __initMenu(self):
"""初始化分类界面菜单"""
utils.setMenu(self.__menu, "添加分类", self.addClassify)
utils.setMenu(self.__menu, "移除分类", self.removeClassify)
utils.setMenu(self.__menu, "重命名分类", self.renemeClassify)
self.__ui.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.__ui.customContextMenuRequested.connect(self.__showMenu)
def __showMenu(self, pos):
"""显示分类界面菜单"""
if len(self.__imageListMgr.filePath) > 0:
self.__menu.exec_(self.__ui.mapToGlobal(pos))
def setClassifyList(self, classify_list):
"""设置分类列表"""
list_model = QtCore.QStringListModel(classify_list)
self.__ui.setModel(list_model)
def uiClicked(self, index):
"""分类列表点击"""
if not self.__ui.currentIndex().isValid():
return
txt = index.data()
self.selected.emit(txt)
def uiDoubleClicked(self, index):
"""分类列表双击"""
if not self.__ui.currentIndex().isValid():
return
ole_name = index.data()
dlg = QtWidgets.QDialog(parent=self.parent)
ui = ui_renameclassifydialog.Ui_RenameClassifyDialog()
ui.setupUi(dlg)
ui.oldNameLineEdit.setText(ole_name)
result = dlg.exec_()
new_name = ui.newNameLineEdit.text()
if result == QtWidgets.QDialog.Accepted:
mgr_result = self.__imageListMgr.renameClassify(ole_name, new_name)
if not mgr_result:
QtWidgets.QMessageBox.warning(self.parent, "重命名分类", "重命名分类错误")
else:
self.setClassifyList(self.__imageListMgr.classifyList)
self.__imageListMgr.writeFile()
def addClassify(self):
"""添加分类"""
if not os.path.exists(self.__imageListMgr.filePath):
QtWidgets.QMessageBox.information(self.__parent, "提示",
"请先打开正确的图像库")
return
dlg = QtWidgets.QDialog(parent=self.parent)
ui = ui_addclassifydialog.Ui_AddClassifyDialog()
ui.setupUi(dlg)
result = dlg.exec_()
txt = ui.lineEdit.text()
if result == QtWidgets.QDialog.Accepted:
mgr_result = self.__imageListMgr.addClassify(txt)
if not mgr_result:
QtWidgets.QMessageBox.warning(self.parent, "添加分类", "添加分类错误")
else:
self.setClassifyList(self.__imageListMgr.classifyList)
def removeClassify(self):
"""移除分类"""
if not os.path.exists(self.__imageListMgr.filePath):
QtWidgets.QMessageBox.information(self.__parent, "提示",
"请先打开正确的图像库")
return
if not self.__ui.currentIndex().isValid():
return
classify = self.__ui.currentIndex().data()
result = QtWidgets.QMessageBox.information(
self.parent,
"移除分类",
"确定移除分类: {}".format(classify),
buttons=QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel,
defaultButton=QtWidgets.QMessageBox.Cancel)
if result == QtWidgets.QMessageBox.Ok:
if len(self.__imageListMgr.imageList(classify)) > 0:
QtWidgets.QMessageBox.warning(self.parent, "移除分类",
"分类下存在图片,请先移除图片")
else:
self.__imageListMgr.removeClassify(classify)
self.setClassifyList(self.__imageListMgr.classifyList())
def renemeClassify(self):
"""重命名分类"""
idx = self.__ui.currentIndex()
if idx.isValid():
self.uiDoubleClicked(idx)
def searchClassify(self, classify):
"""查找分类"""
self.setClassifyList(self.__imageListMgr.findLikeClassify(classify))
import os
class ImageListManager:
"""
图像列表文件管理器
"""
def __init__(self, file_path="", encoding="utf-8"):
self.__filePath = ""
self.__dirName = ""
self.__dataList = {}
self.__findLikeClassifyResult = []
if file_path != "":
self.readFile(file_path, encoding)
@property
def filePath(self):
return self.__filePath
@property
def dirName(self):
return self.__dirName
@dirName.setter
def dirName(self, value):
self.__dirName = value
@property
def dataList(self):
return self.__dataList
@property
def classifyList(self):
return self.__dataList.keys()
@property
def findLikeClassifyResult(self):
return self.__findLikeClassifyResult
def imageList(self, classify: str):
"""
获取分类下的图片列表
Args:
classify (str): 分类名称
Returns:
list: 图片列表
"""
return self.__dataList[classify]
def readFile(self, file_path: str, encoding="utf-8"):
"""
读取文件内容
Args:
file_path (str): 文件路径
encoding (str, optional): 文件编码. 默认 "utf-8".
Raises:
Exception: 文件不存在
"""
if not os.path.exists(file_path):
raise Exception("文件不存在:{}".format(file_path))
self.__filePath = file_path
self.__dirName = os.path.dirname(self.__filePath)
self.__readData(file_path, encoding)
def __readData(self, file_path: str, encoding="utf-8"):
"""
读取文件内容
Args:
file_path (str): 文件路径
encoding (str, optional): 文件编码. 默认 "utf-8".
"""
with open(file_path, "r", encoding=encoding) as f:
self.__dataList.clear()
for line in f:
line = line.rstrip("\n")
data = line.split("\t")
self.__appendData(data)
def __appendData(self, data: list):
"""
添加数据
Args:
data (list): 数据
"""
if data[1] not in self.__dataList:
self.__dataList[data[1]] = []
self.__dataList[data[1]].append(data[0])
def writeFile(self, file_path="", encoding="utf-8"):
"""
写入文件
Args:
file_path (str, optional): 文件路径. 默认 "".
encoding (str, optional): 文件编码. 默认 "utf-8".
"""
if file_path == "":
file_path = self.__filePath
if not os.path.exists(file_path):
return False
self.__dirName = os.path.dirname(self.__filePath)
lines = []
for classify in self.__dataList.keys():
for path in self.__dataList[classify]:
lines.append("{}\t{}\n".format(path, classify))
with open(file_path, "w", encoding=encoding) as f:
f.writelines(lines)
return True
def realPath(self, image_path: str):
"""
获取真实路径
Args:
image_path (str): 图片路径
"""
return os.path.join(self.__dirName, image_path)
def realPathList(self, classify: str):
"""
获取分类下的真实路径列表
Args:
classify (str): 分类名称
Returns:
list: 真实路径列表
"""
if classify not in self.classifyList:
return []
paths = self.__dataList[classify]
if len(paths) == 0:
return []
for i in range(len(paths)):
paths[i] = os.path.join(self.__dirName, paths[i])
return paths
def findLikeClassify(self, name: str):
"""
查找类似的分类名称
Args:
name (str): 分类名称
Returns:
list: 类似的分类名称列表
"""
self.__findLikeClassifyResult.clear()
for classify in self.__dataList.keys():
word = str(name)
if (word in classify):
self.__findLikeClassifyResult.append(classify)
return self.__findLikeClassifyResult
def addClassify(self, classify: str):
"""
添加分类
Args:
classify (str): 分类名称
Returns:
bool: 如果分类名称已经存在,返回False,否则添加分类并返回True
"""
if classify in self.__dataList:
return False
self.__dataList[classify] = []
return True
def removeClassify(self, classify: str):
"""
移除分类
Args:
classify (str): 分类名称
Returns:
bool: 如果分类名称不存在,返回False,否则移除分类并返回True
"""
if classify not in self.__dataList:
return False
self.__dataList.pop(classify)
return True
def renameClassify(self, old_classify: str, new_classify: str):
"""
重命名分类名称
Args:
old_classify (str): 原分类名称
new_classify (str): 新分类名称
Returns:
bool: 如果原分类名称不存在,或者新分类名称已经存在,返回False,否则重命名分类名称并返回True
"""
if old_classify not in self.__dataList:
return False
if new_classify in self.__dataList:
return False
self.__dataList[new_classify] = self.__dataList[old_classify]
self.__dataList.pop(old_classify)
return True
def allClassfiyNotEmpty(self):
"""
检查所有分类是否都有图片
Returns:
bool: 如果有一个分类没有图片,返回False,否则返回True
"""
for classify in self.__dataList.keys():
if len(self.__dataList[classify]) == 0:
return False
return True
def resetImageList(self, classify: str, image_list: list):
"""
重置图片列表
Args:
classify (str): 分类名称
image_list (list): 图片相对路径列表
Returns:
bool: 如果分类名称不存在,返回False,否则重置图片列表并返回True
"""
if classify not in self.__dataList:
return False
self.__dataList[classify] = image_list
return True
import os
from stat import filemode
from PyQt5 import QtCore, QtGui, QtWidgets
from mod import image_list_manager as imglistmgr
from mod import utils
from mod import ui_renameclassifydialog
from mod import imageeditclassifydialog
# 图像缩放基数
BASE_IMAGE_SIZE = 64
class ImageListUiContext(QtCore.QObject):
# 图片列表界面相关业务,style sheet 在 MainWindow.ui 相应的 ImageListWidget 中设置
listCount = QtCore.pyqtSignal(int) # 图像列表图像的数量
selectedCount = QtCore.pyqtSignal(int) # 图像列表选择图像的数量
def __init__(self, ui: QtWidgets.QListWidget,
parent: QtWidgets.QMainWindow,
image_list_mgr: imglistmgr.ImageListManager):
super(ImageListUiContext, self).__init__()
self.__ui = ui
self.__parent = parent
self.__imageListMgr = image_list_mgr
self.__initUi()
self.__menu = QtWidgets.QMenu()
self.__initMenu()
self.__connectSignal()
self.__selectedClassify = ""
self.__imageScale = 1
@property
def ui(self):
return self.__ui
@property
def parent(self):
return self.__parent
@property
def imageListManager(self):
return self.__imageListMgr
@property
def menu(self):
return self.__menu
def __initUi(self):
"""初始化图片列表样式"""
self.__ui.setViewMode(QtWidgets.QListView.IconMode)
self.__ui.setSpacing(15)
self.__ui.setMovement(QtWidgets.QListView.Static)
self.__ui.setSelectionMode(
QtWidgets.QAbstractItemView.ExtendedSelection)
def __initMenu(self):
"""初始化图片列表界面菜单"""
utils.setMenu(self.__menu, "添加图片", self.addImage)
utils.setMenu(self.__menu, "移除图片", self.removeImage)
utils.setMenu(self.__menu, "编辑图片分类", self.editImageClassify)
self.__menu.addSeparator()
utils.setMenu(self.__menu, "选择全部图片", self.selectAllImage)
utils.setMenu(self.__menu, "反向选择图片", self.reverseSelectImage)
utils.setMenu(self.__menu, "取消选择图片", self.cancelSelectImage)
self.__ui.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.__ui.customContextMenuRequested.connect(self.__showMenu)
def __showMenu(self, pos):
"""显示图片列表界面菜单"""
if len(self.__imageListMgr.filePath) > 0:
self.__menu.exec_(self.__ui.mapToGlobal(pos))
def __connectSignal(self):
"""连接信号与槽"""
self.__ui.itemSelectionChanged.connect(self.onSelectionChanged)
def setImageScale(self, scale: int):
"""设置图片大小"""
self.__imageScale = scale
size = QtCore.QSize(scale * BASE_IMAGE_SIZE, scale * BASE_IMAGE_SIZE)
self.__ui.setIconSize(size)
for i in range(self.__ui.count()):
item = self.__ui.item(i)
item.setSizeHint(size)
def setImageList(self, classify: str):
"""设置图片列表"""
size = QtCore.QSize(self.__imageScale * BASE_IMAGE_SIZE,
self.__imageScale * BASE_IMAGE_SIZE)
self.__selectedClassify = classify
image_list = self.__imageListMgr.imageList(classify)
self.__ui.clear()
count = 0
for i in image_list:
item = QtWidgets.QListWidgetItem(self.__ui)
item.setIcon(QtGui.QIcon(self.__imageListMgr.realPath(i)))
item.setData(QtCore.Qt.UserRole, i)
item.setSizeHint(size)
self.__ui.addItem(item)
count += 1
self.listCount.emit(count)
def clear(self):
"""清除图片列表"""
self.__ui.clear()
def addImage(self):
"""添加图片"""
if not os.path.exists(self.__imageListMgr.filePath):
QtWidgets.QMessageBox.information(self.__parent, "提示",
"请先打开正确的图像库")
return
filter = "图片 (*.png *.jpg *.jpeg *.PNG *.JPG *.JPEG);;所有文件(*.*)"
dlg = QtWidgets.QFileDialog(self.__parent)
dlg.setFileMode(QtWidgets.QFileDialog.ExistingFiles) # 多选文件
dlg.setViewMode(QtWidgets.QFileDialog.Detail) # 详细模式
file_paths = dlg.getOpenFileNames(filter=filter)[0]
if len(file_paths) == 0:
return
image_list_dir = self.__imageListMgr.dirName
file_list = []
for path in file_paths:
if not os.path.exists(path):
continue
new_file = self.__copyToImagesDir(path)
if new_file != "" and image_list_dir in new_file:
# 去掉 image_list_dir 的路径和斜杠
begin = len(image_list_dir) + 1
file_list.append(new_file[begin:])
if len(file_list) > 0:
if self.__selectedClassify == "":
QtWidgets.QMessageBox.warning(self.__parent, "提示", "请先选择分类")
return
new_list = self.__imageListMgr.imageList(
self.__selectedClassify) + file_list
self.__imageListMgr.resetImageList(self.__selectedClassify,
new_list)
self.setImageList(self.__selectedClassify)
self.__imageListMgr.writeFile()
def __copyToImagesDir(self, image_path: str):
md5 = utils.fileMD5(image_path)
file_ext = utils.fileExtension(image_path)
to_dir = os.path.join(self.__imageListMgr.dirName, "images")
new_path = os.path.join(to_dir, md5 + file_ext)
if os.path.exists(to_dir):
utils.copyFile(image_path, new_path)
return new_path
else:
return ""
def removeImage(self):
"""移除图片"""
if not os.path.exists(self.__imageListMgr.filePath):
QtWidgets.QMessageBox.information(self.__parent, "提示",
"请先打开正确的图像库")
return
path_list = []
image_list = self.__ui.selectedItems()
if len(image_list) == 0:
return
question = QtWidgets.QMessageBox.question(self.__parent, "移除图片",
"确定移除所选图片吗?")
if question == QtWidgets.QMessageBox.No:
return
for i in range(self.__ui.count()):
item = self.__ui.item(i)
img_path = item.data(QtCore.Qt.UserRole)
if not item.isSelected():
path_list.append(img_path)
else:
# 从磁盘上删除图片
utils.removeFile(
os.path.join(self.__imageListMgr.dirName, img_path))
self.__imageListMgr.resetImageList(self.__selectedClassify, path_list)
self.setImageList(self.__selectedClassify)
self.__imageListMgr.writeFile()
def editImageClassify(self):
"""编辑图片分类"""
old_classify = self.__selectedClassify
dlg = imageeditclassifydialog.ImageEditClassifyDialog(
parent=self.__parent,
old_classify=old_classify,
classify_list=self.__imageListMgr.classifyList)
result = dlg.exec_()
new_classify = dlg.newClassify
if result == QtWidgets.QDialog.Accepted \
and new_classify != old_classify \
and new_classify != "":
self.__moveImage(old_classify, new_classify)
self.__imageListMgr.writeFile()
def __moveImage(self, old_classify, new_classify):
"""移动图片"""
keep_list = []
is_selected = False
move_list = self.__imageListMgr.imageList(new_classify)
for i in range(self.__ui.count()):
item = self.__ui.item(i)
txt = item.data(QtCore.Qt.UserRole)
if item.isSelected():
move_list.append(txt)
is_selected = True
else:
keep_list.append(txt)
if is_selected:
self.__imageListMgr.resetImageList(new_classify, move_list)
self.__imageListMgr.resetImageList(old_classify, keep_list)
self.setImageList(old_classify)
def selectAllImage(self):
"""选择所有图片"""
self.__ui.selectAll()
def reverseSelectImage(self):
"""反向选择图片"""
for i in range(self.__ui.count()):
item = self.__ui.item(i)
item.setSelected(not item.isSelected())
def cancelSelectImage(self):
"""取消选择图片"""
self.__ui.clearSelection()
def onSelectionChanged(self):
"""选择图像该变,发送选择的数量信号"""
count = len(self.__ui.selectedItems())
self.selectedCount.emit(count)
import os
from PyQt5 import QtCore, QtGui, QtWidgets
from mod import image_list_manager
from mod import ui_imageeditclassifydialog
from mod import utils
class ImageEditClassifyDialog(QtWidgets.QDialog):
"""图像编辑分类对话框"""
def __init__(self, parent, old_classify, classify_list):
super(ImageEditClassifyDialog, self).__init__(parent)
self.ui = mod.ui_imageeditclassifydialog.Ui_Dialog()
self.ui.setupUi(self) # 初始化主窗口界面
self.__oldClassify = old_classify
self.__classifyList = classify_list
self.__newClassify = ""
self.__searchResult = []
self.__initUi()
self.__connectSignal()
@property
def newClassify(self):
return self.__newClassify
def __initUi(self):
self.ui.oldLineEdit.setText(self.__oldClassify)
self.__setClassifyList(self.__classifyList)
self.ui.classifyListView.setEditTriggers(
QtWidgets.QAbstractItemView.NoEditTriggers)
def __connectSignal(self):
self.ui.classifyListView.clicked.connect(self.selectedListView)
self.ui.searchButton.clicked.connect(self.searchClassify)
def __setClassifyList(self, classify_list):
list_model = QtCore.QStringListModel(classify_list)
self.ui.classifyListView.setModel(list_model)
def selectedListView(self, index):
if not self.ui.classifyListView.currentIndex().isValid():
return
txt = index.data()
self.ui.newLineEdit.setText(txt)
self.__newClassify = txt
def searchClassify(self):
txt = self.ui.searchWordLineEdit.text()
self.__searchResult.clear()
for classify in self.__classifyList:
if txt in classify:
self.__searchResult.append(classify)
self.__setClassifyList(self.__searchResult)
import json
import os
import urllib3
import urllib.parse
class IndexHttpClient():
"""索引库客户端,使用 urllib3 连接,使用 urllib.parse 进行 url 编码"""
def __init__(self, host: str, port: int):
self.__host = host
self.__port = port
self.__http = urllib3.PoolManager()
self.__headers = {"Content-type": "application/json"}
def url(self):
return "http://{}:{}".format(self.__host, self.__port)
def new_index(self,
image_list_path: str,
index_root_path: str,
index_method="HNSW32",
force=False):
"""新建 重建 库"""
if index_method not in ["HNSW32", "FLAT", "IVF"]:
raise Exception(
"index_method 必须是 HNSW32, FLAT, IVF,实际值为:{}".format(
index_method))
params = {"image_list_path":image_list_path, \
"index_root_path":index_root_path, \
"index_method":index_method, \
"force":force}
return self.__post(self.url() + "/new_index?", params)
def open_index(self, index_root_path: str, image_list_path: str):
"""打开库"""
params = {
"index_root_path": index_root_path,
"image_list_path": image_list_path
}
return self.__post(self.url() + "/open_index?", params)
def update_index(self, image_list_path: str, index_root_path: str):
"""更新索引库"""
params = {"image_list_path":image_list_path, \
"index_root_path":index_root_path}
return self.__post(self.url() + "/update_index?", params)
def __post(self, url: str, params: dict):
"""发送 url 并接收数据"""
http = self.__http
encode_params = urllib.parse.urlencode(params)
get_url = url + encode_params
req = http.request("GET", get_url, headers=self.__headers)
result = json.loads(req.data)
if isinstance(result, str):
result = eval(result)
msg = result["error_message"]
if msg != None and len(msg) == 0:
msg = None
return msg
from multiprocessing.dummy import active_children
from multiprocessing import Process
import os
import sys
import socket
from PyQt5 import QtCore, QtGui, QtWidgets
from mod import ui_mainwindow
from mod import image_list_manager
from mod import classify_ui_context
from mod import image_list_ui_context
from mod import ui_newlibrarydialog
from mod import index_http_client
from mod import utils
from mod import ui_waitdialog
import threading
TOOL_BTN_ICON_SIZE = 64
TOOL_BTN_ICON_SMALL = 48
try:
DEFAULT_HOST = socket.gethostbyname(socket.gethostname())
except:
DEFAULT_HOST = '127.0.0.1'
# DEFAULT_HOST = "localhost"
DEFAULT_PORT = 8000
PADDLECLAS_DOC_URL = "https://gitee.com/paddlepaddle/PaddleClas/docs/zh_CN/inference_deployment/shitu_gallery_manager.md"
class MainWindow(QtWidgets.QMainWindow):
"""主窗口"""
newIndexMsg = QtCore.pyqtSignal(str) # 新建索引库线程信号
openIndexMsg = QtCore.pyqtSignal(str) # 打开索引库线程信号
updateIndexMsg = QtCore.pyqtSignal(str) # 更新索引库线程信号
importImageCount = QtCore.pyqtSignal(int) # 导入图像数量信号
def __init__(self, process=None):
super(MainWindow, self).__init__()
self.server_process = process
self.ui = ui_mainwindow.Ui_MainWindow()
self.ui.setupUi(self) # 初始化主窗口界面
self.__imageListMgr = image_list_manager.ImageListManager()
self.__appMenu = QtWidgets.QMenu() # 应用菜单
self.__libraryAppendMenu = QtWidgets.QMenu() # 图像库附加功能菜单
self.__initAppMenu() # 初始化应用菜单
self.__pathBar = QtWidgets.QLabel(self) # 路径
self.__classifyCountBar = QtWidgets.QLabel(self) # 分类数量
self.__imageCountBar = QtWidgets.QLabel(self) # 图像列表数量
self.__imageSelectedBar = QtWidgets.QLabel(self) # 图像列表选择数量
self.__spaceBar1 = QtWidgets.QLabel(self) # 空格间隔栏
self.__spaceBar2 = QtWidgets.QLabel(self) # 空格间隔栏
self.__spaceBar3 = QtWidgets.QLabel(self) # 空格间隔栏
# 分类界面相关业务
self.__classifyUiContext = classify_ui_context.ClassifyUiContext(
ui=self.ui.classifyListView,
parent=self,
image_list_mgr=self.__imageListMgr)
# 图片列表界面相关业务
self.__imageListUiContext = image_list_ui_context.ImageListUiContext(
ui=self.ui.imageListWidget,
parent=self,
image_list_mgr=self.__imageListMgr)
# 搜索的历史记录回车快捷键
self.__historyCmbShortcut = QtWidgets.QShortcut(
QtGui.QKeySequence(QtCore.Qt.Key_Return),
self.ui.searchClassifyHistoryCmb)
self.__waitDialog = QtWidgets.QDialog() # 等待对话框
self.__waitDialogUi = ui_waitdialog.Ui_WaitDialog() # 等待对话框界面
self.__initToolBtn()
self.__connectSignal()
self.__initUI()
self.__initWaitDialog()
def __initUI(self):
"""初始化界面"""
# 窗口图标
self.setWindowIcon(QtGui.QIcon("./resource/app_icon.png"))
# 初始化分割窗口
self.ui.splitter.setStretchFactor(0, 20)
self.ui.splitter.setStretchFactor(1, 80)
# 初始化图像缩放
self.ui.imageScaleSlider.setValue(4)
# 状态栏界面设置
space_bar = " " # 间隔16空格
self.__spaceBar1.setText(space_bar)
self.__spaceBar2.setText(space_bar)
self.__spaceBar3.setText(space_bar)
self.ui.statusbar.addWidget(self.__pathBar)
self.ui.statusbar.addWidget(self.__spaceBar1)
self.ui.statusbar.addWidget(self.__classifyCountBar)
self.ui.statusbar.addWidget(self.__spaceBar2)
self.ui.statusbar.addWidget(self.__imageCountBar)
self.ui.statusbar.addWidget(self.__spaceBar3)
self.ui.statusbar.addWidget(self.__imageSelectedBar)
def __initToolBtn(self):
"""初始化工具按钮"""
self.__setToolButton(self.ui.appMenuBtn, "应用菜单",
"./resource/app_menu.png", TOOL_BTN_ICON_SIZE)
self.__setToolButton(self.ui.saveImageLibraryBtn, "保存图像库",
"./resource/save_image_Library.png",
TOOL_BTN_ICON_SIZE)
self.ui.saveImageLibraryBtn.clicked.connect(self.saveImageLibrary)
self.__setToolButton(self.ui.addClassifyBtn, "添加分类",
"./resource/add_classify.png",
TOOL_BTN_ICON_SIZE)
self.ui.addClassifyBtn.clicked.connect(
self.__classifyUiContext.addClassify)
self.__setToolButton(self.ui.removeClassifyBtn, "移除分类",
"./resource/remove_classify.png",
TOOL_BTN_ICON_SIZE)
self.ui.removeClassifyBtn.clicked.connect(
self.__classifyUiContext.removeClassify)
self.__setToolButton(self.ui.searchClassifyBtn, "查找分类",
"./resource/search_classify.png",
TOOL_BTN_ICON_SMALL)
self.ui.searchClassifyBtn.clicked.connect(
self.__classifyUiContext.searchClassify)
self.__setToolButton(self.ui.addImageBtn, "添加图片",
"./resource/add_image.png", TOOL_BTN_ICON_SMALL)
self.ui.addImageBtn.clicked.connect(self.__imageListUiContext.addImage)
self.__setToolButton(self.ui.removeImageBtn, "移除图片",
"./resource/remove_image.png",
TOOL_BTN_ICON_SMALL)
self.ui.removeImageBtn.clicked.connect(
self.__imageListUiContext.removeImage)
self.ui.searchClassifyHistoryCmb.setToolTip("查找分类历史")
self.ui.imageScaleSlider.setToolTip("图片缩放")
def __setToolButton(self, button, tool_tip: str, icon_path: str,
icon_size: int):
"""设置工具按钮"""
button.setToolTip(tool_tip)
button.setIcon(QtGui.QIcon(icon_path))
button.setIconSize(QtCore.QSize(icon_size, icon_size))
def __initAppMenu(self):
"""初始化应用菜单"""
utils.setMenu(self.__appMenu, "新建图像库", self.newImageLibrary)
utils.setMenu(self.__appMenu, "打开图像库", self.openImageLibrary)
utils.setMenu(self.__appMenu, "保存图像库", self.saveImageLibrary)
self.__libraryAppendMenu.setTitle("导入图像")
utils.setMenu(self.__libraryAppendMenu, "导入 image_list 图像",
self.importImageListImage)
utils.setMenu(self.__libraryAppendMenu, "导入多文件夹图像",
self.importDirsImage)
self.__appMenu.addMenu(self.__libraryAppendMenu)
self.__appMenu.addSeparator()
utils.setMenu(self.__appMenu, "新建/重建 索引库", self.newIndexLibrary)
utils.setMenu(self.__appMenu, "更新索引库", self.updateIndexLibrary)
self.__appMenu.addSeparator()
utils.setMenu(self.__appMenu, "帮助", self.showHelp)
utils.setMenu(self.__appMenu, "关于", self.showAbout)
utils.setMenu(self.__appMenu, "退出", self.exitApp)
self.ui.appMenuBtn.setMenu(self.__appMenu)
self.ui.appMenuBtn.setPopupMode(QtWidgets.QToolButton.InstantPopup)
def __initWaitDialog(self):
"""初始化等待对话框"""
self.__waitDialogUi.setupUi(self.__waitDialog)
self.__waitDialog.setWindowFlags(QtCore.Qt.Dialog
| QtCore.Qt.FramelessWindowHint)
def __startWait(self, msg: str):
"""开始显示等待对话框"""
self.setEnabled(False)
self.__waitDialogUi.msgLabel.setText(msg)
self.__waitDialog.setWindowFlags(QtCore.Qt.Dialog
| QtCore.Qt.FramelessWindowHint
| QtCore.Qt.WindowStaysOnTopHint)
self.__waitDialog.show()
self.__waitDialog.repaint()
def __stopWait(self):
"""停止显示等待对话框"""
self.setEnabled(True)
self.__waitDialogUi.msgLabel.setText("执行完毕!")
self.__waitDialog.setWindowFlags(QtCore.Qt.Dialog
| QtCore.Qt.FramelessWindowHint
| QtCore.Qt.CustomizeWindowHint)
self.__waitDialog.close()
def __connectSignal(self):
"""连接信号与槽"""
self.__classifyUiContext.selected.connect(
self.__imageListUiContext.setImageList)
self.ui.searchClassifyBtn.clicked.connect(self.searchClassify)
self.ui.imageScaleSlider.valueChanged.connect(
self.__imageListUiContext.setImageScale)
self.__imageListUiContext.listCount.connect(self.__setImageCountBar)
self.__imageListUiContext.selectedCount.connect(
self.__setImageSelectedCountBar)
self.__historyCmbShortcut.activated.connect(self.searchClassify)
self.newIndexMsg.connect(self.__onNewIndexMsg)
self.openIndexMsg.connect(self.__onOpenIndexMsg)
self.updateIndexMsg.connect(self.__onUpdateIndexMsg)
self.importImageCount.connect(self.__onImportImageCount)
def newImageLibrary(self):
"""新建图像库"""
dir_path = self.__openDirDialog("新建图像库")
if dir_path == None:
return
if not utils.isEmptyDir(dir_path):
QtWidgets.QMessageBox.warning(self, "错误", "该目录不为空,请选择空目录")
return
if not utils.initLibrary(dir_path):
QtWidgets.QMessageBox.warning(self, "错误", "新建图像库失败")
return
QtWidgets.QMessageBox.information(self, "提示", "新建图像库成功")
self.__reload(os.path.join(dir_path, "image_list.txt"), dir_path)
def __openDirDialog(self, title: str):
"""打开目录对话框"""
dlg = QtWidgets.QFileDialog(self)
dlg.setWindowTitle(title)
dlg.setOption(QtWidgets.QFileDialog.ShowDirsOnly, True)
dlg.setFileMode(QtWidgets.QFileDialog.Directory)
dlg.setAcceptMode(QtWidgets.QFileDialog.AcceptOpen)
if dlg.exec_() == QtWidgets.QDialog.Accepted:
dir_path = dlg.selectedFiles()[0]
return dir_path
return None
def openImageLibrary(self):
"""打开图像库"""
dir_path = self.__openDirDialog("打开图像库")
if dir_path != None:
image_list_path = os.path.join(dir_path, "image_list.txt")
if os.path.exists(image_list_path) \
and os.path.exists(os.path.join(dir_path, "images")):
self.__reload(image_list_path, dir_path)
self.openIndexLibrary()
def __reload(self, image_list_path: str, msg: str):
"""重新加载图像库"""
self.__imageListMgr.readFile(image_list_path)
self.__imageListUiContext.clear()
self.__classifyUiContext.setClassifyList(
self.__imageListMgr.classifyList)
self.__setPathBar(msg)
self.__setClassifyCountBar(len(self.__imageListMgr.classifyList))
self.__setImageCountBar(0)
self.__setImageSelectedCountBar(0)
def saveImageLibrary(self):
"""保存图像库"""
if not os.path.exists(self.__imageListMgr.filePath):
QtWidgets.QMessageBox.warning(self, "错误", "请先打开正确的图像库")
return
self.__imageListMgr.writeFile()
self.__reload(self.__imageListMgr.filePath,
self.__imageListMgr.dirName)
hint_str = "为保证图片准确识别,请在修改图片库后更新索引库。\n\
如果是新建图像库或者没有索引库,请新建索引库。"
QtWidgets.QMessageBox.information(self, "提示", hint_str)
def __onImportImageCount(self, count: int):
"""导入图像槽"""
self.__stopWait()
if count == -1:
QtWidgets.QMessageBox.warning(self, "错误", "导入到当前图像库错误")
return
QtWidgets.QMessageBox.information(self, "提示",
"导入图像库成功,导入图像:{}".format(count))
self.__reload(self.__imageListMgr.filePath,
self.__imageListMgr.dirName)
def __importImageListImageThread(self, from_path: str, to_path: str):
"""导入 image_list 图像 线程"""
count = utils.oneKeyImportFromFile(from_path=from_path,
to_path=to_path)
if count == None:
count = -1
self.importImageCount.emit(count)
def importImageListImage(self):
"""导入 image_list 图像 到当前图像库,建议当前库是新建的空库"""
if not os.path.exists(self.__imageListMgr.filePath):
QtWidgets.QMessageBox.information(self, "提示", "请先打开正确的图像库")
return
from_path = QtWidgets.QFileDialog.getOpenFileName(
caption="导入 image_list 图像", filter="txt (*.txt)")[0]
if not os.path.exists(from_path):
QtWidgets.QMessageBox.information(self, "提示", "打开的文件不存在")
return
from_mgr = image_list_manager.ImageListManager(from_path)
self.__startWait("正在导入图像,请等待。。。")
thread = threading.Thread(target=self.__importImageListImageThread,
args=(from_mgr.filePath,
self.__imageListMgr.filePath))
thread.start()
def __importDirsImageThread(self, from_dir: str, to_image_list_path: str):
"""导入多文件夹图像 线程"""
count = utils.oneKeyImportFromDirs(
from_dir=from_dir, to_image_list_path=to_image_list_path)
if count == None:
count = -1
self.importImageCount.emit(count)
def importDirsImage(self):
"""导入 多文件夹图像 到当前图像库,建议当前库是新建的空库"""
if not os.path.exists(self.__imageListMgr.filePath):
QtWidgets.QMessageBox.information(self, "提示", "请先打开正确的图像库")
return
dir_path = self.__openDirDialog("导入多文件夹图像")
if dir_path == None:
return
if not os.path.exists(dir_path):
QtWidgets.QMessageBox.information(self, "提示", "打开的目录不存在")
return
self.__startWait("正在导入图像,请等待。。。")
thread = threading.Thread(target=self.__importDirsImageThread,
args=(dir_path,
self.__imageListMgr.filePath))
thread.start()
def __newIndexThread(self, index_root_path: str, image_list_path: str,
index_method: str, force: bool):
"""新建重建索引库线程"""
try:
client = index_http_client.IndexHttpClient(
DEFAULT_HOST, DEFAULT_PORT)
err_msg = client.new_index(image_list_path=image_list_path,
index_root_path=index_root_path,
index_method=index_method,
force=force)
if err_msg == None:
err_msg = ""
self.newIndexMsg.emit(err_msg)
except Exception as e:
self.newIndexMsg.emit(str(e))
def __onNewIndexMsg(self, err_msg):
"""新建重建索引库槽"""
self.__stopWait()
if err_msg == "":
QtWidgets.QMessageBox.information(self, "提示", "新建/重建 索引库成功")
else:
QtWidgets.QMessageBox.warning(self, "错误", err_msg)
def newIndexLibrary(self):
"""新建重建索引库"""
if not os.path.exists(self.__imageListMgr.filePath):
QtWidgets.QMessageBox.information(self, "提示", "请先打开正确的图像库")
return
dlg = QtWidgets.QDialog(self)
ui = ui_newlibrarydialog.Ui_NewlibraryDialog()
ui.setupUi(dlg)
result = dlg.exec_()
index_method = ui.indexMethodCmb.currentText()
force = ui.resetCheckBox.isChecked()
if result == QtWidgets.QDialog.Accepted:
self.__startWait("正在 新建/重建 索引库,请等待。。。")
thread = threading.Thread(target=self.__newIndexThread,
args=(self.__imageListMgr.dirName,
"image_list.txt", index_method,
force))
thread.start()
def __openIndexThread(self, index_root_path: str, image_list_path: str):
"""打开索引库线程"""
try:
client = index_http_client.IndexHttpClient(
DEFAULT_HOST, DEFAULT_PORT)
err_msg = client.open_index(index_root_path=index_root_path,
image_list_path=image_list_path)
if err_msg == None:
err_msg = ""
self.openIndexMsg.emit(err_msg)
except Exception as e:
self.openIndexMsg.emit(str(e))
def __onOpenIndexMsg(self, err_msg):
"""打开索引库槽"""
self.__stopWait()
if err_msg == "":
QtWidgets.QMessageBox.information(self, "提示", "打开索引库成功")
else:
QtWidgets.QMessageBox.warning(self, "错误", err_msg)
def openIndexLibrary(self):
"""打开索引库"""
if not os.path.exists(self.__imageListMgr.filePath):
QtWidgets.QMessageBox.information(self, "提示", "请先打开正确的图像库")
return
self.__startWait("正在打开索引库,请等待。。。")
thread = threading.Thread(target=self.__openIndexThread,
args=(self.__imageListMgr.dirName,
"image_list.txt"))
thread.start()
def __updateIndexThread(self, index_root_path: str, image_list_path: str):
"""更新索引库线程"""
try:
client = index_http_client.IndexHttpClient(
DEFAULT_HOST, DEFAULT_PORT)
err_msg = client.update_index(image_list_path=image_list_path,
index_root_path=index_root_path)
if err_msg == None:
err_msg = ""
self.updateIndexMsg.emit(err_msg)
except Exception as e:
self.updateIndexMsg.emit(str(e))
def __onUpdateIndexMsg(self, err_msg):
"""更新索引库槽"""
self.__stopWait()
if err_msg == "":
QtWidgets.QMessageBox.information(self, "提示", "更新索引库成功")
else:
QtWidgets.QMessageBox.warning(self, "错误", err_msg)
def updateIndexLibrary(self):
"""更新索引库"""
if not os.path.exists(self.__imageListMgr.filePath):
QtWidgets.QMessageBox.information(self, "提示", "请先打开正确的图像库")
return
self.__startWait("正在更新索引库,请等待。。。")
thread = threading.Thread(target=self.__updateIndexThread,
args=(self.__imageListMgr.dirName,
"image_list.txt"))
thread.start()
def searchClassify(self):
"""查找分类"""
if len(self.__imageListMgr.classifyList) == 0:
return
cmb = self.ui.searchClassifyHistoryCmb
txt = cmb.currentText()
is_has = False
if txt != "":
for i in range(cmb.count()):
if cmb.itemText(i) == txt:
is_has = True
break
if not is_has:
cmb.addItem(txt)
self.__classifyUiContext.searchClassify(txt)
def showHelp(self):
"""显示帮助"""
QtGui.QDesktopServices.openUrl(QtCore.QUrl(PADDLECLAS_DOC_URL))
def showAbout(self):
"""显示关于对话框"""
QtWidgets.QMessageBox.information(self, "关于", "识图图像库管理 V1.0.0")
def exitApp(self):
"""退出应用"""
if isinstance(self.server_process, Process):
self.server_process.terminate()
# os.kill(self.server_pid)
sys.exit(0)
def __setPathBar(self, msg: str):
"""设置路径状态栏信息"""
self.__pathBar.setText("图像库路径:{}".format(msg))
def __setClassifyCountBar(self, msg: str):
self.__classifyCountBar.setText("分类总数量:{}".format(msg))
def __setImageCountBar(self, count: int):
"""设置图像数量状态栏信息"""
self.__imageCountBar.setText("当前图像数量:{}".format(count))
def __setImageSelectedCountBar(self, count: int):
"""设置选择图像数量状态栏信息"""
self.__imageSelectedBar.setText("选择图像数量:{}".format(count))
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'ui/AddClassifyDialog.ui'
#
# Created by: PyQt5 UI code generator 5.15.5
#
# 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_AddClassifyDialog(object):
def setupUi(self, AddClassifyDialog):
AddClassifyDialog.setObjectName("AddClassifyDialog")
AddClassifyDialog.resize(286, 127)
AddClassifyDialog.setModal(True)
self.verticalLayout = QtWidgets.QVBoxLayout(AddClassifyDialog)
self.verticalLayout.setObjectName("verticalLayout")
self.label = QtWidgets.QLabel(AddClassifyDialog)
self.label.setObjectName("label")
self.verticalLayout.addWidget(self.label)
self.lineEdit = QtWidgets.QLineEdit(AddClassifyDialog)
self.lineEdit.setObjectName("lineEdit")
self.verticalLayout.addWidget(self.lineEdit)
spacerItem = QtWidgets.QSpacerItem(20, 11,
QtWidgets.QSizePolicy.Minimum,
QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem)
self.buttonBox = QtWidgets.QDialogButtonBox(AddClassifyDialog)
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel
| QtWidgets.QDialogButtonBox.Ok)
self.buttonBox.setObjectName("buttonBox")
self.verticalLayout.addWidget(self.buttonBox)
self.retranslateUi(AddClassifyDialog)
self.buttonBox.accepted.connect(AddClassifyDialog.accept)
self.buttonBox.rejected.connect(AddClassifyDialog.reject)
QtCore.QMetaObject.connectSlotsByName(AddClassifyDialog)
def retranslateUi(self, AddClassifyDialog):
_translate = QtCore.QCoreApplication.translate
AddClassifyDialog.setWindowTitle(
_translate("AddClassifyDialog", "添加分类"))
self.label.setText(_translate("AddClassifyDialog", "分类名称"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
AddClassifyDialog = QtWidgets.QDialog()
ui = Ui_AddClassifyDialog()
ui.setupUi(AddClassifyDialog)
AddClassifyDialog.show()
sys.exit(app.exec_())
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'ui/ImageEditClassifyDialog.ui'
#
# Created by: PyQt5 UI code generator 5.15.5
#
# 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_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(414, 415)
Dialog.setMinimumSize(QtCore.QSize(0, 0))
self.verticalLayout = QtWidgets.QVBoxLayout(Dialog)
self.verticalLayout.setObjectName("verticalLayout")
self.label = QtWidgets.QLabel(Dialog)
self.label.setObjectName("label")
self.verticalLayout.addWidget(self.label)
self.oldLineEdit = QtWidgets.QLineEdit(Dialog)
self.oldLineEdit.setEnabled(False)
self.oldLineEdit.setObjectName("oldLineEdit")
self.verticalLayout.addWidget(self.oldLineEdit)
self.label_2 = QtWidgets.QLabel(Dialog)
self.label_2.setObjectName("label_2")
self.verticalLayout.addWidget(self.label_2)
self.newLineEdit = QtWidgets.QLineEdit(Dialog)
self.newLineEdit.setEnabled(False)
self.newLineEdit.setObjectName("newLineEdit")
self.verticalLayout.addWidget(self.newLineEdit)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.searchWordLineEdit = QtWidgets.QLineEdit(Dialog)
self.searchWordLineEdit.setObjectName("searchWordLineEdit")
self.horizontalLayout.addWidget(self.searchWordLineEdit)
self.searchButton = QtWidgets.QPushButton(Dialog)
self.searchButton.setObjectName("searchButton")
self.horizontalLayout.addWidget(self.searchButton)
self.verticalLayout.addLayout(self.horizontalLayout)
self.classifyListView = QtWidgets.QListView(Dialog)
self.classifyListView.setEnabled(True)
self.classifyListView.setMinimumSize(QtCore.QSize(400, 200))
self.classifyListView.setObjectName("classifyListView")
self.verticalLayout.addWidget(self.classifyListView)
self.buttonBox = QtWidgets.QDialogButtonBox(Dialog)
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel
| QtWidgets.QDialogButtonBox.Ok)
self.buttonBox.setObjectName("buttonBox")
self.verticalLayout.addWidget(self.buttonBox)
self.retranslateUi(Dialog)
self.buttonBox.accepted.connect(Dialog.accept)
self.buttonBox.rejected.connect(Dialog.reject)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "编辑图像分类"))
self.label.setText(_translate("Dialog", "原分类"))
self.label_2.setText(_translate("Dialog", "新分类"))
self.searchButton.setText(_translate("Dialog", "查找"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
Dialog = QtWidgets.QDialog()
ui = Ui_Dialog()
ui.setupUi(Dialog)
Dialog.show()
sys.exit(app.exec_())
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'ui/MainWindow.ui'
#
# Created by: PyQt5 UI code generator 5.15.5
#
# 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(833, 538)
MainWindow.setMinimumSize(QtCore.QSize(0, 0))
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.centralwidget)
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
self.appMenuBtn = QtWidgets.QToolButton(self.centralwidget)
self.appMenuBtn.setObjectName("appMenuBtn")
self.horizontalLayout_3.addWidget(self.appMenuBtn)
self.saveImageLibraryBtn = QtWidgets.QToolButton(self.centralwidget)
self.saveImageLibraryBtn.setObjectName("saveImageLibraryBtn")
self.horizontalLayout_3.addWidget(self.saveImageLibraryBtn)
self.addClassifyBtn = QtWidgets.QToolButton(self.centralwidget)
self.addClassifyBtn.setObjectName("addClassifyBtn")
self.horizontalLayout_3.addWidget(self.addClassifyBtn)
self.removeClassifyBtn = QtWidgets.QToolButton(self.centralwidget)
self.removeClassifyBtn.setObjectName("removeClassifyBtn")
self.horizontalLayout_3.addWidget(self.removeClassifyBtn)
spacerItem = QtWidgets.QSpacerItem(40, 20,
QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_3.addItem(spacerItem)
self.imageScaleSlider = QtWidgets.QSlider(self.centralwidget)
self.imageScaleSlider.setMaximumSize(QtCore.QSize(400, 16777215))
self.imageScaleSlider.setMinimum(1)
self.imageScaleSlider.setMaximum(8)
self.imageScaleSlider.setPageStep(2)
self.imageScaleSlider.setOrientation(QtCore.Qt.Horizontal)
self.imageScaleSlider.setObjectName("imageScaleSlider")
self.horizontalLayout_3.addWidget(self.imageScaleSlider)
self.verticalLayout_3.addLayout(self.horizontalLayout_3)
self.splitter = QtWidgets.QSplitter(self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(
self.splitter.sizePolicy().hasHeightForWidth())
self.splitter.setSizePolicy(sizePolicy)
self.splitter.setOrientation(QtCore.Qt.Horizontal)
self.splitter.setObjectName("splitter")
self.widget = QtWidgets.QWidget(self.splitter)
self.widget.setObjectName("widget")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.widget)
self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.searchClassifyHistoryCmb = QtWidgets.QComboBox(self.widget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(
self.searchClassifyHistoryCmb.sizePolicy().hasHeightForWidth())
self.searchClassifyHistoryCmb.setSizePolicy(sizePolicy)
self.searchClassifyHistoryCmb.setEditable(True)
self.searchClassifyHistoryCmb.setObjectName("searchClassifyHistoryCmb")
self.horizontalLayout.addWidget(self.searchClassifyHistoryCmb)
self.searchClassifyBtn = QtWidgets.QToolButton(self.widget)
self.searchClassifyBtn.setObjectName("searchClassifyBtn")
self.horizontalLayout.addWidget(self.searchClassifyBtn)
self.verticalLayout_2.addLayout(self.horizontalLayout)
self.classifyListView = QtWidgets.QListView(self.widget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(
self.classifyListView.sizePolicy().hasHeightForWidth())
self.classifyListView.setSizePolicy(sizePolicy)
self.classifyListView.setMinimumSize(QtCore.QSize(200, 0))
self.classifyListView.setEditTriggers(
QtWidgets.QAbstractItemView.NoEditTriggers)
self.classifyListView.setObjectName("classifyListView")
self.verticalLayout_2.addWidget(self.classifyListView)
self.widget1 = QtWidgets.QWidget(self.splitter)
self.widget1.setObjectName("widget1")
self.verticalLayout = QtWidgets.QVBoxLayout(self.widget1)
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.verticalLayout.setObjectName("verticalLayout")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.addImageBtn = QtWidgets.QToolButton(self.widget1)
self.addImageBtn.setObjectName("addImageBtn")
self.horizontalLayout_2.addWidget(self.addImageBtn)
self.removeImageBtn = QtWidgets.QToolButton(self.widget1)
self.removeImageBtn.setObjectName("removeImageBtn")
self.horizontalLayout_2.addWidget(self.removeImageBtn)
spacerItem1 = QtWidgets.QSpacerItem(40, 20,
QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem1)
self.verticalLayout.addLayout(self.horizontalLayout_2)
self.imageListWidget = QtWidgets.QListWidget(self.widget1)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(
self.imageListWidget.sizePolicy().hasHeightForWidth())
self.imageListWidget.setSizePolicy(sizePolicy)
self.imageListWidget.setMinimumSize(QtCore.QSize(200, 0))
self.imageListWidget.setStyleSheet(
"QListWidget::Item:hover{background:skyblue;padding-top:0px; padding-bottom:0px;}\n"
"QListWidget::item:selected{background:rgb(245, 121, 0); color:red;}"
)
self.imageListWidget.setObjectName("imageListWidget")
self.verticalLayout.addWidget(self.imageListWidget)
self.verticalLayout_3.addWidget(self.splitter)
MainWindow.setCentralWidget(self.centralwidget)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "识图图像库管理"))
self.appMenuBtn.setText(_translate("MainWindow", "..."))
self.saveImageLibraryBtn.setText(_translate("MainWindow", "..."))
self.addClassifyBtn.setText(_translate("MainWindow", "..."))
self.removeClassifyBtn.setText(_translate("MainWindow", "..."))
self.searchClassifyBtn.setText(_translate("MainWindow", "..."))
self.addImageBtn.setText(_translate("MainWindow", "..."))
self.removeImageBtn.setText(_translate("MainWindow", "..."))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'ui/NewlibraryDialog.ui'
#
# Created by: PyQt5 UI code generator 5.15.5
#
# 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_NewlibraryDialog(object):
def setupUi(self, NewlibraryDialog):
NewlibraryDialog.setObjectName("NewlibraryDialog")
NewlibraryDialog.resize(414, 230)
self.verticalLayout = QtWidgets.QVBoxLayout(NewlibraryDialog)
self.verticalLayout.setObjectName("verticalLayout")
self.label = QtWidgets.QLabel(NewlibraryDialog)
self.label.setObjectName("label")
self.verticalLayout.addWidget(self.label)
self.indexMethodCmb = QtWidgets.QComboBox(NewlibraryDialog)
self.indexMethodCmb.setEnabled(True)
self.indexMethodCmb.setObjectName("indexMethodCmb")
self.indexMethodCmb.addItem("")
self.indexMethodCmb.addItem("")
self.indexMethodCmb.addItem("")
self.verticalLayout.addWidget(self.indexMethodCmb)
self.resetCheckBox = QtWidgets.QCheckBox(NewlibraryDialog)
self.resetCheckBox.setObjectName("resetCheckBox")
self.verticalLayout.addWidget(self.resetCheckBox)
spacerItem = QtWidgets.QSpacerItem(20, 80,
QtWidgets.QSizePolicy.Minimum,
QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem)
self.buttonBox = QtWidgets.QDialogButtonBox(NewlibraryDialog)
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel
| QtWidgets.QDialogButtonBox.Ok)
self.buttonBox.setObjectName("buttonBox")
self.verticalLayout.addWidget(self.buttonBox)
self.retranslateUi(NewlibraryDialog)
self.indexMethodCmb.setCurrentIndex(0)
self.buttonBox.accepted.connect(NewlibraryDialog.accept)
self.buttonBox.rejected.connect(NewlibraryDialog.reject)
QtCore.QMetaObject.connectSlotsByName(NewlibraryDialog)
def retranslateUi(self, NewlibraryDialog):
_translate = QtCore.QCoreApplication.translate
NewlibraryDialog.setWindowTitle(
_translate("NewlibraryDialog", "新建/重建 索引"))
self.label.setText(_translate("NewlibraryDialog", "索引方式"))
self.indexMethodCmb.setItemText(
0, _translate("NewlibraryDialog", "HNSW32"))
self.indexMethodCmb.setItemText(1,
_translate("NewlibraryDialog", "FLAT"))
self.indexMethodCmb.setItemText(2, _translate("NewlibraryDialog",
"IVF"))
self.resetCheckBox.setText(
_translate("NewlibraryDialog", "重建索引,警告:会覆盖原索引"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
NewlibraryDialog = QtWidgets.QDialog()
ui = Ui_NewlibraryDialog()
ui.setupUi(NewlibraryDialog)
NewlibraryDialog.show()
sys.exit(app.exec_())
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'ui/RenameClassifyDialog.ui'
#
# Created by: PyQt5 UI code generator 5.15.5
#
# 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_RenameClassifyDialog(object):
def setupUi(self, RenameClassifyDialog):
RenameClassifyDialog.setObjectName("RenameClassifyDialog")
RenameClassifyDialog.resize(342, 194)
self.verticalLayout = QtWidgets.QVBoxLayout(RenameClassifyDialog)
self.verticalLayout.setObjectName("verticalLayout")
self.oldlabel = QtWidgets.QLabel(RenameClassifyDialog)
self.oldlabel.setObjectName("oldlabel")
self.verticalLayout.addWidget(self.oldlabel)
self.oldNameLineEdit = QtWidgets.QLineEdit(RenameClassifyDialog)
self.oldNameLineEdit.setEnabled(False)
self.oldNameLineEdit.setObjectName("oldNameLineEdit")
self.verticalLayout.addWidget(self.oldNameLineEdit)
self.newlabel = QtWidgets.QLabel(RenameClassifyDialog)
self.newlabel.setObjectName("newlabel")
self.verticalLayout.addWidget(self.newlabel)
self.newNameLineEdit = QtWidgets.QLineEdit(RenameClassifyDialog)
self.newNameLineEdit.setObjectName("newNameLineEdit")
self.verticalLayout.addWidget(self.newNameLineEdit)
spacerItem = QtWidgets.QSpacerItem(20, 14,
QtWidgets.QSizePolicy.Minimum,
QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem)
self.buttonBox = QtWidgets.QDialogButtonBox(RenameClassifyDialog)
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel
| QtWidgets.QDialogButtonBox.Ok)
self.buttonBox.setObjectName("buttonBox")
self.verticalLayout.addWidget(self.buttonBox)
self.retranslateUi(RenameClassifyDialog)
self.buttonBox.accepted.connect(RenameClassifyDialog.accept)
self.buttonBox.rejected.connect(RenameClassifyDialog.reject)
QtCore.QMetaObject.connectSlotsByName(RenameClassifyDialog)
def retranslateUi(self, RenameClassifyDialog):
_translate = QtCore.QCoreApplication.translate
RenameClassifyDialog.setWindowTitle(
_translate("RenameClassifyDialog", "重命名分类"))
self.oldlabel.setText(_translate("RenameClassifyDialog", "原名称"))
self.newlabel.setText(_translate("RenameClassifyDialog", "新名称"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
RenameClassifyDialog = QtWidgets.QDialog()
ui = Ui_RenameClassifyDialog()
ui.setupUi(RenameClassifyDialog)
RenameClassifyDialog.show()
sys.exit(app.exec_())
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'ui/WaitDialog.ui'
#
# Created by: PyQt5 UI code generator 5.15.5
#
# 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_WaitDialog(object):
def setupUi(self, WaitDialog):
WaitDialog.setObjectName("WaitDialog")
WaitDialog.setWindowModality(QtCore.Qt.NonModal)
WaitDialog.resize(324, 78)
self.verticalLayout = QtWidgets.QVBoxLayout(WaitDialog)
self.verticalLayout.setObjectName("verticalLayout")
self.msgLabel = QtWidgets.QLabel(WaitDialog)
self.msgLabel.setObjectName("msgLabel")
self.verticalLayout.addWidget(self.msgLabel)
self.progressBar = QtWidgets.QProgressBar(WaitDialog)
self.progressBar.setMaximum(0)
self.progressBar.setProperty("value", -1)
self.progressBar.setObjectName("progressBar")
self.verticalLayout.addWidget(self.progressBar)
spacerItem = QtWidgets.QSpacerItem(20, 1,
QtWidgets.QSizePolicy.Minimum,
QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem)
self.retranslateUi(WaitDialog)
QtCore.QMetaObject.connectSlotsByName(WaitDialog)
def retranslateUi(self, WaitDialog):
_translate = QtCore.QCoreApplication.translate
WaitDialog.setWindowTitle(_translate("WaitDialog", "请等待"))
self.msgLabel.setText(_translate("WaitDialog", "正在更新索引库,请等待。。。"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
WaitDialog = QtWidgets.QDialog()
ui = Ui_WaitDialog()
ui.setupUi(WaitDialog)
WaitDialog.show()
sys.exit(app.exec_())
import os
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
import hashlib
import shutil
from mod import image_list_manager
def setMenu(menu: QtWidgets.QMenu, text: str, triggered):
"""设置菜单"""
action = menu.addAction(text)
action.triggered.connect(triggered)
def fileMD5(file_path: str):
"""计算文件的MD5值"""
md5 = hashlib.md5()
with open(file_path, 'rb') as f:
md5.update(f.read())
return md5.hexdigest().lower()
def copyFile(from_path: str, to_path: str):
"""复制文件"""
shutil.copyfile(from_path, to_path)
return os.path.exists(to_path)
def removeFile(file_path: str):
"""删除文件"""
if os.path.exists(file_path):
os.remove(file_path)
return not os.path.exists(file_path)
def fileExtension(file_path: str):
"""获取文件的扩展名"""
return os.path.splitext(file_path)[1]
def copyImageToDir(self, from_image_path: str, to_dir_path: str):
"""复制图像文件到目标目录"""
if not os.path.exists(from_image_path) and not os.path.exists(to_dir_path):
return None
md5 = fileMD5(from_image_path)
file_ext = fileExtension(from_image_path)
new_path = os.path.join(to_dir_path, md5 + file_ext)
copyFile(from_image_path, new_path)
return new_path
def oneKeyImportFromFile(from_path: str, to_path: str):
"""从其它图像库 from_path {image_list.txt} 导入到图像库 to_path {image_list.txt}"""
if not os.path.exists(from_path) or not os.path.exists(to_path):
return None
if from_path == to_path:
return None
from_mgr = image_list_manager.ImageListManager(file_path=from_path)
to_mgr = image_list_manager.ImageListManager(file_path=to_path)
return oneKeyImport(from_mgr=from_mgr, to_mgr=to_mgr)
def oneKeyImportFromDirs(from_dir: str, to_image_list_path: str):
"""从其它图像库 from_dir 搜索子目录 导入到图像库 to_image_list_path"""
if not os.path.exists(from_dir) or not os.path.exists(to_image_list_path):
return None
if from_dir == os.path.dirname(to_image_list_path):
return None
from_mgr = image_list_manager.ImageListManager()
to_mgr = image_list_manager.ImageListManager(
file_path=to_image_list_path)
from_mgr.dirName = from_dir
sub_dir_list = os.listdir(from_dir)
for sub_dir in sub_dir_list:
real_sub_dir = os.path.join(from_dir, sub_dir)
if not os.path.isdir(real_sub_dir):
continue
img_list = os.listdir(real_sub_dir)
img_path = []
for img in img_list:
real_img = os.path.join(real_sub_dir, img)
if not os.path.isfile(real_img):
continue
img_path.append("{}/{}".format(sub_dir, img))
if len(img_path) == 0:
continue
from_mgr.addClassify(sub_dir)
from_mgr.resetImageList(sub_dir, img_path)
return oneKeyImport(from_mgr=from_mgr, to_mgr=to_mgr)
def oneKeyImport(from_mgr: image_list_manager.ImageListManager,
to_mgr: image_list_manager.ImageListManager):
"""一键导入"""
count = 0
for classify in from_mgr.classifyList:
img_list = from_mgr.realPathList(classify)
to_mgr.addClassify(classify)
to_img_list = to_mgr.imageList(classify)
new_img_list = []
for img in img_list:
from_image_path = img
to_dir_path = os.path.join(to_mgr.dirName, "images")
md5 = fileMD5(from_image_path)
file_ext = fileExtension(from_image_path)
new_path = os.path.join(to_dir_path, md5 + file_ext)
if os.path.exists(new_path):
# 如果新文件 MD5 重复跳过后面的复制文件操作
continue
copyFile(from_image_path, new_path)
new_img_list.append("images/" + md5 + file_ext)
count += 1
to_img_list += new_img_list
to_mgr.resetImageList(classify, to_img_list)
to_mgr.writeFile()
return count
def newFile(file_path: str):
"""创建文件"""
if os.path.exists(file_path):
return False
else:
with open(file_path, 'w') as f:
pass
return True
def isEmptyDir(dir_path: str):
"""判断目录是否为空"""
return not os.listdir(dir_path)
def initLibrary(dir_path: str):
"""初始化库"""
images_dir = os.path.join(dir_path, "images")
if not os.path.exists(images_dir):
os.makedirs(images_dir)
image_list_path = os.path.join(dir_path, "image_list.txt")
newFile(image_list_path)
return os.path.exists(dir_path)
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AddClassifyDialog</class>
<widget class="QDialog" name="AddClassifyDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>286</width>
<height>127</height>
</rect>
</property>
<property name="windowTitle">
<string>添加分类</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>分类名称</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit"/>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>11</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>AddClassifyDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>AddClassifyDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>414</width>
<height>415</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>编辑图像分类</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>原分类</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="oldLineEdit">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>新分类</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="newLineEdit">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="searchWordLineEdit"/>
</item>
<item>
<widget class="QPushButton" name="searchButton">
<property name="text">
<string>查找</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QListView" name="classifyListView">
<property name="enabled">
<bool>true</bool>
</property>
<property name="minimumSize">
<size>
<width>400</width>
<height>200</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>Dialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Dialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>833</width>
<height>538</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>识图图像库管理</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QToolButton" name="appMenuBtn">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="saveImageLibraryBtn">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="addClassifyBtn">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="removeClassifyBtn">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QSlider" name="imageScaleSlider">
<property name="maximumSize">
<size>
<width>400</width>
<height>16777215</height>
</size>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>8</number>
</property>
<property name="pageStep">
<number>2</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QSplitter" name="splitter">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QWidget" name="">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QComboBox" name="searchClassifyHistoryCmb">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="searchClassifyBtn">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QListView" name="classifyListView">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QToolButton" name="addImageBtn">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="removeImageBtn">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QListWidget" name="imageListWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">QListWidget::Item:hover{background:skyblue;padding-top:0px; padding-bottom:0px;}
QListWidget::item:selected{background:rgb(245, 121, 0); color:red;}</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>NewlibraryDialog</class>
<widget class="QDialog" name="NewlibraryDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>414</width>
<height>230</height>
</rect>
</property>
<property name="windowTitle">
<string>新建/重建 索引</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>索引方式</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="indexMethodCmb">
<property name="enabled">
<bool>true</bool>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<item>
<property name="text">
<string>HNSW32</string>
</property>
</item>
<item>
<property name="text">
<string>FLAT</string>
</property>
</item>
<item>
<property name="text">
<string>IVF</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QCheckBox" name="resetCheckBox">
<property name="text">
<string>重建索引,警告:会覆盖原索引</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>80</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>NewlibraryDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>NewlibraryDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RenameClassifyDialog</class>
<widget class="QDialog" name="RenameClassifyDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>342</width>
<height>194</height>
</rect>
</property>
<property name="windowTitle">
<string>重命名分类</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="oldlabel">
<property name="text">
<string>原名称</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="oldNameLineEdit">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="newlabel">
<property name="text">
<string>新名称</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="newNameLineEdit"/>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>14</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>RenameClassifyDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>RenameClassifyDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>WaitDialog</class>
<widget class="QDialog" name="WaitDialog">
<property name="windowModality">
<enum>Qt::NonModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>324</width>
<height>78</height>
</rect>
</property>
<property name="windowTitle">
<string>请等待</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="msgLabel">
<property name="text">
<string>正在更新索引库,请等待。。。</string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="progressBar">
<property name="maximum">
<number>0</number>
</property>
<property name="value">
<number>-1</number>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>1</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册