diff --git a/api/BOcr.js b/api/BOcr.js index 2b9041f..6976d87 100644 --- a/api/BOcr.js +++ b/api/BOcr.js @@ -22,7 +22,7 @@ export const getBOcr = (url) => { }, { "type": "text", - "text": "要求准确无误的识别B超报告信息,然后请将B超报告单中的肝助下、肝剑突下、脾肋下、门静脉主干内径、肝回声、胆囊大小、胆总管、纤维块大小、门静脉流速、肝弹性值、有无囊肿、有无腹水、弹性成像最小值、弹性成像最大值、弹性成像平均值、弹性成像方差、弹性成像标准差、弹性成像中位数、弹性成像中值、弹性成像中位值、弹性成像中位数、弹性成像中位值、弹性成像中位数、弹性成像中位值、弹性成像中位数、弹性成像中位值、弹性成像中位数、弹性成像中位值、弹性成像中位数、弹性成像中位值、弹性成像中位数等信息提取出来,输出格式为 JSON 数组,每个元素包含 'name' 和 'value' 两个字段。例如:[{\"name\": \"肝助下\", \"value\": \"5.2\"}]。不要返回包含 rotate_rect、text 等字段的原始 OCR 结构化表格数据。" + "text": "要求准确无误的提取上述关键信息、不要遗漏和捏造虚假信息,模糊或者强光遮挡的单个文字可以用英文问号?代替。" } ] }] @@ -30,12 +30,12 @@ export const getBOcr = (url) => { success(res) { // console.log(res.data) // return - let data = parseJsonBlock(res.data.choices[0].message.content) + let data = parseBUltraReportText(res.data.choices[0].message.content) if(data.length == 0){ wx.showToast({ title: '识别失败,请重新选择照片!', icon: 'none', - duration: 2000 + duration: 5000 }); reject('识别失败!') return @@ -47,7 +47,7 @@ export const getBOcr = (url) => { wx.showToast({ title: '识别失败,请重新选择照片!', icon: 'none', - duration: 2000 + duration: 5000 }); reject('识别失败!') return @@ -137,5 +137,72 @@ function extractCheckItemsFromOcrTable(ocrArray) { }); if (name && value) result.push({ name, value }); }); + return result; +} + +/** + * 导出B超报告解析函数 + * @param {string} text - B超报告文本内容 + * @returns {Array} - 格式化的检测项目数组 + */ +export const parseBUltraReportText = (text) => { + return parseBUltraReport(text); +}; + +/** + * 解析B超报告文本内容,提取关键信息 + * @param {string} text - B超报告文本内容 + * @returns {Array} - 格式化的检测项目数组 + */ +function parseBUltraReport(text) { + if (!text) return []; + + const result = []; + const lines = text.split(/[\n\r]+/); + + lines.forEach(line => { + line = line.trim(); + if (!line) return; + + // 匹配 "名称: 数值" 格式,提取数值部分(不包含单位) + const colonMatch = line.match(/^([^::]+)[::]\s*(.+)$/); + if (colonMatch) { + const name = colonMatch[1].trim(); + let value = colonMatch[2].trim(); + + // 提取数值部分,去除单位 + const numberMatch = value.match(/^(\d+(?:\.\d+)?)/); + if (numberMatch) { + value = numberMatch[1]; + } + + if (name && value) { + result.push({ name, value }); + } + return; + } + + // 匹配包含数值的项目(如 "肝肋下: 32mm"),只提取数值 + const valueMatch = line.match(/([^0-9]+?)(\d+(?:\.\d+)?)/); + if (valueMatch) { + const name = valueMatch[1].trim(); + const value = valueMatch[2].trim(); + if (name && value) { + result.push({ name, value }); + } + return; + } + + // 处理特殊格式,如 "未触及"、"显示不清" 等 + const specialMatch = line.match(/^([^::]+)[::]\s*(未触及|显示不清|.+)$/); + if (specialMatch) { + const name = specialMatch[1].trim(); + const value = specialMatch[2].trim(); + if (name && value) { + result.push({ name, value }); + } + } + }); + return result; } \ No newline at end of file diff --git a/api/drugOcr.js b/api/drugOcr.js index 6a11e37..9bc6feb 100644 --- a/api/drugOcr.js +++ b/api/drugOcr.js @@ -34,7 +34,7 @@ export const getOcr = (url) => { wx.showToast({ title: '识别失败,请重新选择照片!', icon: 'none', - duration: 2000 + duration: 5000 }); reject('识别失败,请重新选择照片!') return @@ -47,7 +47,7 @@ export const getOcr = (url) => { wx.showToast({ title: '识别失败,请重新选择照片!', icon: 'none', - duration: 2000 + duration: 5000 }); reject('识别失败!') return diff --git a/api/ocr copy 2.js b/api/ocr copy 2.js new file mode 100644 index 0000000..56b9bd8 --- /dev/null +++ b/api/ocr copy 2.js @@ -0,0 +1,139 @@ +export const getOcr = (url) => { + return new Promise((resolve, reject) => { + wx.request({ + url: 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions', + method: 'POST', + dataType: 'json', // 微信官方文档中介绍会对数据进行一次JSON.parse + header: { + 'Authorization': 'Bearer sk-52414b887aee47e4883caf16cbf801bd', + 'Content-Type': 'application/json' + }, + data: { + "model": "qwen-vl-ocr-latest", + "messages": [{ + "role": "user", + "content": [{ + "type": "image_url", + "image_url": { + "url": url + }, + "min_pixels": 3136, + "max_pixels": 6422528 + }, + { + "type": "text", + "text": "请只返回提取好的检查项目及其结果,格式为 JSON 数组,每个元素包含 'name' 和 'value' 两个字段。例如:[{\"name\": \"白细胞\", \"value\": \"5.2\"}]。不要返回包含 rotate_rect、text 等字段的原始 OCR 结构化表格数据。" + } + ] + }] + }, + success(res) { + let data = parseJsonBlock(res.data.choices[0].message.content) + if(data.length == 0){ + wx.showToast({ + title: '识别失败,请选择带有符合表单字段的照片!', + icon: 'none', + duration: 5000 + }); + reject('识别失败!') + return + } + // 判断是否为 rotate_rect 格式 + if (Array.isArray(data) && data.length && data[0].rotate_rect) { + let items = extractCheckItemsFromOcrTable(data); + if(items.length==0){ + wx.showToast({ + title: '识别失败,请选择带有符合表单字段的照片!', + icon: 'none', + duration: 5000 + }); + reject('识别失败!') + return + } + resolve(items); + return; + } + resolve(data); + + }, + fail(err) { + wx.showToast({ + title: '识别失败,请选择带有符合表单字段的照片!', + icon: 'none', + duration: 5000 + }) + console.log(err) + // 断网、服务器挂了都会fail回调,直接reject即可 + reject(err); + }, + }); + }) +} + + + +/** + * 解析类似 ```json ... ``` 格式的字符串,提取检测项目数组 + * @param {string} str + * @returns {Array} + */ +function parseJsonBlock(str) { + // 匹配 ```json ... ``` 或 ``` ... ``` + const match = str.match(/```(?:json)?\s*([\s\S]*?)\s*```/i); + let jsonStr = match ? match[1] : str; + jsonStr = jsonStr.trim(); + + // 尝试直接解析 + try { + return JSON.parse(jsonStr); + } catch (e) { + // 替换单引号包裹的 key 和 value 为双引号 + let fixedStr = jsonStr + // 替换 key 的单引号(允许有空格、括号等) + .replace(/'([\w\u4e00-\u9fa5\(\)\s]+)'(?=\s*:)/g, '"$1"') + // 替换 value 的单引号(允许有空格、括号等,非贪婪匹配) + .replace(/:\s*'([^']*?)'/g, ': "$1"'); + try { + return JSON.parse(fixedStr); + + } catch (e2) { + console.error('JSON parse error:', e2, fixedStr); + return []; + } + } + +} + + +function extractCheckItemsFromOcrTable(ocrArray) { + // 1. 找到表头的 x 坐标 + const nameHeader = ocrArray.find(item => item.text.includes('检验项目')); + const valueHeader = ocrArray.find(item => item.text.includes('结果')); + if (!nameHeader || !valueHeader) return []; + + const nameX = nameHeader.rotate_rect[0]; + const valueX = valueHeader.rotate_rect[0]; + + // 2. 过滤掉表头,按 y 坐标分组(每一行) + const rows = {}; + ocrArray.forEach(item => { + if (item.text && !['检验项目', '结果', '单位', '提示', '参考区间'].includes(item.text)) { + // 以 y 坐标为 key,允许有一定误差(如±5) + const y = Math.round(item.rotate_rect[1] / 5) * 5; + if (!rows[y]) rows[y] = []; + rows[y].push(item); + } + }); + + // 3. 每一行找 name/value + const result = []; + Object.values(rows).forEach(items => { + let name = null, value = null; + items.forEach(item => { + if (Math.abs(item.rotate_rect[0] - nameX) < 50) name = item.text.trim(); + if (Math.abs(item.rotate_rect[0] - valueX) < 50) value = item.text.trim(); + }); + if (name && value) result.push({ name, value }); + }); + return result; +} \ No newline at end of file diff --git a/test_bultra_parser.js b/test_bultra_parser.js new file mode 100644 index 0000000..2b83e47 --- /dev/null +++ b/test_bultra_parser.js @@ -0,0 +1,34 @@ +// 测试B超报告解析功能 +import { parseBUltraReportText } from './api/BOcr.js'; + +// 测试数据 +const testText = `上海复旦大学儿科医院 +阴道屈笼中儿童 B秒 +肝肋下: 32mm +肝剑突下: 42mm +肝回声 +门静脉流速: 15.5cm/s +肝回声 +肝性性形影像像最大数字: 10.5ka +肝回声 +肝性性形影像像中测数字: 25.5ka +门静脉主干内径: 4.5mm +门静脉主干内径: 6.5mm +胆囊大小: 15.5mm×5.5mm +胆囊大小: 21mm×11mm +膀肋下: 未触及 +膀总管: 显示不清 +膀性声像:`; + +// 执行解析 +const result = parseBUltraReportText(testText); + +// 输出结果 +console.log('解析结果:'); +console.log(JSON.stringify(result, null, 2)); + +// 验证结果 +console.log('\n验证结果:'); +result.forEach((item, index) => { + console.log(`${index + 1}. ${item.name}: ${item.value}`); +}); \ No newline at end of file diff --git a/test_parser.js b/test_parser.js new file mode 100644 index 0000000..d0f6ffe --- /dev/null +++ b/test_parser.js @@ -0,0 +1,85 @@ +// 简单的B超报告解析测试 +const testText = `上海复旦大学儿科医院 +阴道屈笼中儿童 B秒 +肝肋下: 32mm +肝剑突下: 42mm +肝回声 +门静脉流速: 15.5cm/s +肝回声 +肝性性形影像像最大数字: 10.5ka +肝回声 +肝性性形影像像中测数字: 25.5ka +门静脉主干内径: 4.5mm +门静脉主干内径: 6.5mm +胆囊大小: 15.5mm×5.5mm +胆囊大小: 21mm×11mm +膀肋下: 未触及 +膀总管: 显示不清 +膀性声像:`; + +// 解析函数(简化版本) +function parseBUltraReport(text) { + if (!text) return []; + + const result = []; + const lines = text.split(/[\n\r]+/); + + lines.forEach(line => { + line = line.trim(); + if (!line) return; + + // 匹配 "名称: 数值" 格式,提取数值部分(不包含单位) + const colonMatch = line.match(/^([^::]+)[::]\s*(.+)$/); + if (colonMatch) { + const name = colonMatch[1].trim(); + let value = colonMatch[2].trim(); + + // 提取数值部分,去除单位 + const numberMatch = value.match(/^(\d+(?:\.\d+)?)/); + if (numberMatch) { + value = numberMatch[1]; + } + + if (name && value) { + result.push({ name, value }); + } + return; + } + + // 匹配包含数值的项目(如 "肝肋下: 32mm"),只提取数值 + const valueMatch = line.match(/([^0-9]+?)(\d+(?:\.\d+)?)/); + if (valueMatch) { + const name = valueMatch[1].trim(); + const value = valueMatch[2].trim(); + if (name && value) { + result.push({ name, value }); + } + return; + } + + // 处理特殊格式,如 "未触及"、"显示不清" 等 + const specialMatch = line.match(/^([^::]+)[::]\s*(未触及|显示不清|.+)$/); + if (specialMatch) { + const name = specialMatch[1].trim(); + const value = specialMatch[2].trim(); + if (name && value) { + result.push({ name, value }); + } + } + }); + + return result; +} + +// 执行解析 +const result = parseBUltraReport(testText); + +// 输出结果 +console.log('解析结果(value不包含单位):'); +console.log(JSON.stringify(result, null, 2)); + +// 验证结果 +console.log('\n验证结果:'); +result.forEach((item, index) => { + console.log(`${index + 1}. ${item.name}: ${item.value}`); +}); \ No newline at end of file