irc_dicom_service/app/service/util.js

499 lines
19 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

const Service = require("egg").Service;
const crypto = require('crypto');
const zlib = require('zlib');
const fs = require('fs');
const path = require('path');
const net = require('net');
const md5 = require('js-md5')
const {
spawn,
execSync
} = require('child_process');
const dicomParser = require('dicom-parser'); //dicom解析
class UtilService extends Service {
async initData() {
let today = this.app.moment(from).subtract(1, "day");
for (let i = 0; i < 30; i++) {
let date = today.format("YYYY-MM-DD");
try {
console.log('查询成功:', date)
} catch (error) {
console.error(error, date)
}
today.subtract(1, "day")
}
}
enCode(password) {
let date = Date.now()
.toString()
.slice(2, 8);
let salt =
password +
date +
Math.random()
.toString("10")
.slice(2, 6);
return salt;
}
random(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
getGuid (text) {
text = md5(text)
let t1, t2, t3, t4, t5, t6, t7, t8, t9, t10
t1 = text.substr(0, 2)
t2 = text.substr(2, 2)
t3 = text.substr(4, 2)
t4 = text.substr(6, 2)
t5 = text.substr(8, 2)
t6 = text.substr(10, 2)
t7 = text.substr(12, 2)
t8 = text.substr(14, 2)
t9 = text.substr(16, 4)
t10 = text.substr(20, 12)
return `${t4+t3+t2+t1}-${t6+t5}-${t8+t7}-${t9}-${t10}`
}
readDicomToJsonJSON(param) {
try {
if (typeof param === 'string' || typeof param === 'number') {
return param;
} else {
return ""
}
} catch (error) {
return ""
}
}
/**
* 读取dicom文件返回结果
* @param {fileData} fileData dicom文件
*/
readDicomToJson(fileData) {
let dataSet = dicomParser.parseDicom(fileData);
let json = dicomParser.explicitDataSetToJS(dataSet);
let data = {
study: {
PatientID: this.readDicomToJsonJSON(json["x00100020"]), //患者ID
PatientName: this.readDicomToJsonJSON(decodeURIComponent(escape(json["x00100010"]))), //患者姓名
patientBirthDate: this.readDicomToJsonJSON(json["x00100030"]), //患者出生日期
PatientBirthTime: this.readDicomToJsonJSON(json["x00100032"]), //患者出生时间
PatientSex: this.readDicomToJsonJSON(json["x00100040"]), //患者性别
PatientAge: this.readDicomToJsonJSON(json["x00101010"]), //患者年龄
PatientWeight: this.readDicomToJsonJSON(json["x00101030"]), //
StudyInstanceUID: this.readDicomToJsonJSON(json["x0020000d"]), // 检查实例号
StudyDate: this.readDicomToJsonJSON(json["x00080020"]), // 检查开始的日期.
StudyTime: this.readDicomToJsonJSON(json["x00080030"]), //检查开始的时间.
StudyDescription: this.readDicomToJsonJSON(json["x00081030"]), //检查的描述
BodyPartExamined: this.readDicomToJsonJSON(json["x00180015"]), //身体部位
InstitutionName: this.readDicomToJsonJSON(json["x00080080"]), // 机构名称
ModalitiesInStudy: this.readDicomToJsonJSON(json["x00080061"]), //模态
OperatorsName: this.readDicomToJsonJSON(json["x00081070"]), //技师名称
},
serie: {
SeriesInstanceUID: this.readDicomToJsonJSON(json["x0020000e"]), //唯一标记不同序列的号码.
SeriesDescription: this.readDicomToJsonJSON(json["x0008103e"]), //检查描述和说明
SeriesDate: this.readDicomToJsonJSON(json["x00080021"]), //检查日期
SeriesTime: this.readDicomToJsonJSON(json["x00080031"]), //检查时间
Modality: this.readDicomToJsonJSON(json["x00080060"]), //序列的检测模态
AcquisitionTime: this.readDicomToJsonJSON(json["x00080032"]), //获得时间
SliceThickness: this.readDicomToJsonJSON(json["x00180050"]), //层厚
SpacingBetweenSlices: this.readDicomToJsonJSON(json["x00180088"]), //层与层之间的间距,单位为mm
Manufacturer: this.readDicomToJsonJSON(json["x00080070"]), // 制造商
ManufacturerModelName: this.readDicomToJsonJSON(json["x00081090"]), // 制造商模态名称
RadionuclideTotalDose: this.readDicomToJsonJSON(json["x00181074"]), //modify 19.2.22
DoseCalibrationFactor: this.readDicomToJsonJSON(json["x00541322"]), //modify 19.2.22
},
image: {
StudyInstanceUID: this.readDicomToJsonJSON(json["x0020000d"]), // StudyInstanceUID
SeriesInstanceUID: this.readDicomToJsonJSON(json["x0020000e"]), //SeriesInstanceUID
SOPInstanceUID: this.readDicomToJsonJSON(json["x00080018"]), // 4-11新增
InstanceNumber: this.readDicomToJsonJSON(json["x00200013"]), // 实例number
PixelSpacing: this.readDicomToJsonJSON(json["x00280030"]) ? this.readDicomToJsonJSON(json["x00280030"]) : this.readDicomToJsonJSON(json["x00181164"]), //像素
SamplesPerPixel: this.readDicomToJsonJSON(json['x00280002']), //采样率
PixelPaddingValue: this.readDicomToJsonJSON(json['x00280120']), // 像素填充值
Rows: this.readDicomToJsonJSON(json["x00280010"]), //图像行数
Columns: this.readDicomToJsonJSON(json["x00280011"]), //图像列数
WindowWidth: this.readDicomToJsonJSON(json["x00281051"]), //窗宽
WindowCenter: this.readDicomToJsonJSON(json["x00281050"]), //窗位
RescaleIntercept: this.readDicomToJsonJSON(json["x00281052"]), //截距
RescaleSlope: this.readDicomToJsonJSON(json["x00281053"]), //斜率
RescaleType: this.readDicomToJsonJSON(json["x00281054"]), //斜率截距换算类型
ImagePosition: this.readDicomToJsonJSON(json["x00200030"]), //图像位置
ImagePositionPatient: this.readDicomToJsonJSON(json["x00200032"]), //图像相对病人的位置
imageOrientation: this.readDicomToJsonJSON(json["x00200035"]), //图像方位
ImageOrientationPatient: this.readDicomToJsonJSON(json["x00200037"]), //200||'',037 图像相对于病人的方位
SliceLocation: this.readDicomToJsonJSON(json["x00201041"]), //实际的相对位置单位为mm.
BitsAllocated: this.readDicomToJsonJSON(json["x00280100"]), //分配的位数
BitsStored: this.readDicomToJsonJSON(json["x00280101"]), //存储的位数
HighBit: this.readDicomToJsonJSON(json["x00280102"]), //符号型二进制整数,长度 16 比特
PixelRepresentation: this.readDicomToJsonJSON(json['x00280103']), //像素数据的表现类型
}
}
return data;
}
//当没有InstanceNumber时根据SOPInstanceUID不同生成
async createNewInstanceNumber(dicomInfo) {
let SeriesInstanceUID = dicomInfo.image.SeriesInstanceUID;
let SOPInstanceUID = dicomInfo.image.SOPInstanceUID;
//获取该series下的images
let serie = await this.service.series.getSerieBySUID(SeriesInstanceUID);
let num = 1;
if (serie && serie.images_ids) {
for (let i = 0; i < serie.images_ids.length; i++) {
let image = serie.images_ids[i];
if (image.SOPInstanceUID == SOPInstanceUID) {
num = image.InstanceNumber;
break;
}
//如果num小于当前image.InstanceNumber则image.InstanceNumber+1赋给num
num = num < image.InstanceNumber ? image.InstanceNumber + 1 : num;
}
}
return num;
}
readDicom(fileData) {
let dataSet = dicomParser.parseDicom(fileData);
let data = {
study: {
PatientID: dataSet.string("x00100020") || '', //患者ID
PatientName: decodeURIComponent(escape(dataSet.string("x00100010"))) || '', //患者姓名
patientBirthDate: dataSet.string("x00100030") || '', //患者出生日期
PatientBirthTime: dataSet.string("x00100032") || '', //患者出生时间
PatientSex: dataSet.string("x00100040") || '', //患者性别
PatientAge: dataSet.string("x00101010") || '', //患者年龄
PatientWeight: dataSet.string("x00101030") || '', //
StudyInstanceUID: dataSet.string("x0020000d") || '', // 检查实例号
StudyDate: dataSet.string("x00080020") || '', // 检查开始的日期.
StudyTime: dataSet.string("x00080030") || '', //检查开始的时间.
StudyDescription: dataSet.string("x00081030") || '', //检查的描述
BodyPartExamined: dataSet.string("x00180015") || '', //身体部位
InstitutionName: dataSet.string("x00080080") || '', //
ModalitiesInStudy: dataSet.string("x00080060") || '', //模态
OperatorsName: dataSet.string("x00081070") || '', //技师名称
},
serie: {
SeriesInstanceUID: dataSet.string("x0020000e") || '', //唯一标记不同序列的号码.
SeriesDescription: dataSet.string("x0008103e") || '', //检查描述和说明
SeriesDate: dataSet.string("x00080021") || '', //检查日期
SeriesTime: dataSet.string("x00080031") || '', //检查时间
Modality: dataSet.string("x00080060") || '', //序列的检测模态
BodyPartExamined: dataSet.string("x00180015") || '', //身体部位
AcquisitionTime: dataSet.string("x00080031") || '',
SliceThickness: dataSet.floatString("x00180050") || '', //层厚
SpacingBetweenSlices: dataSet.floatString("x00180088") || '', //层与层之间的间距,单位为mm
Manufacturer: dataSet.string("x00080070") || '',
ManufacturerModelName: dataSet.string("x00081090") || '',
RadionuclideTotalDose: dataSet.string("x00181074") || '', //modify 19.2.22
DoseCalibrationFactor: dataSet.string("x00541322") || '', //modify 19.2.22
},
image: {
StudyInstanceUID: dataSet.string("x0020000d") || '', // StudyInstanceUID
SeriesInstanceUID: dataSet.string("x0020000e") || '', //SeriesInstanceUID
SOPInstanceUID: dataSet.string("x00080018"), // 4-11新增
InstanceNumber: dataSet.string("x00200013") || '',
PixelSpacing: dataSet.string("x00280030") || '', //像素
SamplesPerPixel: dataSet.uint16('x00280002') || '', //采样率
PixelPaddingValue: dataSet.int16('x00280120') || '', //
Columns: dataSet.int16("x00280011") || '', //图像列数
Rows: dataSet.int16("x00280010") || '', //图像行数
WindowWidth: dataSet.floatString("x00281051") || '', //窗宽
WindowCenter: dataSet.floatString("x00281050") || '', //窗位
RescaleType: dataSet.string("x00281054") || '', //斜率截距换算类型
RescaleSlope: dataSet.floatString("x00281053") || '', //斜率
RescaleIntercept: dataSet.string("x00281052") || '', //截距
ImagePosition: dataSet.string("x00200030") || '', //图像位置
ImagePositionPatient: dataSet.string("x00200032") || '', //图像相对病人的位置
imageOrientation: dataSet.string("x00200035") || '', //图像方位
ImageOrientationPatient: dataSet.string("x00200037") || '', //200||'',037 图像相对于病人的方位
SliceLocation: dataSet.string("x00201041") || '', //实际的相对位置单位为mm.
BitsAllocated: dataSet.uint16("x00280100") || '', //分配的位数
BitsStored: dataSet.uint16("x00280101") || '', //存储的位数
HighBit: dataSet.uint16("x00280102") || '', //符号型二进制整数,长度 16 比特
PixelRepresentation: dataSet.uint16("x00280103") || '', //像素数据的表现类型
}
}
return data;
}
getDateRange(date) {
try {
console.log(date)
return {
//今天
day: this.app.moment(date).endOf('day'),
//日 中间
mid_day: this.app.moment(date).startOf('day'),
//昨日
pre_day: this.app.moment(date).subtract(1, 'day').startOf('day'),
//上周末
week: this.app.moment(date).subtract(1, 'week').endOf('week').endOf('day'),
//周 中间
mid_week: this.app.moment(date).subtract(1, 'week').startOf('week').startOf('day'),
//上上周
pre_week: this.app.moment(date).subtract(2, 'week').startOf('week').startOf('day'),
//上个月末
month: this.app.moment(date).subtract(1, 'month').endOf('month').endOf('day'),
//月中间
mid_month: this.app.moment(date).subtract(1, 'month').startOf('month').startOf('day'),
//上上个月
pre_month: this.app.moment(date).subtract(2, 'month').startOf('month').startOf('day'),
}
} catch (error) {
throw "参数错误"
}
}
parseResponse(buf) {
let unzipData;
try {
unzipData = this.gunzip(buf).toString();
} catch (error) {
unzipData = buf.toString();
}
let res;
try {
res = JSON.parse(this.aesDecrypt(unzipData, this.getkey()))
} catch (error) {
res = JSON.parse(unzipData);
}
return res;
}
//获取秘钥
getkey() {
let time = this.app.moment().format("YYYY-MM-DD");
return 'rayplus_miyao_' + this.app.moment(time).valueOf();
}
aesEncrypt(data, key) {
const cipher = crypto.createCipher('aes192', key);
var crypted = cipher.update(data, 'utf8', 'hex');
crypted += cipher.final('hex');
return crypted;
}
aesDecrypt(encrypted, key) {
const decipher = crypto.createDecipher('aes192', key);
var decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
gzip(data) {
return zlib.gzipSync(buf);
}
gunzip(buf) {
return zlib.gunzipSync(buf);
}
/**
* 生成操作锁
* @param {*} name 锁名字
* @param {*} data 锁内容
* @param {*} flag 是否强制生成锁
*/
upsertLock(name, data, flag = false) {
let filePath = path.join(__dirname, "../public", `${name}.json`);
let fileExist = fs.existsSync(filePath);
if (fileExist) {
if (flag) {
this.deleteLock(name);
} else {
let fileData = fs.readFileSync(filePath);
let time = Number(JSON.parse(fileData).time);
//超过规定时间就删除锁文件
if (Date.now() - time > this.config.site.MOVE.LOCKTIME) {
this.deleteLock(name);
} else {
//返回正在被锁
return fileData;
}
}
}
let fileData = JSON.stringify({
time: Date.now(),
data
})
fs.writeFileSync(
filePath,
fileData
);
return fileData;
}
//获取lock文件内容
setLockData(name, data) {
try {
let filePath = path.join(__dirname, "../public", `${name}.json`);
let fd = fs.openSync(filePath);
if (!fd) throw "读取文件失败";
data = JSON.stringify(data);
fs.writeSync(fd, data);
fs.closeSync(fd);
} catch (error) {
console.error(error);
}
}
//删除操作锁
deleteLock(name) {
try {
let filePath = path.join(__dirname, "../public", `${name}.json`);
fs.unlinkSync(filePath);
} catch (error) {}
}
/**
* 清空文件夹
* @param {*} PATH 文件夹地址
* @param {*} flag 是否删除根文件
*/
async delFolder(PATH, flag = false) {
return new Promise((resolve, reject) => {
if (fs.existsSync(PATH)) {
fs.readdirSync(PATH).forEach((file) => {
var filePATH = path.join(PATH, file);
if (fs.statSync(filePATH).isDirectory()) {
this.delFolder(filePATH, flag)
} else {
try {
fs.unlinkSync(filePATH);
} catch (error) {
this.logger.error('文件删除失败', error)
}
}
});
if (flag) {
try {
fs.rmdirSync(PATH);
} catch (error) {
this.logger.error('文件夹删除失败', error)
}
}
}
return resolve();
})
}
//创建文件夹
createDirectory(directoryPath) {
if (!fs.existsSync(directoryPath)) {
fs.mkdirSync(directoryPath);
}
}
//检测端口是否被占用
portIsOccupied(port) {
const server = net.createServer().listen(port)
return new Promise((resolve, reject) => {
server.on('listening', () => {
server.close()
resolve(port)
})
server.on('error', (err) => {
if (err.code === 'EADDRINUSE') {
resolve(this.portIsOccupied(port + 1)) //注意这句,如占用端口号+1
console.log(`this port ${port} is occupied.try another.`)
} else {
reject(err)
}
})
})
}
//关闭某个端口的进程
closePort(port) {
let pidQuery, cmd;
if (process.platform == 'win32') {
pidQuery = execSync(`netstat -aon|findstr "${port}"`)
} else {
pidQuery = execSync(`lsof -i:${port}`)
}
var p = pidQuery.toString().trim().split(/\s+/);
console.log(pidQuery.toString(), p)
}
storesPython() {
return new Promise((resolve, reject) => {
try {
console.log('python storycp exec',`python this.config.site.STORYESCPPATH -dicompath=${this.config.site.CACHE_SAVE_PATH}`)
spawn('python', [this.config.site.STORYESCPPATH, `-dicompath=${this.config.site.CACHE_SAVE_PATH}`], {
cwd: this.config.site.PUBLIC,
detached: false,
stdio: ['ignore'],
windowsHide: true
})
// subprocess.on('close', (err) => {
// console.log('python storycp exit')
// setTimeout(() => {
// console.log('python storycp start')
// this.storesPython();
// }, 5e3);
// })
resolve("storescp启动成功");
} catch (e) {
reject("storescp启动失败", e);
}
});
}
/**
* 存储dicom数据
* @param {*} file 文件
* @param {*} dicomInfo 文件名
* @param {*} series_id series_id
*/
dicomSave(file, dicomInfo, series_id) {
series_id = series_id.toString()
// dicom存储主路径
let dicomSavePath = this.config.site.DICOM_SAVE_PATH;
// dicom展示主路径
let dicomShowPath = this.config.site.DICOM_SHOW_PATH;
//文件夹地址 使用series_id作为文件夹名称
let dirPath = path.join(dicomSavePath, series_id);
// 文件夹不存在就创建
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath);
}
let fileName = `${dicomInfo.InstanceNumber}.dcm`;
let showPath = path.join(dicomShowPath, series_id, fileName);
let savePath = path.join(dirPath, fileName);
return new Promise((resolve, reject) => {
fs.writeFile(savePath, file, (err) => {
if (err) reject(err);
resolve(showPath);
})
})
}
//生成PNG
createPng(serie, image) {
return new Promise(async (resolve, reject) => {
let pngPath = this.app.config.site.PNG_SAVE_PATH + '/' + serie._id.toString() + '/'
let dicomFile = `${image.InstanceNumber}.dcm`
let dicomPath = path.join(this.app.config.site.DICOM_SAVE_PATH, serie._id.toString(), dicomFile)
let msg = image.id.toString() + ' ' + dicomPath + ' ' + pngPath + ' ' + image.InstanceNumber + '\n'
try {
await this.app.config.png16.spawn[this.app.config.png16.cur].sendMsg(msg, image)
this.app.config.png16.cur = (this.app.config.png16.cur + 1) % this.config.png16.num >> 0;
} catch (error) {
return reject(`createPng:${dicomPath} 失败`)
}
return resolve();
})
}
}
module.exports = UtilService