irc_web/src/components/uploadDicomAndNonedicom/dicomFile.vue

1502 lines
54 KiB
Vue

<template>
<div>
<div class="top">
<span>{{ $t('upload:dicom:title') }}</span>
<div class="btnBox">
<form id="inputForm" ref="uploadForm" enctype="multipart/form-data">
<div class="form-group">
<div id="directoryInputWrapper" class="btn btn-link file-input">
<el-button
type="primary"
:disabled="btnLoading"
:loading="btnLoading"
size="mini"
>
{{ $t('upload:dicom:button:upload') }}
</el-button>
<input
type="file"
name="file"
ref="pathClear"
:disabled="btnLoading"
webkitdirectory
multiple
title=""
@change="beginScanFiles($event)"
/>
</div>
</div>
</form>
</div>
</div>
<!--检查列表-->
<el-table :data="list" style="width: 100%" height="300" :loading="loading">
<!--受试者-->
<el-table-column
prop="SubjectCode"
:label="$t('upload:dicom:table:subjectCode')"
/>
<!--任务名称-->
<el-table-column
prop="TaskBlindName"
:label="$t('upload:dicom:table:taskBlindName')"
/>
<!--原始检查数-->
<el-table-column
prop="OrginalStudyList"
:label="$t('upload:dicom:table:orginalStudyListNum')"
>
<template slot-scope="scope">
<el-button
v-if="
scope.row.OrginalStudyList &&
scope.row.OrginalStudyList.length >= 1
"
type="text"
@click="handleOpenDialog(scope.row, 'OrginalStudyList')"
>
<span>{{ scope.row.OrginalStudyList.length }}</span>
</el-button>
<span v-else>0</span>
</template>
</el-table-column>
<!--后处理检查数-->
<el-table-column
prop="UploadStudyList"
:label="$t('upload:dicom: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="180"
>
<template slot-scope="scope">
<div class="btnBox">
<!--上传--->
<form
id="inputForm"
:ref="`uploadForm_${scope.row.Id}`"
enctype="multipart/form-data"
>
<div class="form-group" style="margin-right: 10px">
<div
:id="`directoryInputWrapper_${scope.row.Id}`"
class="btn btn-link file-input"
>
<el-button
circle
icon="el-icon-upload2"
:disabled="btnLoading"
:loading="btnLoading"
:title="$t('upload:dicom:button:upload')"
/>
<input
type="file"
:name="`file_${scope.row.Id}`"
:ref="`pathClear_${scope.row.Id}`"
:disabled="btnLoading"
webkitdirectory
multiple
title=""
@change="
($event) => beginScanFiles($event, scope.row.VisitTaskId)
"
/>
</div>
</div>
</form>
<!--预览--->
<el-button
circle
icon="el-icon-view"
:disabled="
!scope.row.UploadStudyList ||
scope.row.UploadStudyList.length <= 0
"
@click.stop="handleViewReadingImages(scope.row)"
:title="$t('upload:dicom:button:preview')"
/>
<!--删除--->
<el-button
circle
:disabled="
!scope.row.UploadStudyList ||
scope.row.UploadStudyList.length <= 0
"
icon="el-icon-delete"
:title="$t('upload:dicom:button:delete')"
@click.stop="remove(scope.row)"
/>
</div>
</template>
</el-table-column>
</el-table>
<div class="top" style="margin: 10px 0">
<span>{{ $t('upload:dicom:uploadTitle') }}</span>
<span style="margin-left: 10px">{{ $store.state.trials.uploadTip }}</span>
</div>
<!--上传列表-->
<el-table
ref="dicomFilesTable"
v-adaptive="{ bottomOffset: 80 }"
height="100"
:data="uploadQueues"
class="dicomFiles-table"
@selection-change="handleSelectionChange"
>
<el-table-column type="index" width="40" />
<el-table-column min-width="200" show-overflow-tooltip>
<template slot="header">
<el-tooltip placement="top">
<div slot="content">
{{ $t('trials:uploadDicomList:table:studyDetail1') }}<br />
{{ $t('trials:uploadDicomList:table:studyDetail2') }}<br />
{{ $t('trials:uploadedDicoms:table:studyDate') }}
</div>
<span>{{ $t('trials:uploadDicomList:table:studyInfo') }}</span>
</el-tooltip>
</template>
<template slot-scope="scope">
<div style="line-height: 15px">
<div>
<div>
<span v-if="scope.row.dicomInfo.accNumber"
><span style="font-weight: 500">Acc:</span>
{{ scope.row.dicomInfo.accNumber }}</span
>
<span v-else style="color: #f44336">N/A</span>
</div>
<div style="display: inline-block; margin-right: 2px">
<span v-if="scope.row.dicomInfo.modality.length > 0">
{{ scope.row.dicomInfo.modality.join('、') }},</span
>
<span v-else style="color: #f44336">N/A,</span>
</div>
<div style="display: inline-block; margin-right: 2px">
<span v-if="scope.row.seriesList.length"
>{{ scope.row.seriesList.length }} Series,</span
>
<span v-else style="color: #f44336">N/A,</span>
</div>
<div style="display: inline-block">
<span v-if="scope.row.fileList.length"
>{{ scope.row.fileList.length }} Instances</span
>
<span v-else style="color: #f44336">N/A</span>
</div>
</div>
<div>
<div style="display: inline-block; margin-right: 2px">
<span v-if="scope.row.dicomInfo.bodyPart">
{{ scope.row.dicomInfo.bodyPart }},
</span>
<span v-else style="color: #f44336">N/A, </span>
</div>
<div style="display: inline-block">
<span v-if="scope.row.dicomInfo.description">
{{ scope.row.dicomInfo.description }}</span
>
<span v-else style="color: #f44336">N/A</span>
</div>
</div>
<div>
{{ scope.row.dicomInfo.studyTime }}
</div>
</div>
</template>
</el-table-column>
<el-table-column min-width="130" show-overflow-tooltip>
<template slot="header">
<el-tooltip placement="top">
<div slot="content">
{{ $t('trials:uploadDicomList:table:pId') }}<br />
{{ $t('trials:uploadDicomList:table:patientName') }}<br />
{{ $t('trials:uploadDicomList:table:pInfo') }}
</div>
<span>{{ $t('trials:uploadDicomList:table:patientInfo') }}</span>
</el-tooltip>
</template>
<template slot-scope="scope">
<div style="line-height: 15px">
<div>
<span v-if="scope.row.dicomInfo.patientId"
><span style="font-weight: 500">PID: </span
>{{ scope.row.dicomInfo.patientId }}</span
>
<span v-else style="color: #f44336">N/A</span>
</div>
<div>
<span
:class="[scope.row.dicomInfo.patientName ? '' : 'colorOfRed']"
>
{{
scope.row.dicomInfo.patientName
? scope.row.dicomInfo.patientName
: 'N/A'
}}
</span>
</div>
<div>
<span
:class="[scope.row.dicomInfo.patientSex ? '' : 'colorOfRed']"
>
{{
scope.row.dicomInfo.patientSex
? scope.row.dicomInfo.patientSex
: 'N/A'
}},
</span>
<span
:class="[scope.row.dicomInfo.patientAge ? '' : 'colorOfRed']"
>
{{
scope.row.dicomInfo.patientAge
? scope.row.dicomInfo.patientAge
: 'N/A'
}},
</span>
<span
:class="[
scope.row.dicomInfo.patientBirthDate ? '' : 'colorOfRed',
]"
>
{{
scope.row.dicomInfo.patientBirthDate
? scope.row.dicomInfo.patientBirthDate
: 'N/A'
}}
</span>
</div>
</div>
</template>
</el-table-column>
<el-table-column
:label="$t('trials:uploadDicomList:table:failedFileCount')"
min-width="150"
show-overflow-tooltip
>
<template slot-scope="scope">
<el-progress
color="#409eff"
:percentage="
(
(scope.row.dicomInfo.uploadFileSize * 100) /
scope.row.dicomInfo.fileSize
).toFixed(2) * 1
"
/>
<span>
{{ $t('trials:uploadDicomList:table:uploadNow')
}}{{ scope.row.dicomInfo.failedFileCount }} /
{{ scope.row.dicomInfo.fileCount }}:[{{
(scope.row.dicomInfo.uploadFileSize / 1024 / 1024).toFixed(2)
}}MB/{{
(scope.row.dicomInfo.fileSize / 1024 / 1024).toFixed(2)
}}MB]
</span>
</template>
</el-table-column>
<el-table-column
:label="$t('trials:uploadDicomList:table:status')"
min-width="140"
show-overflow-tooltip
>
<template slot-scope="scope">
<span
v-if="
!scope.row.dicomInfo.failedFileCount &&
!scope.row.dicomInfo.isInit
"
>
{{ $t('trials:uploadDicomList:table:status1') }}</span
>
<span
style="color: #409eff"
v-else-if="
!scope.row.dicomInfo.failedFileCount &&
scope.row.dicomInfo.isInit &&
btnLoading
"
>{{ $t('trials:uploadDicomList:table:status2') }}</span
>
<span
style="color: #409eff"
v-else-if="
scope.row.dicomInfo.failedFileCount <
scope.row.dicomInfo.fileCount && !scope.row.uploadState.record
"
>{{ $t('trials:uploadDicomList:table:status2') }}</span
>
<span
style="color: #2cc368"
v-else-if="
scope.row.dicomInfo.failedFileCount ===
scope.row.dicomInfo.fileCount
"
>{{ $t('trials:uploadDicomList:table:status3') }}</span
>
<span
style="color: #f66"
v-else-if="
scope.row.uploadState.record &&
scope.row.uploadState.record.fileCount === 0
"
>{{ $t('trials:uploadDicomList:table:status5') }}</span
>
<span style="color: #f66" v-else>{{
$t('trials:uploadDicomList:table:Failed')
}}</span>
</template>
</el-table-column>
<el-table-column
:label="$t('trials:uploadDicomList:table:record')"
min-width="140"
show-overflow-tooltip
>
<template slot-scope="scope">
<el-tooltip placement="top" v-if="scope.row.uploadState.record">
<div slot="content">
<div style="max-height: 500px; overflow-y: auto">
{{ $t('trials:uploadDicomList:table:Existed') }}:
<div v-if="scope.row.uploadState.record.Existed.length">
<div
v-for="item of scope.row.uploadState.record.Existed"
:key="item"
style="font-size: 12px; color: #baa72a"
>
{{ item }}
</div>
</div>
<div v-else>&nbsp;</div>
{{ $t('trials:uploadDicomList:table:Uploaded') }}:
<div v-if="scope.row.uploadState.record.Uploaded.length">
<div
v-for="item of scope.row.uploadState.record.Uploaded"
:key="item"
style="font-size: 12px; color: #24b837"
>
{{ item }}
</div>
</div>
<div v-else>&nbsp;</div>
<br />
{{ $t('trials:uploadDicomList:table:Failed') }}:
<div v-if="scope.row.uploadState.record.Failed.length">
<div
v-for="item of scope.row.uploadState.record.Failed"
:key="item"
style="font-size: 12px; color: #f66"
>
{{ item }}
</div>
</div>
<div v-else>&nbsp;</div>
</div>
</div>
<el-button size="mini" style="cursor: pointer">
<span style="font-size: 12px; color: #baa72a">{{
scope.row.uploadState.record.Existed.length
}}</span>
/
<span style="font-size: 12px; color: #24b837">{{
scope.row.uploadState.record.Uploaded.length
}}</span>
/
<span style="font-size: 12px; color: #f66">{{
scope.row.uploadState.record.Failed.length
}}</span>
</el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<study-view
v-if="model_cfg.visible"
:model_cfg="model_cfg"
:IsDicom="true"
:modelList="modelList"
/>
</div>
</template>
<script>
import { dicomUploadInProgress } from '@/api/trials'
import {
preArchiveDicomStudy,
addOrUpdateArchiveTaskStudy,
} from '@/api/load.js'
import studyView from './study-view.vue'
import {
getSubjectImageUploadList,
deleteTaskStudy,
verifyIRStudyAllowUpload,
} from '@/api/load.js'
import * as dicomParser from 'dicom-parser'
import * as cornerstone from 'cornerstone-core'
import * as cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader'
var config = {
maxWebWorkers: 4,
startWebWorkersOnDemand: true,
taskConfiguration: {
decodeTask: {
initializeCodecsOnStartup: false,
},
},
}
cornerstoneWADOImageLoader.webWorkerManager.initialize(config)
cornerstoneWADOImageLoader.external.dicomParser = dicomParser
cornerstoneWADOImageLoader.external.cornerstone = cornerstone
import { convertBytes } from '@/utils/dicom-character-set'
import { parseDicom } from '@/utils/parseDicom.js'
import { dcmUpload } from '@/utils/dcmUpload/dcmUpload'
import store from '@/store'
import { getToken } from '@/utils/auth'
export default {
name: 'dicomFile',
props: {
SubjectId: {
type: String,
default: '',
},
SubjectCode: {
type: String,
default: '',
},
Criterion: {
type: Object,
default: () => {
return {}
},
},
},
components: {
'study-view': studyView,
},
data() {
return {
list: [],
StudyInstanceUidList: [],
SopInstanceUidList: [],
UploadStudyList: [],
// 检查数弹框
model_cfg: {
visible: false,
showClose: true,
width: '1000px',
title: '',
appendToBody: true,
},
modelList: [],
VisitTaskId: null,
hasOtherStudy: false,
scanState: '', // 当前上传队列上传状态
form: {
SiteName: '',
SubjectName: '',
VisitName: '',
SubjectId: '',
VisitNum: null,
},
isScan: false,
uploadQueues: [], // 上传队列
studyErrorList: [],
warningArr: [],
selectArr: [],
btnLoading: false,
myInterval: [],
isClose: false,
trialId: null,
subjectVisitId: null,
errStudyUidList: [],
open: null,
}
},
created() {
this.getList()
},
async mounted() {
this.trialId = this.$route.query.trialId
this.uploadQueues = []
this.OSSclient.close()
},
beforeDestroy() {
this.myInterval.forEach((v) => {
clearInterval(v)
})
this.myInterval = []
store.dispatch('trials/setUnLock', false)
this.isClose = true
this.OSSclient.close()
},
methods: {
// 获取列表
async getList() {
try {
let params = {
SubjectId: this.SubjectId,
TrialReadingCriterionId: this.Criterion.TrialReadingCriterionId,
SubjectCode: this.SubjectCode,
}
this.loading = true
let res = await getSubjectImageUploadList(params)
this.loading = false
if (res.IsSuccess) {
this.StudyInstanceUidList = []
this.SopInstanceUidList = []
this.UploadStudyList = []
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.subjectVisitId = item.SourceSubjectVisitId
this.StudyInstanceUidList.push(data)
})
}
if (item.UploadStudyList && Array.isArray(item.UploadStudyList)) {
item.UploadStudyList.forEach((data) => {
data.SopInstanceUidList &&
this.SopInstanceUidList.push(...data.SopInstanceUidList)
data.VisitTaskId = item.VisitTaskId
this.UploadStudyList.push(data)
})
}
})
}
} catch (err) {
console.log(err)
this.loading = false
}
},
// 删除
async remove(item) {
try {
let confirm = await this.$confirm(
this.$t('trials:uploadImage:confirm:delMessage'),
{
type: 'warning',
distinguishCancelAndClose: true,
confirmButtonText: this.$t('common:button:confirm'),
cancelButtonText: this.$t('common:button:cancel'),
}
)
if (confirm !== 'confirm') return
let params = {
VisitTaskId: item.VisitTaskId,
IsDicom: true,
}
let res = await deleteTaskStudy(params)
if (res.IsSuccess) {
this.getList()
this.$message.success(
this.$t('trials:uploadImage:message:delSuccess')
)
}
} catch (err) {
console.log(err)
}
},
// 当选择项发生变化时收集待上传文件数据
handleSelectionChange(selection) {
selection.sort((n1, n2) => {
return n1.studyIndex - n2.studyIndex
})
this.selectArr = selection
},
// 根据是否上传状态决定CheckBox 是否可以勾选
handleSelectable(row) {
if (row.uploadState.selected || this.btnLoading) {
return false
} else {
return true
}
},
// 打开弹窗
handleOpenDialog(item, list) {
this.model_cfg.title = `${item.SubjectCode || ''}>${item.TaskBlindName}`
this.modelList = item[list]
this.model_cfg.visible = true
},
// 校验上传数据
async verifyIRStudyAllowUpload(list) {
try {
let data = {
StudyInstanceUidList: list || [],
TrialId: this.$route.query.trialId,
SubjectCode: this.SubjectCode,
SubjectId: this.SubjectId,
}
let res = await verifyIRStudyAllowUpload(data)
if (res.IsSuccess) {
return res.Result
}
return false
} catch (err) {
console.log(err)
return false
}
},
// 扫描待上传文件
async beginScanFiles(e, id = null) {
this.VisitTaskId = id
this.hasOtherStudy = false
var files = e.target.files
if (files.length > 0) {
var checkFiles = [...files]
let max = checkFiles.some((item) => item.size > 20 * 1024 * 1024 * 1024)
if (max)
return this.$message.warning(
this.$t('trials:uploadDicomList:message:maxFileSize')
)
let StudyInstanceUidList = []
for (let i = 0; i < checkFiles.length; i++) {
let item = checkFiles[i]
var dicom = await parseDicom(item, 'StudyInstanceUid')
let has = true,
has2 = false,
has3 = false
if (!this.VisitTaskId) {
has = this.StudyInstanceUidList.some(
(item) => item.StudyInstanceUid === dicom.StudyInstanceUid
)
} else {
has2 = this.StudyInstanceUidList.some((item) => {
return (
item.StudyInstanceUid === dicom.StudyInstanceUid &&
item.VisitTaskId !== this.VisitTaskId
)
})
has3 = this.UploadStudyList.some((item) => {
return (
item.StudyInstanceUid === dicom.StudyInstanceUid &&
item.VisitTaskId !== this.VisitTaskId
)
})
}
if (!has || has2 || has3) {
this.hasOtherStudy = true
checkFiles.splice(i, 1)
i--
} else {
if (!~StudyInstanceUidList.indexOf(dicom.StudyInstanceUid)) {
StudyInstanceUidList.push(dicom.StudyInstanceUid)
}
}
}
this.errStudyUidList = []
let res = await this.verifyIRStudyAllowUpload(StudyInstanceUidList)
if (res) {
res.forEach((item) => {
if (!item.IsAllowUpload && !item.IsAllowReUpload) {
this.errStudyUidList.push(item.StudyInstanceUid)
}
})
}
if (this.errStudyUidList && this.errStudyUidList.length > 0) {
for (let i = 0; i < checkFiles.length; i++) {
let item = checkFiles[i]
var dicom = await parseDicom(item, 'StudyInstanceUid')
if (!!~this.errStudyUidList.indexOf(dicom.StudyInstanceUid)) {
this.hasOtherStudy = true
checkFiles.splice(i, 1)
i--
}
}
}
if (this.hasOtherStudy) {
this.$confirm(this.$t('upload:dicom:confirmMessage:hasNotStudyUid'), {
type: 'warning',
distinguishCancelAndClose: true,
confirmButtonText: this.$t('common:button:confirm'),
cancelButtonText: this.$t('common:button:cancel'),
})
}
var scope = this
scope.scanState = ''
scope.isScan = false
var p = new Promise(function (resolve) {
resolve()
})
var validFilesCount = 0
for (var i = 0; i < checkFiles.length; ++i) {
;(function (index) {
p = p.then(function () {
if (
checkFiles[index].name.toUpperCase().indexOf('DICOMDIR') === -1
) {
validFilesCount = validFilesCount + 1
return scope.parseDicomFile(checkFiles[index])
}
})
})(i)
}
p.then(function (result) {
scope.uploadQueues.forEach((v, i) => {
scope.$refs.dicomFilesTable.toggleRowSelection(v, true)
})
scope.scanState = 'finished'
scope.isScan = true
scope.beginUploadQueues()
})
}
},
// 按序列UUID本地归档
parseDicomFile(file) {
var scope = this
return new Promise(function (resolve, reject) {
if (scope.scanState === 'cancelling') {
resolve()
return
}
var reader = new FileReader()
reader.onload = function (e) {
try {
var data = dicomParser.parseDicom(new Uint8Array(e.target.result))
var instanceUid = data.string('x00080018')
if (!instanceUid) return resolve()
var modality = data.string('x00080060') || ''
var studyUid = data.string('x0020000d')
if (!studyUid) return resolve()
var pixelDataElement = data.elements.x7fe00010
var studyIndex = 0
while (
studyIndex < scope.uploadQueues.length &&
scope.uploadQueues[studyIndex].dicomInfo.studyUid !== studyUid &&
(pixelDataElement || modality === 'SR') &&
modality != ''
) {
++studyIndex
}
if (studyIndex >= scope.uploadQueues.length) {
var date = data.string('x00080020')
var time = data.string('x00080030')
var 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`
}
const patientNameElement = data.elements.x00100010
const patientNameBytes = new Uint8Array(
data.byteArray.buffer,
patientNameElement ? patientNameElement.dataOffset : 0,
patientNameElement ? patientNameElement.length : 0
)
const patientNameStr = convertBytes(
data.string('x00080005'),
patientNameBytes
)
scope.uploadQueues.push({
studyIndex: studyIndex,
seriesList: [],
fileList: [],
dicomInfo: {
studyId: data.string('x00200010'),
studyUid: studyUid,
patientId: data.string('x00100020'),
patientName: patientNameStr,
patientAge: data.string('x00101010')
? data.string('x00101010')
: '',
patientSex: data.string('x00100040')
? data.string('x00100040')
: '',
patientBirthDate: data.string('x00100030'),
hospitalName: data.string('x00080080'),
accNumber: data.string('x00080050'),
bodyPart: data.string('x00180015') || '',
modality: [],
institutionName: data.string('x00080080'),
description: data.string('x00081030'),
//0008,0032
acquisitionTime: data.string('x00080032'),
acquisitionNumber: data.string('x00200012'),
triggerTime: data.string('x00181060'),
studyTime: studyTime,
VisitInfo: {},
SubjectInfo: {},
failedFileCount: 0,
uploadFileSize: 0,
fileSize: 0,
fileCount: 0,
isInit: false,
visitTaskId:
scope.VisitTaskId ||
scope.StudyInstanceUidList.find(
(item) => item.studyInstanceUid === studyUid
).VisitTaskId,
},
uploadState: {
selected: false,
stateCode: '',
beginUploadTime: {},
progressValue: 0,
},
})
}
var modality = scope.uploadQueues[studyIndex].dicomInfo.modality
var currentModality = data.string('x00080060')
if (!(modality.indexOf(currentModality) > -1)) {
modality.push(currentModality)
}
var fileList = scope.uploadQueues[studyIndex].fileList
var instanceUid = data.string('x00080018')
var instanceIndex = 0
while (
instanceIndex < fileList.length &&
fileList[instanceIndex].instanceUid !== instanceUid &&
(pixelDataElement || modality === 'SR')
) {
++instanceIndex
}
if (instanceIndex >= fileList.length) {
fileList.push({
instanceUid: instanceUid,
file: file,
})
}
scope.uploadQueues[studyIndex].dicomInfo.fileCount = fileList.length
scope.uploadQueues[studyIndex].dicomInfo.fileSize = fileList.reduce(
(prev, next) => {
return prev + next.file.size
},
0
)
var seriesUid = data.string('x0020000e')
var seriesList = scope.uploadQueues[studyIndex].seriesList
var seriesItem = seriesList.find(function (item) {
return item.seriesUid === seriesUid
})
if (!seriesItem && (pixelDataElement || modality === 'SR')) {
var date = data.string('x00080021')
var time = data.string('x00080031')
var seriesTime = ''
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) {
seriesTime = time ? `${date} ${time}` : `${date} 00:00:00`
}
const seriesDescriptionElement = data.elements.x0008103e
const seriesDescriptionBytes = new Uint8Array(
data.byteArray.buffer,
seriesDescriptionElement
? seriesDescriptionElement.dataOffset
: 0,
seriesDescriptionElement ? seriesDescriptionElement.length : 0
)
const seriesDescriptionStr = convertBytes(
data.string('x00080005'),
seriesDescriptionBytes
)
seriesItem = {
seriesUid: seriesUid,
seriesNumber: data.intString('x00200011') || 1,
modality: data.string('x00080060') || '',
description: seriesDescriptionStr,
seriesTime: seriesTime,
sliceThickness: data.string('x00180050') || '',
imagePositionPatient: data.string('x00200032') || '',
imageOrientationPatient: data.string('x00200037') || '',
sequenceName: data.string('x00180024') || '',
protocolName: data.string('x00181030') || '',
imagerPixelSpacing: data.string('x00181164') || '',
instanceList: [],
}
seriesList.push(seriesItem)
}
var instanceList = seriesItem.instanceList
var instanceItem = instanceList.find(function (item) {
return item.instanceUid === instanceUid
})
if (!instanceItem) {
var date = data.string('x00080023')
var time = data.string('x00080033')
var instanceTime = ''
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) {
instanceTime = time ? `${date} ${time}` : `${date} 00:00:00`
}
instanceItem = {
instanceUid: instanceUid,
instanceNumber: data.intString('x00200013') || 1,
frameCount: data.intString('x00280008') || 1,
instanceTime: instanceTime,
imageRows: data.uint16('x00280010') || 0,
imageColumns: data.uint16('x00280011') || 0,
sliceLocation: data.intString('x00201041') || 0,
sliceThickness: data.string('x00180050') || '',
numberOfFrames: data.intString('x00280008') || 0,
pixelSpacing: data.string('x00280030') || '',
imagerPixelSpacing: data.string('x00181164') || '',
frameOfReferenceUID: data.string('x00200052') || '',
windowCenter: data.string('x00281050') || '',
windowWidth: data.string('x00281051') || '',
modality: data.string('x00080060') || '',
file: file,
FileSize: file.size,
}
instanceList.push(instanceItem)
}
resolve()
} catch (error) {
console.log(error)
resolve()
}
}
reader.onerror = function (e) {
resolve()
}
reader.readAsArrayBuffer(file)
})
},
// 上传之前校验基本信息及检查是否已上传
beginUploadQueues() {
this.scanState = 'uploading'
this.studyErrorList = []
// console.log(this.uploadQueues, this.selectArr)
this.warningArr = []
for (let i = 0; i < this.selectArr.length; ++i) {
const index = this.selectArr[i].studyIndex
this.uploadQueues[index].uploadState.stateCode = 'uploading'
this.verifyBasicInfos(index)
}
if (this.warningArr.length) {
this.warning_cfg.visible = true
this.btnLoading = false
} else {
this.verifyStudy()
}
},
// 校验影像中检查时间、性别是否与受试者匹配
verifyBasicInfos(index) {
var warnings = []
if (
this.uploadQueues[index].dicomInfo.patientSex &&
this.form.SubjectSex
) {
if (
this.uploadQueues[index].dicomInfo.patientSex.substr(0, 1) !==
this.form.SubjectSex.substr(0, 1)
) {
warnings.push(
this.$t('trials:uploadDicomList:message:genderConsistent')
)
}
}
if (warnings.length > 0) {
this.warningArr.push({
index: this.uploadQueues[index].studyIndex + 1,
accNumber: this.uploadQueues[index].dicomInfo.accNumber,
warnings: warnings,
})
}
},
// 校验该检查是否上传过
async verifyStudy() {
this.btnLoading = true
var studyList = []
this.selectArr.forEach((item) => {
if (!item.uploadState.selected) {
studyList.push({
studyInstanceUid: item.dicomInfo.studyUid,
studyDate: item.dicomInfo.studyTime,
})
}
})
this.uploadCount = this.selectArr.length
this.uploadedCount = 0
for (let i = 0; i < this.selectArr.length; i++) {
const index = this.selectArr[i].studyIndex
if (!this.uploadQueues[index].uploadState.selected) {
await this.archiveStudy(index)
}
}
this.$set(this, 'uploadQueues', [
...this.uploadQueues.filter((v) => {
return (
!v.uploadState.record ||
(v.uploadState.record && !!v.uploadState.record.Failed.length)
)
}),
])
this.$nextTick(() => {
this.selectArr = []
this.uploadQueues.forEach((v, i) => {
if (v.uploadState.record) {
v.uploadState.selected = false
this.$refs.dicomFilesTable.toggleRowSelection(v, true)
}
v.studyIndex = i
})
this.$refs.pathClear.value = ''
this.list.forEach((item) => {
this.$refs[`pathClear_${item.Id}`].value = ''
})
this.btnLoading = false
if (this.studyErrorList.length > 0) {
let msg = this.$t('trials:uploadDicomFiles2:message:studyErrorList')
msg = msg.replaceAll('xxx', this.studyErrorList.join('、'))
this.$confirm(msg, this.$t('trials:hotkeys:message:tip'), {
confirmButtonText: this.$t(
'trials:uploadDicomFiles2:button:reupload'
),
}).then(() => {
this.beginUploadQueues()
})
}
})
},
// 上传影像并归档
archiveStudy(index, config) {
var scope = this
return new Promise(function (resolve, reject) {
try {
preArchiveDicomStudy({
trialId: scope.trialId,
subjectId: scope.SubjectId,
subjectVisitId: scope.subjectVisitId,
fileSize: scope.uploadQueues[index].dicomInfo.fileSize,
fileCount: scope.uploadQueues[index].dicomInfo.fileCount,
IsDicomReUpload:
scope.uploadQueues[index].uploadState.AllowReUpload,
})
.then(async (res) => {
scope.uploadQueues[index].dicomInfo.failedFileCount = 0
scope.$set(scope.uploadQueues[index].dicomInfo, 'isInit', true)
let dicomInfo = scope.uploadQueues[index].dicomInfo
let seriesNum = scope.uploadQueues[index].seriesList.length
let fileNum = scope.uploadQueues[index].fileList.length
let seriesList = scope.uploadQueues[index].seriesList
dicomInfo.seriesNum = seriesNum
dicomInfo.fileNum = fileNum
dicomInfo.subjectId = scope.SubjectId
dicomInfo.subjectVisitId = scope.subjectVisitId
let t = setInterval(() => {
dicomUploadInProgress({
trialId: scope.trialId,
studyInstanceUid: dicomInfo.studyUid,
}).then((res) => {})
}, 5000)
scope.myInterval.push(t)
let Record = {
Failed: [],
Existed: [],
Uploaded: [],
FileCount: 0,
}
let params = {
trialId: scope.trialId,
subjectId: scope.SubjectId,
subjectVisitId: scope.subjectVisitId,
studyMonitorId: res.Result,
failedFileCount: 0,
RecordPath: null,
study: {
studyId: dicomInfo.studyId,
studyInstanceUid: dicomInfo.studyUid,
studyTime: dicomInfo.studyTime,
description: dicomInfo.description,
seriesCount: dicomInfo.seriesNum,
instanceCount: dicomInfo.fileNum,
institutionName: dicomInfo.institutionName,
patientId: dicomInfo.patientId,
patientName: '',
patientAge: '',
patientSex: dicomInfo.patientSex,
accessionNumber: dicomInfo.accNumber,
patientBirthDate: '',
acquisitionTime: dicomInfo.acquisitionTime,
acquisitionNumber: dicomInfo.acquisitionNumber,
triggerTime: dicomInfo.triggerTime,
bodyPartExamined: '',
seriesList: [],
},
}
let arr = []
for (let i = 0; i < seriesList.length; i++) {
let v = seriesList[i]
let instanceList = []
let ImageResizePath
for (let ii = 0; ii < v.instanceList.length; ii++) {
arr.push(
(function () {
return new Promise(async (resolve1) => {
try {
let o = v.instanceList[ii]
let name = `${v.instanceList[ii].file.webkitRelativePath}_${v.instanceList[ii].instanceUid}`
if (o.isReUpload) {
dicomInfo.failedFileCount++
dicomInfo.uploadFileSize += o.file.size
Record.Existed.push(name)
Record.FileCount++
} else if (o.myPath) {
instanceList.push({
studyInstanceUid: dicomInfo.studyUid,
seriesInstanceUid: v.seriesUid,
sopInstanceUid: o.instanceUid,
instanceNumber: o.instanceNumber,
instanceTime: o.instanceTime,
imageRows: o.imageRows,
imageColumns: o.imageColumns,
sliceLocation: o.sliceLocation,
sliceThickness: o.sliceThickness,
numberOfFrames: o.numberOfFrames,
pixelSpacing: o.pixelSpacing,
imagerPixelSpacing: o.imagerPixelSpacing,
frameOfReferenceUID: o.frameOfReferenceUID,
windowCenter: o.windowCenter,
windowWidth: o.windowWidth,
path: o.myPath,
FileSize: o.FileSize,
})
Record.Uploaded.push(name)
dicomInfo.failedFileCount++
Record.FileCount++
} else {
let path = `/${params.trialId}/Image/${
params.subjectId
}/${params.subjectVisitId}/${
dicomInfo.visitTaskId
}/${scope.getGuid(
dicomInfo.studyUid +
v.seriesUid +
o.instanceUid +
params.trialId
)}`
if (scope.isClose) return
let res = await dcmUpload(
{
path: path,
file: o.file,
speed: true,
},
null,
(percentage, checkpoint, lastPer) => {
dicomInfo.uploadFileSize +=
checkpoint.size * (percentage - lastPer)
if (
dicomInfo.uploadFileSize > dicomInfo.fileSize
) {
dicomInfo.uploadFileSize = dicomInfo.fileSize
}
}
)
if (!res || !res.url) {
params.failedFileCount++
} else {
if (ii === 0 && o.modality !== 'SR') {
try {
let fileId =
cornerstoneWADOImageLoader.wadouri.fileManager.add(
o.file
)
let blob = await scope.dicomToPng(
fileId,
o.imageColumns,
o.imageRows
)
let thumbnailPath = `/${params.trialId}/Image/${params.subjectId}/${params.subjectVisitId}/${dicomInfo.visitTaskId}/${dicomInfo.studyUid}/${v.seriesUid}.png`
let OSSclient = scope.OSSclient
let seriesRes = await OSSclient.put(
thumbnailPath,
blob
)
if (seriesRes && seriesRes.url) {
ImageResizePath = scope.$getObjectName(
seriesRes.url
)
}
} catch (e) {
console.log(e)
}
}
}
if (res && res.url) {
instanceList.push({
studyInstanceUid: dicomInfo.studyUid,
seriesInstanceUid: v.seriesUid,
sopInstanceUid: o.instanceUid,
instanceNumber: o.instanceNumber,
instanceTime: o.instanceTime,
imageRows: o.imageRows,
imageColumns: o.imageColumns,
sliceLocation: o.sliceLocation,
sliceThickness: o.sliceThickness,
numberOfFrames: o.numberOfFrames,
pixelSpacing: o.pixelSpacing,
imagerPixelSpacing: o.imagerPixelSpacing,
frameOfReferenceUID: o.frameOfReferenceUID,
windowCenter: o.windowCenter,
windowWidth: o.windowWidth,
path: scope.$getObjectName(res.url),
FileSize: o.FileSize,
})
o.myPath = scope.$getObjectName(res.url)
Record.Uploaded.push(name)
dicomInfo.failedFileCount++
Record.FileCount++
} else {
Record.Failed.push(name)
Record.FileCount++
}
}
resolve1()
} catch (e) {
console.log(e)
resolve1()
}
})
})()
)
if (
(arr.length >= 10 || ii === v.instanceList.length - 1) &&
!scope.isClose
) {
await Promise.all(arr)
arr = []
}
}
params.study.seriesList.push({
studyInstanceUid: dicomInfo.studyUid,
seriesInstanceUid: v.seriesUid,
seriesNumber: v.seriesNumber,
seriesTime: v.seriesTime,
sliceThickness: v.sliceThickness,
imagePositionPatient: v.imagePositionPatient,
imageOrientationPatient: v.imageOrientationPatient,
sequenceName: v.sequenceName,
protocolName: v.protocolName,
imagerPixelSpacing: v.imagerPixelSpacing,
acquisitionTime: dicomInfo.acquisitionTime,
acquisitionNumber: dicomInfo.acquisitionNumber,
triggerTime: dicomInfo.triggerTime,
modality: v.modality,
description: v.description,
instanceCount: v.instanceList.length,
bodyPartExamined: dicomInfo.bodyPart,
instanceList: instanceList,
ImageResizePath: ImageResizePath,
})
}
let text = JSON.stringify(Record)
let logJsonBlob = scope.generateTxtFile(text)
let logJsonObjectName = `/${params.trialId}/Image/${params.subjectId}/${params.subjectVisitId}/${dicomInfo.visitTaskId}/${dicomInfo.studyUid}/${params.studyMonitorId}.txt`
let logRes
try {
logRes = await scope.OSSclient.put(
logJsonObjectName,
logJsonBlob
)
} catch (e) {
scope.uploadQueues[index].uploadState.record = Record
scope.studyErrorList.push(dicomInfo.accNumber)
clearInterval(t)
resolve()
}
for (let i = 0; i < seriesList.length; i++) {
let v = seriesList[i]
let o = params.study.seriesList.find((o) => {
return o.seriesInstanceUid === v.seriesUid
})
if (o && !o.ImageResizePath) {
let fileId =
cornerstoneWADOImageLoader.wadouri.fileManager.add(
v.instanceList[0].file
)
let blob = await scope.dicomToPng(
fileId,
v.instanceList[0].imageColumns,
v.instanceList[0].imageRows
)
let thumbnailPath = `/${params.trialId}/Image/${params.trialSiteId}/${params.subjectId}/${params.subjectVisitId}/${dicomInfo.studyUid}/${v.seriesUid}.png`
let OSSclient = scope.OSSclient
try {
let seriesRes = await OSSclient.put(thumbnailPath, blob)
if (seriesRes && seriesRes.url) {
o.ImageResizePath = scope.$getObjectName(seriesRes.url)
}
} catch (e) {
console.log(e)
}
}
}
if (logRes && logRes.url) {
params.study.instanceCount = dicomInfo.failedFileCount
params.RecordPath = scope.$getObjectName(logRes.url)
if (scope.isClose) return false
console.log(params)
params.VisitTaskId = dicomInfo.visitTaskId
addOrUpdateArchiveTaskStudy(params)
.then((res) => {
if (dicomInfo.failedFileCount === dicomInfo.fileCount) {
scope.$message.success(
scope.$t('trials:uploadDicomList:label:uploaded')
)
} else {
scope.studyErrorList.push(dicomInfo.accNumber)
}
scope.uploadQueues[index].uploadState.record = Record
scope.getList()
if (
scope.$route.path !==
'/trials/trials-panel/visit/crc-question'
) {
scope.$emit('getList')
}
clearInterval(t)
resolve()
})
.catch((res) => {
scope.uploadQueues[index].uploadState.record = Record
scope.studyErrorList.push(dicomInfo.accNumber)
clearInterval(t)
resolve()
})
} else {
scope.uploadQueues[index].uploadState.record = Record
scope.studyErrorList.push(dicomInfo.accNumber)
clearInterval(t)
resolve()
}
})
.catch((err) => {
console.log(err)
let Record = {
Failed: [],
Existed: [],
Uploaded: [],
FileCount: 0,
}
let fileList = scope.uploadQueues[index].fileList
fileList.forEach((v) => {
Record.Failed.push(v.webkitRelativePath)
})
scope.uploadQueues[index].uploadState.record = Record
scope.studyErrorList.push(dicomInfo.accNumber)
resolve()
})
} catch (e) {
console.log(e)
resolve()
}
})
},
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 this.canvasToBlob(canvas)
resolve(blob)
} else {
resolve()
}
})
}).catch((reason) => {
reason()
})
},
canvasToBlob(canvas) {
return new Promise((resolve) => {
canvas.toBlob((blob) => {
resolve(blob)
})
})
},
generateTxtFile(text) {
let blob = new Blob(['\ufeff', text], { type: 'text/plain' })
return blob
},
// 预览阅片影像
handleViewReadingImages(row) {
if (this.open) {
this.open.close()
}
var token = getToken()
let trialId = this.$route.query.trialId
const routeData = this.$router.resolve({
path: `/showvisitdicoms?trialId=${trialId}&visitInfo=${row.VisitName}(${row.VisitNum})&subjectVisitId=${row.SourceSubjectVisitId}&isReading=1&TokenKey=${token}`,
})
this.open = window.open(routeData.href, '_blank')
},
},
}
</script>
<style lang="scss" scoped>
.top {
display: flex;
align-items: center;
justify-content: space-between;
}
.btnBox,
.form-group {
display: flex;
align-items: center;
}
#inputForm .file-input {
position: relative;
overflow: hidden;
display: inline-block;
}
#inputForm .file-input input[type='file'] {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
}
</style>