基于Python与Node.js实现的医疗图像库在线存储与检索平台网站

基于Python与Node,js实现的医疗图像库在线存储与检索平台网站 摘 要 图像数据相对于一般的文本数据来说管理起来更具有复杂性,传统的图像存储方式有两种

本文包含相关资料包-----> 点击直达获取<-------

基于Python与Node.js实现的医疗图像库在线存储与检索平台网站

摘 要

图像数据相对于一般的文本数据来说管理起来更具有复杂性。传统的图像存储方式有两种,一是直接将图像存入数据库,二是将图像存放在文件系统,而将路径存放在数据库,前一种基于“大字段数据最好不要存放在数据库中”这种规则一般不被使用,常用的是后一种,而这种方式也有明显的性能劣势,原因在于访问图像时要两次访问IO,这在高并发访问中很难满足需要。

为了对Dicom格式实现高效的管理以及为项目其他模块提供便捷的服务,特设计与实现了一系列对医疗图像、病人隐私信息、深度学习框架操作的接口。

Mongodb是一个非关系型数据库亦可以称作文档型数据库,因为其存储的节本单位是文档等同于关系型数据库中表的一行。该数据库考虑了图像的存储,提供了Gridfs这种存储方式,来满足大量图像管理的需要。

关键字: Dicom,Mongodb,Gridfs

ABSTRACT

The image data with respect to the general text data management is more complicated. There are two kinds of image storage in the traditional way, one is the image stored in the database, the two image is stored in the file system, and the path stored in the database, which is based on the former "characters of data stored in the database is best not the rule the general is not being used, is commonly used after a while, this approach also has disadvantage performance obviously, because access to the image two times to visit IO, in this highly concurrent access is difficult to meet the needs.

In order to realize the efficient management of the Dicom format and provide convenient service for the project design and implementation of other modules, especially a series of medical images, patient privacy information, deep learning framework interface.

Mongodb is a non relational database can also be called the document database, because its storage cost is equivalent to a unit of the document in a relational database table. The database is considered image storage, Gridfs provides this storage method to meet the need to manage a large number of image.

Key words: Dicom, Mongodb, Gridfs

1 绪论

1.1 研究背景及意义

1.1.1 研究背景

图像作为数据的一种,和其他形式的数据一样必要的时候要被保存起来,以备后用。目前存储图像的方式主要有两种,其一,将图像保存在文件系统,然后将路径存放在数据库,其二,将图像直接存放在数据库中。一般都会采用第一种,因为其二大字段数据会导致数据库压力增大、备份困难、查询速度变慢等问题。第一种存储方式也存在性能,因为会访问IO两次,这在高并发的情况下读写速度会异常的慢。面对高并发医疗图像读写这种局面,迫切需要一种更为优化的方式来满足性能的需要。

1.1.2 研究意义

随着时代的迁徙,医疗图像在医生的诊断过程中已经成为不可或缺的一部分,并且也是诊断中最重要的一环之一,医生通过肉眼观察医疗图像来判断病人的健康情况,然后做出治疗。不过在这种什么都讲究效率的时代,人工观察图像已经不能满足需要,并且这种方法也存在很多的缺点,比如准确性问题。因此,我们研究并设计了一款自动识别医疗图系统,而我的工作就是为其他模块提供数据支持。众所周知,在一个项目中往往IO是影响性能的主要因素,而性能也是所有用户最在乎的一点,所以我的模块可以说是在整个项目中举足轻重。

1.2 技术栈的选择

整个项目主要采用node.js、python两种语言来满足不同模块的需要。而我负责的这部分主要主要是想其他模块提供数据支持,因此就需要分别采用这两种语言来开发各个接口集合。

再数据库选择这块,可以有多种选择,如MySQL、oracle等关系型数据库以及mongodb这种非关系型数据库,不过最终选择了mongodb数据库,具体有如下原因:

  • 高性能、易部署、易使用。

  • 提供Gridfs存储格式来支持文件存储,以这种存储格式存放图像相比其他方式性能更为优越,这也是本系统所追求的。

  • 其他模块有采用node.js 开发,而mongodb对js有很好的支持,node.js本身就是js,所以两者的契合度较高。

综上所述,整个系统采用node.js、python、mongodb技术栈。

1.3 论文的主要工作内容

为了实现该系统,首先得从数据库入手,设计并创建数据库是所有工作的前提。因为其他部分都是基于数据库的接口,没有事先设计好的数据库,后面的工作是无法进行的。

接着就是编写node.js部分的代码了。在这大的一步里,先要进行配置编写这一小步,因为接口的编写依赖配置信息。完了之后进行图像接口的编写,最后在进行xml接口的编写,后两步其实区分先后次序没有多大的意义。最后就是编写mocha测试代码,进行代码的调试。

接着是python部分代码的编写,至于先编写node.js还是python,这都无所谓,这两部分并没有先后次序。Python部分代码编写的时候先进性配置的编写,然后进行图像接口的编写,最后进行病人隐私信息接口的编写。

数据库的设计以及两种语言借口的设计与编写都在第四章详细介绍了。

1.4 本章小结

本章首先介绍了系统的背景,包含传统的图像存储方法、高并发环境下图像访问将会变慢等相关论述。接着详细说明了系统的意义,提到系统的实现有助于缓解高并发环境下图像访问速度变慢的问题。再者介绍了系统实现的技术栈,提到mongodb、python、node.js三种。最后介绍了论文的主要工作内容,说明系统的设计与实现的顺序。

2 相关技术分析

2.1 MongoDB数据库

2.1.1 数据库简介

MongoDB是一个基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。

MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。他支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型。Mongo最大的特点是他支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。

2.1.2 BSON存储格式

BSON是一种类json的一种二进制形式的存储格式,简称Binary JSON,它和JSON一样,支持内嵌的文档对象和数组对象,但是BSON有JSON没有的一些数据类型,如Date和BinData类型。

BSON可以做为网络数据交换的一种存储形式,这个有点类似于Google的Protocol Buffer,但是BSON是一种schema-less的存储形式,它的优点是灵活性高,但它的缺点是空间利用率不是很理想,

BSON有三个特点:轻量性、可遍历性、高效性

{"hello":"world"} 这是一个BSON的例子,其中"hello"是key name,它一般是cstring类型,字节表示是cstring::= (byte*) "/x00" ,其中*表示零个或多个byte字节,/x00表示结束符;后面的"world"是value值,它的类型一般是string, double, array, binary data等类型。

2.1.3 Gridfs存储格式

