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