chatMed开发日志博客(持续更新中)

目录

1. 项目概述

2. 开发人员团队

3. 大致需求

4. 开发内容

4.1. 前端开发

4.1.1: 前端页面开发

4.1.2: 登录机制以及路由守卫的开发

4.1.3: 文件上传机制和保存机制

4.1.4: 消息传递机制'

4.2. 线程池开发

4.3. 在线调试


1. 项目概述

 搭建一个基于深度学习的分析平台, 使研究人员能够使用先进的深度学习架构, 来进行多模态医学影像的分析. 在完成模型多模态交互的同时, 为医护人员提供快捷便利的医学影响参考平台, 减轻医生的工作压力.

本项目采用人机交互设计原则和用户体验研究,设计了直观、简洁、易用的界面,并提供了个性化的用户操作,以增强医生和患则对系统的接受度和使用意愿。


本项目能处理多种不同模态的医学影像数据,如MRI、CT、X光等,以及临床数据,如患者的病史、症状等。有效地整合不同模态的信息,提取更丰富和准确的特征,进一步提高诊断和治疗的效果。


本项目通过多轮对话交互提供更有效的医学影像分析结果,支持病灶分割、疾病分类、病变检测以及疾病进展监测,为医生和患者提供了更好的医疗服务。


本项目能够解决百姓日常生活中大多数的医学问答、健康科普、健康管理等相关问题,采用自然语言处理技术和深度学习算法,通过大模型训练方法,形成人工智能“医生大脑”。

2. 开发人员团队

5人(tg, wyx, wyt, cjy, xpk), 分别负责开发, 测试以及产品设计等等工作.

3. 大致需求

搭建一个基于深度学习的分析平台, 使研究人员能够使用先进的深度学习架构, 来进行多模态医学影像的分析.

4. 开发内容

4.1. 前端开发

前端使用react架构实现开发, 最终预计在服务器环境上完成部署, 下方的目录顺序并非实际开发顺序, 每个部分均有所更新.

4.1.1: 前端页面开发

前端页面使用react开发, 采用组件式开发模式:

分成如图所示的多个界面, 最终效果如图所示:

内部功能如图所示:

4.1.2: 登录机制以及路由守卫的开发

首先先创建一个组件用来保存登录状态并将状态传递给下方的每一个组件, 在组件包括了跟组件