数据库支持以BSON格式保存二进制对象。 但是MongoDB中BSON对象最大不能超过16MB。 GridFS 规范提供了一种透明的机制,可以将一个大文件分割成为多个较小的文档。这将容许我们有效的保存大的文件对象,特别对于那些巨大的文件,比如视频。

  • GridFS 用于存储和恢复那些超过16M(BSON文件限制)的文件(如:图片、音频、视频等)。

  • GridFS 也是文件存储的一种方式,但是它是存储在MonoDB的集合中。

  • GridFS 可以更好的存储大于16M的文件。

  • GridFS 会将大文件对象分割成多个小的chunk(文件片段),一般为256k/个,每个chunk将作为MongoDB的一个文档(document)被存储在chunks集合中。GridFS 用两个集合来存储一个文件:fs.files与fs.chunks。每个文件的实际内容被存在chunks(二进制数据)中,和文件有关的meta数据(filename,content_type,还有用户自定义的属性)将会被存在files集合中。

2.2 传统图像存储方法介绍

图像作为数据的一种也需要被存储起来以备后面的访问,目前业界存储图像有一下两种做法:

  • 把图像直接以二进制形式存储在数据库中,一般数据库会提供一个二进制字段来存储二进制数据,比如mysql中有blob字段,oracle数据库中是blob或bfile类型。这种方法缺点,一方面增加了数据负担,二方面代码处理也比较复杂。

  • 图像存储在磁盘上,数据库字段中保存的时图片的路径。

互联网环境中,大访问量,数据库性能很重要。一般在数据库存储图片的做法比较少,更多的是将图片路径存储在数据库中,展示图片的时候只需要连接磁盘路径把图片载入进来即可。

2.3 性能对比分析

2.4 node.js对MongoDB的支持

在npm上有很多用于node.js对MongoDB支持的包,如mongodb、mongoose等。本系统选用了官方驱动mongodb包,该包提供了很多对mongodb操作的接口,具体的接口说明可以查询官方提供的手册。而为了支持gridfs存储格式,特选用了gridfs包,这也可以在npm上找到,该包提供的接口集可以很方便的实现图片的读写。

2.5 python对MongoDB的支持

本系统选用了pymongo包来作为python访问mongodb的驱动,当然还有其他类型的驱动,不过这个是最常用的一个,所以就选择了这个。为了对gridfs存储格式的支持,系统选用了gridfs这个包来满足mongodb存储和访问图像。

2.6 加密介绍

本系统为了病人隐私信息的安全性,特采用AES加密标准对该数据进行了加密。

AES中文全称是高级加密标准,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。经过五年的甄选流程,高级加密标准由美国国家标准与技术研究院(NIST)于2001年11月26日发布于FIPSPUB 197,并在2002年5月26日成为有效的标准。2006年,高级加密标准已然成为对称密钥加密中最流行的算法之一。

AES只是个基本算法,实现AES有若干模式。其中的CBC模式因为其安全性而被TLS(就是https的加密标准)和IPSec(win采用的)作为技术标准。简单地说,CBC使用密码和salt(起扰乱作用)按固定算法(md5)产生key和iv。然后用key和iv(初始向量,加密第一块明文)加密(明文)和解密(密文)。

系统为了支持AES特选用了Crypto.Cipher包来进行加密解密等操作。

2.7 本章小结

本章首先介绍了mongodb数据库,其中重点说明两种存储格式,分别是BSON和GridFS。前一种是一般的数据存储方式,数据的大小不能超过4M,后者是为了存储大于4M的文件而设计的。接着粗略的介绍了传统有哪些图像存储方法,并且对比说明这些方法优缺点。接着通过数据展示了三种方式随着线程的数量变化的变化情况,最终得出本系统所采用的方式适合高并发的情况下的结论。后面又介绍了python以及node.js为了访问mongodb所使用的驱动。最后详细的介绍了系统所采用的AES加密方式。

3 系统需求分析

3.1 系统可行性分析

对于每个软件项目中的问题并不都有明显的解决办法,有时候很难在预定的时间和费用之内解决这些问题,也无法用现有的技术圆满完成。如果贸然去开发,会造成时间、人力、资源和经费的不必要浪费,或者还会带来一些意料不到的问题。所以本系统在开发之前分别在经济可行、技术可行性、运行可行性、操作可行性几个方面进行了分析。

3.1.1 经济可行性

开发该软件所需的相关资料可以通过文献资料和网络进行调查采集,所需的软件系统、硬件平台等都易于获得,无需特殊工具,开发成本低,简单易实现,从经济角度来看,开发该软件经济可行。

3.1.2 技术可行性

系统涉及的任务主要有各种图像存储方案性能对比分析、数据库设计、接口设计与实现。

性能分析方面我准备采用C++编写,实现各种存储方案的接口,然后线程的个数为参数,时间为结果,并且将结果通过图形化界面展示出来,所涉及的东西目前都是我自身具备的,所以在这方面可行。

数据库设计方面,数据库用到的是mongodb,虽然之前并没接触过,不过网上有大量的资料和书籍以及用到的知识知识mongodb中的基础部分,而且数据库设计大学里已经学过,所以在这方面也技术可行。

接口设计与实现,实现主要采用node.js、python两种语言,这两种语言之前并没有接触过,不过我有大量的时间去学习,所以在这方面也技术可行。

3.1.3 运行可行性

该系统是通过python、node.js、mongodb开发而成,如果要运行只要具备python运行环境、node.js执行环境、mongodb数据库即可。所以在运行上可行的。

3.1.4 操作可行性

由于该系统不是被用户直接操作的,系统所面向的用户是其他模块的开发人员,而其他模块的程序开发人员,都具备扎实的node.js、python知识,所以在操作上是绝对可行的。

综上所述,该系统在经济、技术、运行、操作上完全可行。

3.2 系统需求分析

该系统身为医疗图像识别系统中的一个模块,主要是为其他模块提供数据支持,也就是说,其他模块通过此模块进行数据读写。通过各种需求获取手段包括向用户反复咨询、咨询老师、跟其他成员讨论,最终得出系统的需求如下:

功能需求:

  • 系统可以分别满足node.js、python两种语言的接口需要。

  • 针对python可满足图像以及隐私信息增、删、改、查的需要。

  • 针对node.js可满足图像增、删、改以及xml的增、删、改、查的需要

性能需求:

  • 响应时间不超过1秒

  • 支持2万用户同时操作数据库,保证性能不受影响

