// ============================================== // =============== 功能页:批量OCR =============== // ============================================== import QtQuick 2.15 import QtQuick.Controls 2.15 import ".." import "../../Widgets" import "../../Widgets/ResultLayout" import "../../Widgets/IgnoreArea" TabPage { id: tabPage // ========================= 【逻辑】 ========================= property int errorNum: 0 // 异常的任务个数 property string msnID: "" // 当前任务ID Component.onCompleted: { } // 异步加载一批图像路径 function addImages(paths) { if(ctrlPanel.state_ !== "stop") return if(paths.length <= 0) return const isRecurrence = configsComp.getValue("mission.recurrence") qmlapp.asynFilesLoader.run(paths,"image",isRecurrence,onAddImages) } // 完毕后,将合法路径加入表格 function onAddImages(paths) { for(let i in paths) { filesTableView.add({ path: paths[i], time: "", state: "" }) } } // 运行OCR function ocrStart() { let msnLength = filesTableView.rowCount if(msnLength <= 0) { ctrlPanel.stopFinished() return } // 刷新表格 for(let i = 0; i < msnLength; i++) { filesTableView.set(i, { time: "", state: qsTr("排队") }) } // 刷新计数 errorNum = 0 // 异常任务个数 // 开始运行 const paths = filesTableView.getColumnsValue("path") const argd = configsComp.getValueDict() msnID = tabPage.callPy("msnPaths", paths, argd) // 若tabPanel面板的下标没有变化过,则切换到记录页 if(tabPanel.indexChangeNum < 2) tabPanel.currentIndex = 1 ctrlPanel.runFinished(msnLength) } // 停止OCR function ocrStop() { _ocrStop() tabPage.callPy("msnStop") ctrlPanel.stopFinished() } function _ocrStop() { msnID = "" // 清除任务ID // 刷新表格,清空未执行的任务的状态 let msnLength = filesTableView.rowCount for(let i = 0; i < msnLength; i++) { const row = filesTableView.get(i) if(row.time === "") { filesTableView.setProperty(i, "state", "") } } } // 关闭页面 function closePage() { if(ctrlPanel.state_ !== "stop") { const argd = { yesText: qsTr("依然关闭") } const callback = (flag)=>{ if(flag) { ocrStop() delPage() } } qmlapp.popup.dialog("", qsTr("任务正在进行中。\n要结束任务并关闭页面吗?"), callback, "warning", argd) } else { delPage() } } // 预览 function msnPreview(path) { const argd = configsComp.getValueDict() tabPage.callPy("msnPreview", path, argd) } // ========================= 【python调用qml】 ========================= // 准备开始一个任务 function onOcrReady(path) { // 刷新表格显示 filesTableView.setProperty(path, "state", qsTr("处理")) } // 获取一个OCR的返回值 function onOcrGet(path, res) { const time = res.time.toFixed(2) let state = "" switch(res.code){ case 100: state = "√ "+res.score.toFixed(2);break case 101: state = "√ ---- ";break default: state = "× "+res.code errorNum++ // 异常任务数量+1 break } // 刷新表格显示 filesTableView.set(path, { "time": time, "state": state }) // 提取文字,添加到结果表格 res.title = res.fileName resultsTableView.addOcrResult(res) ctrlPanel.msnStep(1) // 任务计数器步进 } // 任务队列完毕 function onOcrEnd(msg, thisMsnID) { // msg: [Success] [Warning] [Error] if(msnID !== thisMsnID) { // 返回的任务ID不等于前端展示的任务ID,则不处理 return } _ocrStop() // 任务成功 if(msg.startsWith("[Success]")) { let errMsg = "" if(errorNum > 0) { // 有异常任务 errMsg = qsTr("%1 张图片识别失败!").arg(errorNum) } const simpleType = configsComp.getValue("other.simpleNotificationType") qmlapp.popup.simple(qsTr("批量识别完成"), errMsg, simpleType) // 任务完成后续操作 qmlapp.globalConfigs.utilsDicts.postTaskHardwareCtrl( configsComp.getValue("postTaskActions.system") ) } // 任务失败 else if(msg.startsWith("[Error]")) { qmlapp.popup.message(qsTr("批量识别任务异常"), msg, "error") } ctrlPanel.stopFinished() } // 预览 function onPreview(path, res) { ignoreArea.getPreview(res, path, "") } // 路径转文件名 function path2name(path) { const parts = path.split("/") return parts[parts.length - 1] } // 文件表格中单击路径 function onClickPath(index) { let info = filesTableView.get(index) let fileName = path2name(info.path) let res = resultsTableView.getResult(fileName) let data = undefined, text = undefined if(res) { if(res.source) data = JSON.parse(res.source) if(res.resText) text = res.resText } previewImage.show(info.path, data, text) } // ========================= 【布局】 ========================= // 配置 configsComp: BatchOCRConfigs { // 点按钮打开忽略区域 onClickIgnoreArea: { if(filesTableView.rowCount > 0) { const path = filesTableView.get(0).path console.log("打开路径", path) ignoreArea.showPath(path) } else { ignoreArea.show() } } } // 主区域:可切换双栏面板 DoubleSwitchableLayout { id: doubleLayout saveKey: "BatchOCR_1" anchors.fill: parent // 面板A:控制板+文件表格 itemA: Panel { anchors.fill: parent // 上方控制板 MissionCtrlPanel { id: ctrlPanel anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right anchors.margins: size_.spacing height: size_.line * 2 onRunClicked: tabPage.ocrStart() onPauseClicked: { tabPage.callPy("msnPause") pauseFinished() } onResumeClicked: { tabPage.callPy("msnResume") resumeFinished() } onStopClicked: tabPage.ocrStop() } // 下方文件表格 FilesTableView { id: filesTableView anchors.top: ctrlPanel.bottom anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom anchors.margins: size_.spacing anchors.topMargin: size_.smallSpacing headers: [ {key: "path", title: qsTr("图片"), left: true, display:path2name, btn: true, onClicked:onClickPath}, {key: "time", title: qsTr("耗时"), }, {key: "state", title: qsTr("状态"), }, ] openBtnText: qsTr("打开图片") clearBtnText: qsTr("清空") defaultTips: qsTr("拖入图片或文件夹") fileDialogTitle: qsTr("请选择图片") fileDialogNameFilters: [qsTr("图片")+" (*.jpg *.jpe *.jpeg *.jfif *.png *.webp *.bmp *.tif *.tiff)"] isLock: ctrlPanel.state_ !== "stop" onAddPaths: { tabPage.addImages(paths) } } } // 面板B:文字输出 & 设置 itemB: Panel { anchors.fill: parent // 配置项控制板 TabPanel { id: tabPanel anchors.fill: parent anchors.margins: size_.spacing isMenuTop: doubleLayout.isRow // 左右布局时,菜单在顶部;上下布局时菜单在底部 menuHeight: size_.line * 2 // 结果面板 ResultsTableView { id: resultsTableView anchors.fill: parent visible: false } tabsModel: [ { "key": "configs", "title": qsTr("设置"), "component": configsComp.panelComponent, }, { "key": "ocrResult", "title": qsTr("记录"), "component": resultsTableView, }, ] } } } // 鼠标拖入图片 DropArea_ { anchors.fill: parent callback: tabPage.addImages } // 预览面板 PreviewImage { id: previewImage anchors.fill: parent } // 忽略区域编辑器 IgnoreArea { id: ignoreArea anchors.fill: parent pathPreview: msnPreview configsComp: tabPage.configsComp configKey: "tbpu.ignoreArea" } }