美国放射学会(ACR)和国家电气制造联合会(NEMA)在1993年通过协作将医学影像和通讯(DICOM)标准化了,为医学影像设备和相关应用程序的开发提供了可供参照执行的依据,后来成为存储和传输放射影像通用的标准,Oracle 10g R2完全支持DICOM 3.0标准,但是直到Oracle 11g才实现了对DICOM数据进行加密。
Oracle 11g扩展了Oracle 10g R2 中ORDImage对象类型的能力,增加了一个新的ORDDicom对象类型,以便更有效地存储DICOM影像,由于ORDDicom对象可以存储在SecureFile LOB中,这样就可以实现压缩、重复数据删除和加密存储在DICOM文件中的影像和相关的元数据的能力,医生、大学和医院使用计算的病人信息,特别是诊断和研究。
Oracle 10gR2提供了转储病人信息到XML文件的能力,但现在的ORDDicom对象扩展了这些能力,使得可以直接在Oracle 11g数据库查询病人的元数据,这就意味着可以使用新的高级索引特性如XMLIndex数据类型来检索特殊的病人信息,此外,Oracle 11g现在还可以创建、存储、展现和检索存储在ORDDicom对象内的数据影像的指纹特征,也就是说将会更容易选中正确的病人信息,ORDDicom数据模型也可以帮助保证病人信息的机密性,因为它提供了一个方法可以避免泄露来自DICOM文件病人元数据中的私密信息。
1、创建DICOM对象
为了说明如何使用这些强大的功能特性增强Oracle 11g SecureFile的加密能力,我创建了一个新的方案和表MIPS.PATIENT_IMAGES,我将使用这个表存储DICOM元数据,并在ORDDicom和ORDImage数据类型列中存储影像数据,DICOM_IMAGE列将会存储来自对应的源文件直接载入的DICOM信息,ANONYMOUS列将会存储病人的敏感数据,THUMBPRINT列将会存储来自每个DICOM文件中第一个影像的指纹图像,清单4显示了我创建这个表的DDL语句。
清单4 创建加密的SecureFile LOB存储敏感信息
创建一个用户(MIPS)
DROP USER mips CASCADE;
CREATE USER mips
IDENTIFIED BY mips
DEFAULT TABLESPACE patimages
TEMPORARY TABLESPACE temp
QUOTA UNLIMITED ON patimages;
GRANT CONNECT, RESOURCE TO mips;
GRANT EXECUTE ANY PROCEDURE to mips;
GRANT CREATE ANY DIRECTORY TO mips;
创建一个新的目录
DROP DIRECTORY mips_imgs;
CREATE OR REPLACE DIRECTORY mips_imgs
AS '/home/oracle/dicom';
GRANT READ ON DIRECTORY mips_imgs TO mips;
创建一个新表MIPS.PATIENT_IMAGES使用SecureFile LOB存储DICOM文件。
DROP TABLE mips.patient_images PURGE;
CREATE TABLE mips.patient_images (
patient_id NUMBER
,name VARCHAR2(30)
,ssn VARCHAR2(11)
,dob DATE
,dicom_image ORDSYS.ORDDICOM
,anonymous ORDSYS.ORDDICOM
,thumbprint ORDSYS.ORDIMAGE
)
TABLESPACE patimages
LOB(dicom_image.source.localData)
STORE AS SECUREFILE (
TABLESPACE patimages
DISABLE STORAGE IN ROW
DEDUPLICATE
COMPRESS HIGH
CACHE READS
)
LOB(anonymous.source.localData)
STORE AS SECUREFILE (
TABLESPACE patimages
DISABLE STORAGE IN ROW
DEDUPLICATE
COMPRESS HIGH
CACHE READS
)
LOB(thumbprint.source.localData)
STORE AS SECUREFILE (
TABLESPACE patimages
DISABLE STORAGE IN ROW
DEDUPLICATE
COMPRESS HIGH
CACHE READS
)
;
— 注释
COMMENT ON TABLE mips.patient_images
IS 'Contains Patient metadata and DICOM images';
COMMENT ON COLUMN mips.patient_images.patient_id
IS 'Unique identifier for a Patient';
COMMENT ON COLUMN mips.patient_images.name
IS 'Patient Name';
COMMENT ON COLUMN mips.patient_images.ssn
IS 'Patient Social Security Number';
COMMENT ON COLUMN mips.patient_images.dob
IS 'Patient Date of Birth';
COMMENT ON COLUMN mips.patient_images.dicom_image
IS 'DICOM LOB';
COMMENT ON COLUMN mips.patient_images.anonymous
IS 'Anonymous DICOM LOB';
COMMENT ON COLUMN mips.patient_images.thumbprint
IS 'Thumbprint of DICOM image';
— 创建索引和约束
CREATE UNIQUE INDEX mips.patient_images_pk_idx
ON mips.patient_images(patient_id)
TABLESPACE patimages;
ALTER TABLE mips.patient_images
ADD CONSTRAINT patient_images_pk
PRIMARY KEY (patient_id);
2、从DICOM源文件转移到ORDDicom对象
现在我已经为DICOM信息创建好了适当的资料库,我将要使用SQL*Loader直接从DICOM源文件中提取并格式化病人元数据,对于我的源数据,我将使用若干个简单的DICOM文件,这些DICOM文件都是从http://www.barre.nom.fr/medical/下载得到的,病人的姓名和其它信息完全是虚构的,只是为了说明这些概念,清单5显示了SQL*Loader用于初始化、格式化和从这些源文件中载入DICOM数据到ORDDicom和ORDImage数据类型列的参数文件,以及调用SQL*Loader执行这个初始化数据载入的结果。
清单5 使用SQL*Loader载入DICOM原始文件到Oracle 11g数据库中
— SQL*Loader参数文件: LoadDICOMFromFile.sqlparms
— 目的: 这个参数文件将直接从DICOM文件载入DICOM数据到MIPS.PATIENT_IMAGES 表的DICOM_IMAGE 列,它也控制了ANONYMOUS列(它容纳匿名的DICOM数据)和THUMBPRINT列(它容纳DICOM图像本身的指纹图像)的初始化。
LOAD DATA
INFILE *
INTO TABLE mips.patient_images
TRUNCATE
FIELDS TERMINATED BY WHITESPACE
OPTIONALLY ENCLOSED BY '"'
(
patient_id INTEGER EXTERNAL
,name CHAR
,ssn CHAR
,dob DATE "yyyy–mm–dd"
,fn FILLER CHAR
— 载入列对象MIPS.PATIENT_IMAGES.DICOM_IMAGE:
— 1.) LOB属性source.localData和DICOM数据一起载入。
— 2.) 属性srcType被设置为"local"。
— 3.) 属性updateTime被初始化到当前日期。
— 4.) LOB属性扩展使用空的LOB进行初始化。
,dicom_image COLUMN OBJECT (
source COLUMN OBJECT (
localData LOBFILE(fn) TERMINATED BY EOF
,srcType CONSTANT 'local'
,updateTime EXPRESSION "SYSDATE"
)
,extension LOBFILE(fn) TERMINATED BY EOF
DEFAULTIF dicom_image.source.srcType='local'
)
— 初始化(但不载入)列对象MIPS.PATIENT_IMAGES.ANONYMOUS:
— 1.) LOB属性source.localData和扩展被初始化。
— 2.) srcType属性被初始化为"local"。
— 3.) localData LOB将容纳DICOM数据的匿名内容。
— 4.) srcType属性被初始化为"local"。
— 5.) LOB扩展是一个由ORDDICOM使用的内部字段。
,anonymous COLUMN OBJECT (
source COLUMN OBJECT (
localData LOBFILE(fn) TERMINATED BY EOF
DEFAULTIF anonymous.source.srcType='local'
,srcType CONSTANT 'LOCAL'
)
,extension LOBFILE(fn) TERMINATED BY EOF
DEFAULTIF dicom_image.source.srcType='local'
)
— 初始化(但不载入)列对象MIPS.PATIENT_IMAGES.THUMBPRINT:
— 1.) LOB属性扩展被一个空的LOB初始化。
— 2.) LOB属性source.localData被一个空的LOB初始化。
— 3.) 本地属性被初始化为1。
,thumbprint COLUMN OBJECT (
source COLUMN OBJECT (
localData LOBFILE(fn) TERMINATED BY EOF
DEFAULTIF thumbprint.source.local=X'1'
,local CONSTANT 1
)
)
)
BEGINDATA
101 "Ames, Aldritch" 322–51–1111 1907–12–01 CT–MONO2–16–ankle.dcm
202 "Barry, Dave" 345–21–2222 1968–12–24 OT–MONO2–8–colon.dcm
303 "Colson, Charles" 342–43–3333 1931–11–03 MR–MONO2–8–16x–heart.dcm
404 "Dean, John" 322–09–4444 1942–02–18 NM–MONO2–16–13x–heart.dcm
505 "Ehrlichman, John" 345–09–5555 1914–08–01 US–PAL–8–10x–echo.dcm
606 "Haldeman, Robert" 322–18–6666 1918–11–11 US–RGB–8–esopecho.dcm
>>> SQL*Loader results:
SQL*Loader: Release 11.1.0.6.0 – Production on Sat Mar 14 13:00:40 2009
Copyright (c) 1982, 2007, Oracle. All rights reserved.
Control File: LoadDICOMFromFile.sqlparms
Data File: LoadDICOMFromFile.sqlparms
Bad File: LoadDICOMFromFile.bad
Discard File: none specified
(Allow all discards)
Number to load: ALL
Number to skip: 0
Errors allowed: 50
Bind array: 64 rows, maximum of 256000 bytes
Continuation: none specified
Path used: Conventional
Table MIPS.PATIENT_IMAGES, loaded from every logical record.
Insert option in effect for this table: TRUNCATE
Column Name Position Len Term Encl Datatype
——————————- ———- —– —- —- ———————
PATIENT_ID FIRST * WHT O(") CHARACTER
NAME NEXT * WHT O(") CHARACTER
SSN NEXT * WHT O(") CHARACTER
DOB NEXT * WHT O(") DATE yyyy–mm–dd
FN NEXT * WHT O(") CHARACTER
(FILLER FIELD)
DICOM_IMAGE DERIVED * COLUMN OBJECT
*** Fields in DICOM_IMAGE
SOURCE DERIVED * COLUMN OBJECT
*** Fields in DICOM_IMAGE.SOURCE
LOCALDATA DERIVED * EOF CHARACTER
Dynamic LOBFILE. Filename in field FN
SRCTYPE CONSTANT
Value is 'local'
UPDATETIME EXPRESSION
SQL string for column : "SYSDATE"
*** End of fields in DICOM_IMAGE.SOURCE
EXTENSION DERIVED * EOF CHARACTER
Dynamic LOBFILE. Filename in field FN
DEFAULT if DICOM_IMAGE.SOURCE.SRCTYPE = 0X6c6f63616c(character 'local')
*** End of fields in DICOM_IMAGE
ANONYMOUS DERIVED * COLUMN OBJECT
*** Fields in ANONYMOUS
SOURCE DERIVED * COLUMN OBJECT
*** Fields in ANONYMOUS.SOURCE
LOCALDATA DERIVED * EOF CHARACTER
Dynamic LOBFILE. Filename in field FN
DEFAULT if ANONYMOUS.SOURCE.SRCTYPE = 0X6c6f63616c(character 'local')
SRCTYPE CONSTANT
Value is 'LOCAL'
*** End of fields in ANONYMOUS.SOURCE
EXTENSION DERIVED * EOF CHARACTER
Dynamic LOBFILE. Filename in field FN
DEFAULT if DICOM_IMAGE.SOURCE.SRCTYPE = 0X6c6f63616c(character 'local')
*** End of fields in ANONYMOUS
THUMBPRINT DERIVED * COLUMN OBJECT
*** Fields in THUMBPRINT
SOURCE DERIVED * COLUMN OBJECT
*** Fields in THUMBPRINT.SOURCE
LOCALDATA DERIVED * EOF CHARACTER
Dynamic LOBFILE. Filename in field FN
DEFAULT if THUMBPRINT.SOURCE.LOCAL = 0X01(character '')
LOCAL CONSTANT
Value is '1'
*** End of fields in THUMBPRINT.SOURCE
*** End of fields in THUMBPRINT
Table MIPS.PATIENT_IMAGES:
6 Rows successfully loaded.
0 Rows not loaded due to data errors.
0 Rows not loaded because all WHEN clauses were failed.
0 Rows not loaded because all fields were null.
Space allocated for bind array: 83456 bytes(64 rows)
Read buffer bytes: 1048576
Total logical records skipped: 0
Total logical records read: 6
Total logical records rejected: 0
Total logical records discarded: 0
Run began on Sat Mar 14 13:00:40 2009
Run ended on Sat Mar 14 13:00:49 2009
Elapsed time was: 00:00:09.65
CPU time was: 00:00:00.61
3、生成指纹图像和匿名的元数据
为了完成ANONYMOUS和THUMBPRINT列的填充,我将使用一些技巧:
(1)首先,我将通过调用setModel 方法载入ORDDicom对象模型,这个对象模型必须在调用其它方法之前载入。
(2)然后使用processCopy方法生成一张150×200的JPEG图像,将其存储到THUMBPRINT列中。
(3)最后,我使用makeAnonymous方法创建一个匿名版本的DICOM_IMAGE列数据,并将其存储到ANONYMOUS列中。
清单6 显示了如何在最初被载入MIPS.PATIENT_IMAGES表中的所有行上执行这些方法。
清单6 从前面载入的ORDDicom对象创建和存储匿名的病人信息和指纹图像:
1)将DICOM模型载入到内存中。
2)将原始的DICOM文件元数据转换成对应的元素。
3)基于原始的DICOM影像以JPEG格式创建一个缩略图。
4)生成一个匿名的DICOM文件版本。
5)将新的信息保存到它们对应的SecureFile LOB中。
SET SERVEROUTPUT ON
DECLARE
dcm_row ORDSYS.ORDDICOM;
BEGIN
— 载入DICOM数据模型
ord_dicom.setDatamodel;
— 在医学图像表中遍历所有行
FOR dcm_row IN (
SELECT
patient_id
,dicom_image
,anonymous
,thumbprint
FROM mips.patient_images
FOR UPDATE
)
LOOP
BEGIN
——
—通过setProperties存储过程初始化DICOM SecureFile LOB列属性
——
dcm_row.dicom_image.setProperties();
——
— 在原始DICOM影像的基础上构建一个缩略图图像
——
dcm_row.dicom_image.processCopy('fileFormat=JPEG fixedScale=150,200', dcm_row.thumbprint);
——
— 通过makeAnonymous()函数生成DICOM对象的匿名版本
——
dcm_row.dicom_image.makeAnonymous(genUID(dcm_row.patient_id), dcm_row.anonymous);
——
— 将新产生的信息保存到它们对应的SecureFile LOB中
——
UPDATE mips.patient_images
SET dicom_image = dcm_row.dicom_image
,anonymous = dcm_row.anonymous
,thumbprint = dcm_row.thumbprint
WHERE patient_id = dcm_row.patient_id;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Error processing image for Patient ID #' || dcm_row.patient_id);
END;
END LOOP;
COMMIT;
END;
/
4、使用ORDDicom和ORDImage列的内容
最后,我已经准备好研究这些已经载入到DICOM列的DICOM信息,幸运的是,ORDDicom数据类型使得这些工作变得相当简单,因为它提供了多种方法来直接查询病人的元数据属性,清单7显示了一个简单的查询,它直接从DICOM_IMAGE和THUMBPRINT列查询多个属性。
清单7 查看已经载入到ORDDICOM和ORDIMAGE对象中的元数据
显示选择的内容:
1)普通数据类型
2)ORDDICOM数据类型
3)ORDIMAGE数据类型
SET LINESIZE 80
SET PAGESIZE 80
TTITLE 'Sample Patient Metadata|(from MIPS.PATIENT_IMAGES)'
COLUMN patient_id FORMAT 99999 HEADING 'Pat|ID'
COLUMN name FORMAT A20 HEADING 'Patient Name' WRAP
COLUMN ssn FORMAT A11 HEADING 'Patient SSN'
COLUMN di_sop_uid FORMAT A30 HEADING 'DICOM Image SOP UID' WRAP
COLUMN tp_len FORMAT 99999 HEADING 'Thumb|Print|Image|Size'
SELECT
PI.patient_id
,PI.name
,PI.ssn
,PI.dicom_image.sop_instance_uid AS di_sop_uid
,PI.thumbprint.getcontentlength() AS tp_len
FROM mips.patient_images PI
ORDER BY PI.patient_id
;
TTITLE OFF
Sun Mar 15 page 1
Sample Patient Metadata
(from MIPS.PATIENT_IMAGES)
Thumb
Pat Image
ID Patient Name Patient SSN DICOM Image SOP UID Size
——- ——————– ———– —————————— ——
101 Ames, Aldritch 322–51–1111 1.2.840.113619.2.1.2411.103115 5034
2382.365.1.736169244
202 Barry, Dave 345–21–2222 1.3.46.670589.17.1.7.0.16 5677
303 Colson, Charles 342–43–3333 999.999.2.19960619.163000.1.10 3648
3
404 Dean, John 322–09–4444 2.16.840.1.113662.5.8796818449 1596
476.121423489.1.1.3101.5309511
143
505 Ehrlichman, John 345–09–5555 999.999.133.1996.1.1800.1.6.25 5252
606 Haldeman, Robert 322–18–6666 999.999.2.19941105.112000.2.10 4683
7
校验存储在ORDDicom或ORDImage数据类型列中的图像需要一点技巧,因为需要一个接口才能查看这些图像,因为这些图像都采用基于工业标准的格式存储,如JPEG,TIF,PNG等,所以有大量的免费图像查看程序可以拿来使用。
小结
Oracle 11g新的SecureFile特性大大扩展在Oracle数据库中存储大对象的能力,并提高了安全性和有效性,SecureFILE LOB的压缩和重复数据删除功能在空间利用上更是让人惊讶,SecureFILE LOB的透明数据加密特性让存储在Oracle 11g数据库中的敏感信息和机密信息安全性更有保障,这些特性让Oracle 11g数据库成为美国新的联邦政府在启动存储医院、病人和医疗元数据方面的主要候选数据库。