安全性需求:

  • 系统对病人隐私数据进行加密

  • 系统自动进行数据备份,防止数据丢失造成危机

  • 本系统应该能够记录系统运行时所发生的所有错误,这些错误记录便于查找错误的原因

可靠性和可用性需求:

  • 系统提供的接口尽可能满足方便其他模块的操作

  • 系统具有一定的容错和抗干扰能力,在非硬件故障和非通讯故障时,系统能够保证正常运行,并有足够的提示信息帮助用户有效正确的完成任务

3.3 本章小结

可行性分析是一个从经济、技术等多方面进行评估的过程,可行性分析的结果决定这个项目是否值得去做。在正式开发一个项目之前,都应该或者说必须对项目进行可行性分析。本章一开始先从经济可行性、技术可行性、运行可行性、操作可行性四个方面对系统进行了可行分析,结论是该项目可行。可行性分析之后就是需求,需求分析是一个反复确定用户需求的过程,只有完整、准确地对项目进行了需求分析才能做出让用户满意的作品,本章主要从功能、性能、安全性可靠性和可用性等几个方面调查用户的需求。

4 系统设计

4.1 模块结构

系统由于要分别向node.js开发者以及python开发者提供数据访问接口,所以把系统根据语言的不同分为两个部分分别讨论,虽然分开了,但是很多方面还有很相似的,以下是两部分模块结构设计。

4.1.1 node.js模块结构

我们团队中有一人是开发一个平台用来跑深度学习框架,然后对比这些框架的性能,而我这个模块就是为他服务的,而他这一块需要将框架配置信息、原始图像保存进数据库以备后用,具体需要哪些操作,以下的模块图有说明。语言方面我们经过协商,最终选择了node.js。下面说道的系统配置模块,是为了便于系统某系内容的变化而设计的,如数据库的名以及字段的名字等,如果发生改变只需在修改这个文件即可,避免了全面的改动。

4.1.2 python模块结构

团队中其他人都是用python写的,其中一人负责对原始图像的处理以及分析以提取出病人隐私数据和满足深度学习的需要。另外一人是用处理好的图像进行深度学习,训练模型便于系统对图像的识别。经过分析,设计了图像管理模块用于管理各种图像以及病人隐私信息管理模块用于管理提取的病人隐私数据。而至于AES加密模块为了对病人隐私数据进行加密,防止数据泄露,造成不必要的损失。剩下的系统配置模块同上。

4.2 功能设计与分析

系统主要由两部分够成,其一是python接口集合,其二是node.js接口集合。下面将分别介绍这两部分的设计。

1.node.js接口集合

node.js接口集合操作的对象有Dicom医疗图像、深度学习框架配置这两个,所以根据这一点,将这一部分主要分为两部分,其次,为了减少因系统部署的环境的改变而做大量的修改,特将系统配置提取出来作为第三个模块,因此系统总的分为三个模块,以下将详细介绍这三个模块的设计。

2.Dicom图像管理模块

这个模块主要是为其他模块提供Dicom医疗图像操作的接口,根据需要接口的类型被设置为三种,分别是增、删、查等。

“增”就是将图像存放到数据库,考虑到方便用户、性能两方面,增加操作主要由insertOneImage、insertImages两接口负责。这两个接口的区别是前者一次只能插入一张图像,后者可以一次插入多张图像,后者并不是前者的多次重复调用,如果这样,后者就没有单独设计的必要了,插入操作开始先要进行数据库连接,结束后要释放链接,考虑到多次插入仅需要一次连接以及一次释放,据此设计出了insertImages这个接口。不过两者在大体上结构上是相同的,流程图如下:

主要代码说明:

  • 将图像存放进数据库

javascript // 创建用于Gridfs格式的对象进行Gridfs操作 var gfs = grid(db, mongo); var obj = {}; obj.filename = filename; obj.processed_times = 0; // 将图像从文件系统转储到数据库 gfs.fromFile(obj, path + filename, function(err, file){ if (null != err){ error(); return; } else{ sucess(); // 关闭打开的数据库连接 db.close(); } });

  • 判断图像是否存在

