diff --git a/src/api/load.js b/src/api/load.js index e9ba36a1..34cd3179 100644 --- a/src/api/load.js +++ b/src/api/load.js @@ -14,4 +14,20 @@ 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 + }) } \ 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..01e91807 100644 --- a/src/components/uploadImage/index.vue +++ b/src/components/uploadImage/index.vue @@ -1,104 +1,169 @@ @@ -177,4 +633,18 @@ export default { label { cursor: pointer; } +.line { + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + margin: 20px 0; + &::after { + display: block; + content: ""; + flex: 1; + height: 1px; + border-top: 1px solid #ddd; + } +} \ 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..f913cb81 --- /dev/null +++ b/src/utils/parseDicom.js @@ -0,0 +1,188 @@ +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'; +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 getThumbnail = async (file, params, 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 thumbnailPath = `/${params.TrialId}/TaskImage/${params.SubjectId}/${params.VisitTaskId}/${dicomInfo.StudyInstanceUid}/${dicomInfo.SeriesInstanceUid}.png`; + let OSSclient = Vue.prototype.OSSclient; + let seriesRes = await OSSclient.put(thumbnailPath, 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/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 @@ >