import { createContext, useState } from 'react';export const AuthContext = createContext();export const AuthProvider = ({ children }) => {const [isLoggedIn, setIsLoggedIn] = useState(false);const [session, setSession]=useState('')const [email, setUserEmail]=useState('')const login = () => {setIsLoggedIn(true);};const logout = () => {setIsLoggedIn(false);};return (//传递数据为登录信息, 登入和登出操作, 会话id, 用户邮箱, 以及对应的修改操作<AuthContext.Provider value={{ isLoggedIn,login, logout,session,email,setUserEmail, setSession }}>{children} </AuthContext.Provider>);
};

 将组件状态传递给下方的每一个组件

import logo from './logo.svg';
import './App.css';
import Home from './pages/home';
import { BrowserRouter, Routes,Route } from 'react-router-dom';
import Login from './pages/login';
import About from './pages/about';
import { AuthProvider } from './Auth';
import ProtectedRouter from './components/ProtectRouter';
import Chat from './pages/chat';
import Register from './pages/register';function App() {return (<AuthProvider>{/*根组件用来传递状态和登录信息*/}<BrowserRouter><Routes><Route path='/' element={<Home/>} /><Route path='/login' element={<Login/>} /><Route path='/register' element={<Register/>} /><Route path='/chat' element={<ProtectedRouter Component={()=><Chat/>}/>}/><Route path='/about' element={<ProtectedRouter Component={()=><About/>}/>}/></Routes></BrowserRouter></AuthProvider>);
}export default App;

在一些需要登录的路由上创建一个路由守卫组件, 该组件会检测到用户是否正在登录状态, 如果不是, 则会强制跳转到登录界面. 

import { useContext } from 'react';
import { Navigate } from 'react-router-dom';
import { BrowserRouter, Routes,Route } from 'react-router-dom';
import About from '../pages/about';
import { AuthContext } from '../Auth';const ProtectedRouter = ({Component}) => { //传进来的对象以及其他的属性const { isLoggedIn } = useContext(AuthContext); //检查登录状态return (isLoggedIn ?  <Component/> :  <Navigate to={{ pathname: '/login' }} /> //如果已经登录了, 就展示情况);
};export default ProtectedRouter;

4.1.3: 文件上传机制和保存机制

在最开始的时候尝试过使用单独组件封装的方式来实现文件的上传,

import { Upload, message } from 'antd';
import React from 'react';function getBase64(img, callback) {const reader = new FileReader();reader.addEventListener('load', () => callback(reader.result));reader.readAsDataURL(img);
}function beforeUpload(file) {const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';if (!isJpgOrPng) {message.error('You can only upload JPG/PNG file!');}const isLt2M = file.size / 1024 / 1024 < 2;if (!isLt2M) {message.error('Image must smaller than 2MB!');}return isJpgOrPng && isLt2M;
}class Avatar extends React.Component {state = {loading: false,};handleChange = info => {if (info.file.status === 'uploading') {this.setState({ loading: true });return;}if (info.file.status === 'done') {// Get this url from response in real world.getBase64(info.file.originFileObj, imageUrl =>this.setState({imageUrl,loading: false,}),);}};render() {const uploadButton = (<div><div className="ant-upload-text">Upload</div></div>);const { imageUrl } = this.state;return (<Uploadname="avatar"listType="picture-card"className="avatar-uploader"showUploadList={false}action="https://www.mocky.io/v2/5cc8019d300000980a055e76"beforeUpload={beforeUpload}onChange={this.handleChange}>{imageUrl ? <img src={imageUrl} alt="avatar" style={{ width: '100%' }} /> : uploadButton}</Upload>);}
}export default Avatar 

 但是后续出现了很多问题, 所以改用原生来实现提交

 {(!imgUploaded) &&//自定义一个文件输入框<button style={{border: 'solid 5px #284B63',borderRadius: 100,height: 60,width: '100%',color:'#284B63',fontSize:20,fontWeight: 700,marginBottom:20}} onClick={()=>chooseFile()}>choose File</button>}{/*文件上传按钮, 隐藏原始的文件输入, 然后自定义一个*/}<input type="file" id="fileInput" accept="*" style={{margin:10,display:'none'}}/>{/*图片展示*/}{hasImg && <div  style={{height:256,width:256,justifyContent:'center',alignContent:'center'}}><img id="previewImage" alt="Preview" style={imgUploaded?{height:256,width:256}:{height:218,width:218}}></img></div>}{/*文件发送到后端的按钮*/}{hasImg && (!imgUploaded) && <button onClick={()=>sendImage()} style={{border: 'solid 5px #284B63',borderRadius: 100,height: 60,width: '100%',color:'#284B63',color:'#284B63',fontSize:20,fontWeight: 700,marginTop:20}}>Init dialog</button>}</div>

 为了修改原有的ui按钮, 我们将原本的提交机制给隐藏, 再通过js进行点击事件的模拟触发, 最终实现图片的上传和展示, 图片的命名方式为img+会话id

//文件上传对象const fileInput = document.getElementById('fileInput');  //设置监听对象fileInput.addEventListener('change', (event) => {  const file = event.target.files[0];  const previewImage = document.getElementById('previewImage'); if (file) {  if (file.type.startsWith('image/')) { setHasImg(true)  const reader = new FileReader();  reader.onload = (e) => {  // 读取完成后,数据URL会作为结果  previewImage.src = e.target.result;  };  // 以Data URL的形式读取文件  reader.readAsDataURL(file);  } else {  alert('please choose a image file!');  }  }})

4.1.4: 消息传递机制'

消息传递大致为以下三种

指令序号内容
0启动对话并将会话id传递给前后端
1停止会话
2正常的通讯消息
3传递图片

前端的websocket数据监听

    const wsMessageHandler = (message) => {const data = jsonToObject(message.data);if (data.type === 0){ //后端返回一个登录状态setId(data.id); setSession(data.id) //设置登录状态,知道会话id}else if (data.type === 1) {console.log('当前问诊已经结束');}else if (data.type === 2) {console.log('模型消息回复为', String(data.message));appendList(String(data.message)) //是这个的问题, 强制刷新消息setDialogShow(false)}else if (data.type === 3) {console.log('图片的存储地点为', data.url);}};

 后端ws数据监听

if(conn){                       //如果链接对象已经创建work=conn.worker            //获取线程链接if(data.type===2) {         //发送到ws的指令为2, 则把数据传下去work.postMessage(data)}else if(data.type===1){    //接收到1指令console.log('会话'+data.id+'已经停止')work.postMessage(data)  //向线程发送停止信息workers.delete(data.id) //从线程队列中删除会话}else if(data.type===3){    // 将Base64编码的字符串转换为Bufferconst imageBuffer = Buffer.from(data.data.split(';base64,').pop(), 'base64');// 指定要保存图像的文件路径和文件名const imagePath = './imgs/img'+data.id+'.png';// 将图像数据写入文件fs.writeFile(imagePath, imageBuffer, (err) => {if (err) {console.error('Error saving image:', err); return; }});// 将图片的保存地址传递给子进程work.postMessage({...data,url:imagePath})}}

后端监听子线程的消息

worker.on('message', (message) => {console.log(message)conn=workers.get(message.id)if(message.type===2){conn.getWebSocket().send(objectToJson(message))}else if(message.type===1){workers.delete(message.id) //从维护队列中删除conn.getWebSocket().send(objectToJson(message)) //将数据发送给前端}else if(message.type===3){conn.getWebSocket().send(objectToJson(message)) //将数据发送给前端}})

4.2. 线程池开发

为了更好的实现用户的隔离和资源管理, 我们使用线程池的方式来实现前端-后端-模型的交互, 再开始阶段, 我们使用脚本来代替模型的功能

# 导入模块
import sys
import time# 循环接收用户输入并进行对话,直到接收到指令退出
# 模拟的是一个模型的功能
time.sleep(5)
sys.stdout.buffer.write("你好".encode('utf-8'))
while True:# 接收用户输入, 这里是一个组合方法user_input = input() # 如果用户输入指令 "exit",则退出循环if user_input.strip() == "exit":print("Exiting conversation...")break# 模拟数据的发送time.sleep(5)# python的回答, 也就是我们所需要的诊断内容, 果然是把这一句直接ASCII化了sys.stdout.buffer.write("模型输出".encode('utf-8'))

后端主页代码

const WebSocket = require('ws');
const { Worker } = require('worker_threads');
const {jsonToObject,objectToJson,generateUniqueId}=require('./tool.js')
const fs = require('fs');// 创建 WebSocket 服务器, 该服务器监听的是3001端口
const wss = new WebSocket.Server({ port: 3001 });//链接类,id, 和前端的链接ws, 和后端的链接worker
class Connection {constructor(id, ws, worker) {this.id = id;this.ws = ws;this.worker = worker;}getId() {  return this.id;  }getWebSocket() {   return this.ws;  }getWorker() { return this.worker;}setWebSocket(ws) {  this.ws = ws;}setWorker(worker) { this.worker = worker;}
}//进程管理对象队列(映射)
const workers=new Map();// 监听每次创建一个新的链接, 就会
// 创建一个ws对象并且设置监听器
// 将其加入队列中
wss.on('connection', (ws) => {//创建idconst id=generateUniqueId()//生成链接对象并且创建监听ws.addEventListener('open', () => {console.log('WebSocket connection established');});//监听前端的信息ws.addEventListener('message', (event) => { data=jsonToObject(event.data)conn=workers.get(data.id)   //先判断该会话是否还存在, 如果不存在就不需要什么反应if(conn){                       //如果链接对象已经创建work=conn.worker            //获取线程链接if(data.type===2) {         //发送到ws的指令为2, 则把数据传下去work.postMessage(data)}else if(data.type===1){    //接收到1指令console.log('会话'+data.id+'已经停止')work.postMessage(data)  //向线程发送停止信息workers.delete(data.id) //从线程队列中删除会话}else if(data.type===3){    // 将Base64编码的字符串转换为Bufferconst imageBuffer = Buffer.from(data.data.split(';base64,').pop(), 'base64');// 指定要保存图像的文件路径和文件名const imagePath = './imgs/img'+data.id+'.png';// 将图像数据写入文件fs.writeFile(imagePath, imageBuffer, (err) => {if (err) {console.error('Error saving image:', err); return; }});// 将图片的保存地址传递给子进程work.postMessage({...data,url:imagePath})}}});//将id, 链接对象和子线程组合存储, 其中ws代表前端, worker代表后端线程ws.addEventListener('error', (error) => {console.error('WebSocket error:', error);});ws.addEventListener('close', () => {   //监听到链接关闭以后console.log('用户页面已经关闭')     //将ws直接删除掉conn=workers.get(id)   if(conn){              workers.delete(id)}});// 创建子线程const worker=new Worker('./worker.js')// 监听来自子线程的消息worker.on('message', (message) => {console.log(message)conn=workers.get(message.id)if(message.type===2){conn.getWebSocket().send(objectToJson(message))}else if(message.type===1){workers.delete(message.id) //从维护队列中删除conn.getWebSocket().send(objectToJson(message)) //将数据发送给前端}else if(message.type===3){conn.getWebSocket().send(objectToJson(message)) //将数据发送给前端}})//前端链接, 线程链接, 以及id存储起来workers.set(id, new Connection(id,ws,worker))//发送给前端id信息ws.send(objectToJson({type:0,id:id}))//发送给子线程id信息worker.postMessage(objectToJson({type:0, id:id}))console.log('新增会话用户, 当前会话数目为', workers.size)});

子线程管理代码

// worker.js
const { parentPort } = require('worker_threads');
const {jsonToObject,objectToJson}=require('./tool');
const { spawn } = require('child_process');/*
主线程会根据worker.js中的代码创建一个子线程
创建子线程的同时, 启动一个python服务, 该服务在启动的时候, 就把图片数据和第一个prompt传递过去
*///问诊状态
let inquiried=false
//图片地址
let imgUrl=''
// python脚本执行对象
let  pythonProcess;
//会议id
let id;//开启问诊状态,参数为图片的地址
//传递的msg就是图片的地址================================================================
const startInquriy=(msg)=>{//启动模型,pythonProcess = spawn('python', ['./foot.py', msg, '请你帮我诊断这张图片' ], {   stdio: ['pipe', 'pipe', 'pipe'] ,encoding: 'utf-8'});//为模型增加一个输出监听, 从此会开始监听模型的输出pythonProcess.stdout.on('data', (data) => {if(!inquiried){//如果检测到这是第一次输出, 则开启问诊状态inquiried=true//先进行响应3,告知前端图片地址在什么地方parentPort.postMessage({type:3, id:id, url:msg})}//响应2, 告知前端模型的意见// 将Buffer对象转换为十六进制字符串,并使用正则表达式替换掉空格const hexString = data.toString('hex').replace(/ /g, '');console.log(Buffer.from(hexString, 'hex'))// 将十六进制字符串转换为实际的字符串const str = Buffer.from(hexString, 'hex').toString('utf-8');console.log(str)parentPort.postMessage({type:2, id:id, message:str})});
}// 向 Python 脚本发送数据, 参数为发送的数据
const sendDataToPython = (data) => {pythonProcess.stdin.write(data + '\n'); // 在每条消息末尾加上换行符
};// 监听主线程发送的消息
parentPort.on('message', (message) => {data=message//0指令, 给线程分一个idif(data.type===0){id = data.id;}//1指令, 线程停止else if(data.type===1){if(inquiried){ //停止python脚本sendDataToPython('exit');//停止线程process.exit()}}//2指令, 发来数据, 传回数据else if(data.type===2){//输入数据, 这个data.message其实就是前端传入进来的话if(inquiried){ sendDataToPython(data.message);}}//3指令, 开启对话else if(data.type===3){//输入图片, 并且确认开启python脚本imgUrl = data.urlid = data.idstartInquriy(data.url);}
});

4.3. 在线调试

在线调试的过程中遇到cors跨域的问题, 解决方案是使用http-server的包进行本地服务器建立并且运行, 在目录下进入启动http-server指令

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/20090.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot 七牛云 OSS 私有模式 获取访问链接

目录 一、问题引出 二、在SpringBoot中获取私有访问路径的操作 一、问题引出 由于七牛云OSS的公有模式存在被盗刷的风险&#xff0c;可能导致服务器额外的费用&#xff0c;于是我选择私有模式进行操作。私有模式的访问路径是一个问题&#xff0c;因为需要对应着token和e这两…

Linux系统监控

文章目录 一、系统监控基本介绍二、内存监控2.1、内存监控字段解析2.2、windows下查看内存2.2.1、通过cmd中命令查看内存条信息&#xff1a;2.2.2、通过cmd中命令查看物理内存信息&#xff1a;2.2.3、使用任务管理器查看内存2.2.4、使用资源监视器查看内存2.2.5、使用系统信息工…

【Springboot】——项目的创建与请求参数应用

&#x1f4bb;博主现有专栏&#xff1a; C51单片机&#xff08;STC89C516&#xff09;&#xff0c;c语言&#xff0c;c&#xff0c;离散数学&#xff0c;算法设计与分析&#xff0c;数据结构&#xff0c;Python&#xff0c;Java基础&#xff0c;MySQL&#xff0c;linux&#xf…

element-plus中在表格校验输入的值

element-plus中在表格校验输入的值 效果&#xff1a; 注意事项&#xff1a;需要在表单套一个表格的字段 代码&#xff1a; <el-form :model"tableFrom" ref"tableDataRef" :rules"rules" style"margin: 0px !important;">&…

vue中大屏可视化适配所有屏幕大小

1. 外部盒子 .screenBox {width: 100vw;height: 100vh;background: url("/assets/images/bg.png") no-repeat;background-size: cover; }2.比例盒子 外层盒子css定义 .boxScale {width: 1920px;height: 1080px;background-color: orange;transform-origin: left top;…

5.29工效学-人因工程人机交互

对于工效学这门课&#xff0c;一直都感觉很有意思&#xff0c;是一个值得再认真一点的课。可惜上课的时候效率不高&#xff0c;有感兴趣的东西课后也没有自行去拓展开来&#xff0c;前面的课我感觉还讲了比较重要的东西&#xff0c;但是&#xff0c;全忘了呢&#xff08;真的对…

Mac OS 用户开启 80 端口

开启端口 sudo vim /etc/pf.conf # 开放对应端口 pass out proto tcp from any to any port 8080 # 刷新配置文件 sudo pfctl -f /etc/pf.conf sudo pfctl -e获取本机ip地址 ifconfig en0 | grep inet | grep -v inet6 | awk {print $2}访问指定端口

C语言:深入了解(联合体和枚举)

目录 联合体 联合体的类型的声明 联合体的特点 相同成员的结构体和联合体对比 联合体大小的计算 联合体的使用举例 联合体的类型&#xff1a;判断联合体是大端还是小端 枚举类型 枚举类型声明 枚举类型的优点 枚举类型的使用 联合体 联合体的类型的声明 像结构体⼀…

一个浏览器插件,绕过限制,登录微信网页版!

摘要 早在2017年开始&#xff0c;微信网页版就已经住逐渐开始停止登录&#xff0c;以为了保障你的账号安全为由引导你使用电脑版微信。具体如下&#xff1a; 当然这个影响并不是所有账号&#xff0c;还是有一些账号不明觉厉地没有被影响到&#xff0c;我自己有2个号都还是可以…

【机器学习】集成语音与大型语音模型等安全边界探索

探索集成语音与大型语言模型&#xff08;SLMs&#xff09;的安全边界 一、引言二、SLMs的潜在安全风险三、对抗性攻击与越狱实验四、提高SLMs安全性的对策五、总结与展望 一、引言 近年来&#xff0c;随着人工智能技术的飞速发展&#xff0c;集成语音与大型语言模型&#xff08…

OceanBase 4.3.0 列存引擎解读:OLAP场景的入门券

近期&#xff0c;OceanBase 发布了4.3.0版本&#xff0c;该版本成功实现了行存与列存存储的一体化&#xff0c;并同时推出了基于列存的全新向量化引擎和代价评估模型。通过强化这些能力&#xff0c;OceanBase V4.3.0 显著提高了处理宽表的效率&#xff0c;增强了在AP&#xff0…

【计算机毕业设计】谷物识别系统Python+人工智能深度学习+TensorFlow+卷积算法网络模型+图像识别

谷物识别系统&#xff0c;本系统使用Python作为主要编程语言&#xff0c;通过TensorFlow搭建ResNet50卷积神经算法网络模型&#xff0c;通过对11种谷物图片数据集&#xff08;‘大米’, ‘小米’, ‘燕麦’, ‘玉米渣’, ‘红豆’, ‘绿豆’, ‘花生仁’, ‘荞麦’, ‘黄豆’, …

2023年亚太杯A题:果园采摘机器人的图像识别,一二题

问题一&#xff1a;基于附件1中提供的可收获苹果的图像数据集&#xff0c;提取图像特征&#xff0c;建立数学模型&#xff0c;计算每幅图像中的苹果的数量&#xff0c;并绘制附件1中所有苹果的分布直方图。 对于自动采摘机器人&#xff0c;首要的能力就是识别出苹果对象&#…

数字信号处理实验四:IIR数字滤波器设计及软件实现

一、实验目的 1. 掌握MATLAB中进行IIR模拟滤波器的设计的相关函数的应用&#xff1b; 2. 掌握MATLAB的工具箱中提供的常用IIR数字滤波器的设计函数的应用&#xff1b; 3.掌握MATLAB的工具箱中提供的模拟滤波器转数字滤波器的相关的设计函数的应用。 二、实验内容 本实验为…

秋招突击——算法打卡——5/30——复习{最大上升子序列的和、面试算法缺陷补充}——新做:{回文数+补充 自定义Stoi实现、正则表达式匹配}

文章目录 复习导弹拦截——最大上升子序列和推理过程实现代码补充昨日面试 新作回文数实现代码 字符串转整数正则表达式匹配个人实现思路分析实现代码如下 参考做法思路分析实现代码 总结 复习 导弹拦截——最大上升子序列和 同样类型题目链接&#xff1a;导弹拦截重做这道题…

力扣刷题--485. 最大连续 1 的个数【简单】

题目描述 给定一个二进制数组 nums &#xff0c; 计算其中最大连续 1 的个数。 示例 1&#xff1a; 输入&#xff1a;nums [1,1,0,1,1,1] 输出&#xff1a;3 解释&#xff1a;开头的两位和最后的三位都是连续 1 &#xff0c;所以最大连续 1 的个数是 3. 示例 2: 输入&…

WindowManager相关容器类

窗口中容器类介绍&#xff1a; 本节内容较多&#xff0c;建议结合前面的内容一起阅读&#xff1a; 1、addWindow的宏观概念 2、WindowManager#addView_1 3、WindowManager#addView_2 1&#xff09;、WindowContainer&#xff1a; class WindowContainer<E extends WindowC…

算法(一)递归

文章目录 递归的概念递归三要素递归demo打印100次“hello word”斐波那契数列 递归的概念 递归算法是一种直接或者间接调用自身函数或者方法的算法。 递归三要素 递归条件结束 因为递归是循环调用自身&#xff0c;因此就必须要有结束条件&#xff0c;或者就会OOM。 函数的功…

低代码开发系统是什么?它有那些部分组成?

低代码开发系统是什么&#xff1f;它有那些部分组成&#xff1f; 一、引言 在当今快速变化的商业环境中&#xff0c;企业对于快速响应市场需求、降低开发成本和提高开发效率的需求日益增强。低代码开发系统&#xff08;Low-Code Development Platform&#xff09;应运而生&am…

安卓启动 性能提升 20-30% ,基准配置 入门教程

1.先从官方下载demohttps://github.com/android/codelab-android-performance/archive/refs/heads/main.zip 2.先用Android studio打开里面的baseline-profiles项目 3.运行一遍app&#xff0c;这里建议用模拟器&#xff0c;&#xff08;Pixel 6 API 34&#xff09;设备运行&a…