上传、下载影像
continuous-integration/drone/push Build is passing Details

uat_us
wangxiaoshuang 2024-05-23 17:34:29 +08:00
parent 307403d897
commit d7a21af10c
9 changed files with 856 additions and 155 deletions

View File

@ -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
})
}

View File

@ -1,6 +1,16 @@
<template>
<div class="base-model-wrapper">
<el-dialog v-if="config.visible" v-dialogDrag :title="config.title" :append-to-body="config.appendToBody" :visible.sync="config.visible" :close-on-click-modal="false" :show-close="config.showClose" :width="config.width" :fullscreen="config.fullscreen">
<el-dialog
v-if="config.visible"
:v-dialogDrag="!config.dialogDrag"
:title="config.title"
:append-to-body="config.appendToBody"
:visible.sync="config.visible"
:close-on-click-modal="false"
:show-close="config.showClose"
:width="config.width"
:fullscreen="config.fullscreen"
>
<div class="base-modal-body">
<slot name="dialog-body" />
</div>
@ -12,46 +22,52 @@
</template>
<script>
export default {
name: 'BaseDialog',
name: "BaseDialog",
props: {
config: {
type: Object,
default: () => {
return {
visible: false,
title: '',
title: "",
closeOnClickModal: false,
showClose: true,
appendToBody: false,
width: '100px',
fullscreen: false
}
}
}
}
}
width: "100px",
fullscreen: false,
dialogDrag: false,
};
},
},
},
};
</script>
<style lang="scss">
.base-model-wrapper{
.el-dialog__header{
padding: 10px;
.el-dialog__headerbtn{
top: 10px;
right: 10px;
}
}
.el-dialog__body{
padding:10px 10px 10px 10px;
.base-modal-body{
min-height: 100px;
max-height:650px;
overflow-y: auto;
padding: 10px;
border: 1px solid #e0e0e0;
}
}
.el-dialog__footer{
padding: 10px;
.base-model-wrapper {
.el-dialog__header {
padding: 10px;
.el-dialog__headerbtn {
top: 10px;
right: 10px;
}
}
.el-dialog__body {
padding: 10px 10px 10px 10px;
.base-modal-body {
min-height: 100px;
max-height: 650px;
overflow-y: auto;
padding: 10px;
border: 1px solid #e0e0e0;
}
}
.el-dialog__footer {
padding: 10px;
}
}
.notFooter {
.is-fullscreen .el-dialog__footer {
display: none;
}
}
</style>

View File

@ -1,104 +1,169 @@
<template>
<base-model :config="model_cfg">
<div slot="dialog-body">
<div class="top" style="margin-bottom: 10px">
<el-button type="primary" size="small" class="el-icon-upload2">
<label for="file">
{{ $t("trials:uploadImage:button:selectFolder") }}
</label>
</el-button>
<input
type="file"
name="file"
id="file"
ref="file"
style="display: none"
webkitdirectory
multiple
@change="beginScanFiles($event)"
/>
</div>
<el-table
:data="list"
border
style="width: 100%"
height="300"
:loading="loading"
<el-dialog
:visible.sync="visible"
:fullscreen="true"
:close-on-click-modal="false"
custom-class="upload-dialog"
:before-close="beforeClose"
>
<span slot="title">{{ $t("trials:uploadImage:title:uploadImages") }}</span>
<el-table :data="list" style="width: 100%" height="300" :loading="loading">
<!--受试者-->
<el-table-column
prop="SubjectCode"
:label="$t('trials:uploadImage:table:subjectCode')"
/>
<!--任务名称-->
<el-table-column
prop="TaskBlindName"
:label="$t('trials:uploadImage:table:taskBlindName')"
/>
<!--原始检查数-->
<el-table-column
prop="OrginalStudyList"
:label="$t('trials:uploadImage:table:orginalStudyListNum')"
>
<!--受试者-->
<el-table-column
prop="subjectCode"
:label="$t('trials:uploadImage:table:subjectCode')"
/>
<!--任务名称-->
<el-table-column
prop="taskBlindName"
:label="$t('trials:uploadImage:table:taskBlindName')"
/>
<!--原始检查数-->
<el-table-column
prop="orginalStudyList"
:label="$t('trials:uploadImage:table:orginalStudyListNum')"
>
<template slot-scope="scope">{{
scope.row.orginalStudyList.length
}}</template>
</el-table-column>
<!--后处理检查数-->
<el-table-column
prop="uploadStudyList"
:label="$t('trials:uploadImage:table:uploadStudyListNum')"
min-width="140"
>
<template slot-scope="scope">{{
scope.row.orginalStudyList.length
}}</template>
</el-table-column>
<!--上传进度-->
<el-table-column
prop="schedule"
:label="$t('trials:uploadImage:table:schedule')"
>
<template slot-scope="scope">{{
scope.row.orginalStudyList.length
}}</template>
</el-table-column>
<!--上传状态-->
<el-table-column
prop="status"
:label="$t('trials:uploadImage:table:status')"
>
<template slot-scope="scope">{{
scope.row.orginalStudyList.length
}}</template>
</el-table-column>
<!--已有/成功/失败-->
<el-table-column
prop="scheduleNum"
:label="$t('trials:uploadImage:table:scheduleNum')"
min-width="140"
>
<template slot-scope="scope">{{
scope.row.orginalStudyList.length
}}</template>
</el-table-column>
<el-table-column :label="$t('common:action:action')" fixed="right">
<template slot-scope="scope">
<el-button
circle
icon="el-icon-delete"
:title="$t('trials:uploadImage:button:delete')"
@click.stop="remove(scope.row, item)"
/>
</template>
</el-table-column>
</el-table>
<template slot-scope="scope">{{
scope.row.OrginalStudyList &&
Array.isArray(scope.row.OrginalStudyList)
? scope.row.OrginalStudyList.length
: 0
}}</template>
</el-table-column>
<!--后处理检查数-->
<el-table-column
prop="UploadStudyList"
:label="$t('trials:uploadImage:table:uploadStudyListNum')"
>
<template slot-scope="scope">{{
scope.row.UploadStudyList && Array.isArray(scope.row.UploadStudyList)
? scope.row.UploadStudyList.length
: 0
}}</template>
</el-table-column>
<el-table-column
:label="$t('common:action:action')"
fixed="right"
width="140"
>
<template slot-scope="scope">
<!--下载--->
<el-button
circle
icon="el-icon-download"
:title="$t('trials:uploadImage:button:download')"
@click.stop="downloadImage(scope.row)"
/>
<!--删除--->
<el-button
circle
icon="el-icon-delete"
:title="$t('trials:uploadImage:button:delete')"
@click.stop="remove(scope.row)"
/>
</template>
</el-table-column>
</el-table>
<p class="line">{{ $t("trials:uploadImage:button:uploadTableTitle") }}</p>
<div
class="top"
style="margin-bottom: 10px"
v-if="Criterion.ImageUploadEnum > 0"
>
<el-button type="primary" size="small" class="el-icon-upload2">
<label for="file">
{{ $t("trials:uploadImage:button:selectFolder") }}
</label>
</el-button>
<input
type="file"
name="file"
id="file"
ref="file"
style="display: none"
webkitdirectory
multiple
@change="beginScanFiles($event)"
/>
</div>
</base-model>
<el-table
:data="uploadList"
style="width: 100%"
v-adaptive="{ bottomOffset: 40 }"
>
<!--检查编号-->
<el-table-column
prop="StudyId"
:label="$t('trials:uploadImage:table:StudyId')"
/>
<!--患者姓名-->
<el-table-column
prop="PatientName"
:label="$t('trials:uploadImage:table:PatientName')"
/>
<!--上传进度-->
<el-table-column
prop="schedule"
:label="$t('trials:uploadImage:table:schedule')"
min-width="150"
>
<template slot-scope="scope">
<el-progress
color="#409eff"
:percentage="scope.row.schedule"
v-if="scope.row.schedule || scope.row.schedule === 0"
/>
</template>
</el-table-column>
<!--上传状态-->
<el-table-column
prop="status"
:label="$t('trials:uploadImage:table:status')"
min-width="120"
>
<template slot-scope="scope">
<el-button
:type="['warning', 'success', 'danger'][scope.row.status]"
size="mini"
plain
v-if="scope.row.status || scope.row.status === 0"
>{{ $fd("UploadStatus", scope.row.status) }}</el-button
>
</template>
</el-table-column>
<!--已有/成功/失败-->
<el-table-column
prop="scheduleNum"
:label="$t('trials:uploadImage:table:scheduleNum')"
min-width="140"
>
<template slot-scope="scope">
<span v-if="scope.row.count">{{
`${scope.row.count} /${scope.row.successCount} / ${scope.row.failCount}`
}}</span>
</template>
</el-table-column>
<el-table-column
:label="$t('common:action:action')"
fixed="right"
width="140"
>
<!-- <template slot-scope="scope">
</template> -->
</el-table-column>
</el-table>
</el-dialog>
</template>
<script>
import baseModel from "@/components/BaseModel";
import { getSubjectImageUploadList } from "@/api/load.js";
import {
getSubjectImageUploadList,
preArchiveDicomStudy,
addOrUpdateArchiveTaskStudy,
} from "@/api/load.js";
import { downloadImage } from "@/utils/uploadZip.js";
import { parseDicom, getThumbnail } from "@/utils/parseDicom.js";
import { dcmUpload } from "@/utils/dcmUpload/dcmUpload";
export default {
name: "uploadImage",
props: {
@ -122,35 +187,22 @@ export default {
},
data() {
return {
model_cfg: {
visible: false,
showClose: true,
width: "1000px",
title: this.$t("trials:uploadImage:title:uploadImages"),
appendToBody: true,
},
list: [],
StudyInstanceUidList: [],
SopInstanceUidList: [],
uploadList: [], //
dicomList: [], //
requestNum: 6,
};
},
watch: {
visible: {
handler() {
this.model_cfg.visible = this.visible;
},
immediate: true,
deep: true,
},
"model_cfg.visible": {
handler() {
this.$emit("update:visible", this.model_cfg.visible);
},
deep: true,
},
},
created() {
this.getList();
},
methods: {
beforeClose() {
this.$emit("update:visible", false);
},
//
async getList() {
try {
@ -161,7 +213,21 @@ export default {
let res = await getSubjectImageUploadList(params);
this.loading = false;
if (res.IsSuccess) {
this.StudyInstanceUidList = [];
this.SopInstanceUidList = [];
this.list = res.Result;
res.Result.forEach((item) => {
if (item.OrginalStudyList && Array.isArray(item.OrginalStudyList)) {
item.OrginalStudyList.forEach((data) => {
data.SubjectId = item.SubejctId;
data.VisitTaskId = item.VisitTaskId;
data.SourceSubjectVisitId = item.SourceSubjectVisitId;
this.StudyInstanceUidList.push(data);
data.SopInstanceUidList &&
this.SopInstanceUidList.push(...data.SopInstanceUidList);
});
}
});
}
} catch (err) {
console.log(err);
@ -170,6 +236,396 @@ export default {
},
//
remove(item) {},
//
async downloadImage(item) {
try {
await downloadImage(
this.$route.query.trialId,
item.SourceSubjectVisitId
);
} catch (err) {
console.log(err);
}
},
//
async preArchiveDicomStudy(post) {
try {
let res = await preArchiveDicomStudy(post);
if (res.IsSuccess) {
return res.Result;
} else {
return false;
}
} catch (err) {
console.log(err);
return false;
}
},
//
async beginScanFiles(e) {
let files = e.target.files;
try {
this.dicomList = [];
for (var i = 0; i < files.length; ++i) {
let dicom = await parseDicom(files[i]);
let has = this.StudyInstanceUidList.some(
(item) => item.StudyInstanceUid === dicom.StudyInstanceUid
);
if (
!dicom ||
!has ||
this.SopInstanceUidList.includes(dicom.SopInstanceUid)
)
continue;
dicom.file = files[i];
dicom.isUpload = 0;
this.StudyInstanceUidList.some((item) => {
if (item.StudyInstanceUid === dicom.StudyInstanceUid) {
dicom.params = {
TrialId: this.$route.query.trialId,
SubjectId: item.SubjectId,
VisitTaskId: item.VisitTaskId,
SourceSubjectVisitId: item.SourceSubjectVisitId,
};
let uploadHas = this.uploadList.some(
(uploadData, uploadIndex) => {
if (uploadData.StudyInstanceUid === dicom.StudyInstanceUid) {
if (uploadData.status > 0) {
uploadData.count = 1;
uploadData.successCount = 0;
uploadData.failCount = 0;
uploadData.schedule = 0;
uploadData.status = 0;
uploadData.FileSize = dicom.file.size;
uploadData.IsDicomReUpload = false;
} else {
uploadData.FileSize += dicom.file.size;
uploadData.count++;
}
if (
item.SopInstanceUidList &&
item.SopInstanceUidList.length > 0
) {
uploadData.IsDicomReUpload = true;
}
dicom.uploadIndex = uploadIndex;
}
return uploadData.StudyInstanceUid === dicom.StudyInstanceUid;
}
);
if (!uploadHas) {
let uploadData = {
TrialId: this.$route.query.trialId,
SubjectId: item.SubjectId,
SourceSubjectVisitId: item.SourceSubjectVisitId,
StudyId: dicom.StudyId,
StudyInstanceUid: dicom.StudyInstanceUid,
FileSize: dicom.file.size,
count: 1,
successCount: 0,
failCount: 0,
schedule: 0,
status: 0,
IsDicomReUpload: false,
};
if (
item.SopInstanceUidList &&
item.SopInstanceUidList.length > 0
) {
uploadData.IsDicomReUpload = true;
}
this.uploadList.push(uploadData);
dicom.uploadIndex = this.uploadList.length - 1;
}
}
return item.StudyInstanceUid === dicom.StudyInstanceUid;
});
this.dicomList.push(dicom);
}
for (let i = 0; i < this.uploadList.length; i++) {
let item = this.uploadList[i];
let data = {
TrialId: item.TrialId,
SubjectId: item.SubjectId,
SubjectVisitId: item.SourceSubjectVisitId,
FileSize: item.FileSize,
IsDicomReUpload: item.IsDicomReUpload,
FileCount: item.count,
};
let res = await this.preArchiveDicomStudy(data);
if (res) {
item.StudyMonitorId = res;
let arr = this.dicomList.filter((dicom) => dicom.uploadIndex === i);
let num =
arr.length > this.requestNum ? this.requestNum : arr.length;
let funArr = [];
for (let i = 0; i < num; i++) {
funArr.push(this.handleUploadTask(arr, i));
}
if (funArr.length > 0) {
let res = await Promise.all(funArr);
}
}
}
} catch (err) {
console.log(err);
}
},
//
async handleUploadTask(arr, index) {
arr[index].isUpload = 1;
let res = await this.dicomToOSS(
arr[index].file,
arr[index].params,
arr[index]
);
if (res) {
arr[index].path = res;
this.uploadList[arr[index].uploadIndex].successCount++;
arr[index].isUpload = 2;
} else {
this.uploadList[arr[index].uploadIndex].status = 2;
this.uploadList[arr[index].uploadIndex].failCount++;
arr[index].isUpload = 3;
}
this.uploadList[arr[index].uploadIndex].schedule = Math.floor(
((this.uploadList[arr[index].uploadIndex].successCount +
this.uploadList[arr[index].uploadIndex].failCount) /
this.uploadList[arr[index].uploadIndex].count) *
100
);
if (
this.uploadList[arr[index].uploadIndex].schedule >= 100 &&
this.uploadList[arr[index].uploadIndex].successCount ===
this.uploadList[arr[index].uploadIndex].count
) {
this.uploadList[arr[index].uploadIndex].status = 1;
let res = await this.formatDicom(arr, arr[index].uploadIndex);
if (res) {
this.uploadList[arr[index].uploadIndex].Study =
res[arr[index].StudyId];
this.uploadList[arr[index].uploadIndex].status = 3;
res = await addOrUpdateArchiveTaskStudy(
this.uploadList[arr[index].uploadIndex]
);
if (res.IsSuccess) {
this.getList();
}
}
}
let ind = arr.findIndex((item) => !item.isUpload);
if (ind >= 0) {
return this.handleUploadTask(arr, ind);
} else {
return false;
}
},
//
async formatDicom(arr, index) {
let studyObj = {};
for (let i = 0; i < arr.length; i++) {
let dicom = arr[i];
if (dicom.uploadIndex !== index) continue;
if (!studyObj[dicom.StudyId]) {
studyObj[dicom.StudyId] = {
StudyId: dicom.StudyId,
StudyInstanceUid: dicom.StudyInstanceUid,
StudyTime: this.getTime(dicom.StudyDate, dicom.StudyTime),
Modalities: dicom.Modalities,
Description: dicom.StudyDescription,
SeriesCount: 1,
InstanceCount: 1,
InstitutionName: dicom.InstitutionName,
PatientId: dicom.PatientId,
PatientName: dicom.PatientName,
PatientAge: dicom.PatientAge,
PatientSex: dicom.PatientSex,
AccessionNumber: dicom.AccessionNumber,
PatientBirthDate: dicom.PatientBirthDate,
AcquisitionTime: dicom.AcquisitionTime,
AcquisitionNumber: dicom.AcquisitionNumber,
TriggerTime: dicom.TriggerTime,
BodyPartExamined: dicom.BodyPartExamined,
SeriesList: [
{
StudyInstanceUid: dicom.StudyInstanceUid,
SeriesInstanceUid: dicom.SeriesInstanceUid,
SeriesNumber: dicom.SeriesNumber,
SeriesTime: this.getTime(dicom.SeriesDate, dicom.SeriesTime),
Modality: dicom.Modality,
Description: dicom.SeriesDescription,
InstanceCount: 1,
SliceThickness: dicom.SliceThickness,
ImagePositionPatient: dicom.ImagePosition,
ImageOrientationPatient: dicom.ImageOrientation,
BodyPartExamined: dicom.seriesBodyPartExamined,
SequenceName: dicom.SequenceName,
ProtocolName: dicom.ProtocolName,
ImagerPixelSpacing: dicom.ImagePixelSpacing,
AcquisitionTime: dicom.AcquisitionTime,
AcquisitionNumber: dicom.AcquisitionNumber,
TriggerTime: dicom.TriggerTime,
ImageResizePath: await getThumbnail(
dicom.file,
dicom.params,
dicom
),
InstanceList: [
{
StudyInstanceUid: dicom.StudyInstanceUid,
SeriesInstanceUid: dicom.SeriesInstanceUid,
SopInstanceUid: dicom.SopInstanceUid,
InstanceNumber: dicom.ImageOrInstanceNumber,
InstanceTime: this.getTime(
dicom.ContentDate,
dicom.ContentTime
),
ImageRows: dicom.Rows || 0,
ImageColumns: dicom.Columns || 0,
SliceLocation: dicom.SliceLocation,
SliceThickness: dicom.SliceThickness,
NumberOfFrames: dicom.NumberOfFrames || 0,
PixelSpacing: dicom.PixelSpacing,
ImagerPixelSpacing: dicom.ImagePixelSpacing,
FrameOfReferenceUID: dicom.FrameOfReferenceUID,
WindowCenter: dicom.WindowCenter,
WindowWidth: dicom.WindowWidth,
Path: dicom.path,
HtmlPath: "",
},
],
},
],
};
} else {
let seriesHas = studyObj[dicom.StudyId].SeriesList.some((item) => {
if (item.SeriesInstanceUid === dicom.SeriesInstanceUid) {
item.InstanceCount++;
let instance = {
StudyInstanceUid: dicom.StudyInstanceUid,
SeriesInstanceUid: dicom.SeriesInstanceUid,
SopInstanceUid: dicom.SopInstanceUid,
InstanceNumber: dicom.ImageOrInstanceNumber,
InstanceTime: this.getTime(
dicom.ContentDate,
dicom.ContentTime
),
ImageRows: dicom.Rows || 0,
ImageColumns: dicom.Columns || 0,
SliceLocation: dicom.SliceLocation,
SliceThickness: dicom.SliceThickness,
NumberOfFrames: dicom.NumberOfFrames || 0,
PixelSpacing: dicom.PixelSpacing,
ImagerPixelSpacing: dicom.ImagePixelSpacing,
FrameOfReferenceUID: dicom.FrameOfReferenceUID,
WindowCenter: dicom.WindowCenter,
WindowWidth: dicom.WindowWidth,
Path: dicom.path,
HtmlPath: "",
};
item.InstanceList.push(instance);
}
return item.SeriesInstanceUid === dicom.SeriesInstanceUid;
});
if (!seriesHas) {
studyObj[dicom.StudyId].SeriesCount++;
let series = {
StudyInstanceUid: dicom.StudyInstanceUid,
SeriesInstanceUid: dicom.SeriesInstanceUid,
SeriesNumber: dicom.SeriesNumber,
SeriesTime: this.getTime(dicom.SeriesDate, dicom.SeriesTime),
Modality: dicom.Modality,
Description: dicom.SeriesDescription,
InstanceCount: 1,
SliceThickness: dicom.SliceThickness,
ImagePositionPatient: dicom.ImagePosition,
ImageOrientationPatient: dicom.ImageOrientation,
BodyPartExamined: dicom.seriesBodyPartExamined,
SequenceName: dicom.SequenceName,
ProtocolName: dicom.ProtocolName,
ImagerPixelSpacing: dicom.ImagePixelSpacing,
AcquisitionTime: dicom.AcquisitionTime,
AcquisitionNumber: dicom.AcquisitionNumber,
TriggerTime: dicom.TriggerTime,
ImageResizePath: await getThumbnail(
dicom.file,
dicom.params,
dicom
),
InstanceList: [
{
StudyInstanceUid: dicom.StudyInstanceUid,
SeriesInstanceUid: dicom.SeriesInstanceUid,
SopInstanceUid: dicom.SopInstanceUid,
InstanceNumber: dicom.ImageOrInstanceNumber,
InstanceTime: this.getTime(
dicom.ContentDate,
dicom.ContentTime
),
ImageRows: dicom.Rows || 0,
ImageColumns: dicom.Columns || 0,
SliceLocation: dicom.SliceLocation,
SliceThickness: dicom.SliceThickness,
NumberOfFrames: dicom.NumberOfFrames || 0,
PixelSpacing: dicom.PixelSpacing,
ImagerPixelSpacing: dicom.ImagePixelSpacing,
FrameOfReferenceUID: dicom.FrameOfReferenceUID,
WindowCenter: dicom.WindowCenter,
WindowWidth: dicom.WindowWidth,
Path: dicom.path,
HtmlPath: "",
},
],
};
studyObj[dicom.StudyId].SeriesList.push(series);
}
studyObj[dicom.StudyId].InstanceCount++;
}
}
console.log(studyObj);
return studyObj;
},
// dicom
getTime(date, time) {
let studyTime = "";
if (date) {
date = `${date.substring(0, 4)}-${date.substring(
4,
6
)}-${date.substring(6, 8)}`;
}
if (time) {
time = `${time.substring(0, 2)}:${time.substring(
2,
4
)}:${time.substring(4, 6)}`;
}
if (date) {
studyTime = time ? `${date} ${time}` : `${date} 00:00:00`;
}
return studyTime;
},
//
async dicomToOSS(file, params, dicomInfo) {
let path = `/${params.TrialId}/TaskImage/${params.SubjectId}/${
params.VisitTaskId
}/${dicomInfo.StudyInstanceUid}/${this.getGuid(
dicomInfo.StudyInstanceUid +
dicomInfo.SeriesInstanceUid +
dicomInfo.SOPInstanceUid +
params.TrialId +
params.VisitTaskId
)}`;
try {
let res = await dcmUpload(path, file);
if (!res || !res.url) return false;
return this.$getObjectName(res.url);
} catch (err) {
console.log(err);
return false;
}
},
},
};
</script>
@ -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;
}
}
</style>

View File

@ -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)

188
src/utils/parseDicom.js Normal file
View File

@ -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图像用两个枚举值MONOCHROME1MONOCHROME2 用来判断图像是否是彩色的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();
});
};

