This commit is contained in:
@zuopngfei 2025-08-04 18:11:46 +08:00
parent 240e99a962
commit 746614c2cc
5 changed files with 331 additions and 6 deletions

View File

@ -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
@ -139,3 +139,70 @@ function extractCheckItemsFromOcrTable(ocrArray) {
});
return result;
}
/**
* 导出B超报告解析函数
* @param {string} text - B超报告文本内容
* @returns {Array<Object>} - 格式化的检测项目数组
*/
export const parseBUltraReportText = (text) => {
return parseBUltraReport(text);
};
/**
* 解析B超报告文本内容提取关键信息
* @param {string} text - B超报告文本内容
* @returns {Array<Object>} - 格式化的检测项目数组
*/
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;
}

View File

@ -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

139
api/ocr copy 2.js Normal file
View File

@ -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<Object>}
*/
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;
}

34
test_bultra_parser.js Normal file
View File

@ -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}`);
});

85
test_parser.js Normal file
View File

@ -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}`);
});