javascript fs.exists(path + filename, function(exist){ if (exist){ // 如果存在 } else{ //如果不存在 } });

  • 连接数据库

javascript var db = new mongo.Db(config.DB, new mongo.Server(config.HOST, config.PORT, {})); // 异步打开 db.open(function(err, db){ if (err != null){ error(); return; } … }

“删”就是将满足条件的图像从数据库中去除,满足条件的可能不止一个,所以删的时候可能存在多条被删除,这一块接口同样也是这么设计,而不是只删除满足条件的第一条。由于包Gridfs没有直接删除多条的接口,只有一个根据图像的id或者filename一次只删除一条的接口,所以这一块设计的时候,是先将满足条件的图像的id都查询出来,放在一个数组,然后一条一条的调用Gridfs提供的接口去删除。删除操作由imgDelete接口负责, 接口的程序流程图如下。

主要代码说明:

  • 连接到指定名称的集合

javascript db.collection("fs.files", {strict: true}, function (err, collection) { if (err != null) { console.log(err); db.close(); return; } … });

  • 删除指定条件的图像

javascript gfs.remove(obj, function (err) { if (null != err) { error(); console.error(err); } else { // 如果成功删除数组中的最后一张图片,则该接口执行成功,并关闭数据库 if (i == length - 1){ success(); db.close(); } } });

“查”就是将数据库中符合条件的图像取出来,在这里会将取出的结果存储在配置文件指定的目录。查询操作是由imgFind接口负责,该接口可以一次性查询出多张图像,在进行readFile操作之前,先根据参数doc查询出所有的图像信息,然后根据id一个一个的转储数据库中的图像到指定的目录,之所以这样进行,在于readFile一次只能查询一张图像,再者为了在目中情况下提醒用户没有满足条件的图像。流程图如下。

主要代码:

  • 从mongodb数据库查询出图像并存放带指定目录

javascript var id = docs[i]["_id"]; var obj = {}; obj._id = id; (function(i){ // 根据id,查找图像,并写到目录 gfs.readFile(obj, function (err, data) { if (err != null){ error(); } else{ // 写到目录 fs.writeFile(config.IMAGE.FIND_RESULT_PATH + id + config.IMAGE.EXT_NAME, data, {flag: 'w'}, function (err) { if (null != err) { error(); console.error(err); } else { // 如果最后一个图像查询成功,则整个接口才成功 if (i == length - 1){ success(); db.close(); } } }); } }); })(i);//循环加异步所以这块使用闭包函数

系统配置模块

该模块主要为其他模块提供配置信息,包括数据库名称、主机地址、端口、查询结果存储位置等。这些信息可能会因为某些原因要进行修改,这里为了便于修改,特将此部分单独分离出来作为一个模块。

主要代码:

javascript module.exports = { "DB" : "foobar", "HOST" : "localhost", "PORT" : 27017, "XML" : { "COLLECTION_NAME" : "xml_collection", "FRIST_COL_NAME" : "framename", "SECOND_COL_NAME" : "xml_content", "FIND_RESULT_PATH" : "./", "EXT_NAME" : ".xml" }, "IMAGE" : { "FIND_RESULT_PATH" :"C:\\Users\\", "EXT_NAME" : ".jpg" } }

深度学习框架配置文件管理模块

系统采用多个深度学习框架,需要对这几个框架进行对比,根据需要,需要将这些框架的配置文件存放到数据库中,由于框架配置文件的大小没有超过4M,所以还是采用BSON的存储格式,直接将内容以字符串的形式存放在数据库中,考虑到其他模块的需要,特设计增、删、该、查几种接口形式。

“增”这个操作,这一块由xmlInsert接口负责,该接口在对数据库进行操作之前首先对文件是否存在进行判断,接着连接数据库,并判断对应的集合是否存在,如果不存在则创建,接下来从文件系统读取文件的数据,插进数据库当中,最后释放连接。该接口对应的流程图如下。

主要代码:

  • 读取文件内容

javascript // 根据路径和文件名从文件系统读取内容 fs.readFile(path + filename, function(err, data){ if (null != err){ error(); db.close(); return; }

  • 将数据插入到数据库

javascript // 将读取内容转换成字符串 var xmlContent = data.toString(); // 组织文档 var doc = {}; doc[config.XML.FRIST_COL_NAME] = framename; doc[config.XML.SECOND_COL_NAME] = xmlContent; // 插入到数据库 result.insertOne(doc, function(err, r){ if(err != null){ error(); db.close(); return; } else{ success(); db.close(); return; } });

“删”就是根据框架名称删除数据库中对应的文档,删除操作这里是由xmlDelete接口负责,该接口的实现原理是读取用户要删除的框架名称,然后根据名称调用驱动中提供的deleteOne接口删除,不过在删除之前,进行了查询操作,目的是判断要删除的内容是否存在,对于deleteOne接口,如果不存在不会有什么反应,所以对于用户来讲,就无法了解是否已经删除,而增加查询操作,就是为了在对应内容不存在的时候,提示用户。流程图如下。

主要代码:

  • 根据条件删除

javascript // 组织删除的条件 var query = {}; query[config.XML.FRIST_COL_NAME] = framename; // 根据条件删除 collection.deleteOne(query, {}, function(err, result){ if (null != err){ error(); db.close(); return; } else{ success(); db.close(); return; } });

“查”就是根据框架名称查询出对应的框架配置内容,这里根据需要提供了xmlFind接口来负责这个操作,xmlFind查询的结果会自动写入到特定的目录当中。接口流程图如下。

主要代码:

  • 查询并写入到指定目录

javascript var query = {}; query[config.XML.FRIST_COL_NAME] = framename; // 根据query查询 collection.findOne(query, {}, function(err, doc){ if (null != err || doc == null){ error(); db.close(); return; } //写入到指定目录 fs.writeFile(config.XML.FIND_RESULT_PATH +framename+config.XML.EXT_NAME, doc[config.XML.SECOND_COL_NAME], {flag: 'w'}, function (err) { if (err) { error(); db.close(); return; } else { success(); db.close(); return; } }); });

2.python接口集合

Python接口集合管理的对象有Dicom医疗图像以及病人隐私信息,根据这一点首先将系统分为两部分,再者为了保证系统的环境发生改变时不用大面积的去修改,特增加第三个模块配置模块,最后为了保证病人信息的安全,增加了AES加密模块。综上,系统总共分为四个模块,以下将详细介绍这四个模块的设计。

Dicom图像管理模块

这个模块跟node.js部分基本相同,不过还有些差别,差别在于管理的图像在原来原始图像的基础增加了处理过得图像,所以在接口的设计方面也会发生改变。根据需要这部分接口被设置为三个类型:增、删、查,下面将详细介绍这每种接口的设计。

“增”即向数据库中添加图像,在这里并没有设计多个接口,而仅有InsertImage一个接口负责。其接受一个参数,这个参数为Image的对象,这个对象里封装着图像的相关信息,包括图像的名称、路径、处理次数、原始图像id等。至于为什么采用一个接口,而不像node.js部分设计多个接口而适应各种情况,因为在这里并没有出现node.js部分的问题,调用这一个接口多次插入不会影向插入的性能的,这得感谢pymongo包的底层设计,它会在适当的时候创建连接以及在适当的时候释放连接,上层不需要做这些事情。流程图如下。

主要代码:

  • 读取图像数据并存放到数据库

```python img = open(image.GetPath() + image.GetFilename(), 'rb') data = img.read()

存放数据到mongodb数据库

gfs.put(data,filename=image.GetFilename(),process_times =image.GetProcessTimes(),origin_fileid =image.GetOriginFileid()) ```

“删”即从数据库删除满足指定条件的图像,该操作由DeleteImage接口负责,DeleteImage为了保证当数据库中没有符合条件的图像时给予提示,所以在执行删除操作之前先进行了查询操作,如果没有符合条件的则抛出查询结果为空的异常。流程图如下。

主要代码:

  • 查询并删除

```python fileList = files.find(dict)

统计个数

len = 0

根据查询图像的id,分别删除

for file in fileList: len = len + 1 id = file['_id'] gfs.delete(ObjectId(id))

如果没有查询到结果,就抛出异常

if len == 0: raise NoResultException("查询结果为空") ```

“查”即从数据库中查询出满足条件的图像,查询操作由FindImage接口负责,它接受一个字典类型的参数,用来表示查询条件,最后将查询的结果存放到指定的目录。查询成功返回True,查询失败返回False并抛出对应的异常,异常这块可以通过配置文件打开或关闭,需要用户自己设置。流程图如下。

主要代码:

  • 查找满足条件的图像并写入目录

```python

查找满足条件的图像

files = self.__db['fs.files'] fileList = files.find(dict)

用来存放fileList的长度

len = 0

分别取出图像数据并写入指定目录

for file in fileList: len = len + 1 id = file['_id'] data = gfs.get(ObjectId(id)) f=open(configs['IMAGE']['FIND_RESULT_PATH'] + str(id) + configs['IMAGE']['EXT_NAME'], 'wb') f.write(data.read()) f.close()

如果fileList没有元素,则抛出异常

if len == 0: raise NoResultException("查询结果为空") ```

系统配置模块

系统配置模块如果node.js部分,主要包含系统数据库、字段、服务器地址等信息,方便以后修改。代码展示如下。

json configs = { 'DB' : { 'NAME' : 'foobar', 'PORT' : 27017, 'HOST' : 'localhost' }, 'PATIENT_COLLECTION_NAME' : 'patientInfo', 'IMAGE' : { 'FIND_RESULT_PATH' : './', 'EXT_NAME' : '.jpg' }, 'AEX' : { #'MODE' : 'CBC', 'KEY' : 'yangke' }, 'SHOWEXCEPTION' : True }

AES加密模块

AES加密模块主要负责病人隐私信息的加密和解密,AES只是个基本算法,实现AES有若干模式,这里采用CBC模式。Python通过Crypto.Cipher这个包实现AES加密模块代码的编写。

主要代码:

  • 加密

python def encrypt(self, text): cryptor = AES.new(self.key, self.mode, self.key) length = 16 count = len(text) add = length - (count % length) text = text + ('\0' * add) self.ciphertext = cryptor.encrypt(text) return b2a_hex(self.ciphertext)

  • 解密

python def decrypt(self, text): cryptor = AES.new(self.key, self.mode, self.key) plain_text = cryptor.decrypt(a2b_hex(text)) return plain_text.rstrip('\0')

病人隐私信息管理模块

病人隐私数据来源于从医疗图像中提取,这些信息需要存放进数据库来进一步管理,根据需要这里提供增、删、改、查四中操作。至于这些数据怎么在数据库中存储,后面会有相应的数据库设计部分来解释。下面分别针对这四种操作详述他们的设计原理。

“增”即向数据库中添加隐私数据,该操作InsertPatientInfo接口负责,该接口主要调用pymongo包中的insert接口实现。流程图如下。

主要代码:

  • 连接集合并插入数据

python try: #如果集合不存在,插入第一文档时会自动生成 collection = self.__db[configs['PATIENT_COLLECTION_NAME']] # 插入 collection.insert(patientInfo) return True except Exception as e: if configs['SHOWEXCEPTION']: # 打印异常信息 print(e) return False

“删”即从数据库中删除符合条件的病人隐私数据,删除操作由DeletePatientInfo接口负责,该接口接受一个字典类型的参数,用来传递要删除数据的条件,再删除之前首先判断是否存在这样的数据,如果不存在的时候提示用户,避免什么都不做。最后调用remove接口删除。流程图如下。

主要代码:

  • 查找并删除病人隐私信息

```python res = collection.find(dict)

res的长度

len = 0 for r in res: len = len + 1

如果查找结果为空,则抛出异常

if len == 0: raise NoResultException("查询结果为空") collection.remove(dict) ```

“改”即将符合条件的病人隐私数据某些属性值改成其他的,修改这一操作由UpdataPatientInfo接口负责。该接口接受两个参数,分别是dict1,表示条件,dict2表示更新的目标。接口的更新操作主要有pymongo中update接口实现,流程图如下。

主要代码:

  • 查询并更新病人隐私信息

```python

返回指定集合

collection =self.__db[configs['PATIENT_COLLECTION_NAME']]

查找

res = collection.find(dict1)

res的长度

len = 0 for r in res: len = len + 1

如果查找结果为空,则抛出异常

if len == 0: raise NoResultException("查询结果为空")
tmp = {} tmp['$set'] = dict2

更新

collection.update(dict1, tmp) ```

“查”即从数据库中查询符合条件的病人隐私数据,查询操作由FindPatientInfo接口负责,该接口接受一个参数,用来存放查询的条件,如果查询结果为空会抛出异常,当然,用户可以通过设置配置文件来关闭或者打开异常,FindPatientInfo主要是调用pymongo包中find接口实现,流程图如下。

主要代码:

  • 根据条件查询

```python

返回指定的集合

collection=self.__db [configs['PATIENT_COLLECTION_NAME']]

查找

res = collection.find(dict)

res的长度

len = 0 for r in res: len = len + 1

如果查找结果为空,则抛出异常

if len == 0: raise NoResultException("查询结果为空") return res ```

4.3 系统开发方法

目前使用最广泛的软件工程方法学,包括传统方法学和面向对象方法学。

传统方法学

传统方法学又称生命周期方法学,或结构化方法学。传统方法学把软件生命周期的全过程依次划分为若干个阶段,然后顺序地完成各个阶段的任务。它采用结构化技术(结构化分析、结构化设计和结构化实现)来完成软件开发的各项任务。

在这个过程中,软件文档是通信的工具,他们清楚、准确地说明了到这个时候为止,关于该项工作已经知道了什么,同事奠定了下一步的基础。

面向对象方法学

客观世界是由对象构成的,对象是一个属性和数据操作的封装体。数据和对数据的处理原本密切相关的。

传统方法学把数据对数据的操作人为地分离成两个独立的部分,要么面向数据,要么面向对数据的操作。而面向对象方法是把数据和对数据的操作紧密的结合起来的方法。

本系统开发采用的是传统的方法学即结构化开发方法。原因在于这个开发方法更接近人的常规思维。

4.4 定义规范

代码规范在程序开发过程中是很必要的,一般一个项目都是多个人开发,如果代码不遵循一定的规范,这将给后面阅读和维护你代码的开发人员造成极大的困难,从而浪费不必要的时间。本系统虽然从头到尾都是自己一人开发,但还是要遵循严格代码规范。因为平时开发中注意这些,以后就不会犯这种错误。

4.4.1 代码注释规范

1.源文件注释

所有头文件开头都要加注释,写明文件创建时间、作者、用途、概述等。格式如下所示:

```c++ / * 作者:XXX * 功能说明:XXXXXXXXXXXXXXXXX /

… 作者:XXX 功能说明:XXXXXXXXXXXXXXXX … ```

2.函数注释

所有函数一定要注明函数的作用、参数的作用、返回值的作用等。格式如下所示:

```c++ / 函数的作用:XXXX * @param:XXX * @return:XXX /

… 函数的作用:XXXX @param:XXX @return:XXX … ```

3.常量变量注释

所有的常量和变量,无论全局或者局部只要在代码中起关键作用的必须都加上注释。格式如下所示:

```c++ // 变量或常量作用或意义

变量或常亮作用或意义

```

4.4.2 命名规范

采用驼峰命名法。

  • 类命名:英文,单词首字母大写,剩余字母小写。在意义上表达该类的作用。
  • 函数命名:英文,首字母大写,剩余字母小写。在意义上表达该函数的作用,如:InsertImage,是插入图像。
  • 变量命名:英文,首字母小写,在意义上表达该变量保存值得类型。
  • 文件命名:英文,首字母小写,在意义表达该文件的类型以及作用。

4.4 本章小结

本章首先从整体的角度分别介绍node.js、python的模块结构,接着就是本章最核心的部分“功能设计与分析”,针对这两部分的每个模块都进行了分析与介绍。对于每个模块中的接口的设计与流程以及主要代码都进行了说明。最后介绍了系统所定义的规范,来约束系统的设计与代码的编写。

5 系统测试

5.1 程序调试

软件完成设计之后就进入了用语言实现的阶段,这一阶段主要是靠程序员手工编写,过程中难免会因为各种原因出现语法或者语义上的错误,出现了错误就需要去修正,这个修正的过程称为调试,调试的方法有很多,如观察错误提示、打断点单步执行、肉眼观察等。本系统在调试过程中采用的时候第一种,根据错误提示修正错误。在我看来,每种调试方法各有优缺点,在特定的场合某种方法可能比较优秀。不过由于对语言的不熟悉,所以全程采用一种固定的方法调试。

5.2 工具的测试

5.2.1 测试的目的及意义

测试的目的及意义主要有一下几点:

  • 验证软件是否满足软件开发合同或项目开发计划、系统/子系统设计文档、软件需求规格说明书、软件设计说明书和软件产品说明等规定的软件质量要求。
  • 通过测试,发现软件缺陷。
  • 为软件产品的质量测量和评价提供依据。

5.2.2 测试框架

1.node.js代码的测试框架

这部分测试选用的mocha框架。Mocha是一款功能丰富的javascript单元测试框架,它既可以运行在node.js环境中,也可以运行在浏览器环境中。Javascripte是一门单线程语言,最显著的特点就是有很多异步执行。同步代码的测试比较简单,直接判断函数的返回值是否符合预期就行了,而异步的函数,就需要测试框架支持回调了、promise或其他的方式来判断测试结果的正确性。Mocha可以良好的支持javascripte异步单元测试。Mocha会串行地执行我们编写的测试用例,可以在将未捕获异常指向对应用例的同时,保证输出灵活准确的测试结果报告。

2.python代码的测试框架

这部分测试选用的是unittest框架,unittest是python内置的标准类库,是其他测试框架、测试工具的基础。对于这部分的单元测试,每个函数作为一个模块,分别测试,根据需要每个接口设计三个测试用例,两正一反来测试接口的正确性。测试代码根据接口管理的对象分为两部分代码来编写。

5.2.3 测试步骤

虽然该系统规模比较小,模块比较少,不过为了熟悉软件测试方法,以及如果系统某些地方发生错误能及很快很方便的定位,系统根据需要在测试阶段是按照单元测试、集成测试、系统测试、验收测试这个顺序进行的。

首先是单元测试是系统内部测试,该阶段主要是测试各个模块是否满足当初的设计。例如加密模块,主要测试该模块是否能正常加密解密而不出错。

接着是集成测试,集成测试主要测试模块间以及模块与现有系统接口之间是否能正常协作。

然后是系统测试,系统测试阶段将所有的模块拼接在一起,根据最初的设计通过软件测试方法验证整个系统是否满足需要。

最后是验收测试,本系统主要是为其他模块提供数据支持,验收测试的时候主要是其他成员检查当前系统是否满足自己模块的需要。

5.3 测试用例设计

测试的对象分为两部分,一个是python部分,一个是node.js部分,针对这两部分分别设计测试用例,不过由于两部分操作的相似性,测试用例也基本没什么差别。在每个部分项目代码下面有一个test目录,里面存放着该部分的测试代码以及测试所用的数据。

Node.js部分:

代码展示:

javascript // 测试image操作函数的正确性 describe("image", function(){ // 测试insertOneImage接口的正确性 describe("#insertOneImage", function(){ // 给出合法的路径和文件名来测试函数的正确性 it("insert successfully when path and filename is right", function(done){ mongodb.insertOneImage(__dirname+"\\","1.jpg", function(){},function(){done();}); mongodb.insertOneImage(__dirname + "\\", "2.jpg",function(){}, function(){done();}); }); // 给出不合法的路径或文件名来测试函数的正确性 it("insert failly when path or filename is wrong", function(done){ mongodb.insertOneImage(__dirname + "\\", "4.jpg",function(){ done(); }, function(){}); }); }); // 测试insertImage接口的正确性 describe("#insertImages", function(){ // 所有的路径和文件名都合法来测试接口的正确性 it("insert successfully when path and filename of imags is right", function(done){ var images = new Array(3); images[0] = {"path":__dirname + "\\", "filename" : "1.jpg"}; images[1] = {"path" : __dirname + "\\", "filename" : "2.jpg"}; images[2] = {"path" : __dirname + "\\", "filename" : "3.jpg"}; mongodb.insertImages(images, function(){}, function(){ done(); }); }) }); // 存在有路径或文件名不合法来测试接口的正确性 it("insert failly when path and filename of imags is false", function(done){ var images = new Array(3); images[0] = {"path" : __dirname + "\\", "filename" : "1.jpg"}; images[1] = {"path" : __dirname + "\\", "filename" : "4.jpg"}; images[2] = {"path" : __dirname + "\\", "filename" : "3.jpg"}; mongodb.insertImages(images,function(){done();}, function(){}); }); // 测试imgDelete接口的正确性 describe("#imgDelete", function(){ //当给出的条件合法的时候,判断接口的正确性 it("remove successfully when condition is legal", function(done){ mongodb.imgDelete({"filename":"1.jpg"}, function(){}, function(){done();}); }); // 当给出的条件不合法的时候,如,不存在这样的属性, // 判断接口的正确性 it("remove successfully when condition is legal", function(done){ mongodb.imgDelete({"filename":"10.jpg"}, function(){done();}, function(){}); }); }); // 测试imgDelete接口的正确性 describe("#imgFind", function(){ //当给出的条件合法的时候,判断接口的正确性 it("find successfully when condition is legal", function(done){ mongodb.imgFind({"filename":"1.jpg"},function(){}, function(){done();}); }); // 当给出的条件不合法的时候,如,不存在这样的属性, // 判断接口的正确性 it("find successfully when condition is legal", function(done){ mongodb.imgFind({"filename":"10.jpg"}, function(){done();}, function(){}); }); }); }) // 测试xml各接口正确性 describe("xml", function(){ // 测试xmlInsert接口,判断其是否正确 describe("#xmlInsert", function(){ //当参数合法时测试接口是否插入成功 it("insert successfully when param is legal", function(done){ mongodb.xmlInsert(__dirname+"\\","test.xml","abc", function(){}, function(){done();});}); // 当参数不合法时测试接口是否插入失败 it("insert failly when param is not legal", function(done){ mongodb.xmlInsert(__dirname+"\\","test.txt","abc", function(){done();}, function(){}); }); }); // 测试xmlDelete接口的正确性 describe("#xmlDelete", function(){ //当参数合法时测试接口是否删除成功 it("delete successfully when param is legal", function(done){ mongodb.xmlDelete("abc", function(){}, function(){done();}); }); // 当参数不合法时测试接口是否删除失败 it("delete failly when param is not legal", function(done){ mongodb.xmlDelete("abcd",function(){done();},function(){}); }); }); //测试xmlFind接口的正确性 describe("#xmlFind", function(){ //当参数合法时测试接口是否查询成功 it("find successfully when param is legal", function(done){ mongodb.xmlFind("abc", function(){}, function(){done();}); }); // 当参数不合法时测试接口是否查询失败 it("find failly when param is not legal", function(done){ mongodb.xmlFind("abcd", function(){done();}, function(){}); }); }); // 测试xmlUpdate接口的正确性 describe("#xmlUpdate ", function(){ //当参数合法时测试接口是否更新成功 it("update successfully when param is legal", function(done){ mongodb.xmlUpdate("abc",__dirname+"\\","test1.xml", function(){}, function(){done();}); }); // 当参数不合法时测试接口是否更新失败 it("update failly when param is not legal", function(done){ mongodb.xmlUpdate("abcd",__dirname+"\\","test1.xml", function(){done();}, function(){}); mongodb.xmlUpdate("abc", __dirname + "\\", "test2.xml", function(){done();}, function(){}); }); }); });

Python部分:

代码展示:

```python

图像测试类,继承unittest.TestCase类,用于对图像接口的测试

class ImageTest(unittest.TestCase): # unittest里特殊函数,每个测试在运行前都会执行 def setUp(self): # 创建Mongodb对象,以备其成员函数的调用 self.mongodb=Mongodb(configs['DB']['HOST'], configs['DB']['PORT'], configs['DB']['NAME'])

# unittest里的特殊函数,每个测试在运行前都会执行
def tearDown(self):
    self.mongodb = None

'''
函数功能:测试InsertImage接口的正确性
无参数,无返回值
'''

def test_InsertImage(self): # 给出合法的路径 self.assertEqual(self.mongodb.InsertImage(Image(sys.path[0] + "\", "1.jpg", 0, "001")), True) # 给出合法的路径 self.assertEqual(self.mongodb.InsertImage(Image(sys.path[0] + "\", "2.jpg", 0, "001")), True) # 给出不合法的路径 self.assertEqual(self.mongodb.InsertImage(Image(sys.path[0] + "\", "5.jpg", 0, "001")), False) ''' 函数功能:测试DeleteImage接口的正确性 无参数,无返回值 ''' def test_DeleteImage(self): # 条件合法即存在这样的图像满足条件 self.assertEqual(self.mongodb.DeleteImage ({"filename":"1.jpg"}), True) # 条件合法 self.assertEqual(self.mongodb.DeleteImage ({"filename":"2.jpg"}), True) # 条件不合法 self.assertEqual(self.mongodb.DeleteImage ({"filename":"5.jpg"}), False) ''' 函数功能:测试FindImage接口的正确性 无参数,无返回值 ''' def test_FindImage(self): # 条件合法 self.assertEqual(self.mongodb.FindImage ({"filename":"1.jpg"}), True) # 条件合法 self.assertEqual(self.mongodb.FindImage ({"filename":"2.jpg"}), True) #条件不合法 self.assertEqual(self.mongodb.FindImage ({"filename":"5.jpg"}), False) ''' 函数功能:测试FindImageById接口的正确性 无参数,无返回值 ''' def test_FindImageById(self): # id合法 self.assertEqual(self.mongodb.FindImageById ("5930da46d3a3660838899992"), True) # id合法
self.assertEqual(self.mongodb.FindImageById ("5930dad1d3a36621846fdc6f"), True) # id不合法 self.assertEqual(self.mongodb.FindImageById ("5930dad1d3a36321846fdc6f"), False)

图像测试类,继承unittest.TestCase类,

用于对病人隐私信息接口的测试

class PatientInfoTest(unittest.TestCase): # unittest里特殊函数,每个测试在运行前都会执行 def setUp(self): # 创建Mongodb对象,以备其成员函数的调用 self.mongodb=Mongodb(configs['DB']['HOST'], configs['DB']['PORT'], configs['DB']['NAME']) # unittest里的特殊函数,每个测试在运行前都会执行 def tearDown(self): self.mongodb = None

'''
函数功能:测试InsertPatientInfo接口的正确性
无参数,无返回值
'''
def test_InsertPatientInfo(self):
    # 给合法的病人隐私信息以及图像id
    self.assertEqual(self.mongodb.InsertPatientInfo("001", 
        Patient({"patientId": "001"})), True)
    # 给合法的病人隐私信息以及图像id
    self.assertEqual(self.mongodb.InsertPatientInfo("002",

Patient({"patientId": "002"})), True)

'''
函数功能:测试InsertPatientInfo接口的正确性
无参数,无返回值
'''
def test_FindPatientInfo(self):
    # 存在满足条件的病人隐私信息
        self.assertNotEqual(self.mongodb.FindPatientInfo

({"patientId":"001"}), False) # 存在满足条件的病人隐私信息 self.assertNotEqual(self.mongodb.FindPatientInfo ({"patientId":"002"}), False) # 不存在 self.assertEqual(self.mongodb.FindPatientInfo ({"patientId":"005"}), False) ''' 函数功能:测试InsertPatientInfo接口的正确性 无参数,无返回值 ''' def test_UpdataPatientInfo(self): # 存在满足条件的病人隐私信息 self.assertEqual(self.mongodb.UpdataPatientInfo ({"patientId":"001"}, {"patientName":"zhangsan"}), True) # 存在满足条件的病人隐私信息 self.assertEqual(self.mongodb.UpdataPatientInfo ({"patientId":"002"}, {"patientName":"lisi"}), True) # 不存在 self.assertEqual(self.mongodb.UpdataPatientInfo ({"patientId":"006"},{"patientName":"wangwu"}), False) ''' 函数功能:测试InsertPatientInfo接口的正确性 无参数,无返回值 ''' def test_DeletePatientInfo(self): # 存在满足条件的病人隐私信息 self.assertEqual(self.mongodb.DeletePatientInfo ({"patientId":"001"}), True) # 存在满足条件的病人隐私信息 self.assertEqual(self.mongodb.DeletePatientInfo ({"patientId":"002"}), True) # 不存在 self.assertEqual(self.mongodb.DeletePatientInfo ({"patientId":"007"}), False) ```

5.4 测试数据

在每一个功能完成之后都会进行分模块的功能测试,以确保这个功能模块的正常运行,同时也是为了方便之后功能的整合。通过选择某些具有代表性的数据和某些边界数据来测试可以很好的测试程序的健壮性,以保证程序的良好运行。下表是本系统中选择某些主要功能所做的测试以及出现的问题和解决方案,以及应用此解决方案后产生的效果。

功能模块 测试数据 测试出现过的问题 解决方法 结果
插入模块 合法和非法的图像或者xml的路径 插入失败 根据错误和异常修改代码 接口正常
查找模块 合法和非法的查询条件 接口正常
删除模块 合法和非法的删除条件 删除失败 根据错误和异常修改代码 接口正常
更新模块 合法和非法的被更新的限制条件以及更新目标 抛出异常 根据错误和异常修改代码 接口正常

5.5 本章小结

本章详细介绍了系统的测试,首先从调试的方法以及意义的角度介绍了程序的调试。然后介绍了测试的目的以及意义,紧接着又介绍了mocha、unittest两种单元测试框架。后面又具体详述了系统的基于这两种框架测试代码的编写,以及系统所采用的测试数据。

6 结 论

“医疗图像库在线存储与检索平台的设计与实现”作为我的毕业设计题目即将接近尾声,伴随它的也有我的大学生涯,在这里将它比作大学,我觉得挺恰当的,因为,他们都给予了我很多的知识。当然,这仅仅知识一方面,还有很多的相似性,剩下的自己体会。

这个题目,最初理解是做一个可以提供查询、上传操作类似于网站那样的平台。其实不然,简单来说,其实是要实现一个集合,一个可以提供其他模块增、删、改、查操作的接口集合。

这个系统在开发过程中采用了pymongo、mongodb、gridfs等几种包,这些包都是python、node.js官方所提供的包,其实还有其他的包可供使用,之所以选择了前者,是因为前者有完整的文档资料以及用户使用案例,这些都在我开发过程中起着很大的作用。

该系统根据语言的不同被分为两个部分,分别进行开发,两部分的模块划分基本是一样的。主要包含图像管理、系统配置模块、框架配置文件管理模块、病人隐私信息管理模块以及加密模块。在实现上图像管理模块花了比较长的时间,特别是用node.js这样的异步语言去实现。

整个系统在开发过程中都是我一个人负责的,包括最初的需求调查以及后面的设计、实现、测试。系统所采用的技术没有一样是之前接触过的,准确点来说node.js因为采用的是js语法还稍微能熟悉点,不过也就一点。所以最大的收获就是提高了自己学习陌生的东西开发系统的能力,这一点在程序员这个行业是很有必要的。

毕业设计这次机会是好的,学校基于了充足的时间去做这个东西,本来想着做的更好,但由于特殊的原因耽误了很多的时间,在这一点上是比较遗憾的。以后,针对每次开发或者写代码我都会加倍努力,原因只有一点,要对的起自己的兴趣。

7 参考文献

[1] 王金龙, 宋斌, 丁锐. Node. js: 一种新的 Web 应用构建技术[J]. 现代电子技术,2015, 38(6): 70-73.

[2] 谢华成, 马学文. MongoDB 数据库下文件型数据存储研究[J]. 软件, 2015 (11):12-14.

[3] 牛倩. MongoDB 数据库中自动分片技术应用研究[J]. 数字技术与应用, 2016(6):112-112.

[4] 李鹏. Node. js 及 MongoDb 应用研究[J]. 天津科技, 2015 (2015年 06): 34-36,39.

[5] Åkesson, Anders, and KennethLewenhagen. "Node. js in Open Source projects on Github: A literaturestudy and exploratory case study." (2015).

[6] 骆文亮. Node. js 服务器技术初探[J]. 无线互联科技, 2014,3: 178.

[7] 高飞, 何利力, and 高金标. "基于 Node. JS 内存缓存的 Web 服务性能研究."工业控制计算机 11 (2015): 047.

[8] 王金龙, 宋斌, and 丁锐. "Node. js: 一种新的 Web 应用构建技术." 现代电子技术 38.6 (2015): 70-73.

参考文献

  • 基于ASP.NET MVC架构的病例管理系统的设计与实现(东北大学·孙涛)
  • 基于Django框架的脑磁共振图像的可视化平台的设计和开发(东南大学·赵君)
  • 基于B/S架构远程医疗系统的设计与实现(杭州电子科技大学·徐凯)
  • 基于Lucene技术搜索引擎设计与实现(吉林大学·张阳)
  • 基于B/S架构的第三方影像中心远程影像诊断系统设计和应用(郑州大学·郭文泽)
  • 文本综合处理平台的研究与实现(济南大学·王孟孟)
  • 基于云计算模式的社会服务管理信息化平台项目设计与建设(吉林大学·杨刚)
  • 基于B/S的医疗视频点播系统的研究与设计(山东科技大学·张晶)
  • 基于PHP的医院管理信息系统的设计与实现(吉林大学·吕忠文)
  • 数字图像与矢量数据在空间数据库中的存取分析与应用(山东科技大学·姜永阐)
  • 医学影像数据管理系统的设计与实现(吉林大学·刘洋)
  • 基于B/S架构的第三方影像中心远程影像诊断系统设计和应用(郑州大学·郭文泽)
  • 基于B/S的医疗视频点播系统的研究与设计(山东科技大学·张晶)
  • 基于医疗知识图谱的探索式搜索研究(湘潭大学·周晴宇)
  • 基于互联网医疗大数据的分析平台设计与开发(浙江工业大学·靳继伟)

本文内容包括但不限于文字、数据、图表及超链接等)均来源于该信息及资料的相关主题。发布者:代码工厂 ,原文地址:https://m.bishedaima.com/yuanma/35428.html

相关推荐

发表回复

登录后才能评论