View File

@ -43,6 +43,7 @@
prop="OrganizationName"
>
<el-input
:disabled="user.IsZhiZhun"
v-model="user.OrganizationName"
:placeholder="$t('trials:trials-myinfo:form:organization')"
/>

View File

@ -17,7 +17,7 @@
>
<template v-for="item of $d.Criterion_Question_Type">
<el-option
v-if="item.value !== 'calculation' && item.value !== 'increment'"
v-if="item.value !== 'calculation' && item.value !== 'increment'&& item.value !== 'table'&& item.value !== 'group'"
:key="item.value"
:value="item.value"
:label="item.label"

View File

@ -285,7 +285,7 @@ export default {
handleSubjectVisitChange(val) {
if (val) {
const selectArr = this.subjectVisitOptions.filter(item => item.Id === val)
this.form.VisitNum = selectArr[0].VisitNum + 0.1
this.form.VisitNum = Math.ceil((selectArr[0].VisitNum + 0.1)*10)/10;
}
},
handleIsFinalVisitChange(val) {

View File

@ -977,7 +977,15 @@ const PDFViewerApplication = {
await Promise.all(promises);
},
decodeUtf8(bytes) {
let str = bytes.split('?');
let str2 = str[0].split('/');
let name = str2[str2.length-1];
name = encodeURIComponent(name);
str.shift();
str2.pop();
return str2.join("/")+'/'+name+'?'+str.join("?");
},
async open(file, args) {
if (this.pdfLoadingTask) {
await this.close();
@ -992,6 +1000,7 @@ const PDFViewerApplication = {
const parameters = Object.create(null);
if (typeof file === "string") {
file = this.decodeUtf8(file);
this.setTitleUsingUrl(file, file);
parameters.url = file;
} else if (file && "byteLength" in file) {