diff --git a/src/api/load.js b/src/api/load.js index e9ba36a1..cc23cc40 100644 --- a/src/api/load.js +++ b/src/api/load.js @@ -14,4 +14,29 @@ export function getSubjectImageUploadList(params) { method: 'get', params }) +} +// 预上传 +export function preArchiveDicomStudy(data) { + return request({ + url: '/DownloadAndUpload/preArchiveDicomStudy', + method: 'post', + data + }) +} +// 归档 +export function addOrUpdateArchiveTaskStudy(data) { + return request({ + url: '/DownloadAndUpload/addOrUpdateArchiveTaskStudy', + method: 'post', + data + }) +} + +// 删除上传文件 +export function deleteTaskStudy(params) { + return request({ + url: '/DownloadAndUpload/deleteTaskStudy', + method: 'delete', + params + }) } \ No newline at end of file diff --git a/src/components/BaseModel/index.vue b/src/components/BaseModel/index.vue index ac3d7305..aac39ebb 100644 --- a/src/components/BaseModel/index.vue +++ b/src/components/BaseModel/index.vue @@ -1,6 +1,16 @@ diff --git a/src/components/uploadImage/index.vue b/src/components/uploadImage/index.vue index ba7fbcd3..3b7b79b2 100644 --- a/src/components/uploadImage/index.vue +++ b/src/components/uploadImage/index.vue @@ -1,104 +1,310 @@ @@ -177,4 +860,12 @@ export default { label { cursor: pointer; } +::v-deep .el-dialog { + .el-dialog__header { + padding-top: 15px; + } +} +.colorOfRed { + color: #f44336; +} \ No newline at end of file diff --git a/src/utils/dcmUpload/dcmUpload.js b/src/utils/dcmUpload/dcmUpload.js index b405d396..5399e583 100644 --- a/src/utils/dcmUpload/dcmUpload.js +++ b/src/utils/dcmUpload/dcmUpload.js @@ -1,12 +1,13 @@ import Vue from 'vue' -import { encoder } from './encoder' import { anonymization } from './anonymization' -export const dcmUpload = async function(name, file, config){ +export const dcmUpload = async function (name, file, config) { return new Promise(async resolve => { try { // let blob = await encoder(file, config) - // let blob = await fileToBlob(file) - let blob = await anonymization(file, config) + let blob = await fileToBlob(file) + if (config) { + blob = await anonymization(file, config) + } let res = await Vue.prototype.OSSclient.put(name, blob.blob) resolve({ ...res, @@ -35,7 +36,7 @@ function fileToBlob(file) { } else { blob = e.target.result } - resolve(blob) + resolve({ blob }) }) // FileReader 以 ArrayBuffer 格式 读取 File 对象中数据 reader.readAsArrayBuffer(file) diff --git a/src/utils/parseDicom.js b/src/utils/parseDicom.js new file mode 100644 index 00000000..4535eb6a --- /dev/null +++ b/src/utils/parseDicom.js @@ -0,0 +1,200 @@ +import * as dicomParser from "dicom-parser"; +import * as cornerstone from "cornerstone-core"; +import * as cornerstoneWADOImageLoader from "cornerstone-wado-image-loader"; +cornerstoneWADOImageLoader.external.dicomParser = dicomParser; +cornerstoneWADOImageLoader.external.cornerstone = cornerstone; +import { convertBytes } from "@/utils/dicom-character-set"; +import Vue from 'vue'; +import { dcmUpload } from "@/utils/dcmUpload/dcmUpload"; +let dicom = { + PatientName: "x00100010", // 患者姓名 + PatientId: "x00100020", // 患者ID + PatientBirthDate: "x00100030", // 患者出生日期 + PatientBirthTime: "x00100032", // 患者出生时间 + PatientSex: "x00100040", // 患者性别 + PatientWeight: "x00101030", // 患者体重 + PregnancyStatus: "x001021c0", // 怀孕状态 + PatientAge: "x00101010", // 患者年龄(做检查时刻的患者年龄,而不是此刻患者的真实年龄) + AcquisitionTime: "x00080032", + AcquisitionNumber: "x00200012", + TriggerTime: "x00181060", + AccessionNumber: "x00080050", // 检查号 + StudyId: "x00200010", // 检查ID + StudyInstanceUid: "x0020000d", // 检查实例号 + StudyDate: "x00080020", // 检查日期 + StudyTime: "x00080030", // 检查时间 + Modalities: "x00080061", // 一个检查中含有的不同检查类型 + BodyPartExamined: "x00080015", // 检查的部位 + StudyDescription: "x00081030", // 检查的描述 + InstitutionName: "x00080080", + SeriesNumber: "x00200011", // 序列号 + SeriesInstanceUid: "x0020000e", // 序列实例号 + Modality: "x00080060", // 检查模态 + SeriesDescription: "x0008103e", // 检查描述和说明 + SeriesDate: "x00080021", // 检查日期 + SeriesTime: "x00080031", // 检查时间 + SequenceName: "x00180024", + ProtocolName: "x00181030", + ImagePosition: "x00200032", // 图像位置 + ImageOrientation: "x00200037", // 图像方位 + ImagePixelSpacing: "x00181164", + SliceThickness: "x00180050", // 层厚 + SpacingBetweenSlices: "x00180088", // 层间距 + SliceLocation: "x00201041", // 相对位置 + MRAcquisition: "x00180023", + seriesBodyPartExamined: "x00180015", // 身体部位 + ImageType: "x00080008", + SopInstanceUid: "x00080018", // SOP实例UID + ContentDate: "x00080023", // 影像拍摄日期 + ContentTime: "x00080033", // 影像拍摄时间 + ImageOrInstanceNumber: "x00200013", // 图像码 + SamplesPerPixel: "x00280002", // 图像采样率 + PhotometricInterpretation: "x00280004", // 光度计(对于CT图像,用两个枚举值MONOCHROME1,MONOCHROME2 用来判断图像是否是彩色的;MONOCHROME 1/2是灰度图,RGB则是真彩色图) + Rows: "x00280010", // 行数 + Columns: "x00280011", // 列数 + NumberOfFrames: "x00280008", + PixelSpacing: "x00280030", // 像素间距 + BitsAllocated: "x00280100", // 分配的位数 + BitsStored: "x00280101", // 存储的位数 + HighBit: "x00280102", // 高位 + PixelRepresentation: "x00280103", // 像素数据的表现类型(一个枚举值,分别为十六进制数0000和0001.0000H = 无符号整型,0001H = 2的补码) + WindowCenter: "x00281050", // 窗位 + WindowWidth: "x002811051", // 窗宽 + RescaleIntercept: "x00281052", // 截距 + RescaleSlope: "x00281053", // 斜率 + RescaleType: "x00281054", // 输出值的单位 + ParsingFormat: "x00080005", + FrameOfReferenceUID: "x00200052", +}; +// 需要设置默认值 +let defaultKey = ['Rows', 'Columns', 'SliceLocation', 'NumberOfFrames']; +// 需要格式解析 +let pormatParseKey = ['PatientName', 'SeriesDescription', 'StudyDescription']; +// 解析dicom文件 +export const parseDicom = (file, name = false) => { + return new Promise(function (resolve, reject) { + let reader = new FileReader(); + reader.onload = function (e) { + try { + let data = dicomParser.parseDicom(new Uint8Array(e.target.result)); + let res = {}; + if (name && Array.isArray(name)) { + name.forEach((item) => { + if (dicom[item]) { + res[item] = data.string(dicom[item]) || data.uint16(dicom[item]) || data.intString(dicom[item]) || ''; + } + if (item === 'SliceLocation') { + res[item] = data.intString(dicom[item]) + } + }); + } else if (name) { + if (dicom[name]) { + res[name] = data.string(dicom[name]) || data.uint16(dicom[name]) || data.intString(dicom[name]) || ''; + if (name === 'SliceLocation') { + res[name] = data.intString(dicom[name]) + } + } else { + reject("name is inexistence"); + } + } else { + Object.keys(dicom).forEach((key) => { + res[key] = data.string(dicom[key]) || data.uint16(dicom[key]) || data.intString(dicom[key]) || ''; + if (key === 'SliceLocation') { + res[key] = data.intString(dicom[key]) + } + }); + } + pormatParseKey.forEach(key => { + if (res[key]) { + const Element = data.elements[dicom[key]]; + const Bytes = new Uint8Array( + data.byteArray.buffer, + Element ? Element.dataOffset : 0, + Element ? Element.length : 0 + ); + res[key] = convertBytes( + res.ParsingFormat, + Bytes + ); + } + }) + defaultKey.forEach(key => { + if (!res[key] && res.hasOwnProperty(key)) { + res[key] = 0; + } + }) + resolve(res); + } catch (error) { + reject(error); + } + }; + reader.onerror = function (e) { + reject(e); + }; + reader.readAsArrayBuffer(file); + }); +}; +// 影像上传 +// 影像上传 +export const dicomToOSS = async (file, path) => { + try { + let res = await dcmUpload(path, file); + if (!res || !res.url) return false; + return Vue.prototype.$getObjectName(res.url); + } catch (err) { + console.log(err); + return false; + } +}; +// 获取缩略图 +export const getThumbnail = async (file, ossPath, dicomInfo) => { + try { + if (dicomInfo.modality !== "SR") { + let fileId = cornerstoneWADOImageLoader.wadouri.fileManager.add(file); + let blob = await dicomToPng( + fileId, + dicomInfo.Columns, + dicomInfo.Rows + ); + if (!blob) return ""; + let OSSclient = Vue.prototype.OSSclient; + let seriesRes = await OSSclient.put(ossPath, blob); + if (seriesRes && seriesRes.url) { + return Vue.prototype.$getObjectName(seriesRes.url); + } else { + return ""; + } + } else { + return ""; + } + } catch (err) { + console.log(err); + return ""; + } +}; +const canvasToBlob = (canvas) => { + return new Promise((resolve) => { + canvas.toBlob((blob) => { + resolve(blob); + }); + }); +}; +const dicomToPng = (imageId, width, height) => { + return new Promise((resolve) => { + cornerstone.loadImage(imageId).then(async (image) => { + let canvas = document.createElement("canvas"); + canvas.width = width; + canvas.height = height; + if (image) { + cornerstone.renderToCanvas(canvas, image); + // 将 Canvas 图像对象转换为 PNG 格式 + let blob = await canvasToBlob(canvas); + resolve(blob); + } else { + resolve(false); + } + }); + }).catch((reason) => { + reason(); + }); +}; \ No newline at end of file diff --git a/src/views/trials/trials-myinfo/mine.vue b/src/views/trials/trials-myinfo/mine.vue index 5ad63bc1..455623f5 100644 --- a/src/views/trials/trials-myinfo/mine.vue +++ b/src/views/trials/trials-myinfo/mine.vue @@ -43,6 +43,7 @@ prop="OrganizationName" > diff --git a/src/views/trials/trials-panel/setting/reading-unit/components/ArbitrationRules.vue b/src/views/trials/trials-panel/setting/reading-unit/components/ArbitrationRules.vue index 6121d1cb..f655cf74 100644 --- a/src/views/trials/trials-panel/setting/reading-unit/components/ArbitrationRules.vue +++ b/src/views/trials/trials-panel/setting/reading-unit/components/ArbitrationRules.vue @@ -617,7 +617,7 @@ export default { }); }, applySync(ReadingQuestionTrialId, index) { - return new Promise((resolve, reject) => { + return new Promise(async (resolve, reject) => { console.log(this.QuestionList[index].JudgeType); if (this.QuestionList[index].JudgeType === 0) { reject(false); @@ -637,6 +637,21 @@ export default { reject(false); return; } + if ( + this.QuestionList[index].JudgeType === 4 || + this.QuestionList[index].JudgeType === 5 + ) { + try { + let validate = await this.$refs[ + "JudgeDifferenceValue" + + this.QuestionList[index].JudgeType + + index + ][0].validate(); + if (!validate) return reject(false); + } catch (err) { + return reject(false); + } + } this.btnLoading = true; this.loading = true; setTrialCriterionJudgeQuestionAnswerGroup({ @@ -644,6 +659,12 @@ export default { AnswerGroup: this.QuestionList[index].AnswerGroup2List, AnswerCombination: this.QuestionList[index].AnswerGroupList, JudgeType: this.QuestionList[index].JudgeType, + JudgeDifferenceValue: Number( + this.QuestionList[index].JudgeDifferenceValue + ), + JudgeDifferenceType: Number( + this.QuestionList[index].JudgeDifferenceType + ), }) .then((res) => { resolve(); @@ -778,6 +799,8 @@ export default { this.$set(this.QuestionList[index], "AnswerGroup2List", []); this.$set(this.QuestionList[index], "AnswerGroupList", []); this.$set(this.QuestionList[index], "JudgeType", 0); + this.$set(this.QuestionList[index], "JudgeDifferenceValue", 0); + this.$set(this.QuestionList[index], "JudgeDifferenceType", 0); this.$message.success(this.$t("trials:adRules:message:msg7")); // '重置成功' this.btnLoading = false; this.loading = false; diff --git a/src/views/trials/trials-panel/setting/reading-unit/components/TableQsForm.vue b/src/views/trials/trials-panel/setting/reading-unit/components/TableQsForm.vue index 7f68b5da..73c6013c 100644 --- a/src/views/trials/trials-panel/setting/reading-unit/components/TableQsForm.vue +++ b/src/views/trials/trials-panel/setting/reading-unit/components/TableQsForm.vue @@ -17,7 +17,7 @@ >